大家好!我是大聪明-PLUS!

作者长期以来一直致力于为 Windows 应用程序开发多媒体支持。最初,他使用 DirectShow,但后来接触到了 FFmpeg 项目,其强大的功能、多功能性和灵活性深深吸引了他。在使用 FFmpeg 的过程中,他编写了大量代码:一个用于 FFmpeg API 的 C++ 封装器,以及一些用于 .NET 的实用工具和 GUI 解决方案。当这些成果达到一定成熟度后,他希望与编程社区分享,同时阐述他对 FFmpeg 架构以及计算机多媒体的理解。
介绍
本文首先简要概述了 FFmpeg,然后详细描述了其内部结构,包括术语、组件和库。接下来,简要介绍了 FFmpeg 的构建过程以及如何使用 FFmpeg API 进行编程。最后,阐述了作者提出的解决方案并提供了下载链接。
这是作者撰写的第三篇关于 FFmpeg 的文章。由于 FFmpeg 的架构和代码库不断发展演进,编解码器文章中的一些信息需要稍作调整。
1. FFmpeg 的一般特性
FFmpeg 是一个庞大的开源项目,堪称计算机多媒体的百科全书。它的名字来源于运动图像专家组 (MPEG) 和 FF,FF 代表"快进"。它的创始人和领导者是 Fabrice Bellard 和 Michael Niedermayer。
在这个项目中,开发者们力求整合计算机多媒体领域几乎所有已知的解决方案,包括一些较为罕见的方案。使用完整版的 FFmpeg 时,遇到未知容器、编解码器或协议的可能性极小。
FFmpeg 包含八个库和三个应用程序,可以编译用于多个平台,并且几乎不需要任何外部组件或框架。然而,这种便利性是有代价的:完整版 FFmpeg 7.1 的库总大小超过 150 MB(例如,FFmpeg 4.0 "仅" 64 MB)。不过,在 64 位操作系统和 TB 级硬盘盛行的时代,这可能并非一个关键问题。此外,如有必要,您可以自定义构建,仅保留必要的组件。
FFmpeg 有效地利用了现代处理器架构,包括多核、多线程、矢量指令,并且还支持图形处理器。
FFmpeg 库和应用程序代码是用 C 语言编写的,除少数例外情况外,主要使用 C11 规范。公开提供的 FFmpeg API 头文件与 C99 规范兼容。该项目采用单体架构;任何必要的更改,例如添加组件,都必须修改项目代码并重新构建。这种架构与基于独立组件和插件的架构有着本质区别。(例如 DirectShow 或 GStreamer 就属于此类架构。)
FFmpeg 包含三个控制台应用程序ffmpeg------`cmpeg`、`cmpeg`ffplay和ffprobe`cmpeg`------可通过命令行进行配置。它还提供 API,用于创建自定义的基于 FFmpeg 的解决方案。此外,它还提供 C 头文件以及链接库所需的必要文件,方便开发 C/C++ 程序。秉承开源原则,这些库和应用程序均为开源软件,这对于解决复杂问题大有裨益。
2. FFmpeg 术语、主要组件和库
在本节中,我们将回顾 FFmpeg 中使用的术语,描述其组件及其主要交互场景,以及这些组件所在的库。请注意,下面描述的所有库都使用libavutil.
本节内容也可视为对计算机多媒体内部结构的相当一般性的描述,而与所使用的软件工具无关。
2.1 选项
选项用于对许多组件进行额外配置。选项是一组键值对。键是字符串。在使用选项的函数接口中,值也指定为字符串,但实际上,值可以是不同的类型。容器用于存储字符串对集合AVDictionary。内部使用结构体来表示选项AVOption。选项是一种高度灵活且用途广泛的组件配置机制。
2.2 媒体容器
媒体源通常被称为媒体容器或简称容器。这个术语很适合指包含在文件中的媒体,但对于 FFmpeg 可以处理的其他媒体源,例如不包含在单个文件中的媒体、通过网络或其他通信渠道传输的媒体、来自媒体采集设备或由其他生成器生成的媒体,可能显得不太合适。然而,我们仍然会在这些上下文中使用该术语,原因有二:首先,使用单一术语比较方便;其次,FFmpeg 提供了一系列抽象概念,可以对各种媒体源进行一致的处理。
2.3. 解复用
FFmpeg 组件(称为解复用器)用于从媒体容器中检索数据。解复用器位于libavformat.
2.3.1. 格式
解复用器的关键特征是其格式,由一个字符串标识。每个解复用器都有其格式,FFmpeg 中没有两个解复用器使用相同的格式。因此,我们可以讨论容器格式,这意味着可以使用具有该格式的解复用器访问容器。
要打开解复用器,需要设置三个参数:
- URL(统一资源定位符)
- 格式;
- 选项。
让我们更详细地考虑一下这些参数。
URL 是一个必需参数,它是一个字符串,例如文件路径或网络源地址。该字符串并非总是互联网上某个资源的地址;通常,其结构取决于具体的格式。URL 可以以 `.fmpeg`proto_name://或`.fmpeg` 前缀proto_name:开头,该前缀决定了接收数据的协议。FFmpeg 支持数十种协议。主要的协议file:当然是文件协议(可以省略,如果指定,则采用 ` .fmpeg hls...rtsp从视频或音频采集设备接收数据时,未指定协议。
格式是解复用器的一个关键特性;没有格式就无法打开文件。不过,FFmpeg 通常会根据 URL 和容器头部信息来判断格式,在这种情况下无需显式指定。例如,对于文件容器,格式通常与文件扩展名相关联。此类扩展名的示例包括:avi.c mp4、mkv.d/ 、.c、.d/、.c mov、flv.d ogg/、.c、wav.d/ mp3。
选项用于明确协议和格式细节,使用频率很低。对于某些格式(通常称为原始格式),必须使用选项,因为容器本身并不包含解复用所需的所有参数。第 2.9 节提供了示例。
2.3.2. 流和数据包
成功打开解复用器后,媒体数据即可使用,并被分割成多个流(结构AVStream)。流的数量可以是一个或多个,具体取决于解复用器的格式和容器的媒体数据。流通过索引进行引用。
媒体类型
流最重要的特征是它所代表的数据的媒体类型(媒体类型枚举AVMediaType)。FFmpeg 支持的主要媒体类型是视频和音频。另一种媒体类型是字幕。此外还有两种辅助媒体类型:数据和附件。解复用器可能无法检测到流的媒体类型,在这种情况下,流的媒体类型被视为未知。
一个容器可以包含多个相同媒体类型的流,例如,一个多声道环绕声音频流和一个简单立体声音频流。它还可以包含多个字幕流,每个字幕流使用特定的语言。当存在多个相同媒体类型的流时,通常会将其中一个标记为"默认";当没有其他流选择条件时,将选择此流。有时可以使用整数流标识符来区分相同媒体类型的流。
编解码器 ID
流以某种压缩或编码格式表示媒体数据,因此,流除了媒体类型之外最重要的特征就是所表示数据的压缩格式或编码格式。(在此上下文中,"压缩"和"编码"是同义词。)在 FFmpeg 中,这种格式称为编解码器标识符(枚举AVCodecID)。请注意,"编解码器"(编码器/解码器)一词通常用于指代执行编码或解码的软件或硬件组件,即编码器和解码器,但在此处,编解码器标识符标识的是压缩(编码)格式。
编解码器标识符的详细描述见结构体AVCodecDescriptor。该结构体的关键成员是编解码器标识符(成员id),其他成员提供附加信息。首先是媒体类型(成员type),以及唯一名称(成员name)。因此,我们可以讨论编解码器标识符的名称。以下是一些名称示例:h264,,,,,。此名称主要用于 FFmpeg 内部工作;容器内部使用不同的标识符。流通常以某种形式表示编解码器标识符hevc,具体取决于容器格式。它可以是字符串(例如 Matroska 中的 CodecTag)或数字(例如 WAV 文件中的四字节 FourCC 或两字节 FormatTag)。在 FFmpeg 中,编解码器标识符是一个枚举元素,即一个整数常量,解复用器必须根据从流中提取的数据确定此常量。如果失败,则流的编解码器标识符未知。vp9``aac``mp3``pcm_mulaw
媒体流的其他特征
除了媒体类型和编解码器标识符之外,解复用器还可以确定流所代表的媒体数据的其他特征,但这些特征主要取决于流的媒体类型。例如,对于视频,这包括帧大小;对于音频,这包括声道数量和布局以及采样率。
包裹
流所代表的媒体数据被组织成称为数据包(结构AVPacket)的对象。解复用器可以从容器中按顺序提取数据包。提取数据包后,它可以确定该数据包属于哪个流(stream_index结构成员AVPacket)。无法从特定流中提取数据包。流更像是一个逻辑类别,它将具有共同特征的数据包分组在一起。在容器内部,来自不同流的数据包是物理混合的。有关数据包在容器内放置原则的更多信息,请参阅第 2.11.2 节。
2.3.3. 元数据和章节
容器可以包含元数据------一组字符串键值对。元数据可以包含相当任意的数据,但也存在一些传统格式,例如键为 `<value>` title、 `<value>` album、artist`<value>`、 `<value> copyright`comment等的元数据。流也可以包含元数据;存在几种标准格式,例如键为 `<value>`、`<value>` 等的元数据language。encoder解复用器会将元数据加载到容器中AVDictionary。但是,需要注意的是,并非所有格式都支持元数据。
某些文件容器也支持章节。章节是指由开始时间和结束时间定义的媒体播放区间,并且可能包含元数据(通常带有键title)。这些数据会被加载到文件结构中AVChapter。支持章节的格式示例包括 Matroska 文件*.mkv和 QuickTime/MOV 文件*.mov。
2.4 解码
2.4.1. 解码器
如果已知流的编解码器标识符且需要进一步处理数据包,则必须打开解码器。解码器是一种软件组件,其关键特征是编解码器标识符。解码器可以解压缩(解码)具有相应编解码器标识符的压缩(编码)数据,即将其转换为已知未压缩格式的数据。解码器位于库中libavcodec。如果为流定义了编解码器标识符,则可以为其打开默认解码器。(极少数情况下,编解码器标识符没有对应的解码器,此时必须由外部应用程序处理数据包。)所有解码器都有一个唯一的名称。通常,默认解码器名称与编解码器标识符名称相同(例如,`<codec_id>`、`<codec_id>`、`<codec_id>`、`<codec_id> h264` hevc)vp9,aac但这并非强制要求,甚至可能造成混淆。(例如,对于编解码器标识符 `<codec_id>`,mp3默认解码器为 `<codec_id> mp3float`。)许多编解码器标识符对应多个解码器,要打开备用解码器,需要知道其名称。
解码器可以支持在打开解码器时指定的选项,但这很少见,只有一个例外:如果解码器支持多线程解码,threads则可以使用此选项指定使用的线程数。
2.4.2. 视频和音频帧
我们来看一下视频和音频流解码器的工作原理。如果解码器成功启动,它会接收从流中提取的数据包作为输入,并将解码后的数据(称为帧AVFrame)从解码器的输出端提取出来。"帧"这个术语不仅适用于视频,也适用于音频。还要注意的是,每个输入数据包并不总是产生一个输出帧;可能产生零个或多个帧(当然,通常情况下帧与输入帧的比例是 1:1)。帧的特征在于其媒体类型以及其他取决于媒体类型的参数。
视频帧的一个关键特征是其像素格式(枚举AVPixelFormat)。FFmpeg 支持超过两百种像素格式。每种像素格式都有一个字符串表示;例如:yuv420p[此处应列出像素格式]、[此处应列出像素格式]、 [此处应列出像素格式]、[此处应列出像素格式]、[此处应列出像素格式]、[此处应列出像素nv12格式]。视频帧的另一个特征是其像素尺寸------宽度和高度。bgra``rgb24``gray8``pal8
音频帧的关键特征是采样格式(枚举AVSampleFormat)。共有十二种采样格式。每种采样格式都有一个字符串表示,例如fltp12、13、14、15、16 。音频帧的特征还包括通道的数量和布局(通道布局)、采样率(采样率)以及每个通道中的样本数,后者称为帧大小。s16p``flt``s16
从解码器中提取的帧格式(即像素或采样点的格式)是解码器的一个重要特性。视频解码器最常用的两种格式是[yuv420p和nv12]。它们是采用色度子采样(4:2:0)的 YCbCr 颜色模型的平面格式,平均每个像素的位数为 12 位。平面性意味着亮度分量 Y 存储在单独的数组中。[和 ] 是音频解码器最常用的格式fltp。这是一种符合 IEEE Float 标准的 32 位平面格式;平面性意味着每个通道的采样点是连续排列的。较少使用的s16p还有平面 16 位 PCM16 格式;其他格式则非常罕见。
解码器支持多种输出格式。打开解码器时,默认格式会自动选择。如果按名称打开解码器,则需要查询其输出格式,并指定其中一种作为要使用的输出格式。
2.4.3. 未压缩数据
流也可以表示未压缩的数据,但其处理方式仍然相同。必须为流定义一个编解码器标识符,并且必须存在相应的解码器。解码器可以非常简单;有时它只是简单地复制数据。例如,对于存储 PCM16 数据的 WAV 文件,单个流的编解码器标识符定义为pcm_s16le0x0 ...pcm_s16be``pcm_s16le``s16``pcm_s16be``big-endian
2.4.4. 字幕
字幕处理也遵循上述模式。字幕流具有编解码器标识符(例如 `<subtitle_id> subrip`)和相应的解码器。字幕包的解码方式与视频或音频不同;解码需要使用一个特殊的函数。该函数的输出是由结构体 `<subtitle_id>` 描述的对象AVSubtitle。该结构体包含指定字幕在帧中的位置及其起始和结束时间的成员。字幕文本可以采用多种形式:纯文本、特殊的 ASS(Substation Alpha)格式文本或位图。文本变体使用 UTF-8 编码。
如果需要多种语言的字幕,则会为每种语言创建一个单独的字幕流。该字幕流必须包含元数据,其中包含一个键language,其值是根据 ISO 639-2 标准编写的三字母语言代码(eng、ger、rus 等)。
字幕流可以与音频和视频流放在同一个容器中,也可以放在单独的容器中。有几种专门用于字幕的格式,例如srtSubRip 字幕。这种格式的文件(扩展名为 .src srt)存储的数据包含编解码器标识符subrip。
2.4.5. 提高视频解码速度
随着4K(超高清)等高分辨率视频的出现,高效的视频流解码问题变得尤为紧迫。在普通计算机上,可能需要采取特殊措施才能实时解码此类视频流。
FFmpeg 有三种方法可以提高解码速度。
- 连接额外的执行线程(线程)进行解码;为此,可以使用解码器选项
threads。 - 启用硬件加速。在这种情况下,解码器将以特殊模式打开,启用基于 GPU 的硬件加速。具体的硬件加速选项由一个字符串标识,例如 `--hardware-acceleration-options`
dxva2。可用的硬件加速选项取决于操作系统和已安装的硬件。 - 使用专用的解码器,这些解码器利用 GPU 进行解码。FFmpeg 提供了两类这样的解码器:一类使用采用 QSV 技术的 Intel GPU,另一类使用采用 CUVID 技术的 NVIDIA GPU。这些解码器的名称分别带有 `--cvd`
_qsv或 ` --cvd` 后缀_cuvid。针对最常用的编解码器标识符,例如h264_qsv`cvd`、 `cvd`hevc_qsv、 `cvd`vp9_qsv、h264_cuvid`cvd` 和hevc_cuvid`cvd`,都存在相应的解码器vp9_cuvid。
请注意,并非所有解码器都支持额外的流和硬件加速,但最流行的解码器,例如h264[[[ hevc[ [[[ [ [ [ ...vp9``theora
2.5. 处理媒体文件的一些特点
上述处理媒体数据的方案非常灵活;特别是,可以处理存储单个图像(*.png、、、、等)或简单动画( )*.jpeg的文件。*.bmp``*.tiff``*.gif
例如,我们可以将 PNG 文件的路径作为 URL 传递给解复用器来打开它。解复用器将成功打开,并且文件格式将被确定为png_pipe(管道 PNG 序列)。此时将有一个视频流可用,该视频流的编解码器 ID 为 `<codec ID>` png,对应的默认解码器名称也相同。该视频流将包含一个数据包,解码后生成的视频帧的像素格式为 `<pixel format>` rgba。
在某些情况下,FFmpeg 可以处理乍看之下似乎与多媒体无关的数据。例如,我们可以将一个纯文本文件(扩展名为 .txt txt)的路径作为 URL 传递给 FFmpeg 来打开解复用器。解复用器会成功打开,我们可以在媒体播放器中看到该文件的内容,并以视频片段的形式显示。在这种情况下,文件格式会被识别为ttyTeletypewriter(电传打字机),只有一个视频流可用,该视频流的编解码器标识符为ansi,对应的默认解码器名称也相同,解码帧的像素格式为pal8。默认情况下,帧大小为 640x400,帧速率为 25 fps,但这些参数可以通过选项进行更改。
2.6. 使用滤镜进行格式转换和帧处理
从解码器提取的视频和音频帧格式通常非常特殊;虽然这种格式便于编码/解码算法,但可能无法用于后续用途。因此,解码后通常需要将帧转换为另一种格式。这可以通过两种方式实现。
- 使用特殊组件:视频帧的缩放器(视频缩放器)和音频帧的重采样器(音频重采样器)。这些组件分别位于 `<library_name>`
libswscale和 ` <library_name>` 库中libswresample。缩放器允许您更改视频帧的像素格式和尺寸。重采样器允许您更改音频帧的采样格式、声道数量和布局以及采样率。 - 构建滤波器图。最简单的滤波器图执行与重缩放器或重采样器相同的功能,但其功能更加强大------它可以连接滤波器,滤波器是用于额外帧处理的专用组件。滤波器可以串联起来,也可以构建更复杂的结构。滤波器图和滤波器本身可以有多个输入和输出。甚至还有一些特殊的滤波器,它们的输入和输出媒体类型不匹配。滤波器可以支持允许动态更改滤波器参数的命令。滤波器图和滤波器位于库中
libavfilter。该库中的滤波器支持各种各样的帧变换。例如,有一个滤波器subtitles可以将字幕叠加到视频帧上。该滤波器可以使用播放容器的字幕流,也可以使用外部容器的字幕流。
帧的后续处理取决于应用程序的用途。媒体播放器会渲染帧,即将其输出到可视化或音频播放设备。转码器会对帧进行编码,并将其写入目标媒体容器。其他应用程序可能会执行专门的帧处理,例如机器视觉系统或人脸搜索和识别系统。
2.7. 比特流过滤器
FFmpeg 还包含称为比特流过滤器的组件。这些组件对数据包进行转换,而无需对其进行解码。比特流过滤器位于libavcodec.
2.8. 将数据写入媒体容器
FFmpeg 允许您创建媒体容器并向其中填充数据。一个名为复用器 (muxer) 的组件用于创建媒体容器。复用器位于库中libavformat。复用器的关键特性在于其格式本身。因此,FFmpeg 支持的每种格式都对应一个解复用器 (demuxer)、一个复用器 (muxer) 或两者兼有。要打开一个复用器,您需要指定与解复用器相同的参数------URL、格式和选项。解复用器中描述的这些参数同样适用于复用器的参数。打开复用器后,您需要添加所需的流。对于每个流,您需要指定媒体类型、编解码器标识符以及一些其他参数,具体取决于媒体类型。打开流后,您可以将压缩(编码)数据写入其中。请注意,这种压缩可能是虚拟的;如上所述,某些编解码器标识符实际上代表的是未压缩的数据。
如果源数据未压缩,则需要打开对应编解码器标识符的编码器。编码器通常简称为编码器。编码器位于库中libavcodec。编码器接收包含未压缩数据的帧作为输入。每个编码器都有其自身可接受的帧格式。在编码器的输出端,包含压缩数据的包被提取出来,并传递给多路复用器以写入容器。要打开编码器,您需要知道它的名称。此外,您几乎总是需要指定一组选项,这些选项决定了压缩数据的特性和编码器的运行功能。
在某些情况下,可以控制编码速度;对于某些编码器,您可以指定编码线程数并定义硬件加速模式。此外,还有一些编码器支持 GPU 编码技术,例如 Intel QSV 或 NVIDIA NVENC。这些编码器的名称分别带有后缀 `-QSV`_qsv或 ` -NVENC` _nvenc。
在某些情况下,不使用编码器;压缩数据包会从一个媒体容器中提取出来并写入另一个媒体容器。数据包数据可能经过比特流过滤器处理,也可能保持不变。
2.9 编解码器 ID 和容器格式
格式作为一个抽象概念,与任何媒体类型或编解码器标识符都没有严格的联系,但对于特定格式而言,这种联系可能存在。
许多编解码器标识符都有一个专用于存储具有该编解码器标识符的数据的容器格式。在这种情况下,通常只能包含单个数据流,并且不支持元数据。这些所谓的原始格式主要用于从媒体采集设备检索数据。通常,此类格式的名称与其编解码器标识符相匹配,但这并非强制要求。以下是一些匹配的示例:,,,,。h264然而,对于许多以前缀开头的未压缩(或轻度压缩)音频编解码器标识符,其对应的格式名称不同,这是通过移除该前缀得到的。hevc``rawvideo``aac``flac``pcm_
为了使解复用器能够将数据流分割成数据包,rawvideo未压缩的音频格式需要额外的选项:对于视频,这些选项包括像素格式(pixel_format)、帧大小(video_size)和帧速率(framerate);对于音频,这些选项包括采样率(sample_rate)和声道数(channels)。其他原始格式也可能需要额外的选项。
有些格式专为特定媒体类型的数据而设计,但允许使用不同的编解码器标识符。WAV 格式可能是最广为人知的。它可以用于具有不同编解码器标识符的音频数据。这些数据可以是未压缩的,也可以是压缩的。例如,未压缩的数据可以使用pcm_s16lePCM16 或pcm_f32leIEEE Float 编解码器标识符。压缩数据可以使用最简单的压缩类型pcm_mulaw,pcm_alaw也可以使用更复杂的压缩类型,例如mp3。
最常用的容器格式,例如文件扩展名分别为 .v1 、avi.v2 、.v3、.v4、.v5 等的格式,支持具有不同媒体类型和编解码器 ID 的多个流。这些格式通常支持元数据,有些格式甚至支持章节。然而,需要注意的是,尽管这些格式功能多样,但某些格式可能会对流的媒体类型和编解码器 ID 施加一些限制。mp4``mkv``ogg``flv``mov
总的来说,FFmpeg 的命名系统有时会让人困惑。你可能会遇到编解码器标识符、解码器、编码器和容器格式都使用相同名称的情况(例如 [ aac]),但在使用 FFmpeg 时,务必清楚地了解每个名称在特定上下文中指的是哪个实体。
2.10. 与时间打交道
在多媒体应用中正确处理时间的重要性不言而喻。FFmpeg 使用时间基来指定时间,以秒为单位,用有理数表示;例如,1/1000 表示毫秒。(C++11 标准库也采用了类似的方法chrono。)流、编解码器、章节和其他组件都使用时间基,相应的结构体包含一个成员time_base。FFmpeg 使用该结构体来表示有理数AVRational。
帧、数据包和章节都带有时间戳。相应的结构体包含一些成员,int64_t这些成员的值以特定单位表示时间。例如,结构体AVFrame包含一个成员pts(呈现时间戳),其值决定了帧中捕获的场景的相对时间。
在某些情况下,FFmpeg 使用标准时间单位,即微秒(1/AV_TIME_BASE)。
对于视频流,帧速率也由一个有理数指定,例如,24000/1001(NTSC 电影帧速率)几乎是每秒 24 帧。
这种方法可以尽可能精确地计算所需时间,避免累积舍入误差。
2.11. 其他
本节探讨两个常用术语,由于它们在英语中表面上相似,有时会被混淆。此外,还会讨论"非正方形像素"的问题。
2.11.1. 交错
电视刚出现时,由于技术原因,帧的形成需要两次扫描------一次扫描奇数行,一次扫描偶数行。(行数从1开始编号;)如果帧的形成只需一次扫描,则称为逐行扫描。目前,几乎所有模拟摄像机都使用隔行扫描;数码摄像机并非总是使用隔行扫描,但有时也会使用。如果使用隔行扫描获得帧,则可以将其分成两半------一半用于奇数行,另一半用于偶数行。这两半称为场,奇数行用上场表示,偶数行用下场表示。在编码过程中,每个场单独编码。编码后,这两个场可以放在同一个数据包中,也可以放在两个单独的场中。扫描顺序也可以不同:可以先生成上场(上场优先),也可以先生成下场(下场优先)。在解码过程中,解码器将这两个场合并成一个帧。在动态场景中,这类帧会呈现出一种称为"梳状效应"的特征缺陷。这是因为,随着场景的变化,图像中的奇数行和偶数行在第二次扫描时会"分开"。这种现象在高对比度区域通常尤为明显。
为了减少这种影响(虽然无法完全消除),这类帧需要进行一种称为反交错的特殊处理。FFmpeg 有几个滤镜可以解决这个问题,其中最著名的是yadif(另一个反交错滤镜)。
反交错滤波器通常有两种主要模式:第一种模式为每个输入帧生成一个输出帧,而第二种模式为每个场生成输出帧,从而使每个输入帧生成两个输出帧,帧速率翻倍。例如,yadif第一种模式通过设置参数来指定mode=send_frame,第二种模式通过设置参数来指定mode=send_field。
解复用器可以(尽管并非总是如此)将扫描类型作为视频流数据的特征之一来确定。扫描类型也可以直接从解码帧中确定;该结构AVFrame包含成员interlaced_frame和top_field_first。
有时您可能会遇到帧中场顺序错误的情况,即下场被放置在上场的位置,上场被放置在下场的位置(解码器错误地确定了扫描顺序)。这会导致图像质量下降(尽管乍一看可能并不明显)。在这种情况下,反交错滤波器可以再次发挥作用;例如,yadif有一个参数parity可以控制场顺序。此外,还有一个特殊的滤波器fieldorder可以交换场。
2.11.2. 交错
为了同步不同媒体类型的流,来自不同流但时间戳相近的数据包必须在容器中彼此靠近。在这种情况下,数据包被称为交错式数据包。(众所周知的AVI格式代表音频视频交错格式。)如果此条件不满足,流同步就会出现问题,需要在内存中维护大量的数据包队列。
FFmpeg 通常使用函数将数据包写入容器av_interleaved_write_frame(),以确保数据包顺序正确。这是通过避免立即写入数据包,而是先将它们放入数据包队列来实现的。然而,需要注意的是,该函数存在局限性;在组织数据包写入时,必须谨慎操作,避免出现严重的同步问题。
2.11.3. 样品长宽比
某些摄像机的水平和垂直方向每毫米像素数 (ppm) 并非正方形,这可以理解为像素并非正方形。像素的宽高比由一对称为样本宽高比 (SAR) 的数字来描述。对于正方形像素,SAR 为 1:1,但对于非正方形像素,SAR 则不同。如果显示视频帧的设备具有相同的垂直和水平分辨率,则包含非正方形像素的帧将以不成比例的方式显示,需要进行校正才能正确显示。SAR 是视频流数据的一个特性,解复用器可以确定它。如果已知 SAR,则可以使用重缩放器或滤波器图轻松执行必要的校正。
3. 构建 FFmpeg
与其他许多开源解决方案一样,FFmpeg 可以从源代码独立构建,并根据您的需要进行配置。
FFmpeg 的构建过程对于类 Unix 操作系统来说相当传统:它使用 shell、shell 脚本configure(shebang #!/bin/sh)、Makefile 文件和 GCC C 编译器。然而,对于 FFmpeg 而言,这项任务远非易事。让我们来看看主要的挑战。
3.1 外部库
FFmpeg 构建过程中遇到的第一个问题是它使用了大量的外部库来扩展其功能。从项目网站下载的代码远非 FFmpeg 的全部;许多重要组件都包含在外部库中,而且这类库有几十个。例如,许多编码器(例如libx264、libmp3lame)都包含在外部库中,许多滤镜也需要外部库。每个这样的库都必须下载(你需要知道库的地址!)并构建(你需要了解各种选项!),只有这样才能构建 FFmpeg。这些库并非作为单独的共享库包含;它们是静态链接的,其代码包含在 FFmpeg 的库和应用程序中。
3.2 配置和编译
和往常一样,要开始构建 FFmpeg,你需要运行带有所需选项的脚本configure。该脚本支持数百个选项(你可以使用configure`--options` 选项运行脚本来获取选项列表--help)。特别是,每个外部库都需要自己的选项(例如 `--external-library`、--enable-libx264`--external-library`、`--external-library` --enable-libmp3lame)。显然,从如此多的选项中选择正确的选项并非易事。
为了成功运行configure,通常需要安装额外的软件包(autotools例如,等等),具体安装哪些软件包取决于操作系统;您需要查找特定操作系统的说明。
主要任务configure是生成一个头文件定义文件config.h,该文件指定构建过程中应包含哪些组件以及其他编译选项。此文件包含在 Makefile 文件中。此外,configure该过程还会分析一些 C 源文件,之后生成其他 C 源文件。
执行成功后configure,您需要运行该命令make并等待成功完成(或错误消息)。
3.3. 为 Windows 构建
在任何 Linux 操作系统上,都可以使用 MinGW 进行交叉编译,从而在 Windows 下构建 FFmpeg。也可以直接在 Windows 中安装 MSYS(或 MSYS2)环境进行构建。该环境提供了一个控制台,本质上是一个类似 UNIX 的 shell,用于支持 MinGW。在这种情况下,整个过程都在 Windows 中完成,并由 MSYS 控制台控制。还有其他选项;
3.4 使用 FFmpeg API 进行编程的汇编语言
FFmpeg 功能可以集成到用 C 或 C++ 编写的项目中。为此,可以使用共享构建,该构建通过在配置过程中使用 `--shared-build` 和 ` --disable-static--shared-build`选项创建--enable-shared。在这种情况下,构建包含八个共享库(对于 Windows 系统,这些是*.dll`.fmpeg` 文件)和三个应用程序ffmpeg------`.fmpeg`、`.fmpeg`ffprobe和ffplay`.fmpeg`(对于 Windows 系统,这些是*.exe`.fmpeg` 文件)。构建还包含 C 头文件(*.h`.fmpeg` 文件)以及链接共享库所需的文件(对于 Windows 系统,这些是 ` *.lib.fmpeg` 文件)。在 C++ 项目中,FFmpeg API 头文件应包含在 `.fmpeg` 文件中extern "C" { }。
请注意,在配置过程中使用 `--static`--enable-static和 `--static`选项--disable-shared会生成所谓的静态构建版本,该版本仅构建三个应用程序:`app` ffmpeg、`app`ffprobe和ffplay`app`(库代码直接包含在这些应用程序中)。在这种情况下,FFmpeg 的功能只能通过命令行使用。