一、为什么需要两个时间戳#
I 帧:不需要参考其他帧,解码顺序 = 显示顺序
P 帧:参考前一帧,解码顺序 = 显示顺序
B 帧:需要参考前后帧 → 解码顺序 ≠ 显示顺序!
视频帧顺序(显示顺序):
I B B P B B P B B ...
1 2 3 4 5 6 7 8 9 ...
解码器需要的顺序(解码顺序):
I P B B P B B ... ← 因为解码 B 帧需要前后的 P 和 I
1 4 2 3 7 5 6 ...
所以每个帧有两个时间戳:
DTS(Decode TimeStamp):解码器按这个顺序解码
PTS(Present TimeStamp):播放器按这个顺序显示
举个实际例子:
帧序列:I1(pts=0) B2(pts=1) B3(pts=2) P4(pts=3) B5(pts=4) B6(pts=5) P7(pts=6)
解码顺序:I1(dts=0) P4(dts=1) B2(dts=2) B3(dts=3) P7(dts=4) B5(dts=5) B6(dts=6)
↓解码 ↓需要 P4 解码后才能解 B2
显示顺序:I1 B2 B3 P4 B5 B6 P7
二、在 FFmpeg 中拿正确的 PTS#
// 解码后的 frame 中的 pts 要用 stream 的 time_base 转换成秒
void print_frame_timestamp(AVFormatContext* fmt_ctx, int stream_idx, AVFrame* frame) {
AVStream* stream = fmt_ctx->streams[stream_idx];
AVRational tb = stream->time_base;
// frame->pts 的单位是 time_base
// 例如 time_base = {1, 1000} 表示 pts 单位是毫秒
// 例如 time_base = {1, 90000} 表示 pts 单位是 1/90000 秒(H.264 常用)
double pts_seconds = frame->pts * av_q2d(tb);
printf("PTS = %ld (raw), %.3f (seconds)\n", frame->pts, pts_seconds);
}
// 把 packet 的时间戳从编码器时基转换到流时基
void convert_packet_ts(AVFormatContext* fmt_ctx, AVCodecContext* enc_ctx,
AVStream* stream, AVPacket* packet) {
av_packet_rescale_ts(packet,
enc_ctx->time_base, // 编码器时基
stream->time_base); // 容器时基
}