ffmpeg分割视频与音频中的处理问题分析
Published in:2025-04-07 |
Words: 4k | Reading time: 14min | reading:

背景

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)。

视频处理

视频帧

如图

img

  • I 帧 (关键帧): 完整的图像,独立解码,是解码起点和参考基准,压缩率最低,数据量最大。
  • P 帧: 存储与前一帧的差异,依赖前一帧解码,压缩率较高,数据量中等。
  • B 帧: 存储与前、后帧的差异,依赖前后帧解码,压缩率最高,数据量最小。
  • GOP: 由一个 I 帧和若干 P/B 帧组成的序列,其长度影响压缩率和随机访问性能。

帧构成

如图

img

  • 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 帧本身,上述视频帧在解码与显示视频时关系如下图所示:

img

主要功能及构成

功能

  1. 格式转换 (Transcoding / Format Conversion): FFmpeg 最常用的功能之一。它可以轻松地将视频或音频文件从一种格式转换为另一种格式(例如,MP4 转 AVI,WAV 转 MP3)。
  2. 编码与解码 (Encoding / Decoding): 支持几乎所有已知的音频和视频编解码器(Codecs)。你可以用它来压缩媒体文件(编码)或播放/处理它们(解码)。
  3. 封装与解封装 (Muxing / Demuxing): 可以将单独的音频流和视频流合并到一个容器文件(如 MP4, MKV)中(封装),或者从一个容器文件中提取出单独的音频、视频或字幕流(解封装)。
  4. 流媒体处理 (Streaming): FFmpeg 可以捕捉、编码并将音视频流实时推送到流媒体服务器(如 RTMP, HLS),也可以作为客户端接收和处理流。
  5. 编辑与处理 (Editing & Filtering):
    • 裁剪 (Cropping): 截取视频画面的特定区域。
    • 缩放 (Scaling): 改变视频分辨率。
    • 旋转/翻转 (Rotating/Flipping): 调整视频方向。
    • 合并/分割 (Concatenating/Splitting): 连接或切分媒体文件。
    • 添加水印/字幕 (Watermarking/Subtitles): 在视频上叠加图片或文字。
    • 调整速度 (Speed Adjustment): 快放或慢放。
    • 应用滤镜 (Applying Filters): 调整亮度、对比度、饱和度,添加模糊、锐化等各种视觉效果,以及音频效果如音量调整、降噪等。
  6. 屏幕录制与设备捕捉 (Screen Recording & Device Capture): 可以录制桌面屏幕、摄像头输入或麦克风音频。
  7. 媒体信息分析 (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秒视频存在

问题分析

  1. 流拷贝 (-c copy): 此模式不解码和重新编码视频。它直接复制原始视频流的数据包。
  2. 关键帧 (Keyframes): 视频压缩(如 H.264, H.265)依赖于关键帧。只有关键帧可以独立解码,后续的 P 帧和 B 帧需要依赖关键帧或其他帧才能解码。因此,一个独立的、可播放的视频片段必须以关键帧开始。
  3. 分割点: 当指定按 n 秒分割时,ffmpeg(特别是使用 segment muxer 或类似的切割逻辑配合 -c copy 时)会寻找时间戳接近 n, 2n, 3n… 的位置。但是,为了确保输出的每个片段都能独立播放,它不能在任意帧(如 P 帧或 B 帧)处切割,而必须在分割点之前或之后最近的关键帧处进行实际切割。
  4. 时长偏差:
    • 如果 n 秒处恰好是关键帧,那么分割可能比较准确。
    • 如果 n 秒处不是关键帧,ffmpeg 通常会回溯到之前的那个关键帧作为上一个片段的结束点,并从这个关键帧开始新的片段。
    • 这就导致了实际分割点与理论上的 n 秒点有偏差。这个偏差取决于关键帧之间的距离(GOP size)。如果关键帧间隔很大(比如 10 秒),偏差可能会很明显。如果关键帧间隔小(比如 1 秒),偏差通常较小,但仍可能导致 n-1n+1 秒的情况。
  • command 如下:
1
2
3
4
5
6
7
8
9
10
11
12
command = [
'ffmpeg',
'-i', str(input_path), # Input file
'-ss', start_timecode_str, # Start time
'-to', end_timecode_str, # End time
'-copyts', # Copy timestamps
'-avoid_negative_ts', 'make_zero', # Handle timestamp issues
'-c', 'copy', # Copy codecs (no re-encoding)
'-y', # Overwrite output file if it exists
str(output_path) # Output file
]

使用上述command切割视频后存在黑屏、分段视频时长小于目标时长问题 command采用copy视频流

  • 更换以下command 重新编码视频流
1
2
3
4
5
6
7
8
9
10
11
12
13
14
command = [
ffmpeg_path,
'-y',
'-ss', str(start_segment_time),
'-i', video_file,
'-t', str(end_segment_time - start_segment_time),
'-c:v', 'h264_nvenc' if gpu_available else 'libx264', # 使用NVIDIA GPU加速的编码器, 如果没有GPU,则使用CPU
'-preset', preset,
'-crf', str(crf), # 指定视觉质量
'-pix_fmt', 'yuv420p', # 指定像素格式
'-c:a', 'copy',
'-avoid_negative_ts', '1',
output_file
]

ffmpeg 转码

  • 转码过程如下

转码是指先解码 (decode) 一个流得到原始数据(如视频帧或音频样本),然后再用指定的编码器重新编码 (encode) 这些数据的过程

img

  • 复制数据流(流复制)

流复制是指直接从输入文件中“提取”媒体流(如视频流、音频流)的数据包 (packets),然后原封不动地将这些数据包“放入”输出文件中,整个过程不经过解码和重新编码。

图中用流复制合并视频与音频流 流复制可用于合并、分割、提取数据流

img

  • 流复制用于视频分割过程

demuxer 分离出两个流,流 0 被送到 muxer 0 生成 OUTPUT0.mp4,流 1 被送到 muxer 1 生成 OUTPUT1.mp4

img

参考

Next:
视频场景切换检测