FFmpeg之timebase和timestamp

来源:互联网 发布:淘宝怎么代销商品 编辑:程序博客网 时间:2024/05/22 04:52

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;

}

根据上面的解释,ptsframe的时间戳,在解码时pkt_ptspkt_dts是复制的AVPacket中对应的pkt_ptspkt_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, ///<Round toward zero.

   AV_ROUND_INF      = 1, ///<Round away from zero.

   AV_ROUND_DOWN     = 2, ///< Round toward -infinity.

   AV_ROUND_UP       = 3, ///<Round toward +infinity.

   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

};