Android音视频编解码全流程之Muxer

系列文章

Android音视频编解码全流程之Extractor

Android音视频编解码全流程之Muxer


承接上篇文章 Android音视频编解码全流程之Extractor ,媒体文件提取(Extractor)并读取每个数据包后向目标文件一次写入每个数据包。

下面分别介绍FFmpeg向目标文件写入数据包的操作Android中Muxer向目标写入数据包的操作

一 .FFmpeg向目标文件写入数据包的操作:

首先ffmpeg交叉编译Android平台,在之前的博客中有介绍交叉编译的全过程,感兴趣的可以查看博客:

Android Liunx ffmpeg交叉编译

macOs上交叉编译ffmpeg及安装ffmpeg工具

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() 进行操作。

效果展示:

相关推荐
xiaohouzi11223312 天前
Python读取视频-硬解和软解
python·opencv·ffmpeg·视频编解码·gstreamer
TSINGSEE21 天前
数据库选择有讲究?SQLite、PostgreSQL还是MySQL?
mysql·音视频·实时音视频·数据库架构·视频编解码
Font Tian23 天前
FFmpeg 不同编码的压缩命令详解
ffmpeg·音视频·视频编解码·视频·视频压缩
Natsume171023 天前
音视频开发入门:FFmpeg vs GStreamer,新手该如何选择?
c语言·c++·ffmpeg·音视频·webrtc·实时音视频·视频编解码
DogDaoDao1 个月前
视频软解码技术详解:原理、应用与未来发展
ffmpeg·音视频·实时音视频·视频编解码·hevc·视频解码·视频软解码
DogDaoDao2 个月前
深入理解VideoToolbox:iOS/macOS视频硬编解码实战指南
macos·ios·音视频·实时音视频·视频编解码·videotoolbox·硬编码
DogDaoDao2 个月前
WebRTC音视频编码模块深度解析:从编解码器到自适应码率控制(2025技术实践)
音视频·webrtc·实时音视频·视频编解码·h264·vp9·svc编码
朱古力(音视频开发)2 个月前
NDI开发指南
fpga开发·音视频·实时音视频·视频编解码·流媒体
Kandiy180253981872 个月前
DP4871音频放大芯片3W功率单通道AB类立体声/音频放大器
音视频·音频·视频编解码