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

效果展示:

相关推荐
ℳ₯㎕ddzོꦿ࿐2 天前
告别延迟:HLS (m3u8) 实时转 FLV 全栈方案实战
视频编解码
霜雪i3 天前
ISP模式
视频编解码·h.266
CheungChunChiu3 天前
视频编解码与 GOP 结构详解
linux·视频编解码
深圳市友昊天创科技有限公司7 天前
友昊天创推出8K ,4K 120Hz 100米延长器方案
音视频·实时音视频·视频编解码
深圳市友昊天创科技有限公司7 天前
ASM4242 雷电扩展坞 CV4242 PCIE扩展坞
音视频·实时音视频·视频编解码
深圳市友昊天创科技有限公司7 天前
友昊天创推出延长器方案GSV5600+HDBase VS010**/VS100**
音视频·实时音视频·视频编解码
TEL1892462247711 天前
IT6227:USB-C(DP Alt 模式)及电源传输控制器 内置 4 通道 DP 1.4 转 HDMI 2.1 转换器
音视频·实时音视频·视频编解码
小咖自动剪辑14 天前
视频去水印与去字幕教程:免费去水印软件与去字幕工具推荐
人工智能·音视频·实时音视频·视频编解码
TEL1892462247715 天前
IT6225B:USB-C(DP Alt 模式)及电源传输控制器 内置双通道 DP 1.4 转 HDMI 2.0 转换器
音视频·实时音视频·视频编解码
小脑斧要动脑16 天前
视频编解码开发工具合集
视频编解码