音视频同步知识
时间基和时间戳
实际时间(秒) = 时间戳 × 时间基值
下面详细解释这两个概念及其关系:
1. 时间基(Time Base)
时间基是一个分数({num, den}),表示时间戳的单位。它定义了每个时间戳单位对应的实际时间。
示例:
{1, 1000} 表示每个时间戳单位为 1/1000 秒(毫秒)。
{1, 90000} 表示每个时间戳单位为 1/90000 秒(常用于视频流,如 MPEG-2)。
{1, 44100} 表示每个时间戳单位为 1/44100 秒(常用于 44.1kHz 音频采样)。
作用:不同的媒体流(视频、音频)可能使用不同的时间基,通过转换到统一的时间基,可以实现时间同步。
2. 时间戳(Timestamp)
时间戳是一个整数,指示媒体数据(如帧、包)的播放或解码时间点。常见的时间戳类型:
PTS(Presentation Time Stamp):指示帧应该何时被显示。
DTS(Decoding Time Stamp):指示帧应该何时被解码(对 B 帧编码的视频特别重要)。
示例:
若时间基为 {1, 1000},时间戳 5000 表示 5000 × (1/1000) = 5 秒。
若时间基为 {1, 90000},时间戳 450000 表示 450000 × (1/90000) = 5 秒。
常见时间基
(1)编解码器时间基(Codec Time Base)
用途:编码器或解码器内部使用的时间基。
获取方式:AVCodecContext.time_base
示例:
视频编码器可能使用 {1, 25} 表示 25fps(每帧 40ms)。
音频编码器可能使用 {1, 44100} 与采样率匹配。
(2)流时间基(Stream Time Base)
用途:输出文件中每个流的时间基。
获取方式:AVStream.time_base
示例:
MP4 容器的视频流可能使用 {1, 90000}。
音频流可能使用 {1, 48000}。
编码的时间基操作
step 1 计算帧的时间戳增量
double audio_frame_duration = 1.0 *audio_encoder.GetFrameSize()/pcm_sample_rate*audio_time_base;
double video_frame_duration = 1.0/yuv_fps * video_time_base;
step2 转化时间基
pts = av_rescale_q(pts, AVRational{1, (int)time_base}, codec_ctx_->time_base);
frame_->pts = pts;
将我自己的时间基转化为了编码时间基,实现了pts同步
流时间基操作
step1 判断大小
if((video_finish != 1 && audio_pts > video_pts) // audio和vidoe都还有数据,优先audio(audio_pts > video_pts)|| (video_finish != 1 && audio_finish == 1)) {read_len = fread(yuv_frame_buf, 1, yuv_frame_size, in_yuv_fd);if(read_len < yuv_frame_size) {video_finish = 1;printf("fread yuv_frame_buf finish\n");}
条件 1:audio_pts > video_pts时优先处理视频,避免视频落后太多。
条件 2:音频处理完毕但视频未完成时,继续处理视频帧。
核心逻辑:通过比较audio_pts和video_pts,确保音视频时间戳保持相近,实现同步输出。
等于1的时候 代表已经全部编码完成了
step2 转换流的时间基
AVRational src_time_base; // 编码后的包 时间基AVRational dst_time_base; // mp4输出文件对应流的time_baseif(vid_stream_ && vid_codec_ctx_ && stream_index == video_index_) {src_time_base = vid_codec_ctx_->time_base;dst_time_base = vid_stream_->time_base;} else if(aud_stream_ && aud_codec_ctx_ && stream_index == audio_index_) {src_time_base = aud_codec_ctx_->time_base;dst_time_base = aud_stream_->time_base;}// 时间基转换 这确保了音视频时间戳在输出文件中具有统一的时间刻度。packet->pts = av_rescale_q(packet->pts, src_time_base, dst_time_base);packet->dts = av_rescale_q(packet->dts, src_time_base, dst_time_base);packet->duration = av_rescale_q(packet->duration, src_time_base, dst_time_base);
ret = av_interleaved_write_frame(fmt_ctx_, packet);
av_interleaved_write_frame() 是 FFmpeg 中用于将编码后的音视频数据包写入输出文件的核心函数,它在实现音视频同步和交错输出中起着关键作用。以下是对该函数的详细解析:
- 函数作用
av_interleaved_write_frame() 用于将音视频数据包(AVPacket)按照正确的时间顺序写入输出媒体文件(如 MP4、MKV 等)。它主要完成以下工作:
时间戳检查与修正:确保数据包的时间戳(PTS/DTS)单调递增且合法。
缓存与排序:内部维护一个缓冲区,对音视频包进行排序,确保按时间顺序输出。
交错输出:将不同流(音频、视频、字幕)的数据包按时间顺序交错写入文件,实现同步
总结
基于时间戳同步:这是最常见的方法。在播放过程中,系统会不断获取音频和视频帧的时间戳,并根据时间戳来调整播放进度。例如,如果发现视频帧的时间戳比音频帧的时间戳大,说明视频播放进度落后于音频,此时可以适当加快视频的播放速度,或者暂停音频的播放,直到两者的时间戳接近。
补充一 不同时间基
1. 核心处理原则
- 统一时间轴:将不同流的时间戳转换到同一时间基下,才能进行同步比较。
- 转换方向:通常将所有流的时间戳转换为输出流的时间基(如 MP4 容器要求的{1, 90000})。
- 使用 FFmpeg 工具:通过av_rescale_q()或av_packet_rescale_ts()函数完成转换,避免手动计算。
// 视频流信息
AVRational video_codec_time_base = video_codec_ctx->time_base; // 编码器时间基
AVRational video_stream_time_base = video_stream->time_base; // 输出流时间基// 音频流信息
AVRational audio_codec_time_base = audio_codec_ctx->time_base; // 编码器时间基
AVRational audio_stream_time_base = audio_stream->time_base; // 输出流时间基
(2)时间基转换
在将数据包写入输出文件前,必须将其时间戳从编码器时间基转换为输出流时间基:
// 对视频包进行时间基转换
av_packet_rescale_ts(packet, video_codec_time_base, video_stream_time_base);// 对音频包进行时间基转换
av_packet_rescale_ts(packet, audio_codec_time_base, audio_stream_time_base);
(3)写入交错帧
使用av_interleaved_write_frame()确保按时间戳排序输出:
ret = av_interleaved_write_frame(fmt_ctx, packet);
常见时间基
场景 | 编码器时间基 | 输出流时间基 |
---|---|---|
MP4视频 | {1, 90000} | {1, 90000} |
MP4音频(48kHz) | {1, 48000} | {1, 48000} |
自定义帧率视频 | {1, fps} (如{1, 25} ) | {1, 90000} |
网络流 | {1, 1000} (毫秒级) | {1, 90000} |
补充二 H.264 中 PTS≠DTS 时的音视频同步
1. 问题背景
H.264 特性:因 B 帧(双向预测帧)存在,解码顺序(DTS)与显示顺序(PTS)不同,导致DTS ≤ PTS(B 帧 DTS 可能小于前序 P 帧)。
同步核心:以音频为基准(人耳对音频延迟更敏感),通过时间戳转换和动态调整,确保视频显示与音频播放同步。
2. 关键处理步骤
- (1)时间基统一与时间戳转换
目标:将音视频时间戳转换为同一时间基(如输出流时间基)。
操作:
使用 FFmpeg 的 av_packet_rescale_ts() 或 av_rescale_q(),将编码器时间基(如 H.264 的{1, 90000})转换为输出流时间基。
确保数据包的PTS/DTS在转换后保持单调递增,供后续排序和同步使用。 - (2)以音频为基准的同步逻辑
- 音频时钟:
基于音频实际播放的样本数计算实时时钟(如 audio_clock = 已播放样本数 / 采样率),作为同步基准。 - 视频帧处理:
解码后按PTS(显示顺序)排序,而非DTS(解码顺序)。
计算视频帧显示时间与音频时钟的差值 delay = 视频PTS - 音频时钟:
delay > 0:视频超前,等待或降低播放速度。
delay < -阈值:视频落后,丢弃非关键帧或加快播放。
- 音频时钟:
- (3)动态调整策略
- 缓冲与排序:
解码器输出的帧按PTS存入队列,确保显示顺序正确(处理 B 帧乱序问题)。 - 丢帧与重复帧:
严重落后时丢弃 B 帧 / P 帧(保留 I 帧),避免卡顿。
严重超前时重复显示上一帧,避免音画脱节。
播放速度微调:
轻微差异时调整视频播放速度(±5% 范围内),平滑同步误差。
- 缓冲与排序:
- (4)FFmpeg 关键 API 支持
时间基转换:av_packet_rescale_ts(pkt, 源时间基, 目标时间基),统一音视频时间戳单位。
交错写入:av_interleaved_write_frame() 按PTS排序输出数据包,确保文件中音视频交错正确。
精准跳转:avformat_seek_file() 基于音频时钟定位,处理快进 / 快退后的同步恢复。
3 总结
H.264 中 PTS 与 DTS 的差异通过以下步骤实现音视频同步:
统一时间基:转换音视频时间戳到相同单位,消除格式差异。
音频基准:以音频时钟为参考,动态调整视频显示时机(等待、调速、丢帧)。
FFmpeg 工具:利用 API 处理时间戳转换、帧排序和交错输出,简化同步逻辑。
鲁棒性策略:通过缓冲、丢帧等机制应对解码乱序和同步误差,确保播放流畅。