音视频同步必须在采集、编码、封装三阶段持续对齐PTS,且默认以音频PTS为基准;因人耳更敏感,丢帧或变速对音频破坏远大于视频,故视频需追赶音频时钟,而非相反。

音视频同步不是“设个时间戳就完事”,而是必须在采集、编码、封装三个阶段持续对齐PTS,且默认应以音频PTS为基准——因为人耳比人眼更敏感,丢帧或变速对音频的破坏远大于视频。
为什么直接用 frame->pts 会出错?
原始帧的 pts 值依赖于输入源的时间基(AVRational time_base),而不同环节使用的时间基完全不同:
- 摄像头采集的视频帧可能用 {1, 1000}(毫秒级)
- 麦克风采集的音频帧常用 {1, 44100}(采样率级)
- H.264 编码器输出的视频流常设为 {1, 30}(对应 30fps)
- 最终 MP4 封装要求所有流统一到容器时间基,如 {1, 1000} 或 {1, 90000}
不转换就硬塞进 AVPacket,会导致 av_interleaved_write_frame() 写入后播放严重脱节。正确做法是:所有帧进入封装前,必须调用 av_packet_rescale_ts() 对齐到目标流的时间基。
av_packet_rescale_ts() 的调用时机和参数陷阱
这个函数不是“可选优化”,而是封装前的强制步骤。常见错误是只对视频流做、漏掉音频流,或传反了 src_tb 和 dst_tb。
立即学习“C++免费学习笔记(深入)”;
- 视频编码后:用
enc_ctx->time_base作为src_tb,MP4 视频流的st->time_base作为dst_tb - 音频编码后:用
enc_ctx->time_base作为src_tb,MP4 音频流的st->time_base作为dst_tb - 绝对不要用
av_q2d()手动算乘除再赋值pkt->pts——浮点误差+溢出风险极高 - 若编码器未设置
time_base(比如某些硬件编码器),需手动初始化:enc_ctx->time_base = av_inv_q(enc_ctx->framerate)(视频)或enc_ctx->time_base = {1, enc_ctx->sample_rate}(音频)
采集阶段就要预留同步锚点:音频 PTS 必须成为唯一可信基准
同步不能等到封装才开始。从第一帧音频采集起,就得构建单调递增的音频时钟:
- 采集音频帧时,记录其真实采集时间(如
clock_gettime(CLOCK_MONOTONIC, &ts)),而非依赖设备给的frame->pts - 按采样数推算 PTS:若一帧含
nb_samples个样本,采样率sr,则该帧 PTS = 上一帧 PTS +nb_samples(单位:1/sr) - 视频采集帧也需打上对应音频时间戳:比如第 N 帧音频 PTS 是 T,那么最近采集的视频帧就设为
frame->pts = av_rescale_q(T, audio_tb, video_tb) - 关键:所有后续处理(编码、封装)都以这个音频 PTS 链为唯一时间参考,视频只负责“追赶”它
封装时丢帧/插帧的实操边界在哪里?
同步不是靠“完美对齐”,而是靠可控的动态补偿。FFmpeg 封装器本身不帮你丢帧,你得自己判断:
- 当视频帧 PTS 比当前音频时钟早 > 100ms,说明已超前,应丢弃该帧(
continue) - 当视频帧 PTS 比当前音频时钟晚 > 200ms,说明严重滞后,可考虑重复上一帧(复制
AVFrame数据并更新pts)或跳过若干帧 - 音频绝不可丢帧或变速——否则破音;宁可让视频卡顿,也不能让声音撕裂
- 检查是否启用 B 帧:
codec_ctx->max_b_frames > 0会打乱 DTS/PTS 顺序,编码前务必设codec_ctx->max_b_frames = 0(H.264)或改用 CBR 模式
最易被忽略的一点:时间基转换函数 av_packet_rescale_ts() 的返回值不报错,但若输入 pkt->pts == AV_NOPTS_VALUE,它会静默失败——务必在调用前断言 pkt->pts != AV_NOPTS_VALUE,否则封装出来的文件必然不同步。


















