使用FFMPEG库将内存中的H264跟AAC数据流合成MP4文件

之前使用的FFMPEG基本都是打开指定链接或者文件读取数据进行操作, 此次要实现的功能是在服务器中读取内存中的H264跟AAC裸流进行文件合成保存, 这就有些难度了, 经过了两个周的资料搜索研究终于完成了该功能, 在此记录说明下, DEMO开源地址:

https://gitee.com/careye_open_source_platform_group/MP4MuxerTest

首先要感谢两篇博客, 没有这两篇博客是几乎没法完成这个功能的:

雷神的:https://blog.csdn.net/leixiaohua1020/article/details/39802913

Jons伯恩特: https://blog.csdn.net/huangyifei_1111/article/details/46955021

总体思路是利用avio_alloc_context函数创建一个带回调读取数据的AVIOContext实体类, 这样就可以通过队列的方式将内存数据追加到队列中, 然后在ffmpeg读取媒体帧的时候在回调函数中输出队列中的数据, 这样达成使用FFMPEG读取内存数据的目的.

程序合成流程是追加数据后就进行流的读取, 所以自制了一个简单的队列结构, 定义如下:

// 用于存储流数据的队列项结构
typedef struct _CE_QUEUE_ITEM_T_
{
    // 申请的缓冲区指针
    uint8_t* Bytes;
    // 申请的缓冲区大小
    int TotalSize;
    // 入队填充的数据字节数
    int EnqueueSize;
    // 出队读取的数据字节数
    int DequeueSize;
}CEQueueItem;
 
// 一个简单的队列结构, 用于暂存流数据
typedef struct _CE_QUEUE_T_
{
    // 队列项指针
    CEQueueItem *Items;
    // 队列的标识, 使用者可以自定义标识进行队列区分
    uint8_t Flag;
    // 队列项个数
    int Count;
    // 出队计数索引
    int DequeueIndex;
    // 入队索引
    int EnqueueIndex;
}CESimpleQueue;
没有用到链表的结构, 使用数组的方式对队列进行管理.

混流器做成了一个实体类, 方便外部调用, 对外接口也非常简单:

/*
    * Comments: 混流器初始化 实例化后首先调用该方法进行内部参数的初始化
    * Param aFileName: 要保存的文件名
    * @Return 是否成功
    */
    bool MP4Muxer::Start(std::string aFileName);
 
    /*
    * Comments: 追加一帧AAC数据到混流器
    * Param aBytes: 要追加的字节数据
    * Param aSize: 追加的字节数
    * @Return 成功与否
    */
    bool MP4Muxer::AppendAudio(uint8_t* aBytes, int aSize);
 
    /*
    * Comments: 追加一帧H264视频数据到混流器
    * Param aBytes: 要追加的字节数据
    * Param aSize: 追加的字节数
    * @Return 成功与否
    */
    bool MP4Muxer::AppendVideo(uint8_t* aBytes, int aSize);
 
    /*
    * Comments: 关闭并释放输出格式上下文
    * Param : None
    * @Return None
    */
    void MP4Muxer::Stop(void);
Start则申请各种资源, 有些参数在方法实现内部固定了, 可根据需要自己进行修改, Stop则释放申请的各种资源并结束文件录制.

Append两个方法分别是追加音视频帧, 输入的数据要是一个完整帧, 另外做了一个该实体类的使用DEMO:

 
int main()
{
    AVFormatContext* fmt_h264 = NULL;
    AVFormatContext* fmt_aac = NULL;
    AVPacket pkt_v;
    int result = 0;
    bool video_finished = false, audio_finished = false;
 
    std::cout << "Start muxer..." << std::endl;
 
    av_register_all();
 
    if ((result = avformat_open_input(&fmt_h264, TestH264, 0, 0)) < 0)
    {
        DEBUG_E("Could not open input file: -%08X.", -result);
        goto end;
    }
    if ((result = avformat_find_stream_info(fmt_h264, 0)) < 0)
    {
        DEBUG_E("Failed to retrieve input stream information -%08X.", -result);
        goto end;
    }
 
    if ((result = avformat_open_input(&fmt_aac, TestAAC, 0, 0)) < 0)
    {
        DEBUG_E("Could not open input file: -%08X.", -result);
        goto end;
    }
    if ((result = avformat_find_stream_info(fmt_aac, 0)) < 0)
    {
        DEBUG_E("Failed to retrieve input stream information -%08X.", -result);
        goto end;
    }
 
    // 输出文件信息
    av_dump_format(fmt_h264, 0, TestH264, 0);
    av_dump_format(fmt_aac, 0, TestAAC, 0);
 
    // 实例化混流器
    MP4Muxer *muxer = new MP4Muxer();
    if (!muxer->Start("./test.mp4"))
    {
        delete muxer;
        DEBUG_E("Muxer start fail.");
        goto end;
    }
 
    while (1)
    {
        // 读取一帧视频数据
        if (!video_finished)
        {
            if ((result = av_read_frame(fmt_h264, &pkt_v)) < 0)
            {
                DEBUG_W("Read frame fail: -%08X.", -result);
                video_finished = true;
                if (audio_finished)
                {
                    break;
                }
            }
            // 需要确保传入的是一帧数据
            muxer->AppendVideo(pkt_v.data, pkt_v.size);
        }
 
        // 读取一帧音频数据
        if (!audio_finished)
        {
            if ((result = av_read_frame(fmt_aac, &pkt_v)) < 0)
            {
                DEBUG_W("Read frame fail: -%08X.", -result);
                audio_finished = true;
                if (video_finished)
                {
                    break;
                }
            }
            // 需要确保传入的是一帧数据
            muxer->AppendAudio(pkt_v.data, pkt_v.size);
        }
    }
 
    av_free_packet(&pkt_v);
    avformat_close_input(&fmt_h264);
    muxer->Stop();
    delete muxer;
 
end:
    _DEBUG_I("Muxer finished.\n");
    getchar();
}
DEMO就是利用FFMPEG读取一帧帧音视频数据, 然后将字节数组传入到混流器中进行合成, 示例中的音视频测试文件使用了雷神的264跟AAC文件.

话说真的不想写FFMPEG的博客, 每次都绕不过雷神的博客, 看到雷神的博客就伤感, 天妒英才, 哎~! 在此感谢各位开源先驱们~!

car-eye开源官方网址:www.car-eye.cn  

car-eye项目地址:https://github.com/Car-eye-team
--------------------- 
作者:路过之君 
来源:CSDN 
原文:https://blog.csdn.net/xinxinsky/article/details/88531524 
版权声明:本文为博主原创文章,转载请附上博文链接!

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值