Android音视频编解码全流程之Muxer
系列文章:
Android音视频编解码全流程之Extractor
Android音视频编解码全流程之Muxer
承接上篇文章 Android音视频编解码全流程之Extractor ,媒体文件提取(Extractor)并读取每个数据包后向目标文件一次写入每个数据包。
下面分别介绍FFmpeg向目标文件写入数据包的操作和Android中Muxer向目标写入数据包的操作。
一 .FFmpeg向目标文件写入数据包的操作:
首先ffmpeg交叉编译Android平台,在之前的博客中有介绍交叉编译的全过程,感兴趣的可以查看博客:
Android Liunx ffmpeg交叉编译
macOs上交叉编译ffmpeg及安装ffmpeg工具
1.声明一个AVFormatConext结构体的指针变量:
AVFormatContext *in_fmt_ctx = avformat_alloc_context();
2.调用avformat_open_input函数指定文件路径:
// 打开音视频文件int ret = avformat_open_input(&in_fmt_ctx, srcPath, nullptr, nullptr);
3.调用avformat_find_stream_info函数查找文件中的数据流:
// 查找音视频文件中的流信息ret = avformat_find_stream_info(in_fmt_ctx, nullptr);
4.调用av_find_best_stream分别查找视频流及音频流的索引
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.分配音视频文件的封装实例:
AVFormatContext *out_fmt_ctx; // 输出文件的封装器实例// 分配音视频文件的封装实例ret = avformat_alloc_output_context2(&out_fmt_ctx, nullptr, nullptr, destPath);
6.打开输出流:
ret = avio_open(&out_fmt_ctx->pb, destPath, AVIO_FLAG_READ_WRITE);
7.拷贝源文件的参数:
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.写文件头:
ret = avformat_write_header(out_fmt_ctx, nullptr); // 写文件头
9.轮询数据包并写入数据包:
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.写文件尾:
av_write_trailer(out_fmt_ctx); // 写文件尾
11.释放数据包资源/关闭输入流/释放封装实例/关闭音视频文件:
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类中:
//
// 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) 开始复用器。
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写入多路复用器中。
int32_t HwMuxer::mux(uint8_t *inputBuffer, vector<AMediaCodecBufferInfo> &frameInfos) {// Mux frame datasize_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完整代码:
//
// 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 datasize_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:
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)进行复用操作。
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 propertiesstruct 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 Datawhile (1) {status = mHwExtractor->getFrameSample(info);if (status || !info.size) break;// copy the meta data and buffer to be passed to muxerif (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完整代码:
//
// 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 propertiesstruct 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 Datawhile (1) {status = mHwExtractor->getFrameSample(info);if (status || !info.size) break;// copy the meta data and buffer to be passed to muxerif (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() 进行操作。