Android音视频编解码全流程之Extractor
对于Android的编解码可分为软件编解码及硬件编解码,软件编解码可利用ffmpeg进行编解码,而硬件编解码时对于平台而有各自平台的编解码芯片完成,在Android中编解码芯片暴露上层用的是MeidaCodec。
本篇文章介绍了ffmpeg对媒体文件的提取过程和Android中Extractor的对媒体文件提取的全过程。
一.ffmpeg对媒体文件的提取及操作:
首先ffmpeg交叉编译Android平台,在之前的博客中有介绍交叉编译的全过程,感兴趣的可以查看博客:
Android Liunx ffmpeg交叉编译
macOs上交叉编译ffmpeg及安装ffmpeg工具
1.声明一个AVFormatConext结构体的指针变量:
AVFormatContext *fmt_ctx = avformat_alloc_context();
2.调用avformat_open_input函数指定文件路径:
int ret = avformat_open_input(&fmt_ctx, mediaPath, nullptr, nullptr);
3.调用avformat_find_stream_info函数查找文件中的数据流:
ret = avformat_find_stream_info(fmt_ctx, nullptr);
4.调用av_find_best_stream分别查找视频流及音频的索引:
查找视频流索引:
// 找到视频流的索引int video_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
查找音频流索引:
// 找到音频流的索引int audio_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
5.调用avcodec_find_decoder分别查找出视频及音频的解码器:
查找视频解码器:
if (video_index >= 0) {AVStream *video_stream = fmt_ctx->streams[video_index];enum AVCodecID video_codec_id = video_stream->codecpar->codec_id;// 查找视频解码器AVCodec *video_codec = (AVCodec *) avcodec_find_decoder(video_codec_id);if (!video_codec) {LOGE("video_codec not found\n");mediaInfo = "video_codec not found";return mediaInfo;}LOGI("video_codec name=%s\n", video_codec->name);
}
查找音频解码器:
if (audio_index >= 0) {AVStream *audio_stream = fmt_ctx->streams[audio_index];enum AVCodecID audio_codec_id = audio_stream->codecpar->codec_id;// 查找音频解码器AVCodec *audio_codec = (AVCodec *) avcodec_find_decoder(audio_codec_id);if (!audio_codec) {LOGE("audio_codec not found\n");mediaInfo = "audio_codec not found";return mediaInfo;}LOGI("audio_codec name=%s\n", audio_codec->name);
}
6.打印出音视频流中的内部信息:
媒体文件中视频流信息:
AVCodecParameters *video_codecpar = video_stream->codecpar;
// 计算帧率,每秒有几个视频帧
int fps = video_stream->r_frame_rate.num / video_stream->r_frame_rate.den;
//int fps = av_q2d(video_stream->r_frame_rate);
LOGI("video_codecpar bit_rate=%d\n", video_codecpar->bit_rate);
LOGI("video_codecpar width=%d\n", video_codecpar->width);
LOGI("video_codecpar height=%d\n", video_codecpar->height);
LOGI("video_codecpar fps=%d\n", fps);
int per_video = 1000 / fps; // 计算每个视频帧的持续时间
mediaInfo = mediaInfo + "视频的比特率=" + to_string(video_codecpar->bit_rate)+ "\n 视频画面的宽度=" + to_string(video_codecpar->width)+ "\n 视频画面的高度=" + to_string(video_codecpar->height)+ "\n 视频的帧率=" + to_string(fps);
LOGI("one video frame's duration is %dms\n", per_video);
媒体文件中音频流信息:
AVCodecParameters *audio_codecpar = audio_stream->codecpar;LOGI("audio_codecpar bit_rate=%d\n", audio_codecpar->bit_rate);LOGI("audio_codecpar frame_size=%d\n", audio_codecpar->frame_size);LOGI("audio_codecpar sample_rate=%d\n", audio_codecpar->sample_rate);LOGI("audio_codecpar nb_channels=%d\n",audio_codecpar->ch_layout.nb_channels);// 计算音频帧的持续时间。frame_size为每个音频帧的采样数量,sample_rate为音频帧的采样频率int per_audio = 1000 * audio_codecpar->frame_size / audio_codecpar->sample_rate;LOGI("one audio frame's duration is %dms\n", per_audio);mediaInfo = mediaInfo + "音频的比特率=" + to_string(audio_codecpar->bit_rate)+ "\n 音频帧的大小=" + to_string(audio_codecpar->frame_size)+ "\n 音频的采样率=" + to_string(audio_codecpar->sample_rate)+ "\n 音频的声道信息=" + to_string(audio_codecpar->ch_layout.nb_channels);
7.完整代码:
以上的代码放在本人的GitHub项目:https://github.com/wangyongyao1989/FFmpegPractices
中codecTraningLib模块的GetMediaMsg.cpp类中:
//
// Created by wangyao on 2025/8/16.
//#include "includes/GetMediaMsg.h"GetMediaMsg::GetMediaMsg() {}GetMediaMsg::~GetMediaMsg() {if (fmt_ctx) {avformat_close_input(&fmt_ctx); // 关闭音视频文件}
}string GetMediaMsg::getMediaMsg(const char *mediaPath) {fmt_ctx = avformat_alloc_context();// 打开音视频文件int ret = avformat_open_input(&fmt_ctx, mediaPath, nullptr, nullptr);if (ret < 0) {LOGE("Can't open file %s.\n", mediaPath);mediaInfo = "Can't open file";return mediaInfo;}LOGI("Success open input_file %s.\n", mediaPath);// 查找音视频文件中的流信息ret = avformat_find_stream_info(fmt_ctx, nullptr);if (ret < 0) {LOGE("Can't find stream information.\n");mediaInfo = "Can't find stream information.";return mediaInfo;}LOGI("duration=%d\n", fmt_ctx->duration); // 持续时间,单位微秒LOGI("nb_streams=%d\n", fmt_ctx->nb_streams); // 数据流的数量LOGI("max_streams=%d\n", fmt_ctx->max_streams); // 数据流的最大数量LOGI("video_codec_id=%d\n", fmt_ctx->video_codec_id);LOGI("audio_codec_id=%d\n", fmt_ctx->audio_codec_id);// 找到视频流的索引int video_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);LOGI("video_index=%d\n", video_index);if (video_index >= 0) {AVStream *video_stream = fmt_ctx->streams[video_index];enum AVCodecID video_codec_id = video_stream->codecpar->codec_id;// 查找视频解码器AVCodec *video_codec = (AVCodec *) avcodec_find_decoder(video_codec_id);if (!video_codec) {LOGE("video_codec not found\n");mediaInfo = "video_codec not found";return mediaInfo;}LOGI("video_codec name=%s\n", video_codec->name);AVCodecParameters *video_codecpar = video_stream->codecpar;// 计算帧率,每秒有几个视频帧int fps = video_stream->r_frame_rate.num / video_stream->r_frame_rate.den;//int fps = av_q2d(video_stream->r_frame_rate);LOGI("video_codecpar bit_rate=%d\n", video_codecpar->bit_rate);LOGI("video_codecpar width=%d\n", video_codecpar->width);LOGI("video_codecpar height=%d\n", video_codecpar->height);LOGI("video_codecpar fps=%d\n", fps);int per_video = 1000 / fps; // 计算每个视频帧的持续时间mediaInfo = mediaInfo + "视频的比特率=" + to_string(video_codecpar->bit_rate)+ "\n 视频画面的宽度=" + to_string(video_codecpar->width)+ "\n 视频画面的高度=" + to_string(video_codecpar->height)+ "\n 视频的帧率=" + to_string(fps);LOGI("one video frame's duration is %dms\n", per_video);}// 找到音频流的索引int audio_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);LOGI("audio_index=%d\n", audio_index);if (audio_index >= 0) {AVStream *audio_stream = fmt_ctx->streams[audio_index];enum AVCodecID audio_codec_id = audio_stream->codecpar->codec_id;// 查找音频解码器AVCodec *audio_codec = (AVCodec *) avcodec_find_decoder(audio_codec_id);if (!audio_codec) {LOGE("audio_codec not found\n");mediaInfo = "audio_codec not found";return mediaInfo;}LOGI("audio_codec name=%s\n", audio_codec->name);AVCodecParameters *audio_codecpar = audio_stream->codecpar;LOGI("audio_codecpar bit_rate=%d\n", audio_codecpar->bit_rate);LOGI("audio_codecpar frame_size=%d\n", audio_codecpar->frame_size);LOGI("audio_codecpar sample_rate=%d\n", audio_codecpar->sample_rate);LOGI("audio_codecpar nb_channels=%d\n", audio_codecpar->ch_layout.nb_channels);// 计算音频帧的持续时间。frame_size为每个音频帧的采样数量,sample_rate为音频帧的采样频率int per_audio = 1000 * audio_codecpar->frame_size / audio_codecpar->sample_rate;LOGI("one audio frame's duration is %dms\n", per_audio);mediaInfo = mediaInfo + "音频的比特率=" + to_string(audio_codecpar->bit_rate)+ "\n 音频帧的大小=" + to_string(audio_codecpar->frame_size)+ "\n 音频的采样率=" + to_string(audio_codecpar->sample_rate)+ "\n 音频的声道信息=" + to_string(audio_codecpar->ch_layout.nb_channels);}avformat_close_input(&fmt_ctx); // 关闭音视频文件return mediaInfo;
}
二.MediaCodec对媒体文件的提取及操作:
硬件编解码在Android中必须用到MediaCodec提供的下层编解码芯片的接口。
1.引入NdkMediaCodec:
使用NdkAMediaCodec必须在CMakeList.txt中的target_link_libraries添加mediandk内部so库。
target_link_libraries(${CMAKE_PROJECT_NAME}# List libraries link to the target libraryandroidmediandk //用到NdkAMediaCodec.h必须引入该库log${third-party-libs}
)
2.基准类BenchmarkCommon.cpp:
该类是一个基类封装了AMediaCodec创建过程,并提供一些状态回调函数及回调的线程安全队列。
核心函数createMediaCodec:
AMediaCodec_createCodecByName根据name创建获取编解码——>或者AMediaCodec_createEncoderByType根据mime创建获取编解码——>AMediaCodec_configure设置
AMediaCodec *createMediaCodec(AMediaFormat *format, const char *mime, string codecName, bool isEncoder) {ALOGV("In %s", __func__);if (!mime) {ALOGE("Please specify a mime type to create codec");return nullptr;}AMediaCodec *codec;if (!codecName.empty()) {codec = AMediaCodec_createCodecByName(codecName.c_str());if (!codec) {ALOGE("Unable to create codec by name: %s", codecName.c_str());return nullptr;}} else {if (isEncoder) {codec = AMediaCodec_createEncoderByType(mime);} else {codec = AMediaCodec_createDecoderByType(mime);}if (!codec) {ALOGE("Unable to create codec by mime: %s", mime);return nullptr;}}/* Configure codec with the given format*/const char *s = AMediaFormat_toString(format);ALOGI("Input format: %s\n", s);media_status_t status = AMediaCodec_configure(codec, format, nullptr, nullptr, isEncoder);if (status != AMEDIA_OK) {ALOGE("AMediaCodec_configure failed %d", status);return nullptr;}return codec;
}
头文件BenchmarkCommon.h:
/** Copyright (C) 2019 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/#ifndef __BENCHMARK_COMMON_H__
#define __BENCHMARK_COMMON_H__#include <sys/stat.h>
#include <inttypes.h>
#include <mutex>
#include <queue>
#include <thread>
#include <iostream>#include <media/NdkMediaCodec.h>
#include <media/NdkMediaError.h>#include "Stats.h"
#define UNUSED(x) (void)(x)using namespace std;constexpr uint32_t kQueueDequeueTimeoutUs = 1000;
constexpr uint32_t kMaxCSDStrlen = 16;
constexpr uint32_t kMaxBufferSize = 1024 * 1024 * 16;
// Change in kDefaultAudioEncodeFrameSize should also be taken to
// AUDIO_ENCODE_DEFAULT_MAX_INPUT_SIZE present in Encoder.java
constexpr uint32_t kDefaultAudioEncodeFrameSize = 4096;template <typename T>
class CallBackQueue {public:CallBackQueue() {}~CallBackQueue() {}void push(T elem) {bool needsNotify = false;{lock_guard<mutex> lock(mMutex);needsNotify = mQueue.empty();mQueue.push(move(elem));}if (needsNotify) mQueueNotEmptyCondition.notify_one();}T pop() {unique_lock<mutex> lock(mMutex);if (mQueue.empty()) {mQueueNotEmptyCondition.wait(lock, [this]() { return !mQueue.empty(); });}auto result = mQueue.front();mQueue.pop();return result;}private:mutex mMutex;queue<T> mQueue;condition_variable mQueueNotEmptyCondition;
};class CallBackHandle {public:CallBackHandle() : mSawError(false), mIsDone(false), mStats(nullptr) {mStats = new Stats();}virtual ~CallBackHandle() {if (mIOThread.joinable()) mIOThread.join();if (mStats) delete mStats;}void ioThread();// Implementation in child class (Decoder/Encoder)virtual void onInputAvailable(AMediaCodec *codec, int32_t index) {(void)codec;(void)index;}virtual void onFormatChanged(AMediaCodec *codec, AMediaFormat *format) {(void)codec;(void)format;}virtual void onError(AMediaCodec *codec, media_status_t err) {(void)codec;(void)err;}virtual void onOutputAvailable(AMediaCodec *codec, int32_t index,AMediaCodecBufferInfo *bufferInfo) {(void)codec;(void)index;(void)bufferInfo;}Stats *getStats() { return mStats; }// Keep a queue of all function callbacks.typedef function<void()> IOTask;CallBackQueue<IOTask> mIOQueue;thread mIOThread;bool mSawError;bool mIsDone;protected:Stats *mStats;
};// Async API's callback
void OnInputAvailableCB(AMediaCodec *codec, void *userdata, int32_t index);void OnOutputAvailableCB(AMediaCodec *codec, void *userdata, int32_t index,AMediaCodecBufferInfo *bufferInfo);void OnFormatChangedCB(AMediaCodec *codec, void *userdata, AMediaFormat *format);void OnErrorCB(AMediaCodec *codec, void * /* userdata */, media_status_t err, int32_t actionCode,const char *detail);// Utility to create and configure AMediaCodec
AMediaCodec *createMediaCodec(AMediaFormat *format, const char *mime, string codecName,bool isEncoder);#endif // __BENCHMARK_COMMON_H__
BenchmarkCommon.cpp:
/** Copyright (C) 2019 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*///#define LOG_NDEBUG 0
#define LOG_TAG "BenchmarkCommon"#include "../includes/BenchmarkCommon.h"
#include <iostream>void CallBackHandle::ioThread() {ALOGV("In %s mIsDone : %d, mSawError : %d ", __func__, mIsDone, mSawError);while (!mIsDone && !mSawError) {auto task = mIOQueue.pop();task();}
}void OnInputAvailableCB(AMediaCodec *codec, void *userdata, int32_t index) {ALOGV("OnInputAvailableCB: index(%d)", index);CallBackHandle *self = (CallBackHandle *)userdata;self->getStats()->addInputTime();self->mIOQueue.push([self, codec, index]() { self->onInputAvailable(codec, index); });
}void OnOutputAvailableCB(AMediaCodec *codec, void *userdata, int32_t index,AMediaCodecBufferInfo *bufferInfo) {ALOGV("OnOutputAvailableCB: index(%d), (%d, %d, %lld, 0x%x)", index, bufferInfo->offset,bufferInfo->size, (long long)bufferInfo->presentationTimeUs, bufferInfo->flags);CallBackHandle *self = (CallBackHandle *)userdata;self->getStats()->addOutputTime();AMediaCodecBufferInfo bufferInfoCopy = *bufferInfo;self->mIOQueue.push([self, codec, index, bufferInfoCopy]() {AMediaCodecBufferInfo bc = bufferInfoCopy;self->onOutputAvailable(codec, index, &bc);});
}void OnFormatChangedCB(AMediaCodec *codec, void *userdata, AMediaFormat *format) {ALOGV("OnFormatChangedCB: format(%s)", AMediaFormat_toString(format));CallBackHandle *self = (CallBackHandle *)userdata;self->mIOQueue.push([self, codec, format]() { self->onFormatChanged(codec, format); });
}void OnErrorCB(AMediaCodec *codec, void *userdata, media_status_t err, int32_t actionCode,const char *detail) {(void)codec;ALOGE("OnErrorCB: err(%d), actionCode(%d), detail(%s)", err, actionCode, detail);CallBackHandle *self = (CallBackHandle *)userdata;self->mSawError = true;self->mIOQueue.push([self, codec, err]() { self->onError(codec, err); });
}AMediaCodec *createMediaCodec(AMediaFormat *format, const char *mime, string codecName,bool isEncoder) {ALOGV("In %s", __func__);if (!mime) {ALOGE("Please specify a mime type to create codec");return nullptr;}AMediaCodec *codec;if (!codecName.empty()) {codec = AMediaCodec_createCodecByName(codecName.c_str());if (!codec) {ALOGE("Unable to create codec by name: %s", codecName.c_str());return nullptr;}} else {if (isEncoder) {codec = AMediaCodec_createEncoderByType(mime);} else {codec = AMediaCodec_createDecoderByType(mime);}if (!codec) {ALOGE("Unable to create codec by mime: %s", mime);return nullptr;}}/* Configure codec with the given format*/const char *s = AMediaFormat_toString(format);ALOGI("Input format: %s\n", s);media_status_t status = AMediaCodec_configure(codec, format, nullptr, nullptr, isEncoder);if (status != AMEDIA_OK) {ALOGE("AMediaCodec_configure failed %d", status);return nullptr;}return codec;
}
3.工具类stats:
用于编解码过程中的各种时间统计,并统计输出到.cvs文件表格中。
核心函数dumpStatistics:
把编解码过程中的时间间隔及参数输出到.cvs文件表格中。
/*** Dumps the stats of the operation for a given input media.** \param operation describes the operation performed on the input media* (i.e. extract/mux/decode/encode)* \param inputReference input media* \param durationUs is a duration of the input media in microseconds.* \param componentName describes the codecName/muxFormat/mimeType.* \param mode the operating mode: sync/async.* \param statsFile the file where the stats data is to be written.*/
void Stats::dumpStatistics(string operation, string inputReference, int64_t durationUs,string componentName, string mode, string statsFile) {ALOGV("In %s", __func__);if (!mOutputTimer.size()) {ALOGE("No output produced");return;}nsecs_t totalTimeTakenNs = getTotalTime();nsecs_t timeTakenPerSec = (totalTimeTakenNs * 1000000) / durationUs;nsecs_t timeToFirstFrameNs = *mOutputTimer.begin() - mStartTimeNs;int32_t size = std::accumulate(mFrameSizes.begin(), mFrameSizes.end(), 0);// get min and max output intervals.nsecs_t intervalNs;nsecs_t minTimeTakenNs = INT64_MAX;nsecs_t maxTimeTakenNs = 0;nsecs_t prevIntervalNs = mStartTimeNs;for (int32_t idx = 0; idx < mOutputTimer.size() - 1; idx++) {intervalNs = mOutputTimer.at(idx) - prevIntervalNs;prevIntervalNs = mOutputTimer.at(idx);if (minTimeTakenNs > intervalNs) minTimeTakenNs = intervalNs;else if (maxTimeTakenNs < intervalNs) maxTimeTakenNs = intervalNs;}// Write the stats data to file.int64_t dataSize = size;int64_t bytesPerSec = ((int64_t)dataSize * 1000000000) / totalTimeTakenNs;string rowData = "";rowData.append(to_string(systemTime(CLOCK_MONOTONIC)) + ", ");rowData.append(inputReference + ", ");rowData.append(operation + ", ");rowData.append(componentName + ", ");rowData.append("NDK, ");rowData.append(mode + ", ");rowData.append(to_string(mInitTimeNs) + ", ");rowData.append(to_string(mDeInitTimeNs) + ", ");rowData.append(to_string(minTimeTakenNs) + ", ");rowData.append(to_string(maxTimeTakenNs) + ", ");rowData.append(to_string(totalTimeTakenNs / mOutputTimer.size()) + ", ");rowData.append(to_string(timeTakenPerSec) + ", ");rowData.append(to_string(bytesPerSec) + ", ");rowData.append(to_string(timeToFirstFrameNs) + ", ");rowData.append(to_string(size) + ",");rowData.append(to_string(totalTimeTakenNs) + ",\n");ofstream out(statsFile, ios::out | ios::app);if(out.bad()) {ALOGE("Failed to open stats file for writing!");return;}out << rowData;out.close();
}
头文件stats.h:
/** Copyright (C) 2019 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/#ifndef __STATS_H__
#define __STATS_H__#include <android/log.h>
#include <inttypes.h>#ifndef ALOG
#define ALOG(priority, tag, ...) ((void)__android_log_print(ANDROID_##priority, tag, __VA_ARGS__))#define ALOGI(...) ALOG(LOG_INFO, LOG_TAG, __VA_ARGS__)
#define ALOGE(...) ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define ALOGD(...) ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define ALOGW(...) ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__)#ifndef LOG_NDEBUG
#define LOG_NDEBUG 1
#endif#if LOG_NDEBUG
#define ALOGV(cond, ...) ((void)0)
#else
#define ALOGV(...) ALOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
#endif
#endif // ALOG#include <sys/time.h>
#include <algorithm>
#include <numeric>
#include <vector>// Include local copy of Timers taken from system/core/libutils
#include "../includes/Timers.h"using namespace std;class Stats {public:Stats() {mInitTimeNs = 0;mDeInitTimeNs = 0;}~Stats() {reset();}private:nsecs_t mInitTimeNs;nsecs_t mDeInitTimeNs;nsecs_t mStartTimeNs;std::vector<int32_t> mFrameSizes;std::vector<nsecs_t> mInputTimer;std::vector<nsecs_t> mOutputTimer;public:nsecs_t getCurTime() { return systemTime(CLOCK_MONOTONIC); }void setInitTime(nsecs_t initTime) { mInitTimeNs = initTime; }void setDeInitTime(nsecs_t deInitTime) { mDeInitTimeNs = deInitTime; }void setStartTime() { mStartTimeNs = systemTime(CLOCK_MONOTONIC); }void addFrameSize(int32_t size) { mFrameSizes.push_back(size); }void addInputTime() { mInputTimer.push_back(systemTime(CLOCK_MONOTONIC)); }void addOutputTime() { mOutputTimer.push_back(systemTime(CLOCK_MONOTONIC)); }void reset() {if (!mFrameSizes.empty()) mFrameSizes.clear();if (!mInputTimer.empty()) mInputTimer.clear();if (!mOutputTimer.empty()) mOutputTimer.clear();}std::vector<nsecs_t> getOutputTimer() { return mOutputTimer; }nsecs_t getInitTime() { return mInitTimeNs; }nsecs_t getDeInitTime() { return mDeInitTimeNs; }nsecs_t getTimeDiff(nsecs_t sTime, nsecs_t eTime) { return (eTime - sTime); }nsecs_t getTotalTime() {if (mOutputTimer.empty()) return -1;return (*(mOutputTimer.end() - 1) - mStartTimeNs);}void dumpStatistics(string operation, string inputReference, int64_t duarationUs,string codecName , string mode , string statsFile);
};#endif // __STATS_H__
stats.cpp:
/** Copyright (C) 2019 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*///#define LOG_NDEBUG 0
#define LOG_TAG "Stats"#include <ctime>
#include <iostream>
#include <stdint.h>
#include <fstream>#include "../includes/Stats.h"/*** Dumps the stats of the operation for a given input media.** \param operation describes the operation performed on the input media* (i.e. extract/mux/decode/encode)* \param inputReference input media* \param durationUs is a duration of the input media in microseconds.* \param componentName describes the codecName/muxFormat/mimeType.* \param mode the operating mode: sync/async.* \param statsFile the file where the stats data is to be written.*/
void Stats::dumpStatistics(string operation, string inputReference, int64_t durationUs,string componentName, string mode, string statsFile) {ALOGV("In %s", __func__);if (!mOutputTimer.size()) {ALOGE("No output produced");return;}nsecs_t totalTimeTakenNs = getTotalTime();nsecs_t timeTakenPerSec = (totalTimeTakenNs * 1000000) / durationUs;nsecs_t timeToFirstFrameNs = *mOutputTimer.begin() - mStartTimeNs;int32_t size = std::accumulate(mFrameSizes.begin(), mFrameSizes.end(), 0);// get min and max output intervals.nsecs_t intervalNs;nsecs_t minTimeTakenNs = INT64_MAX;nsecs_t maxTimeTakenNs = 0;nsecs_t prevIntervalNs = mStartTimeNs;for (int32_t idx = 0; idx < mOutputTimer.size() - 1; idx++) {intervalNs = mOutputTimer.at(idx) - prevIntervalNs;prevIntervalNs = mOutputTimer.at(idx);if (minTimeTakenNs > intervalNs) minTimeTakenNs = intervalNs;else if (maxTimeTakenNs < intervalNs) maxTimeTakenNs = intervalNs;}// Write the stats data to file.int64_t dataSize = size;int64_t bytesPerSec = ((int64_t)dataSize * 1000000000) / totalTimeTakenNs;string rowData = "";rowData.append(to_string(systemTime(CLOCK_MONOTONIC)) + ", ");rowData.append(inputReference + ", ");rowData.append(operation + ", ");rowData.append(componentName + ", ");rowData.append("NDK, ");rowData.append(mode + ", ");rowData.append(to_string(mInitTimeNs) + ", ");rowData.append(to_string(mDeInitTimeNs) + ", ");rowData.append(to_string(minTimeTakenNs) + ", ");rowData.append(to_string(maxTimeTakenNs) + ", ");rowData.append(to_string(totalTimeTakenNs / mOutputTimer.size()) + ", ");rowData.append(to_string(timeTakenPerSec) + ", ");rowData.append(to_string(bytesPerSec) + ", ");rowData.append(to_string(timeToFirstFrameNs) + ", ");rowData.append(to_string(size) + ",");rowData.append(to_string(totalTimeTakenNs) + ",\n");ofstream out(statsFile, ios::out | ios::app);if(out.bad()) {ALOGE("Failed to open stats file for writing!");return;}out << rowData;out.close();
}
4. 媒体文件提取器类HwExtractor:
初始化提取器initExtractor() ——>提取extract()
核心函数initExtractor():
创建AMediaExtractor_new ——>设置fd参数AMediaExtractor_setDataSourceFd ——>获取流数据的数量AMediaExtractor_getTrackCount。
int32_t HwExtractor::initExtractor(int32_t fd, size_t fileSize) {mStats = new Stats();mFrameBuf = (uint8_t *) calloc(kMaxBufferSize, sizeof(uint8_t));if (!mFrameBuf) return -1;int64_t sTime = mStats->getCurTime();mExtractor = AMediaExtractor_new();if (!mExtractor) return AMEDIACODEC_ERROR_INSUFFICIENT_RESOURCE;media_status_t status = AMediaExtractor_setDataSourceFd(mExtractor, fd, 0, fileSize);if (status != AMEDIA_OK) return status;int64_t eTime = mStats->getCurTime();int64_t timeTaken = mStats->getTimeDiff(sTime, eTime);mStats->setInitTime(timeTaken);return AMediaExtractor_getTrackCount(mExtractor);
}
核心函数extract():
通过数据流trackId选择数据流的轨道提取出AMediaFormat后,用AMediaFormat_getInt64/AMediaFormat_getString/AMediaFormat_getInt32等方法获取到AMediaFormat里媒体文件中的Format信息。
int32_t HwExtractor::setupTrackFormat(int32_t trackId) {AMediaExtractor_selectTrack(mExtractor, trackId);mFormat = AMediaExtractor_getTrackFormat(mExtractor, trackId);if (!mFormat) return AMEDIA_ERROR_INVALID_OBJECT;bool durationFound = AMediaFormat_getInt64(mFormat, AMEDIAFORMAT_KEY_DURATION, &mDurationUs);if (!durationFound) return AMEDIA_ERROR_INVALID_OBJECT;return AMEDIA_OK;
}int32_t HwExtractor::extract(int32_t trackId) {int32_t status = setupTrackFormat(trackId);if (status != AMEDIA_OK) return status;int32_t idx = 0;AMediaCodecBufferInfo frameInfo;while (1) {memset(&frameInfo, 0, sizeof(AMediaCodecBufferInfo));void *csdBuffer = getCSDSample(frameInfo, idx);if (!csdBuffer || !frameInfo.size) break;idx++;}mStats->setStartTime();while (1) {int32_t status = getFrameSample(frameInfo);if (status || !frameInfo.size) break;mStats->addOutputTime();}if (mFormat) {AMediaFormat_delete(mFormat);mFormat = nullptr;}AMediaExtractor_unselectTrack(mExtractor, trackId);return AMEDIA_OK;
}
头文件HwExtractor.h:
//
// Created by wangyao on 2025/9/20.
//#ifndef FFMPEGPRACTICE_HWEXTRACTOR_H
#define FFMPEGPRACTICE_HWEXTRACTOR_H
#include <media/NdkMediaExtractor.h>
#include <string>
#include "Stats.h"
#include "BenchmarkCommon.h"
#include "LogUtils.h"class HwExtractor {public:HwExtractor(): mFormat(nullptr),mExtractor(nullptr),mStats(nullptr),mFrameBuf{nullptr},mDurationUs{0} {}~HwExtractor() {if (mStats) delete mStats;}int32_t initExtractor(int32_t fd, size_t fileSize);int32_t setupTrackFormat(int32_t trackId);void *getCSDSample(AMediaCodecBufferInfo &frameInfo, int32_t csdIndex);int32_t getFrameSample(AMediaCodecBufferInfo &frameInfo);int32_t extract(int32_t trackId);void dumpStatistics(string inputReference, string componentName = "", string statsFile = "");void deInitExtractor();AMediaFormat *getFormat() { return mFormat; }uint8_t *getFrameBuf() { return mFrameBuf; }int64_t getClipDuration() { return mDurationUs; }private:AMediaFormat *mFormat;AMediaExtractor *mExtractor;Stats *mStats;uint8_t *mFrameBuf;int64_t mDurationUs;};#endif //FFMPEGPRACTICE_HWEXTRACTOR_H
HwExtractor.cpp:
//
// Created by wangyao on 2025/9/20.
//#include "includes/HwExtractor.h"int32_t HwExtractor::initExtractor(int32_t fd, size_t fileSize) {mStats = new Stats();mFrameBuf = (uint8_t *) calloc(kMaxBufferSize, sizeof(uint8_t));if (!mFrameBuf) return -1;int64_t sTime = mStats->getCurTime();mExtractor = AMediaExtractor_new();if (!mExtractor) return AMEDIACODEC_ERROR_INSUFFICIENT_RESOURCE;media_status_t status = AMediaExtractor_setDataSourceFd(mExtractor, fd, 0, fileSize);if (status != AMEDIA_OK) return status;int64_t eTime = mStats->getCurTime();int64_t timeTaken = mStats->getTimeDiff(sTime, eTime);mStats->setInitTime(timeTaken);return AMediaExtractor_getTrackCount(mExtractor);
}void *HwExtractor::getCSDSample(AMediaCodecBufferInfo &frameInfo, int32_t csdIndex) {char csdName[kMaxCSDStrlen];void *csdBuffer = nullptr;frameInfo.presentationTimeUs = 0;frameInfo.flags = AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG;snprintf(csdName, sizeof(csdName), "csd-%d", csdIndex);size_t size;bool csdFound = AMediaFormat_getBuffer(mFormat, csdName, &csdBuffer, &size);if (!csdFound) return nullptr;frameInfo.size = (int32_t) size;mStats->addFrameSize(frameInfo.size);return csdBuffer;
}int32_t HwExtractor::getFrameSample(AMediaCodecBufferInfo &frameInfo) {int32_t size = AMediaExtractor_readSampleData(mExtractor, mFrameBuf, kMaxBufferSize);if (size < 0) return -1;frameInfo.flags = AMediaExtractor_getSampleFlags(mExtractor);frameInfo.size = size;mStats->addFrameSize(frameInfo.size);frameInfo.presentationTimeUs = AMediaExtractor_getSampleTime(mExtractor);AMediaExtractor_advance(mExtractor);return 0;
}int32_t HwExtractor::setupTrackFormat(int32_t trackId) {AMediaExtractor_selectTrack(mExtractor, trackId);mFormat = AMediaExtractor_getTrackFormat(mExtractor, trackId);if (!mFormat) return AMEDIA_ERROR_INVALID_OBJECT;bool durationFound = AMediaFormat_getInt64(mFormat, AMEDIAFORMAT_KEY_DURATION, &mDurationUs);if (!durationFound) return AMEDIA_ERROR_INVALID_OBJECT;return AMEDIA_OK;
}int32_t HwExtractor::extract(int32_t trackId) {int32_t status = setupTrackFormat(trackId);if (status != AMEDIA_OK) return status;int32_t idx = 0;AMediaCodecBufferInfo frameInfo;while (1) {memset(&frameInfo, 0, sizeof(AMediaCodecBufferInfo));void *csdBuffer = getCSDSample(frameInfo, idx);if (!csdBuffer || !frameInfo.size) break;idx++;}mStats->setStartTime();while (1) {int32_t status = getFrameSample(frameInfo);if (status || !frameInfo.size) break;mStats->addOutputTime();}if (mFormat) {AMediaFormat_delete(mFormat);mFormat = nullptr;}AMediaExtractor_unselectTrack(mExtractor, trackId);return AMEDIA_OK;
}void HwExtractor::dumpStatistics(string inputReference, string componentName, string statsFile) {string operation = "extract";mStats->dumpStatistics(operation, inputReference, mDurationUs, componentName, "", statsFile);
}void HwExtractor::deInitExtractor() {if (mFrameBuf) {AMediaFormat_delete(mFormat);free(mFrameBuf);mFrameBuf = nullptr;}int64_t sTime = mStats->getCurTime();if (mExtractor) {// TODO: (b/140128505) Multiple calls result in DoS.// Uncomment call to AMediaExtractor_delete() once this is resolved// AMediaExtractor_delete(mExtractor);mExtractor = nullptr;}int64_t eTime = mStats->getCurTime();int64_t deInitTime = mStats->getTimeDiff(sTime, eTime);mStats->setDeInitTime(deInitTime);
}
5.媒体文件提取器处理类ProcessExtractor:
传入媒体文件的路径地址——>创建提取器类HwExtractor——>调用initExtractor()——>调用extract()——>分别选择音视频轨打印出相关参数。
创建提取器类HwExtractor:
void ProcessExtractor::startProcessExtractor(const char *srcPath, const char *outPath) {sSrcPath = srcPath;sOutPath = outPath;LOGI("sSrcPath :%s \n sOutPath: %s ", sSrcPath.c_str(), sOutPath.c_str());callbackInfo ="sSrcPath:" + sSrcPath + "\n";PostStatusMessage(callbackInfo.c_str());mHwExtractor = new HwExtractor();if (mHwExtractor == nullptr) {LOGE("Extractor creation failed ");callbackInfo ="Extractor creation failed \n";PostStatusMessage(callbackInfo.c_str());return;}LOGI("Extractor creation Success!");processProcessExtractor();
}
调用initExtractor()/extract()/选择音视频轨打印出相关参数:
void ProcessExtractor::processProcessExtractor() {inputFp = fopen(sSrcPath.c_str(), "rb");if (!inputFp) {LOGE("Unable to open :%s", sSrcPath.c_str());callbackInfo ="Unable to open " + sSrcPath + "\n";PostStatusMessage(callbackInfo.c_str());return;}LOGI("Success open file :%s", sOutPath.c_str());callbackInfo ="Success open file:" + sOutPath + "\n";PostStatusMessage(callbackInfo.c_str());// Read file propertiesstruct stat buf;stat(sSrcPath.c_str(), &buf);size_t fileSize = buf.st_size;int32_t fd = fileno(inputFp);int32_t trackCount = mHwExtractor->initExtractor((long) fd, fileSize);if (trackCount < 0) {LOGE("initExtractor failed");callbackInfo = "initExtractor failed \n";PostStatusMessage(callbackInfo.c_str());return;}LOGI("initExtractor Success");callbackInfo = "initExtractor Success \n";PostStatusMessage(callbackInfo.c_str());int32_t trackID = 1;int32_t status = mHwExtractor->extract(trackID);if (status != AMEDIA_OK) {LOGE("Extraction failed");callbackInfo = "Extraction failed \n";PostStatusMessage(callbackInfo.c_str());return;}LOGI("Extraction Success");callbackInfo = "Extraction Success \n";PostStatusMessage(callbackInfo.c_str());//选择视频轨打印出相关参数mHwExtractor->setupTrackFormat(0);AMediaFormat *videoFormat = mHwExtractor->getFormat();if (videoFormat) {const char *video_mime_type = nullptr;AMediaFormat_getString(videoFormat, AMEDIAFORMAT_KEY_MIME, &video_mime_type);LOGI("video mime_type: %s", video_mime_type);callbackInfo = "video mime_type:" + string(video_mime_type) + "\n";delete (video_mime_type);video_mime_type = nullptr;int32_t width;AMediaFormat_getInt32(videoFormat, AMEDIAFORMAT_KEY_WIDTH, &width);LOGI("video width: %d", width);callbackInfo = callbackInfo + "video width:" + to_string(width) + "\n";int32_t height;AMediaFormat_getInt32(videoFormat, AMEDIAFORMAT_KEY_HEIGHT, &height);LOGI("video height: %d", height);callbackInfo = callbackInfo + "video height:" + to_string(height) + "\n";int32_t color_format;AMediaFormat_getInt32(videoFormat, AMEDIAFORMAT_KEY_COLOR_FORMAT, &color_format);LOGI("video color_format: %d", color_format);callbackInfo = callbackInfo + "video color_format:" + to_string(color_format) + "\n";int32_t bit_rate;AMediaFormat_getInt32(videoFormat, AMEDIAFORMAT_KEY_BIT_RATE, &bit_rate);LOGI("video bit_rate: %d", bit_rate);callbackInfo = callbackInfo + "video bit_rate:" + to_string(bit_rate) + "\n";int32_t frame_rate;AMediaFormat_getInt32(videoFormat, AMEDIAFORMAT_KEY_FRAME_RATE, &frame_rate);LOGI("video frame_rate: %d", frame_rate);callbackInfo = callbackInfo + "video frame_rate:" + to_string(frame_rate) + "\n";int32_t i_frame_rate;AMediaFormat_getInt32(videoFormat, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, &i_frame_rate);LOGI("video i_frame_rate: %d", i_frame_rate);callbackInfo = callbackInfo + "video i_frame_rate:" + to_string(i_frame_rate) + "\n";PostStatusMessage(callbackInfo.c_str());}//选择音频轨打印出相关参数mHwExtractor->setupTrackFormat(1);AMediaFormat *audioFormat = mHwExtractor->getFormat();if (audioFormat) {const char *audio_mime_type = nullptr;AMediaFormat_getString(audioFormat, AMEDIAFORMAT_KEY_MIME, &audio_mime_type);LOGI("audio mime_type: %s", audio_mime_type);callbackInfo = "audio mime_type:" + string(audio_mime_type) + "\n";delete (audio_mime_type);audio_mime_type = nullptr;int32_t frame_rate;AMediaFormat_getInt32(audioFormat, AMEDIAFORMAT_KEY_FRAME_RATE, &frame_rate);LOGI("audio frame_rate: %d", frame_rate);callbackInfo = callbackInfo + "audio frame_rate:" + to_string(frame_rate) + "\n";int32_t bit_rate;AMediaFormat_getInt32(audioFormat, AMEDIAFORMAT_KEY_BIT_RATE, &bit_rate);LOGI("audio bit_rate: %d", bit_rate);callbackInfo = callbackInfo + "audio bit_rate:" + to_string(bit_rate) + "\n";PostStatusMessage(callbackInfo.c_str());}bool writeStat = writeStatsHeader();mHwExtractor->deInitExtractor();mHwExtractor->dumpStatistics(sSrcPath, "", sOutPath);LOGI("dumpStatistics Success");callbackInfo = "dumpStatistics Success file:" + sOutPath + "\n";PostStatusMessage(callbackInfo.c_str());fclose(inputFp);
}
头文件ProcessExtractor.h:
//
// Created by wangyao on 2025/9/21.
//#ifndef FFMPEGPRACTICE_PROCESSEXTRACTOR_H
#define FFMPEGPRACTICE_PROCESSEXTRACTOR_H#include <jni.h>
#include <thread>
#include "string"
#include "LogUtils.h"
#include "HwExtractor.h"using namespace std;class ProcessExtractor {private:string callbackInfo;JavaVM *mJavaVm = nullptr;jobject mJavaObj = nullptr;JNIEnv *mEnv = nullptr;string sSrcPath;string sOutPath;HwExtractor *mHwExtractor;FILE *inputFp;void processProcessExtractor();JNIEnv *GetJNIEnv(bool *isAttach);void PostStatusMessage(const char *msg);bool writeStatsHeader();public:ProcessExtractor(JNIEnv *env, jobject thiz);~ProcessExtractor();void startProcessExtractor(const char *srcPath, const char *outPath);};#endif //FFMPEGPRACTICE_PROCESSEXTRACTOR_H
ProcessExtractor.cpp:
//
// Created by wangyao on 2025/9/21.
//#include "includes/ProcessExtractor.h"ProcessExtractor::ProcessExtractor(JNIEnv *env, jobject thiz) {mEnv = env;env->GetJavaVM(&mJavaVm);mJavaObj = env->NewGlobalRef(thiz);
}ProcessExtractor::~ProcessExtractor() {mEnv->DeleteGlobalRef(mJavaObj);if (mEnv) {mEnv = nullptr;}if (mJavaVm) {mJavaVm = nullptr;}if (mJavaObj) {mJavaObj = nullptr;}if (mHwExtractor) {mHwExtractor->deInitExtractor();mHwExtractor = nullptr;}if (inputFp) {fclose(inputFp);}}void ProcessExtractor::startProcessExtractor(const char *srcPath, const char *outPath) {sSrcPath = srcPath;sOutPath = outPath;LOGI("sSrcPath :%s \n sOutPath: %s ", sSrcPath.c_str(), sOutPath.c_str());callbackInfo ="sSrcPath:" + sSrcPath + "\n";PostStatusMessage(callbackInfo.c_str());mHwExtractor = new HwExtractor();if (mHwExtractor == nullptr) {LOGE("Extractor creation failed ");callbackInfo ="Extractor creation failed \n";PostStatusMessage(callbackInfo.c_str());return;}LOGI("Extractor creation Success!");processProcessExtractor();
}void ProcessExtractor::processProcessExtractor() {inputFp = fopen(sSrcPath.c_str(), "rb");if (!inputFp) {LOGE("Unable to open :%s", sSrcPath.c_str());callbackInfo ="Unable to open " + sSrcPath + "\n";PostStatusMessage(callbackInfo.c_str());return;}LOGI("Success open file :%s", sOutPath.c_str());callbackInfo ="Success open file:" + sOutPath + "\n";PostStatusMessage(callbackInfo.c_str());// Read file propertiesstruct stat buf;stat(sSrcPath.c_str(), &buf);size_t fileSize = buf.st_size;int32_t fd = fileno(inputFp);int32_t trackCount = mHwExtractor->initExtractor((long) fd, fileSize);if (trackCount < 0) {LOGE("initExtractor failed");callbackInfo = "initExtractor failed \n";PostStatusMessage(callbackInfo.c_str());return;}LOGI("initExtractor Success");callbackInfo = "initExtractor Success \n";PostStatusMessage(callbackInfo.c_str());int32_t trackID = 1;int32_t status = mHwExtractor->extract(trackID);if (status != AMEDIA_OK) {LOGE("Extraction failed");callbackInfo = "Extraction failed \n";PostStatusMessage(callbackInfo.c_str());return;}LOGI("Extraction Success");callbackInfo = "Extraction Success \n";PostStatusMessage(callbackInfo.c_str());//选择视频轨打印出相关参数mHwExtractor->setupTrackFormat(0);AMediaFormat *videoFormat = mHwExtractor->getFormat();if (videoFormat) {const char *video_mime_type = nullptr;AMediaFormat_getString(videoFormat, AMEDIAFORMAT_KEY_MIME, &video_mime_type);LOGI("video mime_type: %s", video_mime_type);callbackInfo = "video mime_type:" + string(video_mime_type) + "\n";delete (video_mime_type);video_mime_type = nullptr;int32_t width;AMediaFormat_getInt32(videoFormat, AMEDIAFORMAT_KEY_WIDTH, &width);LOGI("video width: %d", width);callbackInfo = callbackInfo + "video width:" + to_string(width) + "\n";int32_t height;AMediaFormat_getInt32(videoFormat, AMEDIAFORMAT_KEY_HEIGHT, &height);LOGI("video height: %d", height);callbackInfo = callbackInfo + "video height:" + to_string(height) + "\n";int32_t color_format;AMediaFormat_getInt32(videoFormat, AMEDIAFORMAT_KEY_COLOR_FORMAT, &color_format);LOGI("video color_format: %d", color_format);callbackInfo = callbackInfo + "video color_format:" + to_string(color_format) + "\n";int32_t bit_rate;AMediaFormat_getInt32(videoFormat, AMEDIAFORMAT_KEY_BIT_RATE, &bit_rate);LOGI("video bit_rate: %d", bit_rate);callbackInfo = callbackInfo + "video bit_rate:" + to_string(bit_rate) + "\n";int32_t frame_rate;AMediaFormat_getInt32(videoFormat, AMEDIAFORMAT_KEY_FRAME_RATE, &frame_rate);LOGI("video frame_rate: %d", frame_rate);callbackInfo = callbackInfo + "video frame_rate:" + to_string(frame_rate) + "\n";int32_t i_frame_rate;AMediaFormat_getInt32(videoFormat, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, &i_frame_rate);LOGI("video i_frame_rate: %d", i_frame_rate);callbackInfo = callbackInfo + "video i_frame_rate:" + to_string(i_frame_rate) + "\n";PostStatusMessage(callbackInfo.c_str());}//选择音频轨打印出相关参数mHwExtractor->setupTrackFormat(1);AMediaFormat *audioFormat = mHwExtractor->getFormat();if (audioFormat) {const char *audio_mime_type = nullptr;AMediaFormat_getString(audioFormat, AMEDIAFORMAT_KEY_MIME, &audio_mime_type);LOGI("audio mime_type: %s", audio_mime_type);callbackInfo = "audio mime_type:" + string(audio_mime_type) + "\n";delete (audio_mime_type);audio_mime_type = nullptr;int32_t frame_rate;AMediaFormat_getInt32(audioFormat, AMEDIAFORMAT_KEY_FRAME_RATE, &frame_rate);LOGI("audio frame_rate: %d", frame_rate);callbackInfo = callbackInfo + "audio frame_rate:" + to_string(frame_rate) + "\n";int32_t bit_rate;AMediaFormat_getInt32(audioFormat, AMEDIAFORMAT_KEY_BIT_RATE, &bit_rate);LOGI("audio bit_rate: %d", bit_rate);callbackInfo = callbackInfo + "audio bit_rate:" + to_string(bit_rate) + "\n";PostStatusMessage(callbackInfo.c_str());}bool writeStat = writeStatsHeader();mHwExtractor->deInitExtractor();mHwExtractor->dumpStatistics(sSrcPath, "", sOutPath);LOGI("dumpStatistics Success");callbackInfo = "dumpStatistics Success file:" + sOutPath + "\n";PostStatusMessage(callbackInfo.c_str());fclose(inputFp);
}bool ProcessExtractor::writeStatsHeader() {char statsHeader[] ="currentTime, fileName, operation, componentName, NDK/SDK, sync/async, setupTime, ""destroyTime, minimumTime, maximumTime, averageTime, timeToProcess1SecContent, ""totalBytesProcessedPerSec, timeToFirstFrame, totalSizeInBytes, totalTime\n";FILE *fpStats = fopen(sOutPath.c_str(), "w");if (!fpStats) {return false;}int32_t numBytes = fwrite(statsHeader, sizeof(char), sizeof(statsHeader), fpStats);fclose(fpStats);if (numBytes != sizeof(statsHeader)) {return false;}return true;
}JNIEnv *ProcessExtractor::GetJNIEnv(bool *isAttach) {JNIEnv *env;int status;if (nullptr == mJavaVm) {LOGD("SaveYUVFromVideo::GetJNIEnv mJavaVm == nullptr");return nullptr;}*isAttach = false;status = mJavaVm->GetEnv((void **) &env, JNI_VERSION_1_6);if (status != JNI_OK) {status = mJavaVm->AttachCurrentThread(&env, nullptr);if (status != JNI_OK) {LOGD("SaveYUVFromVideo::GetJNIEnv failed to attach current thread");return nullptr;}*isAttach = true;}return env;
}void ProcessExtractor::PostStatusMessage(const char *msg) {bool isAttach = false;JNIEnv *pEnv = GetJNIEnv(&isAttach);if (pEnv == nullptr) {return;}jobject javaObj = mJavaObj;jmethodID mid = pEnv->GetMethodID(pEnv->GetObjectClass(javaObj), "CppStatusCallback","(Ljava/lang/String;)V");jstring pJstring = pEnv->NewStringUTF(msg);pEnv->CallVoidMethod(javaObj, mid, pJstring);if (isAttach) {JavaVM *pJavaVm = mJavaVm;pJavaVm->DetachCurrentThread();}
}
6.完整代码:
以上的代码放在本人的GitHub项目:https://github.com/wangyongyao1989/FFmpegPracticeshttps://github.com/wangyongyao1989/FFmpegPractices
中hwCodecLib模块的BenchmarkCommon.cpp/Stats.cpp/HwExtractor.cpp/ProcessExtractor.cpp类中
三.FFmpeg/MediaCodec媒体文件提取对比:
1.打开媒体文件:
FFmpeg:调用avformat_open_input()得到AVFormatConext结构体;
MediaCodec:通过fopen() ——> fileno() 得到file的文件描述——> AMediaExtractor_setDataSourceFd(mExtractor, fd, 0, fileSize)传入mExtractor得到AMediaExtractor指针。
2.获取音视频流的索引:
FFmpeg:通过avformat_find_stream_info() ——> av_find_best_stream() 得到音视频流的索引。
MediaCodec:通过AMediaExtractor_getTrackCount(mExtractor)获取到音视频的轨道数。
3.编解码器的获取:
FFmpeg:通过AVStream *video_stream = fmt_ctx->streams[video_index] ——>
enum AVCodecID video_codec_id = video_stream->codecpar->codec_id ——>
AVCodec *video_codec = (AVCodec *) avcodec_find_decoder(video_codec_id) 得到对应的编解码器;
MediaCodec:通过AMediaCodec_createCodecByName/AMediaCodec_createEncoderByType得到AMediaCodec的结构体。
4.音视频流轨道选择:
FFmpeg: 通过av_find_best_stream()来选择对应的流轨道;
MediaCodec:通过AMediaExtractor_selectTrack() 来选择对应的流轨道;
5.获取媒体文件的参数:
FFmpeg:通过AVCodecParameters *video_codecpar = video_stream->codecpar得到AVCodecParameters的结构体,里面成员就包含需要的媒体文件内部参数。
MediaCodec:通过AMediaFormat *mFormat =
AMediaExtractor_getTrackFormat(mExtractor, trackId) 得到AMediaFormat结构体,里面成员就包含需要的媒体文件内部参数。