在FFmpeg中,时间基(time_base)是时间戳(timestamp)的单位,时间戳值乘以时间基,可以得到实际的时刻值(以秒等为单位)。例如,如果一个视频帧的dts是40,pts是160,其time_base是1/1000秒,那么可以计算出此视频帧的解码时刻是40毫秒(40/1000),显示时刻是160毫秒(160/1000)。FFmpeg中时间戳(pts/dts)的类型是int64_t类型,把一个time_base看作一个时钟脉冲,则可把dts/pts看作时钟脉冲的计数。
不同的封装格式具有不同的时间基。在FFmpeg处理音视频过程中的不同阶段,也会采用不同的时间基。
FFmepg中有三种时间基,命令行中tbr、tbn和tbc的打印值就是这三种时间基的倒数:
tbn:对应容器中的时间基(对应的结构体为AVPacket)。值是AVStream.time_base的倒数
tbc:对应编解码器中的时间基(对应的结构体为AVFrame)。值是AVCodecContext.time_base的倒数
tbr:从视频流中猜算得到,可能是帧率或场率(帧率的2倍)
除以上三种时间基外,FFmpeg还有一个内部时间基AV_TIME_BASE(以及分数形式的AV_TIME_BASE_Q)
#define AV_TIME_BASE 1000000
#define AV_TIME_BASE_Q (AVRational){1, AV_TIME_BASE}
AV_TIME_BASE及AV_TIME_BASE_Q用于FFmpeg内部函数处理,使用此时间基计算得到时间值表示的是微秒。
因为数据状态不同,时间基不一样,所以我们必须转换,这就是pts的转换,以下是时间基转换函数。
static inline double av_q2d(AVRational a){
return a.num / (double) a.den;
}
av_q2d 函数负责把AVRational结构转换成double,通过这个函数可以计算出某一帧在视频中的时间位置:
AVStream stream;
AVPacket packet;
packet播放时刻值:timestamp(单位秒) = packet.pts × av_q2d(stream.time_base);
packet播放时长值:duration(单位秒) = packet.duration × av_q2d(stream.time_base);
av_rescale_q 函数:这个函数的作用是计算a*bq / cq来把时间戳从一个时间基调整到另外一个时间基。在进行时间基转换的时候,应该首先这个函数,因为它可以避免溢出的情况发生。
int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq) av_const;
av_packet_rescale_ts 用于将AVPacket中各种时间值从一种时间基转换为另一种时间基。
void av_packet_rescale_ts(AVPacket *pkt, AVRational tb_src, AVRational tb_dst);
容器中的时间基(AVStream.time_base)定义如下:
typedef struct AVStream {
......
/**
* This is the fundamental unit of time (in seconds) in terms
* of which frame timestamps are represented.
*
* decoding: set by libavformat
* encoding: May be set by the caller before avformat_write_header() to
* provide a hint to the muxer about the desired timebase. In
* avformat_write_header(), the muxer will overwrite this field
* with the timebase that will actually be used for the timestamps
* written into the file (which may or may not be related to the
* user-provided one, depending on the format).
*/
AVRational time_base;
......
}
AVStream.time_base是AVPacket中pts和dts的时间单位,输入流与输出流中time_base按如下方式确定:
对于输入流:打开输入文件后,调用avformat_find_stream_info()可获取到每个流中的time_base
对于输出流:打开输出文件后,调用avformat_write_header()可根据输出文件封装格式确定每个流的time_base并写入输出文件中
不同封装格式具有不同的时间基,在转封装(将一种封装格式转换为另一种封装格式)过程中,时间基转换相关代码如下:
av_read_frame(ifmt_ctx, &pkt);
pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
下面的代码具有和上面代码相同的效果:
// 从输入文件中读取packet
av_read_frame(ifmt_ctx, &pkt);
// 将packet中的各时间值从输入流封装格式时间基转换到输出流封装格式时间基
av_packet_rescale_ts(&pkt, in_stream->time_base, out_stream->time_base);
这里流里的时间基in_stream->time_base
和out_stream->time_base
,是容器中的时间基.
例如,flv封装格式的time_base为{1,1000},ts封装格式的time_base为{1,90000}.
我们编写程序将flv封装格式转换为ts封装格式,抓取原文件(flv)的前四帧显示时间戳:
think@opensuse> ffprobe -show_frames -select_streams v tnmil3.flv | grep pkt_pts
ffprobe version 4.1 Copyright (c) 2007-2018 the FFmpeg developers
Input #0, flv, from 'tnmil3.flv':
Metadata:
encoder : Lavf58.20.100
Duration: 00:00:03.60, start: 0.017000, bitrate: 513 kb/s
Stream #0:0: Video: h264 (High), yuv420p(progressive), 784x480, 25 fps, 25 tbr, 1k tbn, 50 tbc
Stream #0:1: Audio: aac (LC), 44100 Hz, stereo, fltp, 128 kb/s
pkt_pts=80
pkt_pts_time=0.080000
pkt_pts=120
pkt_pts_time=0.120000
pkt_pts=160
pkt_pts_time=0.160000
pkt_pts=200
pkt_pts_time=0.200000
再抓取转换的文件(ts)的前四帧显示时间戳:
think@opensuse> ffprobe -show_frames -select_streams v tnmil3.ts | grep pkt_pts
ffprobe version 4.1 Copyright (c) 2007-2018 the FFmpeg developers
Input #0, mpegts, from 'tnmil3.ts':
Duration: 00:00:03.58, start: 0.017000, bitrate: 619 kb/s
Program 1
Metadata:
service_name : Service01
service_provider: FFmpeg
Stream #0:0[0x100]: Video: h264 (High) ([27][0][0][0] / 0x001B), yuv420p(progressive), 784x480, 25 fps, 25 tbr, 90k tbn, 50 tbc
Stream #0:1[0x101]: Audio: aac (LC) ([15][0][0][0] / 0x000F), 44100 Hz, stereo, fltp, 127 kb/s
pkt_pts=7200
pkt_pts_time=0.080000
pkt_pts=10800
pkt_pts_time=0.120000
pkt_pts=14400
pkt_pts_time=0.160000
pkt_pts=18000
pkt_pts_time=0.200000
可以发现,对于同一个视频帧,它们时间基(tbn)不同因此时间戳(pkt_pts)也不同,但是计算出来的时刻值(pkt_pts_time)是相同的。
看第一帧的时间戳,计算关系:80×{1,1000} == 7200×{1,90000} == 0.080000
编解码器中的时间基(AVCodecContext.time_base)定义如下:
typedef struct AVCodecContext {
......
/**
* This is the fundamental unit of time (in seconds) in terms
* of which frame timestamps are represented. For fixed-fps content,
* timebase should be 1/framerate and timestamp increments should be
* identically 1.
* This often, but not always is the inverse of the frame rate or field rate
* for video. 1/time_base is not the average frame rate if the frame rate is not
* constant.
*
* Like containers, elementary streams also can store timestamps, 1/time_base
* is the unit in which these timestamps are specified.
* As example of such codec time base see ISO/IEC 14496-2:2001(E)
* vop_time_increment_resolution and fixed_vop_rate
* (fixed_vop_rate == 0 implies that it is different from the framerate)
*
* - encoding: MUST be set by user.
* - decoding: the use of this field for decoding is deprecated.
* Use framerate instead.
*/
AVRational time_base;
......
}
AVCodecContext.time_base是帧率的倒数。在视频解码过程中,我们不使用AVCodecContext.time_base,而用帧率倒数作时间基,在视频编码过程中,我们将AVCodecContext.time_base设置为帧率的倒数。
视频按帧播放,所以解码后的原始视频帧时间基为 1/framerate。
视频解码过程中的时间基转换处理:
AVFormatContext *ifmt_ctx;
AVStream *in_stream;
AVCodecContext *dec_ctx;
AVPacket packet;
AVFrame *frame;
// 从输入文件中读取编码帧
av_read_frame(ifmt_ctx, &packet);
// 时间基转换
int raw_video_time_base = av_inv_q(dec_ctx->framerate);
av_packet_rescale_ts(packet, in_stream->time_base, raw_video_time_base);
// 解码
avcodec_send_packet(dec_ctx, packet)
avcodec_receive_frame(dec_ctx, frame);
视频编码过程中的时间基转换处理:
AVFormatContext *ofmt_ctx;
AVStream *out_stream;
AVCodecContext *dec_ctx;
AVCodecContext *enc_ctx;
AVPacket packet;
AVFrame *frame;
// 编码
avcodec_send_frame(enc_ctx, frame);
avcodec_receive_packet(enc_ctx, packet);
// 时间基转换
packet.stream_index = out_stream_idx;
enc_ctx->time_base = av_inv_q(dec_ctx->framerate);
av_packet_rescale_ts(&opacket, enc_ctx->time_base, out_stream->time_base);
// 将编码帧写入输出媒体文件
av_interleaved_write_frame(o_fmt_ctx, &packet);
音频按采样点播放,所以解码后的原始音频帧时间基为 1/sample_rate
音频解码过程中的时间基转换处理:
AVFormatContext *ifmt_ctx;
AVStream *in_stream;
AVCodecContext *dec_ctx;
AVPacket packet;
AVFrame *frame;
// 从输入文件中读取编码帧
av_read_frame(ifmt_ctx, &packet);
// 时间基转换
int raw_audio_time_base = av_inv_q(dec_ctx->sample_rate);
av_packet_rescale_ts(packet, in_stream->time_base, raw_audio_time_base);
// 解码
avcodec_send_packet(dec_ctx, packet)
avcodec_receive_frame(dec_ctx, frame);
音频编码过程中的时间基转换处理:
AVFormatContext *ofmt_ctx;
AVStream *out_stream;
AVCodecContext *dec_ctx;
AVCodecContext *enc_ctx;
AVPacket packet;
AVFrame *frame;
// 编码
avcodec_send_frame(enc_ctx, frame);
avcodec_receive_packet(enc_ctx, packet);
// 时间基转换
packet.stream_index = out_stream_idx;
enc_ctx->time_base = av_inv_q(dec_ctx->sample_rate);
av_packet_rescale_ts(&opacket, enc_ctx->time_base, out_stream->time_base);
// 将编码帧写入输出媒体文件
av_interleaved_write_frame(o_fmt_ctx, &packet);