背景
ffmpeg is a universal media converter. It can read a wide variety of inputs - including live grabbing/recording devices - filter, and transcode them into a plethora of output formats.
ffmpg包含了一整套用于处理音频、视频、字幕以及其他多média数据流的库(libraries)和程序(programs)。
视频处理
视频帧
如图
- I 帧 (关键帧): 完整的图像,独立解码,是解码起点和参考基准,压缩率最低,数据量最大。
- P 帧: 存储与前一帧的差异,依赖前一帧解码,压缩率较高,数据量中等。
- B 帧: 存储与前、后帧的差异,依赖前后帧解码,压缩率最高,数据量最小。
- GOP: 由一个 I 帧和若干 P/B 帧组成的序列,其长度影响压缩率和随机访问性能。
帧构成
如图
-
I 帧 (Intra-coded Picture / Keyframe / 关键帧)
-
构成: I 帧是一帧完整的图像。它不依赖于任何其他帧来进行解码。它本身包含了在该时间点显示完整画面所需的所有信息。
-
编码方式: 它只利用了空间冗余进行压缩,其编码方式类似于静态图像压缩(如 JPEG)。它不对前后帧进行参考。
-
作用:
-
解码起点: 播放器可以从任何一个 I 帧开始解码并正确显示视频,因此 I 帧是视频随机访问(快进、快退、定位播放)的基础。
-
参考基准: 它作为后续 P 帧和 B 帧进行预测和参考的“基准帧”。
-
错误隔离: 如果传输或存储中出现错误,错误的影响通常只持续到下一个 I 帧,I 帧可以阻止错误的进一步传播。
-
特点: 数据量最大,压缩率最低(相比 P/B 帧)。
-
P 帧 (Predicted Picture / 前向预测帧)
-
构成: P 帧不是一个完整的图像。它存储的是当前画面与之前的某个 I 帧或 P 帧之间的差异信息。
-
编码方式: 它利用了时间冗余。编码器会分析当前 P 帧与它参考的前一帧(I 或 P)之间的区别,主要是物体的运动(通过运动矢量 (Motion Vectors 描述物体移动的方向和距离)以及运动补偿后的残差信息(预测不完全准确的部分)。
-
作用: 大大提高压缩率,因为只存储变化的部分,数据量远小于 I 帧。
-
依赖性: 解码 P 帧必须先解码它所参考的那个前面的 I 帧或 P 帧。
-
特点: 数据量介于 I 帧和 B 帧之间,压缩率较高。
-
B 帧 (Bi-directionally Predicted Picture / 双向预测帧)
-
构成: B 帧也不是一个完整的图像。它存储的是当前画面与它前面的一个参考帧(I 或 P)以及后面的一个参考帧(I 或 P)之间的差异信息。
-
编码方式: 它利用了更充分的时间冗余。编码器可以同时参考过去和未来的帧来预测当前 B 帧的内容,通常能找到更相似的块,从而更有效地描述差异(运动矢量和残差)。
-
作用: 提供最高的压缩率,数据量通常是最小的。
-
依赖性: 解码 B 帧必须先解码它所参考的前、后两个参考帧。这意味着解码器需要先解码未来的参考帧,才能回头解码当前的 B 帧,这会引入一定的解码延迟。
-
特点: 数据量最小,压缩率最高。
图像序列
- GOP (Group of Pictures / 图像组)
这些 I、P、B 帧通常被组织成一个称为 GOP 的序列。一个 GOP 以一个 I 帧开始,后面跟着一系列 P 帧和 B 帧,直到下一个 I 帧。
- 结构示例: 一个常见的 GOP 结构可能是
I B B P B B P B B I
。这个序列会重复出现。 - GOP 长度: 指两个连续 I 帧之间的距离(以帧数或时间衡量)。
- 长 GOP: 包含更多的 P 和 B 帧,压缩率更高,但随机访问性能差(定位播放时需要找到更久之前的 I 帧),错误恢复慢。
- 短 GOP: I 帧更频繁,压缩率较低,但随机访问快,错误恢复快。直播或需要频繁编辑的场景通常使用较短的 GOP。
由于 B 帧需要参考后面的帧,所以解码器处理帧的顺序(解码顺序)可能与最终屏幕上播放的顺序(显示顺序)不同。解码器需要先解码 B 帧所依赖的后面的参考帧,然后才能解码 B 帧本身,上述视频帧在解码与显示视频时关系如下图所示:
主要功能及构成
功能
- 格式转换 (Transcoding / Format Conversion): FFmpeg 最常用的功能之一。它可以轻松地将视频或音频文件从一种格式转换为另一种格式(例如,MP4 转 AVI,WAV 转 MP3)。
- 编码与解码 (Encoding / Decoding): 支持几乎所有已知的音频和视频编解码器(Codecs)。你可以用它来压缩媒体文件(编码)或播放/处理它们(解码)。
- 封装与解封装 (Muxing / Demuxing): 可以将单独的音频流和视频流合并到一个容器文件(如 MP4, MKV)中(封装),或者从一个容器文件中提取出单独的音频、视频或字幕流(解封装)。
- 流媒体处理 (Streaming): FFmpeg 可以捕捉、编码并将音视频流实时推送到流媒体服务器(如 RTMP, HLS),也可以作为客户端接收和处理流。
- 编辑与处理 (Editing & Filtering):
- 裁剪 (Cropping): 截取视频画面的特定区域。
- 缩放 (Scaling): 改变视频分辨率。
- 旋转/翻转 (Rotating/Flipping): 调整视频方向。
- 合并/分割 (Concatenating/Splitting): 连接或切分媒体文件。
- 添加水印/字幕 (Watermarking/Subtitles): 在视频上叠加图片或文字。
- 调整速度 (Speed Adjustment): 快放或慢放。
- 应用滤镜 (Applying Filters): 调整亮度、对比度、饱和度,添加模糊、锐化等各种视觉效果,以及音频效果如音量调整、降噪等。
- 屏幕录制与设备捕捉 (Screen Recording & Device Capture): 可以录制桌面屏幕、摄像头输入或麦克风音频。
- 媒体信息分析 (Media Information Analysis): 通过
ffprobe
工具,可以详细分析媒体文件的各种参数,如编码格式、分辨率、比特率、帧率、时长等。
关键流程解读
- FFmpeg 的基本工作流程可以理解为一个处理管线:
解复用 (Demuxing): ffmpeg 读取输入文件 (-i input.mkv)。解复用器 (Demuxer) 负责解析容器格式 (如 MKV, MP4, AVI),并将其中包含的各个基本流 (Elementary Streams, ES) 分离出来,这些流是编码过的数据包 (Packets)。
解码 (Decoding): 对于需要处理或重新编码的流,其数据包会被送入相应的解码器 (Decoder)。解码器将压缩的编码数据还原成原始的、未压缩的帧 (Frames) - 视频帧或音频采样。如果使用了 -c copy (流复制),则跳过此步骤。
滤镜 (Filtering): 解码后的原始帧可以被送入滤镜图 (Filtergraph) (-vf, -af, -filter_complex) 进行处理。滤镜可以修改帧的内容,例如:
视频滤镜:缩放、裁剪、旋转、叠加水印、调色、去隔行等。
音频滤镜:重采样、改变音量、混音、降噪等。
如果未使用滤镜,则跳过此步骤。
编码 (Encoding): 经过滤镜处理(或直接来自解码器)的原始帧被送入指定的编码器 (Encoder) (-c:v libx264, -c:a aac 等)。编码器将原始帧压缩成编码后的数据包 (Packets)。如果使用了 -c copy (流复制),则跳过此步骤。
复用 (Muxing): 编码后的数据包(或者从流复制直接过来的数据包)被送入复用器 (Muxer) (-f mp4 等指定格式)。复用器负责将来自不同流的数据包按照目标容器格式 (如 MP4, MKV, FLV) 的规范,交织写入到输出文件中 (output.mp4)。
构成
ffmpeg
: 核心的命令行工具,用于执行上述大部分的转换、处理任务。ffplay
: 一个基于 SDL 和 FFmpeg 库的简单媒体播放器。ffprobe
: 一个命令行工具,用于分析媒体文件并以文本、JSON、XML 等多种格式输出详细信息。libavcodec
: 包含了所有音频/视频编码器和解码器的库。libavformat
: 包含了处理各种媒体容器格式(封装/解封装)的库。libavfilter
: 包含了各种音频和视频滤镜的库。libswscale
: 用于图像缩放和颜色空间/像素格式转换的库。libswresample
: 用于音频重采样、格式转换和通道布局管理的库。libavutil
: 包含各种辅助工具函数的基础库。
选项指定
-i url
: 指定输入文件或来源。可以有多个-i
。-f fmt
: 强制指定输出文件格式 (如-f mp4
,-f flv
)。通常 FFmpeg 能根据扩展名自动判断,但有时需要强制指定。-map [-]input_file_id[:stream_specifier][?]
: 极其重要。用于手动控制哪些输入流被包含到哪个输出文件中。0:v
选择第一个输入文件的所有视频流。0:a:1
选择第一个输入文件的第二个音频流 (索引从0开始)。1:s?
选择第二个输入文件的第一个字幕流(如果存在)。-map 0
选择第一个输入文件的所有流。-map -0:a
从自动选择中排除第一个输入文件的所有音频流。
-c[:stream_specifier] codec
: 极其重要。选择编码器。-c copy
: 进行流复制 (Stream Copy),不重新编码。-c:v libx264
: 为视频流选择 H.264 (libx264) 编码器。-c:a aac
: 为音频流选择 AAC 编码器。-c:s mov_text
: 为字幕流选择 mov_text 编码器。-vn
,-an
,-sn
: 分别是-c:v copy -an -sn
、-c:a copy -vn -sn
、-c:s copy -vn -an
的简写,但更常用的含义是完全禁用视频/音频/字幕的录制/输出 (相当于映射到 /dev/null)。
-t duration
: 指定输出文件的时长。-to position
: 指定输出文件的结束时间点。-ss position
: 指定输出文件的开始时间点 (如果放在输出选项位置,通常会进行精确查找,但可能较慢;放在输入选项-i
之前则查找更快但不精确)。-vf filtergraph
: 设置视频滤镜链 (简单滤镜图)。-af filtergraph
: 设置音频滤镜链 (简单滤镜图)。-filter_complex filtergraph
: 设置复杂滤镜图,用于处理多个输入/输出流或需要复杂连接的滤镜。- 比特率/质量控制:
-b:v bitrate
: 设置视频目标比特率 (如-b:v 1M
表示 1 Mbps)。-b:a bitrate
: 设置音频目标比特率 (如-b:a 128k
表示 128 kbps)。-crf value
(Constant Rate Factor): 恒定质量因子,是 x264/x265 等编码器常用的质量控制模式,数值越低质量越好,文件越大。-q:v value
/-qscale:v value
: 控制视频质量(某些编码器)。
- 视频选项:
-r fps
: 设置帧率 (如-r 25
)。-s WxH
: 设置视频分辨率 (如-s 1280x720
)。-aspect ratio
: 设置画面宽高比 (如-aspect 16:9
)。-pix_fmt format
: 设置像素格式。
- 音频选项:
-ar freq
: 设置音频采样率 (如-ar 44100
)。-ac channels
: 设置音频通道数 (如-ac 2
表示立体声)。-vol volume
: 调整音量 (如-vol 512
表示 2 倍音量)。
流指定符 (Stream Specifiers):
这是一个强大的机制,允许你将选项精确地应用到特定的流上。格式通常是 选项名:流类型[:流索引]
。
-c:v libx264
: 将编码器libx264
应用于所有视频流。-b:a:1 192k
: 将比特率192k
应用于第二个音频流。-disposition:s:0 default
: 将第一个字幕流的 disposition 设置为 default。-map 0:v -map 0:a:0
: 选择第一个输入文件的所有视频流和第一个音频流。
问题
视频切割中存在的问题
- 切割视频后存在开始前几秒为黑屏
问题分析
常见的原因是剪辑的起始点 (-ss) 没有落在关键帧 (Keyframe / I-frame) 上。
流拷贝模式直接复制视频数据包,而不进行解码和重新编码。视频播放器通常需要从一个关键帧开始解码,如果剪辑后的视频片段开头不是关键帧,而是 P 帧或 B 帧(它们依赖于之前的帧进行解码),播放器就无法正确显示画面,导致黑屏或花屏,直到遇到第一个关键帧为止;重新编码可解决此问题。
- 按照n秒切割,大批量切割时存在:n-1或n+1秒视频存在
问题分析
- 流拷贝 (
-c copy
): 此模式不解码和重新编码视频。它直接复制原始视频流的数据包。 - 关键帧 (Keyframes): 视频压缩(如 H.264, H.265)依赖于关键帧。只有关键帧可以独立解码,后续的 P 帧和 B 帧需要依赖关键帧或其他帧才能解码。因此,一个独立的、可播放的视频片段必须以关键帧开始。
- 分割点: 当指定按
n
秒分割时,ffmpeg
(特别是使用segment
muxer 或类似的切割逻辑配合-c copy
时)会寻找时间戳接近n
,2n
,3n
… 的位置。但是,为了确保输出的每个片段都能独立播放,它不能在任意帧(如 P 帧或 B 帧)处切割,而必须在分割点之前或之后最近的关键帧处进行实际切割。 - 时长偏差:
- 如果
n
秒处恰好是关键帧,那么分割可能比较准确。 - 如果
n
秒处不是关键帧,ffmpeg
通常会回溯到之前的那个关键帧作为上一个片段的结束点,并从这个关键帧开始新的片段。 - 这就导致了实际分割点与理论上的
n
秒点有偏差。这个偏差取决于关键帧之间的距离(GOP size)。如果关键帧间隔很大(比如 10 秒),偏差可能会很明显。如果关键帧间隔小(比如 1 秒),偏差通常较小,但仍可能导致n-1
或n+1
秒的情况。
- 如果
- command 如下:
1 | command = [ |
使用上述command切割视频后存在黑屏、分段视频时长小于目标时长问题 command采用copy视频流
- 更换以下command 重新编码视频流
1 | command = [ |
ffmpeg 转码
- 转码过程如下
转码是指先解码 (decode) 一个流得到原始数据(如视频帧或音频样本),然后再用指定的编码器重新编码 (encode) 这些数据的过程
- 复制数据流(流复制)
流复制是指直接从输入文件中“提取”媒体流(如视频流、音频流)的数据包 (packets),然后原封不动地将这些数据包“放入”输出文件中,整个过程不经过解码和重新编码。
图中用流复制合并视频与音频流 流复制可用于合并、分割、提取数据流
- 流复制用于视频分割过程
demuxer 分离出两个流,流 0 被送到 muxer 0 生成 OUTPUT0.mp4,流 1 被送到 muxer 1 生成 OUTPUT1.mp4