对于Android的编解码可分为软件编解码及硬件编解码,软件编解码可利用ffmpeg进行编解码,而硬件编解码时对于平台而有各自平台的编解码芯片完成,在Android中编解码芯片暴露上层用的是MeidaCodec。
本篇文章介绍了ffmpeg对媒体文件的提取过程和Android中Extractor的对媒体文件提取的全过程。
一.ffmpeg对媒体文件的提取及操作:
首先ffmpeg交叉编译Android平台,在之前的博客中有介绍交叉编译的全过程,感兴趣的可以查看博客:
1.声明一个AVFormatConext结构体的指针变量:
cpp
AVFormatContext *fmt_ctx = avformat_alloc_context();
2.调用avformat_open_input函数指定文件路径:
cpp
int ret = avformat_open_input(&fmt_ctx, mediaPath, nullptr, nullptr);
3.调用avformat_find_stream_info函数查找文件中的数据流:
cpp
ret = avformat_find_stream_info(fmt_ctx, nullptr);
4.调用av_find_best_stream分别查找视频流及音频的索引:
查找视频流索引:
cpp
// 找到视频流的索引
int video_index = av_find_best_stream(fmt_ctx,
AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
查找音频流索引:
cpp
// 找到音频流的索引
int audio_index = av_find_best_stream(fmt_ctx,
AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
5.调用avcodec_find_decoder分别查找出视频及音频的解码器:
查找视频解码器:
cpp
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);
}
查找音频解码器:
cpp
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.打印出音视频流中的内部信息:
媒体文件中视频流信息:
cpp
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);
媒体文件中音频流信息:
cpp
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类中:
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库。
bash
target_link_libraries(${CMAKE_PROJECT_NAME}
# List libraries link to the target library
android
mediandk //用到NdkAMediaCodec.h必须引入该库
log
${third-party-libs}
)
2.基准类BenchmarkCommon.cpp:
该类是一个基类封装了AMediaCodec创建过程,并提供一些状态回调函数及回调的线程安全队列。
核心函数createMediaCodec:
AMediaCodec_createCodecByName根据name创建获取编解码------>或者AMediaCodec_createEncoderByType根据mime创建获取编解码------>AMediaCodec_configure设置
cpp
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:
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.
*/
#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:
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文件表格中。
cpp
/**
* 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:
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.
*/
#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:
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。
cpp
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信息。
cpp
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:
cpp
//
// 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:
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:
cpp
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()/选择音视频轨打印出相关参数:
cpp
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 properties
struct 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:
cpp
//
// 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:
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 properties
struct 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/NdkMediaExtractor媒体文件提取对比:
1.打开媒体文件:
FFmpeg: 调用avformat_open_input() 得到AVFormatConext结构体;
NdkMediaExtractor: 通过fopen() ------>fileno() 得到file的文件描述------> AMediaExtractor_setDataSourceFd(mExtractor, fd, 0, fileSize) 传入mExtractor 得到AMediaExtractor指针。
2.获取音视频流的索引:
FFmpeg: 通过avformat_find_stream_info() ------> **av_find_best_stream()**得到音视频流的索引。
NdkMediaExtractor**:** 通过**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)**得到对应的编解码器;
NdkMediaExtractor**:** 通过AMediaCodec_createCodecByName/AMediaCodec_createEncoderByType 得到AMediaCodec的结构体。
4.音视频流轨道选择:
FFmpeg: 通过**av_find_best_stream()**来选择对应的流轨道;
MediaCodec: 通过**AMediaExtractor_selectTrack()**来选择对应的流轨道;
5.获取媒体文件的参数:
FFmpeg: 通过AVCodecParameters *video_codecpar = video_stream->codecpar 得到AVCodecParameters的结构体,里面成员就包含需要的媒体文件内部参数。
NdkMediaExtractor : 通过AMediaFormat *mFormat =
AMediaExtractor_getTrackFormat(mExtractor, trackId) 得到AMediaFormat结构体,里面成员就包含需要的媒体文件内部参数。
效果演示:
