FFmpeg 实际工作的方式(以及为何你的第一个命令失败)
FFmpeg 运行在一个简单的原则上,大家最初都理解错了:它从输入读取流,通过过滤器处理它们,并将它们写入输出。困惑来自于单个视频文件包含多个流——通常是一个视频流,一个或多个音频流,有时还有字幕流或元数据流。 当你运行 `ffmpeg -i input.mp4 output.mp4` 时,FFmpeg 会对你想要的内容做出一系列假设。它选择“最佳”视频流,“最佳”音频流,通过默认编码器复制它们,并将其复用到输出容器中。这在简单转换时可以很好地工作,但当你需要控制时就会崩溃。 我第一个命令生成 0 字节文件的原因是因为我指定了不兼容的编码器和容器组合。我试图将 VP9 视频流放入一个 MP4 容器,这并不被支持。FFmpeg 开始编码,意识到无法写入输出,然后放弃了。错误信息埋在 200 行的输出中,我不知道该如何解读。 这是改变我一切的思维模型:将 FFmpeg 想象成一个有三个阶段的管道。首先是解复用——FFmpeg 打开你的输入文件并将其分离为单独的流。其次是处理——每个流都通过一个编解码器(编码器/解码器)和可选过滤器。最后是复用——被处理的流被打包到输出容器中。 每个 FFmpeg 命令都遵循这个模式: ``` ffmpeg [全局选项] [输入选项] -i 输入 [输出选项] 输出 ``` 顺序非常重要。在 `-i` 之前的选项适用于输入。在 `-i` 之后的选项适用于输出。如果你将 `-c:v libx264` 放在 `-i` 之前,FFmpeg 会尝试将输入解码为 H.264,这可能不是你想要的。将其放在 `-i` 之后,它会将输出编码为 H.264。 另一个至关重要的概念是流说明符。当你写 `-c:v` 时,你是在说“将此编解码器应用于视频流。” `-c:a` 针对音频流。 `-c:s` 针对字幕。你可以使用 `-c:v:0` 针对第一个视频流,或 `-c:a:1` 针对第二个音频流来进行更精确的指定。 一旦我理解了这个结构,我就不再产生 0 字节文件。我能读取 FFmpeg 的输出,并理解它在每个阶段所做的事情。我可以通过隔离问题是在解复用、处理还是复用来调试问题。我转码 50,000 个视频的那天(以及我所学到的)
三年前,我们公司收购了一家竞争对手。收购的一部分包括他们的整个视频库——50,000 个我们不支持的格式的视频。他们使用了一种专有编码器,需要特定播放器,我们需要将所有内容转换为标准 H.264 以适配我们的平台。 幼稚的方法是写一个简单的循环:对每个视频,运行 FFmpeg 基本设置,等待它完成,然后移动到下一个。以每个视频平均 2 分钟计算,这将需要 69 天的连续处理时间。我们只有两周的时间。 这个项目让我学到了比之前三年加起来还要多的 FFmpeg 知识。我了解了硬件加速、并行处理以及实际重要的数十个编码器设置。我明白了哪些质量指标是有意义的,哪些只是市场噱头。最重要的是,我了解到“最佳” FFmpeg 命令完全取决于你的限制。 我们最终构建了一个分布式转码系统,可以在 40 台机器上同时处理 200 个视频。每台机器运行 5 个 FFmpeg 实例,经过精心调整以最大化 CPU 使用率而不造成混乱。我们在可用的情况下使用硬件加速解码,但因为质量差异对我们使用案例显著,所以选择了软件编码。 我们最后确定的命令如下: ```bash ffmpeg -hwaccel auto -i input.mov \ -c:v libx264 -preset medium -crf 23 \ -c:a aac -b:a 128k \ -movflags +faststart \ -max_muxing_queue_size 1024 \ output.mp4 ``` 让我分解一下每个选项的重要性。 `-hwaccel auto` 告诉 FFmpeg 如果可用,请使用硬件解码——这将我们的解码时间缩短了 60% 在具有兼容 GPU 的机器上。 `-preset medium` 在编码速度和压缩效率之间取得平衡。我们测试了所有的预设; `medium` 是甜蜜点,在半时间内获得 `slower` 的 95% 质量。 `-crf 23` 设置使用恒定速率因子来控制质量。较低的数字意味着更高的质量和更大的文件。我们在 100 个视频样本上测试了 CRF 值从 18 到 28,并让我们的录像团队进行盲质量比较。没有人能可靠地区分 CRF 23 和 CRF 20,但文件大小小了 30%。 `-movflags +faststart` 将 moov atom 移动到文件开头,从而支持通过 HTTP 的渐进式播放。没有这个标志,浏览器必须在开始播放之前下载整个文件。这个单一的选项改善了我们的用户体验指标 15%。 `-max_muxing_queue_size 1024` 选项解决了一个让我们耗费三天调试的问题。有些源视频的可变帧频导致 FFmpeg 的内部缓冲区溢出。默认队列大小为 8 个数据包,这对于 VFR 内容来说不够。将其增加到 1024 消除了“输出流缓冲区的数据包过多”这一使 5% 的转换失败的错误。 我们在 11 天内完成了这个项目。分布式系统每天处理 4,545 个视频,成功率为 99.2%。失败的都是损坏的源文件或使用了我们无法解码的编解码器。到今天为止,我仍然在使用该命令的变体——它是我们整个视频处理管道的基础。编解码器和容器兼容性矩阵
FFmpeg 对于初学者最令人沮丧的方面之一是理解哪些编解码器与哪些容器一起工作。你可能花一个小时制作完美命令,却因为尝试将不兼容的编解码器放入一个不支持它的容器而失败。 这是我经常引用的兼容性矩阵:| 容器 | 视频编解码器 | 音频编解码器 | 最佳适用 |
|---|---|---|---|
| MP4 | H.264, H.265, AV1 | AAC, MP3, Opus | 网络播放,移动设备,通用兼容性 |
| WebM | VP8, VP9, AV1 | Vorbis, Opus | 网页流媒体,开源项目,YouTube |
| MKV | 任何 | 任何 | 归档,多音轨,字幕 |
| MOV | H.264, ProRes, DNxHD | AAC, PCM | 专业编辑,苹果生态系统 |
| AVI | MPEG-4, H.264(有限) | MP3, PCM | 传统系统(避免用于新项目) |
| TS | H.264, H.265, MPEG-2 | AAC, MP3, AC-3 | 广播,HLS 流媒体 |
没有人正确解释的质量设置
每个 FFmpeg 教程都告诉你使用 `-crf 23` 来获得“良好质量”或者 `-b:v 5M` 作为“5 兆比特每秒”。但没有人解释这些设置实际上是做什么的,或者如何选择适合你内容的正确值。 我花了数百小时测试不同类型内容的质量设置。以下是我了解到的:没有通用的“最佳”设置。最佳质量参数取决于你的内容类型、目标受众和分发方法。“恒定速率因子(CRF)是一种基于质量的编码模式,你指定一个质量水平,让编码器根据需要使用任意位数以达到该质量。较低的 CRF 值意味着更高的质量和更大的文件。对于 H.264,范围是 0-51,其中 0 是无损的,51 是最糟糕的质量。默认值为 23,通常被认为对于大多数内容‘视觉透明’——意味着大多数人无法将其与源区分开。”CRF 的问题在于它产生了可变比特率输出。一个高动作场景可能使用 10 Mbps,而一个静态的访谈场景则使用 2 Mbps。这对文件大小是高效的,但可能会在流媒体时造成可预测带宽使用的问题。 对于流媒体,你希望使用恒定比特率(CBR)或带约束的可变比特率(VBR)。这是我用于流媒体的命令: ```bash ffmpeg -i input.mp4 \ -c:v libx264 -preset medium \ -b:v 5M -maxrate 5M -bufsize 10M \ -c:a aac -b:a 128k \ output.mp4 ``` `-b:v 5M` 将目标比特率设置为每秒 5 兆比特。 `-maxrate 5M` 确保它不会超过该速率。 `-bufsize 10M` 设置解码缓冲区大小为比特率的两倍,符合标准推荐。这会生成输出平稳流媒体播放而不需缓冲。 但这里是大多数人搞错的:比特率需求随着分辨率和运动而改变,而不是线性增长。一个 1080p 视频不需要是 720p 视频比特率的两倍——它大约需要 1.5 倍。一个 4K 视频不需要是 1080p 的四倍比特率——它大约需要 2.5 倍。
“人眼视觉系统是对数的,而不是线性的。将比特率加倍并不意味着质量线性增倍。”