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 知识体系总览

下面这张图把输入源调优的核心逻辑串起来了。你可以看到,实时流和文件源走的是完全不同的两条路。

输入源调优知识体系 输入源调优 实时流 文件源 -re -fflags nobuffer -flags low_delay -copyts -start_at_zero 核心原则 实时流:降低延迟,避免积压 → 用 -re + nobuffer + low_delay 文件源:保留时间戳,对齐起点 → 用 -copyts 或 -start_at_zero 实时流典型命令 -re -fflags nobuffer -flags low_delay 文件源典型命令 -copyts 或 -start_at_zero 配合 -ss 做截取

3.4 实战建议

说了这么多,我总结几条实战中可以直接用的经验:

  • 实时流场景:无脑上 -re -fflags nobuffer -flags low_delay。如果网络不稳定,可以去掉 nobuffer,保留 -re 和 low_delay。
  • 文件源场景:先试 -copyts,看看时间戳对不对。如果不对,再试 -start_at_zero。两个不要同时用。
  • 混合场景:比如你从实时流录制文件,我建议输入用实时流参数,输出用文件源参数。各管各的,互不干扰。

最后提醒一句:参数不是越多越好。我见过有人把 -re 和 -copyts 一起用,结果时间戳乱成一锅粥。每个参数都有它的适用场景,搞清楚再动手。