【FFmpeg】音视频MP4封装格式转封装MOV

一.源码总览

cpp 复制代码
#include <iostream>
extern "C"
{
#include <libavformat/avformat.h>
}

int main()
{
    //1.打开输入文件
    const char* infile = "E:/videos/test.mp4";
    char outfile[] = "E:/videos/test.mov";
    AVFormatContext* ic = nullptr;
    int ret = avformat_open_input(&ic, infile, NULL, NULL);
    if (ret < 0)
    {
        char errbuf[1024];
        av_strerror(ret, errbuf, sizeof(errbuf));
        std::cout << "avformat_open_input failed : " << errbuf << std::endl;
        return -1;
    }

    // 扒开包裹看详细参数
    ret = avformat_find_stream_info(ic, nullptr);
    if (ret < 0)
    {
        std::cout << "获取流信息失败" << std::endl;
        avformat_close_input(&ic);
        return -1;
    }

    //2.创建输出上下文
    AVFormatContext* oc = nullptr;
    ret = avformat_alloc_output_context2(&oc,NULL,NULL,outfile);
    if (ret < 0)
    {
        char errbuf[1024];
        av_strerror(ret, errbuf, sizeof(errbuf));
        std::cout << "avformat_alloc_output_context2 failed : " << errbuf << std::endl;
        return -1;
    }

    //3.添加流
    AVStream* videoStream = avformat_new_stream(oc, NULL);
    AVStream* audioStream = avformat_new_stream(oc, NULL);

    //4.复制参数
    avcodec_parameters_copy(videoStream->codecpar, ic->streams[0]->codecpar);
    avcodec_parameters_copy(audioStream->codecpar, ic->streams[1]->codecpar);

    //5.标签清零
    videoStream->codecpar->codec_tag = 0;
    audioStream->codecpar->codec_tag = 0;
    av_dump_format(ic, 0, infile, 0);
    std::cout << "=====================================" << std::endl;
    av_dump_format(oc, 0, outfile, 1);

    //6.打开输出文件io,写入头部信息
    ret = avio_open(&oc->pb, outfile, AVIO_FLAG_WRITE);
    if (ret < 0)
    {
        std::cout << "avio open failed!" << std::endl;
        return -1;
    }

    //写入头
    ret = avformat_write_header(oc, NULL);
    if (ret < 0)
    {
        char errbuf[1024];
        av_strerror(ret, errbuf, sizeof(errbuf));
        std::cout << "avformat_write_header failed: " << errbuf << std::endl;
    }

    //写入帧数据
    AVPacket* pkt = av_packet_alloc();
    for (;;)
    {
        int re = av_read_frame(ic, pkt);        //读取帧
        if (re < 0)
            break;

        //根据time_base不同,进行转换
        pkt->pts = av_rescale_q_rnd(pkt->pts,
            ic->streams[pkt->stream_index]->time_base,
            oc->streams[pkt->stream_index]->time_base,
            (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)
        );
        pkt->dts = av_rescale_q_rnd(pkt->dts,
            ic->streams[pkt->stream_index]->time_base,
            oc->streams[pkt->stream_index]->time_base,
            (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)
        );
        pkt->pos = -1;
        pkt->duration = av_rescale_q_rnd(pkt->duration,
            ic->streams[pkt->stream_index]->time_base,
            oc->streams[pkt->stream_index]->time_base,
            (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)
        );

        av_interleaved_write_frame(oc, pkt);    //写入帧
        av_packet_unref(pkt);
    }
    av_write_trailer(oc);

    av_packet_free(&pkt);
    avio_close(oc->pb);
    avformat_close_input(&ic);
    avformat_free_context(oc);

    return 0;
}

二、流程拆解

第一步:打开输入文件,摸底源文件 (输入管家)

在内存中建立"输入大管家"。它像 X 光机一样扫描 MP4,摸清了里面有 H.264 视频轨和 AAC 音频轨,拿到了第一手资料。

对应代码块:

cpp 复制代码
    //1.打开输入文件
    const char* infile = "E:/videos/test.mp4";
    char outfile[] = "E:/videos/test.mov";
    AVFormatContext* ic = nullptr;
    int ret = avformat_open_input(&ic, infile, NULL, NULL);
    if (ret < 0)
    {
        char errbuf[1024];
        av_strerror(ret, errbuf, sizeof(errbuf));
        std::cout << "avformat_open_input failed : " << errbuf << std::endl;
        return -1;
    }

    // 扒开包裹看详细参数
    ret = avformat_find_stream_info(ic, nullptr);
    if (ret < 0)
    {
        std::cout << "获取流信息失败" << std::endl;
        avformat_close_input(&ic);
        return -1;
    }

第二步:创建输出文件上下文凭空捏造图纸 (输出空壳)

在内存中新建一个针对 MOV 格式的"输出空壳"。注意:此时这只是个内存图纸,硬盘上依然什么都没有发生。

对应代码块:

cpp 复制代码
    //2.创建输出上下文
    AVFormatContext* oc = nullptr;
    ret = avformat_alloc_output_context2(&oc,NULL,NULL,outfile);
    if (ret < 0)
    {
        char errbuf[1024];
        av_strerror(ret, errbuf, sizeof(errbuf));
        std::cout << "avformat_alloc_output_context2 failed : " << errbuf << std::endl;
        return -1;
    }

第三步:克隆轨道数据

根据旧管家探明的参数,在新管家这里铺设对应的视频/音频轨道,并 1:1 复印参数说明书。清除旧标签,准备生成新暗号。

对应代码块:

cpp 复制代码
    //3.添加流
    AVStream* videoStream = avformat_new_stream(oc, NULL);
    AVStream* audioStream = avformat_new_stream(oc, NULL);

    //4.复制参数
    avcodec_parameters_copy(videoStream->codecpar, ic->streams[0]->codecpar);
    avcodec_parameters_copy(audioStream->codecpar, ic->streams[1]->codecpar);

    //5.标签清零
    videoStream->codecpar->codec_tag = 0;
    audioStream->codecpar->codec_tag = 0;

第四步:点亮硬盘,写入文件头

关键时刻!打通内存到硬盘的 I/O 管道,在硬盘真正创建 test.mov 文件,并把图纸上的"轨道参数说明书"写在文件的最头部。

对应代码块:

cpp 复制代码
   //6.打开输出文件io,写入头部信息
   ret = avio_open(&oc->pb, outfile, AVIO_FLAG_WRITE);
   if (ret < 0)
   {
       std::cout << "avio open failed!" << std::endl;
       return -1;
   }

   //写入头
   ret = avformat_write_header(oc, NULL);
   if (ret < 0)
   {
       char errbuf[1024];
       av_strerror(ret, errbuf, sizeof(errbuf));
       std::cout << "avformat_write_header failed: " << errbuf << std::endl;
   }

第五步:死循环搬运与时间换算

买一个"结实的空信封(Packet)"。不断从旧管家摸出数据,经过"时间比例尺"的精准换算,变成 MOV 的时间,再交织写入新文件,最后倒空信封复用。

这里由于MP4与MOV的时间基数(time_base)不同,需要换算一下pts,bts,duration等。(具体自行查询)

对应代码块:

cpp 复制代码
    //写入帧数据
    AVPacket* pkt = av_packet_alloc();
    for (;;)
    {
        int re = av_read_frame(ic, pkt);        //读取帧
        if (re < 0)
            break;

        //根据time_base不同,进行转换
        pkt->pts = av_rescale_q_rnd(pkt->pts,
            ic->streams[pkt->stream_index]->time_base,
            oc->streams[pkt->stream_index]->time_base,
            (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)
        );
        pkt->dts = av_rescale_q_rnd(pkt->dts,
            ic->streams[pkt->stream_index]->time_base,
            oc->streams[pkt->stream_index]->time_base,
            (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)
        );
        pkt->pos = -1;
        pkt->duration = av_rescale_q_rnd(pkt->duration,
            ic->streams[pkt->stream_index]->time_base,
            oc->streams[pkt->stream_index]->time_base,
            (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)
        );

        av_interleaved_write_frame(oc, pkt);    //写入帧
        av_packet_unref(pkt);
    }

第六步:写入文件尾,全剧终

极其致命的收尾!把最重要的轨道索引表写入文件尾端(救活文件)。然后销毁信封、拔掉管道、拆除所有内存管家,完美退场。

代码:

cpp 复制代码
av_write_trailer(oc);
相关推荐
m0_726365831 天前
Ai漫剧系统 几分钟,让AI 把一篇小说变成了一部漫剧成片:从剧本到视频的全流程系统实现
人工智能·语言模型·ai作画·音视频
非凡ghost1 天前
可拓浏览器:给手机浏览器装上“外挂“!2W+拓展+AI搜索,玩出无限可能!
windows·智能手机·音视频·firefox
美狐美颜SDK开放平台1 天前
多场景美颜SDK解决方案:直播APP(iOS/安卓)开发接入详解
android·人工智能·ios·音视频·美颜sdk·第三方美颜sdk·短视频美颜sdk
ai产品老杨1 天前
深度解析:基于国产化异构计算的 AI 视频管理平台架构——从 GB28181 接入到 NPU 边缘推流的解耦实践
人工智能·架构·音视频
watson_pillow1 天前
音视频相关基础知识储备入门-字幕
音视频
程序员JerrySUN1 天前
Jetson边缘嵌入式实战课程第二讲:JetPack 和 SDK Manager 是什么
c语言·开发语言·网络·udp·音视频
happybasic1 天前
在CMD下使用FFmpeg将.wav文件转换成指定的格式~
ffmpeg
weixin_6682 天前
NVIDIA VSSVideo Search and Summarization视频搜索与摘要蓝图详尽使用说明与技术报告版本
人工智能·音视频
jiayong232 天前
国内外视频/图像大模型与智能体工具平台竞品对比
ai·音视频·agent
视频技术分享2 天前
技术赋能生态革新:音视频产业开启千亿增长新周期 视频会议成核心增长亮点
音视频