跳至主要内容

FFmpeg 深度学习笔记 (二):Remuxing (转封装) 的艺术 - 2_remuxing.c 详解

· 閱讀時間約 19 分鐘
w0x7ce
MySelf

发布于 2025-05-17 17:17:21(微信公众号导出记录)。

本文来自公众号后台的“导出文章内容”功能。博客正文由导出长图进行本地 OCR 转写,并保留原始排版图用于逐段核对。

原文链接:查看原文

OCR 转写有效文字约 11225 字;代码、流程图和版式以文末原始排版图为准。

正文(本地 OCR 转写)

FFmpeg深度学习笔记(二):Remuxing(转封装)的艺术- 2_remuxing.c详解 original we×x7ce EI V1ajero 2825年5月17日 17:17 日本 在上一篇笔记中,我们通过 e_hello_world.c学习了FFmpegLibav库的基础解码流程和媒体信 息获取。现在,我们将进入下一个重要主题:Remuxing(转封装)。 Remuxing是一种在不重新编码音视频内容的前提下,改变其容器格 式的强大技术,它快速且能保持原始质量。本篇将以Leandromoreira/ffn peg-Libav-tutorial项目中的2_renuxing.c为例,深入探讨其背后的理 论、应用场景、FFmpegAPI的使用方法,并详细解析主流容器格式 的特性及Remuxing的无损本质。

一、Remuxing(转封装)核心理论与原理

要造彻理解Remuxing,首先需要明确媒体文件的基本构成及其与编码、封装的关系。

1.1媒体文件的构成:数据流(ELementaryStreams)与容器(Container

Format) 由两部分组成:

1.数据流(ELementary Streams -ES):

  • 这些是纯粹的、经过特定编解码器(如H.264视频、AAC音)编码(压缩)后的音

视频数据,例如,H.264视频流就是一连电按照H.264标准压缩的图像数据:AAC 音频流就是一连事AAC编码的音频数据。

  • 致据流本身只包含原始的压继信息,通常不包含直接插放所需的全部同步和元数据信息。

?.容器格式(Container Format):

  • 组织与复用(Multiplexing-Huxing):将一个或多个数据流(视频流、一个或多

个音频流、字幕流、章节信息等)按照特定的规则打包、交织进一个单一的文件中。

  • 元数据(Metadata):存储关于整个文件和内部各个流的描述性信息。这可以包括标

题、作者、专辑封面、视频的分辨率、帧率、音频的采样率、声道数、字幕语言、章节标 记等等。

  • 同步(Synchronization):提供精确的时间醛信易,主要是PTS(Presentation

Timestamp-显示时间戳)和 DTS(Decode Timestamp-解码时间戳)。PTS 指示一个数据单元(如一帧视频或一段音频)应该在何时呈现给用户(播放)。DTS指 示一个压缩的数据包应该在何时被解码器解码。对于包含B帧的视频,DTS和PTS的顺 序可能不同。容器格式负责存储这些时间数,握放器则依据它们来确保音视频同步和流畅 推放。

  • 索引(Indexing)(可选):某些容器格式会包含索引信息(例如,关键帧的位置列

表)。这使得播放器能够快速定位(seek}到文件的任意时间点,而不需要从头扫描整 个文件。

  • 可以将其比作媒体文件的“包装盒"或"信封"(例如.np4,.nkV,-t5,fL

V)。容器并不关心里面装的“货物”(音视频数据)具体是什么编码(只要它支持这种编 码类型),它的主要职责是;

1.2到底什么是Remuxing(转封装)?

L.定义:Remuxing是一个过程,它在完全不改变内部数据流(即音视频的编玛数据本身, 例如H.264的比特流、AAC的比特流)的前提下,将这些数据流从一种容器格式中“拆 解”出来,然后再“封装“到另一种新的容器格式中。 ?,改变的是什么:

  • 客器本身的结构和组织方式(例如,MP4的aton/box结构与TS的固定包结构完全

不同)。

  • 元效据的存储和表示方法。

之-).

  • 文件头(Header)和文件尾(Trailer)的特定信息。

  • 可能的索引方式

3.不变的是什么:

  • 视频流的编码格式(如H.264,HEVC,VP9)及其底层的压缩比特流数据。

  • 音频流的编码格式(如AAC,MP3,Opus)及其底层的压缩比特流效据。

  • 字幕流(如果是基于文本的,如SRT,ASS)的内容本是。

  • 核心结论:因此,Renuxing对音视频的画质和音质理论上是完全无损的。

1.3Remuxing vs.Transcoding(转码/压缩)-关键区别十

理解 Remuxing 和 Transcoding 的区别至关重要:

  • Remuxing(转封装):

  • 操作对象:容器格式

  • 音视频数据:不解码,不重新编码。编码数据被原样复制

  • 质量损失:无损(针对音视频编码数据本身)。

  • 速度:非常快,主要受限于磁盘I/0.

  • 目的:改变文件“外壳”以适应不同播放环境或需求,不改变“内容”的编码。

  • Transcoding(转码):

  • 如果目标编码是有损压缩(如H.264,HEVC,AAC,MP3),几乎总是会伴随质量损

失,每次有损压缩都会为了减小体积而牺牲一部分信息。

  • 如果目标编码是无损压缩(如FLAC音频,FFV1视频),则相对于解码后的原始数据

是无损的,但无法恢复源文件在有损压缩时已丢失的信息。

  • 操作对象:音视频流的编码格式或编码参数(如码率、分辨率、躺率)。

  • 音视频数据:通常需要先解码成原始数据,然后用新的编码格式或参数量新编码。

  • 质量损失:

  • 速度:通常非常慢,因为编解码是计算密集型操作。

  • 目的:改变音视频”内容"的编钙方式,以减小体积、提高压缩率、薰容特定设备(该设

备不支持源编码格式)或改变媒体属性。 简单来说:Remuxing是给书换个封皮,书的内容不变;Transcoding(有损压缩时)像是 把书的内容重写一遍并进行删减概括,内容会发生变化(通常是信息量的减少)。

1.4Remuxing的核心操作原理(基于Libavformat)

FFnpeg的Lilbavfornat库处理Remuxing的过程可以概括为以下几个步骤,这正是2 remuxing.c所澳示的: L.解封装(Denuxing)输入端:

  • 通过avfornat open input(1打开输入的媒体文件,FFmpeg 会自动识别其容器格

  • 调用avfornat_find_strcan_info()读取文件头部和一部分数据,以获取文件中所有

流的详细信息。此步骤会填充每个输入流的AVCodecParameters结构体,该结构体描 述了流是如何被编码的。

2.配置输出端:

■调用avfornat_ncw_strean()在输出AVFornatContext中创建一个对应的新流。 【至关量要的一步】调用avcodec_paraneters_copy()将输入流的AVCodecParom eters完整地复制到新创建的输出流的AvCodecParaneters 中。因为 Renuxing 不改变编码,所以这份推述对于输出流来说仍然是准确的。它告诉新的封装器(muxer) 即将写入的数据包具有何种编码特性。

  • 使用avfornat_allac_autput_contextz(1根据用户提供的输出文件名(通过其扩展

名推断)或直接指定的格式名称,来分配并初始化一个用于输出的AVFormatContex t。

  • 遍历输入AVFarnatContext中的每一个流。如果决定要将某个输入流复制到输出文

件:

3.写入输出文件头:

  • 如果输出格式需要一个物理文件(绝大多数情况如此),则调用av1o_openI)来打开

指定的输出文件并准备写入。这会将一个AVICContext(I/O上下文)关联到输出 VFormatContext 的 pb (Packet Buffer)成员。

  • 调用avfornat_write_header》将输出容器的头部信息写入文件。此时,所有流

的AVcodecParaneters必须已经设置完毕。如果需要为 muxer 设置特定选项(例 如,为MP4设置movflag5以生成分段MP4),这些选项会在此函数误用时传递给 nuxer。

1.逐包复制与时间戳调整(核心循环):

  • 一个AVPacket包含PTS(显示时间截),DTS(解码时间戳)和 duration(时

长)等时间信息。这些值是以其所在流的时间基(AVStrean->tine_base,一个分数, 如1/90806表示时间单位是1/9008秒)来表示的。 ■输入流的时间基和输出流的时间基可能不同。

  • 因此,在将包写入输出流之前,必震使用av_rescale_q_rnd(1或av_rescale_q

[)函数,将包的 PTS,DTS 和 duration 从输人流的时间基精确地转换(或“缩 放”)到输出流的时间基。如果忽略这一步,将导致输出文件播故时速度异常、音视颜不 同步等严量问题。

  • 在一个循环中,反复调用av_rcad_franc)从输人AVFommatContcxt中读取一

个AVPacket。这个包包含了某个流的一小段压缩数据及其原始时间戳。

  • 检查这个包所属的流是否是我们需要复制的流,

  • 【极其关键的技术点】时间戳量计算(TinestampRescaling):

  • 通常将packet.pDs(包在源文件中的物理位置信息)设为-1。

  • 调用av_interleaved write_frane)将调整好时间的AVPacket写入输出AV

FormatContcxt。此函数会负责根据输出容器的要求,正确地交织来自不同流的数据 包。

  • 调用av_packet_unref4)释放当前包所引用的数据缓冲区。

5.写入输出文件尾:

  • 当所有数据包都从输入读取并写入辅出后,调用av_uritetrailer(》。这个函数会写

入输出容器格式所要求的任何尾部信息(例如索引表、文件校验和等)。

二、Remuxing的需求与原因:为什么要转封装?何时使用?

A.核心忧势回题:1.速度极快。2.质量无损(针对音视频编码数据)。 B.主要应用场景与原因分析: l.解决播放/编辑盖容性问题(CompatibiLity):

  • 原因:不同设备、软件或服务器对容器格式的支持各异。

  • 场景示例:MKV转MP4(用于移动设备),MTS转MP4(用于编辑软件)。

2.满足特定功能或特性需求(Feature Support):

  • 原因:不同容器对多音轨、多字幕、章节、元数据等特性的支持程度不同。

  • 场景示例:MP4转MKV以添加ASS特效字幕和多语言评论普轨

3.适应流媒体传输协议的要求(Streaming ProtocolRequirements):

  • 原因:HLS、DASH等流媒体技术对媒体段的容器格式有特定要求。

  • 场景示例:将完整MP4 转为 HLS的,t5段或DASH/fMP4 HLS 的分段MP4(

m4s )。2_renuxing.c 中的 fragniented_mp4_options 即为此服务。

1.优化文件结构或修复轻微损坏(Optimization/Repair):

  • 原因:MP4的noavatom在尾部不利于网络流握“快速启动";文件索引可能损

  • 场景示例:Renuxing MP4将moovatom 移到文件头部;尝试通过renuxing

修复损坏的索引。 .去除不需要的流(Strean Stripping/ Selective Copying):

  • 原因:文件中包含多种语言的音轨、字幕等,实际只需一部分。

  • 场景示例:Remuxing时只选择需要的流,减小文件体积。

i.合并或分割媒体文件(Concatenating/SpLitting):

  • 原因:便于管理、分享或后续处理。

  • 场景示例:将多个编码参数相同的视颜片段合并;将长视频分割核心操作仍基于包复

制和时间酸调整。

三、2_remuxing.c代码逻辑详解

以下是对2_reuxing.c核心代码段落的分析和解释: //based on https:/ fapeg-org/doxygen/ Lrunk/remuxing_8c-example.htm #include <libavutil/timestanp.hx include<libavfornat/avtormat.h>// 包含了格式处理的核心API int main(int argc, char **argv) ( AVFormatContext *1nput_fornat_context = NULL, *output_fornat_cont: AVPacket packet; const char *in_filenane, out_filename; int ret, 1; int *streans_list = NULL; 1nt number_of_streams = 0; int fragmented_mp4 options = B; /11.解析命令行餐数 if (argc < 3) { printf(*You need to pass at least tuo pareneters.\nusage: %5 return -1; } else if (argc == 4 && strcnp(argv[3]。 *fragnented") == θ) { fragnented_np4_options = 1; printf(Fragmented MP4 options enabled \n); in_filenane = argv[1];

112.打开轴入文件并获取流信息

1f ((ret = avfornat_open_input(&input_fornat_context, 1n_filename fprintf(stderr, “Could not open input file &s': es\n, in_fi gota end; If({ret = avfornat_find_strean_info(input_format_context, NULL)) fprintf(stderr, "Failed to retrieve input stream infornation: goto end; // av_dunp_format(input_format_context, o, in_filenane, θ); //3.为输出文件分配 AVFormatContext avformat_alloc_output_contextz(5output_fornat_context, NULL, NULL 1f (!output_fornat_context){ fprintf(stderr, “Could not create output contextyn*); ret = AVERDR_UNKNOWN; goto end; 1/4.流的映射与创建 number_of_streans = input_fornat_context->nb_streams; streans_list = av_mallocz_array (nurher_of_streans, sizeof(*streas 1f (1streans_list){ ret = AVERRDR[ENOMEN); goto end; pua p↓6 for (i = θ; i < input_format_context->nb_streans; i++) { AVStrean *out_stream; AVStrean *in_strean = input_format_context->streams[il; AVCodecParancters *in_codecpar = in_stream->codecpar;

In_codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) { streans_list[i] = -1; continuc; streans_list[i] = stream_index++: out_strean = avformat_new_stream(output_format_context, NULL) if (!out_stream) { fprintf(stderr, Failed allocating output strean\n); ret = AVERROR_UNKNONN; :pua ojo6

  • 核心:复制编解码参数

ret = avcodec_parameters copy{out _strean->codecpar, in_codecp- if (ret < ){ fprintf(stderr, *Falled to copy codec parameters: as\n", :pua ojo6 out_strean->codecpar->codec_tag = 0; // av_dunp_format(output_formot_context, 0, out_filenane, 1); //5.打开输出文件I0 if (1(output_fornat_context->oformat->flags & AVFMT_NOFILE)) { ret = avio_open(&output_format_context->pb, out_fiLenane, AVI if (ret < ){ fprintf(stderr, *Could not open output file *5s": %s\n", goto end;

116.设置Muxer特定进项并写人输出文件头

AVDictionary* opts = NULL; 1f (fragnented_np4_options) { av_dict_set(sopts, *novflags", "frag_keyframe+empty_moov+defa ret - avfornat_write_header(output_format_context, &opts); if (ret < θ) { fprintf(stderr, "Errar occurred when opening output file or w qoto end; } av_dict_free(&opts) ; //7.Renuxing核心循环:读取包->调整时衍露->写人包 while (1){ AVStrean *in_strean, *out_stream; ret = av_read_frame(input_format_context, &packet); if (ret < B) break; in_strean = input_format_context->streans [packet.strean_inde: if (packet.stream_index >= mumber_of_streams I| streans_1list[ av_packet_unref(6packet); continue; packet.strean_index = streans_list[packet.stream_index]; L 1 : ut_strean = output_fornat_context ->streans [packet ,stream_ind

  • 核心:时问数转换*

packet -pts = av_rescale_q_rnd(packet -pts, in_stream->tine_bas packet.duratlon = av_rescale_q(packet.duration, in_strean->t11 . 2 ] packet.pos = -1; ret = av_lnterleaved_write_frame(output_format_context, &pack if (ret < ){ fprintf(stderr, *Error muxing packet: %s'n", av_errzstr(n break; av_packet_unref (6packet); 写入轴出文件尾 8/ av_write_traller(output_tornat_context); cnd : 1f (input_fornat_context) avformat_close_input(&input_format_cont: avio_closcp(&output_format_context->pb); 1f (output_fornat_context) avfonmat_free_context(output_fornat_co

1.3 9

if (streans_list) av_freep(&streams_list); 1f (ret < 0 66 ret != AVERROR_EOF) { fprintf(stderr, Error occurred: ss\n, av_err2str(ret)); return 1; printf( Renuxing completed successfully-\n); return 8;

四、主流容器格式特性详解

理解各种容器的特性,有助于我们决定何时需要将一种格式RemuX到另一种。 A.MP4(MPEG-4 Part 14)*文件扩展名:m04,.n4a(纯音频),n4v(视频),.mov (QuickTime 格式,MP4 的重要基础)*结构:基于 IS0 Base Media File Format [ISOBMFF),由“aton”或“box”组成(ftyP,moov 存元数据和索引l,mdat 存媒体致 据)。*优点:兼容性极佳、流媒体友好(特别是fMP4和moov在头部的“fast start” MP4)、特性支持良好(章节、字幕、多种编码)、高度标准化。*献点:高级字幕支持不如 MKV:ncov在尾部不利于流播,*典型应用:Web视频、移动设备视频、视频点播、 DASH/HLS fMP4 流媒体、数字发行。 B.TS(MPEG Transport Stream)文件扩展名:ts,-tsv,.tsa,.m2ts(蓝光) 结构:固定长度(188字节)的TS包,每个包有PID区分流。PAT/PMT表描述节目和 流。*优点:强大的错误恢复能力、为流式传输设计(直播/广播)、支持多节目。*缺点:封 装开销较大、文件seek不便、元数据支持有限、*典型应用:数字电视广播、早期HLS、蓝 光 C.MKV(Matroska Video)文件扩展名:.nkv(视频),.nka (纯音疑),.nks(字慕) 结构:基于EBML(类似XML的二进制语言),高度可扩展。主要元素Scgnent(含ScekH d,Info,Tracks,Chapters,Cues,Cluster)。*优点:极致灵活开放(可封装几平任 何流),特性支持强大(多字幕轨、多音轨、章节、刚件、精确时间致、错误检测),开源。 缺点:薰容性不如MP4广泛(尤其非PC设备):直接大规模流媒体应用不如fMP4。*典型 应用:高清电影存储分享(多语言/字幕)、个人媒体库。 (音、视频、脚本数据)。*优点:曾为网络流式传输设计良好(配合RTMP),封装开销较 小。*缺点:技术逐渐过时(Flash淘汰),编码器支持有限,特性支持少。*典型应 用:历史遗留内容、部分仍在使用的RTMP推流场景。

五、关于Remuxing无损转换的进一步探讨

L.音视频编码数据的无损性:正如之前强调的,Remuxing的核心在于不解码也不重新编码 音视频数据流本身。这意味着视频帧的压缩数据(如H.264比特流)和音频样本的压缩数据 (如AAC比特流)被原封不动地从旧容器“搬运"到新容器。因此,就音视频内容的感知质量 和编码精度而言,Remuxing是无损的。 ?.“反复转换不丢失精度”的理解:

  • 基于上述无损原理,理论上,如果目标容器支持源容器中的所有音视频编码格式,那么可

以反复在这些容器之间进行Remuxing(例如MP4<->MKV<->TS),核心音视频 数据不会因为容器的改变而发生质量下降或编码精度丢失。

3.需要注意的细微之处(潜在的“非完全字节等同"):虽然核心音视频数据无损,但在反复转

换或在特性差异较大的容器间转换时,以下方面可能会引入微小变化,导致最终文件与原始 文件在字节层面上不完全相同,尽管播放效果和核心数据质量一致:

  • 元数据(Metadata):不同容器对元数据(标题、作者、章节、封面等)的支持范围

和存储方式不同。从特性丰富的容器转到特性较少的容器时,无法表示的元数据可能会丢 失。即使是相同元数据,在不同容器间的表示也可能需要适配,导致字节差异。丢失的元 数据在转回原格式时无法恢复。

  • 字幕(SubtitLes):文本字幕(SRT,ASS)通常能良好保留。但对于ASS/SSA

特效字幕,MKV支持完美,MP4支持有限,转换可能导致样式信息丢失或改变。

  • 时间戳精度与时间基(TimestampPrecisionandTimebase):FFmpeg会尽力

精确转换时间戳。但如果时间基差异悬殊且进行极多次转换,理论上极微小的浮点舍入误 差可能累积。实际几次转换中,这通常可忽略,不影响同步或感知质量。

  • 容器特定特性(Container-SpecificFeatures):某些容器独有的底层特性或标

志位,在其他容器中无对应物,转换时会丢失。

  • Muxer(封装器)的行为差异:FFmpeg中每种容器格式的muxer在写入文件时,

可能有其默认行为或优化策略(如数据包交织方式、文件头可选字段默认值)。这可能导 致即使核心数据相同,多次往返转换后的文件在字节上也不完全一致。

1.总结与实际建议:

  • 对于音视频内容的质量和编码精度:Remuxing是无损的。

  • 对于文件的“绝对字节同一性”:不能保证。若此为硬性要求,需谨慎。

  • 元数据:依赖特定高级元数据时,转换到支持较弱的容器需注意信息丢失风险。

  • 结论:对于绝大多数为了兼容性或功能需求而改变容器格式的场景,Remuxing都是快

速、安全且在音视频质量上无损的理想选择。

六、2_remuxing.c的学习意义与拓展方向

2_remuxing.c不仅演示了如何改变文件的“外壳”,更重要的是,它揭示了Libavforma t在处理输入(demuxing)和输出(muxing)时的对称性与通用性。通过此示例,可以深 刻理解:

  • AVCodecParameters在不解码情况下描述数据流本质的核心作用。

  • 时间戳处理(特别是时间基转换)在Remuxing中的绝对重要性。

  • 如何通过AVDictionary向Muxer传递特定选项以微调输出容器行为。

基于此示例的拓展方向: L.选择性Remuxing:修改streams_List的逻辑,允许用户通过命令行参数指定要保留 哪些类型的流(例如,只保留视频和英文音轨)。

2.元数据编辑:在avformat_write_header之前,修改output_format_context->met

adata或各个output_stream->metadata来添加、删除或修改文件的全局或流元数据。

3.简单的流剪辑(基于关键帧):结合av_seek_frame定位到起始和结束的关键帧,然后

只复制这两个关键帧之间的包。

1.Remuxing网络流:将输入文件名in_filename替换为网络流地址(如rtm

p://...,http://...),实现网络流的录制或转封装;或将输出out_filename配置 为网络推流地址。 通过对2_remuxing.c的学习和实践,为后续更复杂的FFmpeg操作,如Transcoding (转码)和Filter(滤镜处理),打下了坚实的基础。

ffmpeg·目录三 <上一篇 下一篇> FFmpeg深度学习笔记(三): 示例深度解析系列一 Transcoding(转码)的艺术与实践- 作者提示:个人观点,仅供参考

原始排版图

FFmpeg 深度学习笔记 (二):Remuxing (转封装) 的艺术 - 2_remuxing.c 详解:微信公众号导出原始排版图