FFmpeg之timebase和timestamp
以下姑且将timebase翻译为时间基,timestamp翻译为时间戳。时间戳是以时间基为单位的具体时间表示。
在音视频编解码时,经常会遇到时间基和时间戳的问题,导致音频视频解码播放出现问题。因此有必要做一个总结,以免下次再犯同样的错误。
一、timebase
ffmpeg存在多个时间基准(time_base),对应不同的阶段(结构体),每个time_base具体的值不一样,ffmpeg提供函数在各个time_base中进行切换。
1、AVStream
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是1S对应的时间单位,在 avformat_write_header()前可以设置AVStream->time_base,根据封装格式不一样,avformat_write_header()可能修改AVStream->time_base,比如mpegts修改为90000,flv修改为1000,mp4根据设置time_base,如果小于10000,会将time_base*2的幂直到大于10000。
2、AVCodecContext
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.
* - encoding: MUST be set by user.
* - decoding: Set by libavcodec.
*/
AVRational time_base;
}
AVCodecContext ->time_base是1S对应的时间单位,一般以帧率为作为timebase。
一、timestamp
Timestamp有PTS和DTS,一般在有B帧编码的情况下两者都会用到,没有B帧时,两者一般保持一样。
PTS(Presentation timestamp)即显示时间戳,就是一副图片或音频帧显示或播放的时间。
DTS(Decompressiontimestamp)即解码时间戳,就是一副图片或音频帧解码的时间。
1、 AVPacket
typedef struct AVPacket {
/**
* Presentation timestamp in AVStream->time_base units; the time at which
* the decompressed packet will be presented to the user.
* Can be AV_NOPTS_VALUE if it is not stored in the file.
* pts MUST be larger or equal to dts as presentation cannot happen before
* decompression, unless one wants to view hex dumps. Some formats misuse
* the terms dts and pts/cts to mean something different. Such timestamps
* must be converted to true pts/dts before they are stored in AVPacket.
*/
int64_t pts;
/**
* Decompression timestamp in AVStream->time_base units; the time at which
* the packet is decompressed.
* Can be AV_NOPTS_VALUE if it is not stored in the file.
*/
int64_t dts;
}
一帧原始数据压缩后的数据用AVPacket表示,pts指示帧显示时间,dts指示帧解码时间。
2、 AVFrame
typedef struct AVFrame {
/**
* Presentation timestamp in time_base units (time when frame should be shown to user).
*/
int64_t pts;
/**
* PTS copied from the AVPacket that was decoded to produce this frame.
*/
int64_t pkt_pts;
/**
* DTS copied from the AVPacket that triggered returning this frame. (if frame threading isn't used)
* This is also the Presentation time of this AVFrame calculated from
* only AVPacket.dts values without pts values.
*/
int64_t pkt_dts;
}
根据上面的解释,pts为frame的时间戳,在解码时pkt_pts和pkt_dts是复制的AVPacket中对应的pkt_pts和pkt_dts。需要说明的是pts在解码时是没有赋值的,需要调用pts =av_frame_get_best_effort_timestamp(p_frame)获取,这个函数是一个宏定义,实际上是读取的AVframe->best_effort_timestamp。编码时需要带上pts。
三、不同timebase之间timestamp的转换
FFmpeg提供一系列的函数用于此目的。
1、av_rescale_q
int64_t av_rescale_q(int64_ta, AVRational bq, AVRational cq)
{
return av_rescale_q_rnd(a, bq, cq, AV_ROUND_NEAR_INF);
}
2、av_rescale_q_rnd
int64_t av_rescale_q_rnd(int64_t a,AVRational bq, AVRational cq,
enum AVRounding rnd)
{
int64_t b = bq.num * (int64_t)cq.den;
int64_t c = cq.num * (int64_t)bq.den;
return av_rescale_rnd(a, b, c, rnd);
}
3、av_rescale_rnd
int64_t av_rescale_rnd(int64_ta, int64_t b, int64_t c, enum AVRounding rnd)
{
int64_t r = 0;
av_assert2(c > 0);
av_assert2(b >=0);
av_assert2((unsigned)(rnd&~AV_ROUND_PASS_MINMAX)<=5 &&(rnd&~AV_ROUND_PASS_MINMAX)!=4);
if (c <= 0 || b < 0 || !((unsigned)(rnd&~AV_ROUND_PASS_MINMAX)<=5&& (rnd&~AV_ROUND_PASS_MINMAX)!=4))
return INT64_MIN;
if (rnd & AV_ROUND_PASS_MINMAX) {
if (a == INT64_MIN || a == INT64_MAX)
return a;
rnd -= AV_ROUND_PASS_MINMAX;
}
if (a < 0 && a != INT64_MIN)
return -av_rescale_rnd(-a, b, c, rnd ^ ((rnd >> 1) & 1));
if (rnd == AV_ROUND_NEAR_INF)
r = c / 2;
else if (rnd & 1)
r = c - 1;
if (b <= INT_MAX && c <= INT_MAX) {
if (a <= INT_MAX)
return (a * b + r) / c;
else
return a / c * b + (a % c * b + r) / c;
}else {
#if 1
uint64_t a0 = a & 0xFFFFFFFF;
uint64_t a1 = a >> 32;
uint64_t b0 = b & 0xFFFFFFFF;
uint64_t b1 = b >> 32;
uint64_t t1 = a0 * b1 + a1 * b0;
uint64_t t1a = t1 << 32;
int i;
a0 = a0 * b0 + t1a;
a1 = a1 * b1 + (t1 >> 32) +(a0 < t1a);
a0 += r;
a1 += a0 < r;
for (i = 63; i >= 0; i--) {
a1 += a1 + ((a0 >> i) & 1);
t1 += t1;
if (c <= a1) {
a1 -= c;
t1++;
}
}
return t1;
}
#else
AVInteger ai;
ai = av_mul_i(av_int2i(a), av_int2i(b));
ai = av_add_i(ai, av_int2i(r));
return av_i2int(av_div_i(ai, av_int2i(c)));
}
#endif
}
这几个函数说简单点就是实现a*b/c,对尾数有一些舍入方法。如下
enum AVRounding {
AV_ROUND_ZERO = 0, /// AV_ROUND_INF = 1, /// AV_ROUND_DOWN = 2, ///< Round toward -infinity. AV_ROUND_UP = 3, /// AV_ROUND_NEAR_INF = 5, ///< Round to nearest and halfway cases awayfrom zero. AV_ROUND_PASS_MINMAX = 8192, ///< Flag to pass INT64_MIN/MAX throughinstead of rescaling, this avoids special cases for AV_NOPTS_VALUE };