3、输入源调优:实时流与文件源的不同打法
音画同步的起点,其实不在滤镜链,也不在编码器——而在输入源。你想想看,如果数据进来的时候就是乱的,后面再怎么调也是白搭。这一章我就专门聊聊输入源层面的调优,分成实时流和文件源两个场景来讲。
核心观点:实时流追求“低延迟、不积压”,文件源追求“时间戳准确、起始点对齐”。两个场景的参数配置几乎是反着来的。
3.1 实时流场景:-re、-fflags nobuffer、-flags low_delay
实时流,说白了就是直播、摄像头、网络流这类数据。它们的特点是:数据源源不断,但到达时间不可控。如果你用默认参数去读,FFmpeg 会拼命地读,能读多快读多快——这在文件源里是好事,但在实时流里就是灾难。
3.1.1 -re:让 FFmpeg 慢下来
-re 这个参数,全称是 "read at native frame rate"。它的作用就是告诉 FFmpeg:别抢跑,按真实帧率来读。
我举个例子。你有一个 25fps 的 RTMP 流,不加 -re 的话,FFmpeg 可能 0.1 秒就把 1 秒的数据全读完了。然后呢?后面的滤镜、编码器全都在空等,音画同步直接乱掉。
# 不加 -re:读太快,容易导致缓冲区空
ffmpeg -i rtmp://example.com/live/stream output.mp4
# 加 -re:按帧率读取,保持节奏
ffmpeg -re -i rtmp://example.com/live/stream output.mp4
我的习惯:只要输入源是实时流,我第一件事就是加 -re。但注意,如果你同时做推流和录制,-re 可能会让录制变慢,这时候需要拆成两个进程或者用 tee muxer。
3.1.2 -fflags nobuffer:关掉缓冲区
FFmpeg 默认会做输入缓冲,目的是平滑抖动。但对于实时流来说,这个缓冲就是延迟的元凶。我曾经在做一个低延迟直播项目时,发现画面总是比声音慢 500ms 左右。查了半天,罪魁祸首就是输入缓冲。
# 关掉输入缓冲
ffmpeg -fflags nobuffer -i rtmp://example.com/live/stream output.mp4
-fflags nobuffer 会禁用 demuxer 层的内部缓冲。数据进来就处理,不攒批。代价是:如果网络抖动,你可能会看到卡顿。所以它适合网络稳定的内网环境。
3.1.3 -flags low_delay:低延迟模式
这个参数作用于编解码器层面。它告诉解码器:别缓存太多帧,能出就出。我一般把它和 -fflags nobuffer 搭配使用。
# 实时流标准三件套
ffmpeg -re -fflags nobuffer -flags low_delay -i rtmp://example.com/live/stream \
-c copy -f flv rtmp://output.com/live/stream
| 参数 | 作用层级 | 主要效果 | 副作用 |
|---|---|---|---|
-re |
输入读取 | 按帧率读取,防止读太快 | 可能拖慢录制速度 |
-fflags nobuffer |
demuxer | 关掉输入缓冲,降低延迟 | 网络抖动时易卡顿 |
-flags low_delay |
编解码器 | 减少解码器内部缓存 | 部分编码器不支持 |
我曾经踩过的坑:有一次我把 -flags low_delay 用在了 H.265 的硬件解码器上,结果解码器直接报错。后来查文档才发现,部分硬件解码器不支持这个 flag。所以用之前最好先测一下。
3.2 文件源场景:-copyts 与 -start_at_zero
文件源的情况正好相反。文件里的数据是完整的,时间戳也是写死的。问题往往出在:时间戳的起点不对,或者 FFmpeg 自作主张帮你改了时间戳。
3.2.1 -copyts:保留原始时间戳
FFmpeg 默认会对输入时间戳做归一化处理。什么意思呢?它会找到所有流里最早的时间戳,然后把这个时间戳当作 0,其他时间戳都减去这个偏移量。
听起来挺合理对吧?但问题来了:如果视频流和音频流的起始时间戳不一样,FFmpeg 归一化之后,两个流的相对关系可能就变了。音画同步就是这么被搞乱的。
# 不加 -copyts:FFmpeg 会重新归一化时间戳
ffmpeg -i input.ts -c copy output.mp4
# 加 -copyts:保留原始时间戳,不做归一化
ffmpeg -copyts -i input.ts -c copy output.mp4
我个人习惯是:只要源文件的时间戳是可信的,我就加 -copyts。尤其是处理 TS 流或者从专业摄像机录制的素材时,原始时间戳往往比 FFmpeg 自己算的准。
3.2.2 -start_at_zero:强制从零开始
这个参数和 -copyts 正好相反。它的作用是:不管原始时间戳是多少,强制把第一个有效时间戳当作 0。
什么时候用?比如你从某个点开始截取视频,截出来的片段时间戳可能不是从 0 开始的。这时候加 -start_at_zero,可以让输出文件的时间戳从 0 开始,方便后续处理。
# 从第 10 秒开始截取,并让时间戳从 0 开始
ffmpeg -ss 10 -i input.mp4 -start_at_zero -c copy output.mp4
注意:-start_at_zero 通常和 -copyts 是互斥的。一个要保留原始,一个要强制归零。我一般只在做截取或者拼接时用 -start_at_zero。
3.3 知识体系总览
下面这张图把输入源调优的核心逻辑串起来了。你可以看到,实时流和文件源走的是完全不同的两条路。
3.4 实战建议
说了这么多,我总结几条实战中可以直接用的经验:
- 实时流场景:无脑上
-re -fflags nobuffer -flags low_delay。如果网络不稳定,可以去掉nobuffer,保留-re和low_delay。 - 文件源场景:先试
-copyts,看看时间戳对不对。如果不对,再试-start_at_zero。两个不要同时用。 - 混合场景:比如你从实时流录制文件,我建议输入用实时流参数,输出用文件源参数。各管各的,互不干扰。
最后提醒一句:参数不是越多越好。我见过有人把 -re 和 -copyts 一起用,结果时间戳乱成一锅粥。每个参数都有它的适用场景,搞清楚再动手。