系列文章:
Android音视频编解码全流程之Muxer
承接上篇文章 Android音视频编解码全流程之Extractor ,媒体文件提取(Extractor)并读取每个数据包后向目标文件一次写入每个数据包。
下面分别介绍FFmpeg向目标文件写入数据包的操作和Android中Muxer向目标写入数据包的操作。
一 .FFmpeg向目标文件写入数据包的操作:
首先ffmpeg交叉编译Android平台,在之前的博客中有介绍交叉编译的全过程,感兴趣的可以查看博客:
1.声明一个AVFormatConext结构体的指针变量:
cpp
AVFormatContext *in_fmt_ctx = avformat_alloc_context();
2.调用avformat_open_input函数指定文件路径:
cpp
// 打开音视频文件
int ret = avformat_open_input(&in_fmt_ctx, srcPath
, nullptr, nullptr);
3.调用avformat_find_stream_info函数查找文件中的数据流:
cpp
// 查找音视频文件中的流信息
ret = avformat_find_stream_info(in_fmt_ctx, nullptr);
4.调用av_find_best_stream分别查找视频流及音频流的索引
cpp
AVStream *src_video = nullptr;
// 找到视频流的索引
int video_index = av_find_best_stream(in_fmt_ctx
, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
if (video_index >= 0) {
src_video = in_fmt_ctx->streams[video_index];
}
AVStream *src_audio = nullptr;
// 找到音频流的索引
int audio_index = av_find_best_stream(in_fmt_ctx
, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
if (audio_index >= 0) {
src_audio = in_fmt_ctx->streams[audio_index];
}
5.分配音视频文件的封装实例:
cpp
AVFormatContext *out_fmt_ctx; // 输出文件的封装器实例
// 分配音视频文件的封装实例
ret = avformat_alloc_output_context2(&out_fmt_ctx, nullptr
, nullptr, destPath);
6.打开输出流:
cpp
ret = avio_open(&out_fmt_ctx->pb, destPath
, AVIO_FLAG_READ_WRITE);
7.拷贝源文件的参数:
cpp
if (video_index >= 0) { // 源文件有视频流,就给目标文件创建视频流
// 创建数据流
AVStream *dest_video = avformat_new_stream(out_fmt_ctx
, nullptr);
// 把源文件的视频参数原样复制过来
avcodec_parameters_copy(dest_video->codecpar
, src_video->codecpar);
dest_video->time_base = src_video->time_base;
dest_video->codecpar->codec_tag = 0;
}
if (audio_index >= 0) { // 源文件有音频流,就给目标文件创建音频流
// 创建数据流
AVStream *dest_audio = avformat_new_stream(out_fmt_ctx
, nullptr);
// 把源文件的音频参数原样复制过来
avcodec_parameters_copy(dest_audio->codecpar, src_audio->codecpar);
dest_audio->codecpar->codec_tag = 0;
}
8.写文件头:
cpp
ret = avformat_write_header(out_fmt_ctx, nullptr); // 写文件头
9.轮询数据包并写入数据包:
cpp
AVPacket *packet = av_packet_alloc(); // 分配一个数据包
while (av_read_frame(in_fmt_ctx, packet) >= 0) { // 轮询数据包
// 有的文件视频流没在第一路,需要调整到第一路,因为目标的视频流默认第一路
if (packet->stream_index == video_index) { // 视频包
packet->stream_index = 0;
// 往文件写入一个数据包
ret = av_write_frame(out_fmt_ctx, packet);
} else { // 音频包
packet->stream_index = 1;
// 往文件写入一个数据包
ret = av_write_frame(out_fmt_ctx, packet);
}
if (ret < 0) {
LOGE("write frame occur error %d.\n", ret);
copyInfo = copyInfo + "\n write frame occur error: "
+ to_string(ret);
break;
}
av_packet_unref(packet); // 清除数据包
}
10.写文件尾:
cpp
av_write_trailer(out_fmt_ctx); // 写文件尾
11.释放数据包资源/关闭输入流/释放封装实例/关闭音视频文件:
cpp
av_packet_free(&packet); // 释放数据包资源
avio_close(out_fmt_ctx->pb); // 关闭输出流
avformat_free_context(out_fmt_ctx); // 释放封装器的实例
avformat_close_input(&in_fmt_ctx); // 关闭音视频文件
11.向目标文件写入数据包的完整代码:
以上的代码放在本人的GitHub项目:https://github.com/wangyongyao1989/FFmpegPractices
中codecTraningLib模块的CopyMeidaFile.cpp类中:
cpp
//
// Created by wangyao on 2025/8/17.
//
#include "includes/CopyMeidaFile.h"
CopyMeidaFile::CopyMeidaFile() {
}
CopyMeidaFile::~CopyMeidaFile() {
}
string CopyMeidaFile::copyMediaFile(const char *srcPath, const char *destPath) {
// 打开音视频文件
int ret = avformat_open_input(&in_fmt_ctx, srcPath, nullptr, nullptr);
if (ret < 0) {
LOGE("Can't open file %s.\n", srcPath);
copyInfo = "Can't open file :" + string(srcPath);
return copyInfo;
}
LOGI("Success open input_file %s.\n", srcPath);
copyInfo = copyInfo + "\nSuccess open input_file :" + string(srcPath);
// 查找音视频文件中的流信息
ret = avformat_find_stream_info(in_fmt_ctx, nullptr);
if (ret < 0) {
LOGE("Can't find stream information.\n");
copyInfo = "\nCan't find stream information. ";
return copyInfo;
}
AVStream *src_video = nullptr;
// 找到视频流的索引
int video_index = av_find_best_stream(in_fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
if (video_index >= 0) {
src_video = in_fmt_ctx->streams[video_index];
}
AVStream *src_audio = nullptr;
// 找到音频流的索引
int audio_index = av_find_best_stream(in_fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
if (audio_index >= 0) {
src_audio = in_fmt_ctx->streams[audio_index];
}
AVFormatContext *out_fmt_ctx; // 输出文件的封装器实例
// 分配音视频文件的封装实例
ret = avformat_alloc_output_context2(&out_fmt_ctx, nullptr, nullptr, destPath);
if (ret < 0) {
LOGE("Can't alloc output_file %s.\n", destPath);
copyInfo = "\nCan't alloc output_file : " + string(destPath);
return copyInfo;
}
// 打开输出流
ret = avio_open(&out_fmt_ctx->pb, destPath, AVIO_FLAG_READ_WRITE);
if (ret < 0) {
LOGE("Can't open output_file %s.\n", destPath);
copyInfo = "\nCan't open output_file: " + string(destPath);
return copyInfo;
}
LOGI("Success open output_file %s.\n", destPath);
copyInfo = copyInfo + "\nSuccess open output_file :" + string(destPath);
if (video_index >= 0) { // 源文件有视频流,就给目标文件创建视频流
AVStream *dest_video = avformat_new_stream(out_fmt_ctx, nullptr); // 创建数据流
// 把源文件的视频参数原样复制过来
avcodec_parameters_copy(dest_video->codecpar, src_video->codecpar);
dest_video->time_base = src_video->time_base;
dest_video->codecpar->codec_tag = 0;
}
if (audio_index >= 0) { // 源文件有音频流,就给目标文件创建音频流
AVStream *dest_audio = avformat_new_stream(out_fmt_ctx, nullptr); // 创建数据流
// 把源文件的音频参数原样复制过来
avcodec_parameters_copy(dest_audio->codecpar, src_audio->codecpar);
dest_audio->codecpar->codec_tag = 0;
}
ret = avformat_write_header(out_fmt_ctx, nullptr); // 写文件头
if (ret < 0) {
LOGE("write file_header occur error %d.\n", ret);
copyInfo = "\n write file_header occur error: " + to_string(ret);
return copyInfo;
}
LOGI("Success write file_header.\n");
copyInfo = copyInfo + "\nSuccess write file_header.";
AVPacket *packet = av_packet_alloc(); // 分配一个数据包
while (av_read_frame(in_fmt_ctx, packet) >= 0) { // 轮询数据包
// 有的文件视频流没在第一路,需要调整到第一路,因为目标的视频流默认第一路
if (packet->stream_index == video_index) { // 视频包
packet->stream_index = 0;
ret = av_write_frame(out_fmt_ctx, packet); // 往文件写入一个数据包
} else { // 音频包
packet->stream_index = 1;
ret = av_write_frame(out_fmt_ctx, packet); // 往文件写入一个数据包
}
if (ret < 0) {
LOGE("write frame occur error %d.\n", ret);
copyInfo = copyInfo + "\n write frame occur error: " + to_string(ret);
break;
}
av_packet_unref(packet); // 清除数据包
}
av_write_trailer(out_fmt_ctx); // 写文件尾
LOGI("Success copy file.\n");
copyInfo = copyInfo + "\n Success copy file.";
av_packet_free(&packet); // 释放数据包资源
avio_close(out_fmt_ctx->pb); // 关闭输出流
avformat_free_context(out_fmt_ctx); // 释放封装器的实例
avformat_close_input(&in_fmt_ctx); // 关闭音视频文件
return copyInfo;
}
二.NdkMediaMuxer向目标文件写入数据包的操作:
上一篇 Android音视频编解码全流程之Extractor 已经对引入内部库libmediandk.so,及使用到一些基准类作了介绍,这里不再赘述。
1.媒体文件复用器类HwMuxer:
初始化提取器initMuxer() ------>提取mux()
核心函数initMuxer():
在AMediaMuxer_new()传入fd和输出文件格式outputFormat创建出AMediaMuxer------> AMediaMuxer_addTrack(mMuxer, mFormat)获取轨道的index------> AMediaMuxer_start(mMuxer) 开始复用器。
cpp
int32_t HwMuxer::initMuxer(int32_t fd, MUXER_OUTPUT_T outputFormat) {
if (!mFormat) mFormat = mExtractor->getFormat();
if (!mStats) mStats = new Stats();
int64_t sTime = mStats->getCurTime();
mMuxer = AMediaMuxer_new(fd, (OutputFormat)outputFormat);
if (!mMuxer) {
ALOGV("Unable to create muxer");
return AMEDIA_ERROR_INVALID_OBJECT;
}
/*
* AMediaMuxer_addTrack returns the index of the new track or a negative value
* in case of failure, which can be interpreted as a media_status_t.
*/
ssize_t index = AMediaMuxer_addTrack(mMuxer, mFormat);
if (index < 0) {
ALOGV("Format not supported");
return index;
}
AMediaMuxer_start(mMuxer);
int64_t eTime = mStats->getCurTime();
int64_t timeTaken = mStats->getTimeDiff(sTime, eTime);
mStats->setInitTime(timeTaken);
return AMEDIA_OK;
}
核心函数mux:
遍历vector<AMediaCodecBufferInfo> ------> AMediaMuxer_writeSampleData(mMuxer, 0, inputBuffer, &info) 把inputBuffer写入多路复用器中。
cpp
int32_t HwMuxer::mux(uint8_t *inputBuffer
, vector<AMediaCodecBufferInfo> &frameInfos) {
// Mux frame data
size_t frameIdx = 0;
mStats->setStartTime();
while (frameIdx < frameInfos.size()) {
AMediaCodecBufferInfo info = frameInfos.at(frameIdx);
media_status_t status = AMediaMuxer_writeSampleData(mMuxer
, 0, inputBuffer, &info);
if (status != 0) {
ALOGE("Error in AMediaMuxer_writeSampleData");
return status;
}
mStats->addOutputTime();
mStats->addFrameSize(info.size);
frameIdx++;
}
return AMEDIA_OK;
}
HwMuxer完整代码:
cpp
//
// Created by wangyao on 2025/9/20.
//
#include "includes/HwMuxer.h"
int32_t HwMuxer::initMuxer(int32_t fd, MUXER_OUTPUT_T outputFormat) {
if (!mFormat) mFormat = mExtractor->getFormat();
if (!mStats) mStats = new Stats();
int64_t sTime = mStats->getCurTime();
mMuxer = AMediaMuxer_new(fd, (OutputFormat)outputFormat);
if (!mMuxer) {
ALOGV("Unable to create muxer");
return AMEDIA_ERROR_INVALID_OBJECT;
}
/*
* AMediaMuxer_addTrack returns the index of the new track or a negative value
* in case of failure, which can be interpreted as a media_status_t.
*/
ssize_t index = AMediaMuxer_addTrack(mMuxer, mFormat);
if (index < 0) {
ALOGV("Format not supported");
return index;
}
AMediaMuxer_start(mMuxer);
int64_t eTime = mStats->getCurTime();
int64_t timeTaken = mStats->getTimeDiff(sTime, eTime);
mStats->setInitTime(timeTaken);
return AMEDIA_OK;
}
void HwMuxer::deInitMuxer() {
if (mFormat) {
AMediaFormat_delete(mFormat);
mFormat = nullptr;
}
if (!mMuxer) return;
int64_t sTime = mStats->getCurTime();
AMediaMuxer_stop(mMuxer);
AMediaMuxer_delete(mMuxer);
int64_t eTime = mStats->getCurTime();
int64_t timeTaken = mStats->getTimeDiff(sTime, eTime);
mStats->setDeInitTime(timeTaken);
}
void HwMuxer::resetMuxer() {
if (mStats) mStats->reset();
}
void HwMuxer::dumpStatistics(string inputReference, string componentName, string statsFile) {
string operation = "mux";
mStats->dumpStatistics(operation, inputReference, mExtractor->getClipDuration(), componentName,
"", statsFile);
}
int32_t HwMuxer::mux(uint8_t *inputBuffer, vector<AMediaCodecBufferInfo> &frameInfos) {
// Mux frame data
size_t frameIdx = 0;
mStats->setStartTime();
while (frameIdx < frameInfos.size()) {
AMediaCodecBufferInfo info = frameInfos.at(frameIdx);
media_status_t status = AMediaMuxer_writeSampleData(mMuxer, 0, inputBuffer, &info);
if (status != 0) {
ALOGE("Error in AMediaMuxer_writeSampleData");
return status;
}
mStats->addOutputTime();
mStats->addFrameSize(info.size);
frameIdx++;
}
return AMEDIA_OK;
}
2.媒体文件复用器类ProcessMuxer:
创建复用器HwMuxer及提取器类HwExtractor:
cpp
void
ProcessMuxer::startProcessMuxer(const char *srcPath, const char *outPath1,
const char *outPat2, const char *fmt) {
sSrcPath = srcPath;
sOutPath1 = outPath1;
sOutPath2 = outPat2;
sFmt = fmt;
LOGI("sSrcPath :%s \n sOutPath2: %s ", sSrcPath.c_str(), sOutPath2.c_str());
callbackInfo =
"sSrcPath:" + sSrcPath + "\n";
PostStatusMessage(callbackInfo.c_str());
outputFormat = getMuxerOutFormat(sFmt);
mHwMuxer = new HwMuxer();
if (mHwMuxer == nullptr) {
LOGE("Muxer creation failed ");
callbackInfo =
"Muxer creation failed \n";
PostStatusMessage(callbackInfo.c_str());
return;
}
mHwExtractor = mHwMuxer->getExtractor();
writeStatsHeader();
processProcessMuxer();
}
处理复用器的过程:
打开输入文件fopen() ------> HwExtractor初始化提取器initExtractor() ------> mHwExtractor->setupTrackFormat(curTrack)选择对应的轨道track ------> mHwExtractor->getFrameSample(info)提取frame数据 ------> 打开输出文件fopen() ------> mHwMuxer->initMuxer(fd, outputFormat)初始化 ------> mHwMuxer->mux(inputBuffer, frameInfos)进行复用操作。
cpp
void ProcessMuxer::processProcessMuxer() {
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", sOutPath1.c_str());
callbackInfo =
"Success open file:" + sOutPath1 + "\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(fd, fileSize);
if (trackCount < 0) {
LOGE("initExtractor failed");
callbackInfo = "initExtractor failed \n";
PostStatusMessage(callbackInfo.c_str());
return;
}
LOGI("initExtractor Success trackCount: %d", trackCount);
callbackInfo = "initExtractor Success trackCount:" + to_string(trackCount) + "\n";
PostStatusMessage(callbackInfo.c_str());
for (int curTrack = 0; curTrack < trackCount; curTrack++) {
int32_t status = mHwExtractor->setupTrackFormat(curTrack);
if (status != AMEDIA_OK) {
LOGE("Track Format invalid");
callbackInfo = "Track Format invalid \n";
PostStatusMessage(callbackInfo.c_str());
return;
}
LOGI("curTrack : %d", curTrack);
callbackInfo = "curTrack:" + to_string(curTrack) + "\n";
PostStatusMessage(callbackInfo.c_str());
uint8_t *inputBuffer = (uint8_t *) malloc(kMaxBufferSize);
if (inputBuffer == nullptr) {
LOGE("Insufficient memory");
callbackInfo = "Insufficient memory \n";
PostStatusMessage(callbackInfo.c_str());
return;
}
// AMediaCodecBufferInfo : <size of frame> <flags> <presentationTimeUs> <offset>
vector<AMediaCodecBufferInfo> frameInfos;
AMediaCodecBufferInfo info;
uint32_t inputBufferOffset = 0;
// Get Frame Data
while (1) {
status = mHwExtractor->getFrameSample(info);
if (status || !info.size) break;
// copy the meta data and buffer to be passed to muxer
if (inputBufferOffset + info.size >= kMaxBufferSize) {
LOGE("Memory allocated not sufficient");
callbackInfo = "Memory allocated not sufficient \n";
PostStatusMessage(callbackInfo.c_str());
return;
}
memcpy(inputBuffer + inputBufferOffset, mHwExtractor->getFrameBuf(), info.size);
info.offset = inputBufferOffset;
frameInfos.push_back(info);
inputBufferOffset += info.size;
}
string outputFileName = sOutPath2;
FILE *outputFp = fopen(outputFileName.c_str(), "w+b");
if (!outputFp) {
LOGE("Unable to open output file :%s", outputFileName.c_str());
callbackInfo =
"Unable to open output file:" + outputFileName + " for writing" + "\n";
PostStatusMessage(callbackInfo.c_str());
return;
}
LOGI("Success open file :%s", outputFileName.c_str());
callbackInfo =
"Success open file:" + outputFileName + "\n";
PostStatusMessage(callbackInfo.c_str());
int32_t fd = fileno(outputFp);
status = mHwMuxer->initMuxer(fd, outputFormat);
if (status != AMEDIA_OK) {
LOGE("initMuxer failed");
callbackInfo = "initMuxer failed \n";
PostStatusMessage(callbackInfo.c_str());
return;
}
LOGI("initMuxer Success");
callbackInfo = "initMuxer Success \n";
PostStatusMessage(callbackInfo.c_str());
status = mHwMuxer->mux(inputBuffer, frameInfos);
if (status != AMEDIA_OK) {
LOGE("Mux failed");
callbackInfo = "Mux failed \n";
PostStatusMessage(callbackInfo.c_str());
return;
}
LOGI("Mux Success");
callbackInfo = "Mux Success \n";
PostStatusMessage(callbackInfo.c_str());
mHwMuxer->deInitMuxer();
mHwMuxer->dumpStatistics(sSrcPath, sFmt, sOutPath1);
free(inputBuffer);
fclose(outputFp);
mHwMuxer->resetMuxer();
}
LOGI("processProcessMuxer Success");
fclose(inputFp);
mHwExtractor->deInitExtractor();
}
ProcessMuxer.cpp完整代码:
cpp
//
// Created by wangyao on 2025/9/22.
//
#include "includes/ProcessMuxer.h"
ProcessMuxer::ProcessMuxer(JNIEnv *env, jobject thiz) {
mEnv = env;
env->GetJavaVM(&mJavaVm);
mJavaObj = env->NewGlobalRef(thiz);
}
ProcessMuxer::~ProcessMuxer() {
mEnv->DeleteGlobalRef(mJavaObj);
if (mEnv) {
mEnv = nullptr;
}
if (mJavaVm) {
mJavaVm = nullptr;
}
if (mJavaObj) {
mJavaObj = nullptr;
}
if (inputFp) {
fclose(inputFp);
}
}
void
ProcessMuxer::startProcessMuxer(const char *srcPath, const char *outPath1,
const char *outPat2, const char *fmt) {
sSrcPath = srcPath;
sOutPath1 = outPath1;
sOutPath2 = outPat2;
sFmt = fmt;
LOGI("sSrcPath :%s \n sOutPath2: %s ", sSrcPath.c_str(), sOutPath2.c_str());
callbackInfo =
"sSrcPath:" + sSrcPath + "\n";
PostStatusMessage(callbackInfo.c_str());
outputFormat = getMuxerOutFormat(sFmt);
mHwMuxer = new HwMuxer();
if (mHwMuxer == nullptr) {
LOGE("Muxer creation failed ");
callbackInfo =
"Muxer creation failed \n";
PostStatusMessage(callbackInfo.c_str());
return;
}
mHwExtractor = mHwMuxer->getExtractor();
writeStatsHeader();
processProcessMuxer();
}
void ProcessMuxer::processProcessMuxer() {
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", sOutPath1.c_str());
callbackInfo =
"Success open file:" + sOutPath1 + "\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(fd, fileSize);
if (trackCount < 0) {
LOGE("initExtractor failed");
callbackInfo = "initExtractor failed \n";
PostStatusMessage(callbackInfo.c_str());
return;
}
LOGI("initExtractor Success trackCount: %d", trackCount);
callbackInfo = "initExtractor Success trackCount:" + to_string(trackCount) + "\n";
PostStatusMessage(callbackInfo.c_str());
for (int curTrack = 0; curTrack < trackCount; curTrack++) {
int32_t status = mHwExtractor->setupTrackFormat(curTrack);
if (status != AMEDIA_OK) {
LOGE("Track Format invalid");
callbackInfo = "Track Format invalid \n";
PostStatusMessage(callbackInfo.c_str());
return;
}
LOGI("curTrack : %d", curTrack);
callbackInfo = "curTrack:" + to_string(curTrack) + "\n";
PostStatusMessage(callbackInfo.c_str());
uint8_t *inputBuffer = (uint8_t *) malloc(kMaxBufferSize);
if (inputBuffer == nullptr) {
LOGE("Insufficient memory");
callbackInfo = "Insufficient memory \n";
PostStatusMessage(callbackInfo.c_str());
return;
}
// AMediaCodecBufferInfo : <size of frame> <flags> <presentationTimeUs> <offset>
vector<AMediaCodecBufferInfo> frameInfos;
AMediaCodecBufferInfo info;
uint32_t inputBufferOffset = 0;
// Get Frame Data
while (1) {
status = mHwExtractor->getFrameSample(info);
if (status || !info.size) break;
// copy the meta data and buffer to be passed to muxer
if (inputBufferOffset + info.size >= kMaxBufferSize) {
LOGE("Memory allocated not sufficient");
callbackInfo = "Memory allocated not sufficient \n";
PostStatusMessage(callbackInfo.c_str());
return;
}
memcpy(inputBuffer + inputBufferOffset, mHwExtractor->getFrameBuf(), info.size);
info.offset = inputBufferOffset;
frameInfos.push_back(info);
inputBufferOffset += info.size;
}
string outputFileName = sOutPath2;
FILE *outputFp = fopen(outputFileName.c_str(), "w+b");
if (!outputFp) {
LOGE("Unable to open output file :%s", outputFileName.c_str());
callbackInfo =
"Unable to open output file:" + outputFileName + " for writing" + "\n";
PostStatusMessage(callbackInfo.c_str());
return;
}
LOGI("Success open file :%s", outputFileName.c_str());
callbackInfo =
"Success open file:" + outputFileName + "\n";
PostStatusMessage(callbackInfo.c_str());
int32_t fd = fileno(outputFp);
status = mHwMuxer->initMuxer(fd, outputFormat);
if (status != AMEDIA_OK) {
LOGE("initMuxer failed");
callbackInfo = "initMuxer failed \n";
PostStatusMessage(callbackInfo.c_str());
return;
}
LOGI("initMuxer Success");
callbackInfo = "initMuxer Success \n";
PostStatusMessage(callbackInfo.c_str());
status = mHwMuxer->mux(inputBuffer, frameInfos);
if (status != AMEDIA_OK) {
LOGE("Mux failed");
callbackInfo = "Mux failed \n";
PostStatusMessage(callbackInfo.c_str());
return;
}
LOGI("Mux Success");
callbackInfo = "Mux Success \n";
PostStatusMessage(callbackInfo.c_str());
mHwMuxer->deInitMuxer();
mHwMuxer->dumpStatistics(sSrcPath, sFmt, sOutPath1);
free(inputBuffer);
fclose(outputFp);
mHwMuxer->resetMuxer();
}
LOGI("processProcessMuxer Success");
fclose(inputFp);
mHwExtractor->deInitExtractor();
}
MUXER_OUTPUT_T ProcessMuxer::getMuxerOutFormat(string fmt) {
static const struct {
string name;
MUXER_OUTPUT_T value;
} kFormatMaps[] = {{"mp4", MUXER_OUTPUT_FORMAT_MPEG_4},
{"webm", MUXER_OUTPUT_FORMAT_WEBM},
{"3gpp", MUXER_OUTPUT_FORMAT_3GPP},
{"ogg", MUXER_OUTPUT_FORMAT_OGG}};
MUXER_OUTPUT_T format = MUXER_OUTPUT_FORMAT_INVALID;
for (size_t i = 0; i < sizeof(kFormatMaps) / sizeof(kFormatMaps[0]); ++i) {
if (!fmt.compare(kFormatMaps[i].name)) {
format = kFormatMaps[i].value;
break;
}
}
return format;
}
bool ProcessMuxer::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(sOutPath1.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 *ProcessMuxer::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 ProcessMuxer::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();
}
}
三.FFmpeg/NdkMediaMuxer向目标文件写入数据包的操作的对比:
1.获取目标文件的复用器:
FFmpeg:通过avio_open(&out_fmt_ctx->pb, destPath, AVIO_FLAG_READ_WRITE) 得到out_fmt_ctx的封装实例。
NdkMediaMuxer:
FILE *outputFp = fopen(outputFileName.c_str(), "w+b") ------>
int32_t fd = fileno(outputFp) ------>
mMuxer = AMediaMuxer_new(fd, (OutputFormat)outputFormat) ------>
ssize_t index = AMediaMuxer_addTrack(mMuxer, mFormat); ------>
AMediaMuxer_start(mMuxer) 得到AMediaMuxer的复用器实例。
2.写入目标文件的操作:
FFmpeg :avformat_write_header()/av_write_frame()/av_write_trailer() 进行操作;
NdkMediaMuxer:AMediaMuxer_writeSampleData() 进行操作。
效果展示:
