ffmpeg里time_base总结

一、time_base
AVStream的time_base的单位是秒。每种格式的time_base的值不一样,根据采样来计算,比如mpeg的pts、dts都是以90kHz来采样的,所以采样间隔就是1/900000秒。

AVCodecContext的time_base单位同样为秒,不过精度没有AVStream->time_base高,大小为1/framerate。

AVPacket下的pts和dts以AVStream->time_base为单位(数值比较大),时间间隔就是AVStream->time_base。

AVFrame里面的pkt_pts和pkt_dts是拷贝自AVPacket,同样以AVStream->time_base为单位;而pts是为输出(显示)准备的,以AVCodecContex->time_base为单位。

输入流InputStream下的pts和dts以AV_TIME_BASE为单位(微秒),至于为什么要转化为微秒,可能是为了避免使用浮点数。

输出流OutputStream涉及音视频同步,结构和InputStream不同,暂时只作记录,不分析。

二、各个time_base之前的转换
ffmpeg提供av_rescale_q函数用于time_base之间转换,av_rescale_q(a,b,c)作用相当于执行a*b/c,通过设置b,c的值,可以很方便的实现time_base之间转换。

** 例如 **

InputStream(AV_TIME_BASE)到AVPacket(AVStream->time_base)

static int decode_video(InputStream *ist, AVPacket *pkt, int *got_output)
{
     
        pkt->dts  = av_rescale_q(ist->dts, AV_TIME_BASE_Q, ist->st->time_base);
}

AVPacket(AVStream->time_base)到InputStream(AV_TIME_BASE)

static int process_input_packet(InputStream *ist, const AVPacket *pkt) 
{
         
      if (pkt->dts != AV_NOPTS_VALUE) 
      {
            
        ist->next_dts = ist->dts = av_rescale_q(pkt->dts, ist->st->time_base, AV_TIME_BASE_Q); 
      }
}

三、其他
AVFrame->pts和AVPacket->pts、AVPacket->dts的值,在解码/编码后,会经历短暂的time_base不匹配的情况:

解码后,decoded_frame->pts的值使用AVStream->time_base为单位,后在AVFilter里面转换成以AVCodecContext->time_base为单位。
编码后,pkt.pts、pkt.dts使用AVCodecContext->time_base为单位,后通过调用"av_packet_rescale_ts"转换为AVStream->time_base为单位。


**

视频的显示和存放原理

**
对于一个电影,帧是这样来显示的:I B B P。现在我们需要在显示B帧之前知道P帧中的信息。因此,帧可能会按照这样的方式来存储:IPBB。这就是为什么我们会有一个解码时间戳和一个显示时间戳的原因。解码时间戳告诉我们什么时候需要解码,显示时间戳告诉我们什么时候需要显示。所以,在这种情况下,我们的流可以是这样的:

PTS: 1 4 2 3
DTS: 1 2 3 4
Stream: I P B B
通常PTS和DTS只有在流中有B帧的时候会不同。

DTS和PTS
音频和视频流都有一些关于以多快速度和什么时间来播放它们的信息在里面。音频流有采样,视频流有每秒的帧率。然而,如果我们只是简单的通过数帧和乘以帧率的方式来同步视频,那么就很有可能会失去同步。于是作为一种补充,在流中的包有种叫做DTS(解码时间戳)和PTS(显示时间戳)的机制。为了这两个参数,你需要了解电影存放的方式。像MPEG等格式,使用被叫做B帧(B表示双向bidrectional)的方式。另外两种帧被叫做I帧和P帧(I表示关键帧,P表示预测帧)。I帧包含了某个特定的完整图像。P帧依赖于前面的I帧和P帧并且使用比较或者差分的方式来编码。B帧与P帧有点类似,但是它是依赖于前面和后面的帧的信息的。这也就解释了为什么我们可能在调用avcodec_decode_video以后会得不到一帧图像。

ffmpeg中的时间单位
AV_TIME_BASE
ffmpeg中的内部计时单位(时间基),ffmepg中的所有时间都是于它为一个单位,比如AVStream中的duration即以为着这个流的长度为duration个AV_TIME_BASE。AV_TIME_BASE定义为:

#define         AV_TIME_BASE   1000000
 

AV_TIME_BASE_Q
ffmpeg内部时间基的分数表示,实际上它是AV_TIME_BASE的倒数。从它的定义能很清楚的看到这点:

#define         AV_TIME_BASE_Q   (AVRational){1, AV_TIME_BASE}

AVRatioal的定义如下:

typedef struct AVRational{
     
int num; //numerator
int den; //denominator
} AVRational;

ffmpeg提供了一个把AVRatioal结构转换成double的函数:

static inline double av_q2d(AVRational a)/**
* Convert rational to double.
* @param a rational to convert
**/
    return a.num / (double) a.den;
}

现在可以根据pts来计算一桢在整个视频中的时间位置:

timestamp() = pts * av_q2d(st->time_base)

计算视频长度的方法:

time() = st->duration * av_q2d(st->time_base)

这里的st是一个AVStream对象指针。

时间基转换公式

timestamp(ffmpeg内部时间戳) = AV_TIME_BASE * time(秒)
time(秒) = AV_TIME_BASE_Q * timestamp(ffmpeg内部时间戳)
所以当需要把视频跳转到N秒的时候可以使用下面的方法:

int64_t timestamp = N * AV_TIME_BASE; 
av_seek_frame(fmtctx, index_of_video, timestamp, AVSEEK_FLAG_BACKWARD);

ffmpeg同样为我们提供了不同时间基之间的转换函数:

int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq)

这个函数的作用是计算a * bq / cq,来把时间戳从一个时基调整到另外一个时基。在进行时基转换的时候,我们应该首选这个函数,因为它可以避免溢出的情况发生。

AVPacket

1、AVPacket用于存储压缩数据,位于avcodec.h文件中。
通常由demuxer(解复用)导出,然后作为输入传递给解码器;或者作为编码器的输出,然后传递给muxer(复用)
对于视频,它通常应包含一个压缩帧。 对于音频,它可能包含几个压缩帧。 允许编码器输出空分组,没有压缩数据,仅包含辅助数据(例如,在编码结束时更新一些参数)。
2、AVPacket这个结构体变量分析:

AVBufferRef *buf; //缓冲包数据存储位置。
int64_t pts;        //显示时间戳,以AVStream->time_base为单位。
int64_t dts;        //解码时间戳,以AVStream->time_base为单位。
uint8_t *data;     //压缩编码的数据。
int   size;        //data的大小。
int   stream_index;//给出所属媒体流的索引。
int   flags;       //标识域,其中,最低位置1表示该数据是一个关键帧。
AVPacketSideData *side_data;//容器提供的额外数据包。
int64_t duration;  //Packet持续的时间或叫数据时长,以AVStream->time_base为单位。
int64_t pos;//表示该数据在媒体流中的字节偏移量。

3、AVPacket结构本身只是一个容器,它使用data成员引用实际的数据缓冲区。这个缓冲区通常是由av_new_packet创建的,但也可能由FFmpeg的API创建(如av_read_frame)。当某个AVPacket结构的数据缓冲区不再被使用时,要需要通过调用av_free_packet释放。
这个结构体在FFmpeg中非常常见,需要重点掌握。

你可能感兴趣的:(流媒体)