如下代码:
while( av_read_frame(pFormatCtxSource,&packet)>=0 )
{
if( packet.stream_index==videoStream )
{
int out_size = avcodec_decode_video(pCodecCtxSource,pFrameSource, &bFrameFinished, packet.data, packet.size); // Decode fromsource frame
if( bFrameFinished )
{
pFrameSource->pts =av_rescale_q(packet.pts, pCodecCtxSource->time_base,pStCodec->time_base);
int out_size =avcodec_encode_video(pStCodec, video_buffer, 200000, pFrameSource); // Encodeto output
if( out_size>0 )
{
// ...
}
}
}
av_free_packet(&packet);
}
在我Decode的时候,第一帧得到的 pFrameSource->pts 是96,再解第二帧的时候,pFrameSource->pts 计算完后就成了80几,后几帧也是比96小,过一会又会解出来一个100多的,接下来又是比100多小的,这是为什么?在Encode的时候,先 Encode一个pts=96的,再去Encode比96小的帧就返回-1了,直到找到一个比96大的。
另外,我计算pts的方法正确吗?
答复:
Because you have B - Frame
for example:
the Inputsequence for video encoder
1 2 3 4 5 6 7
I B B P B B I
Let's take1,2,3.. as PTS for simplification
the out sequencefor video encoder ( this equals the decoder sequence)
1 4 2 3 7 5 6
I P B B I B B
you will get aPTS sequence as following:
1 4 2 3 7 5 6
7 5 6sequence will be same as your question
问:
哦,那是不是我的pts不能这么算呢?而是要每次+1,对吗?那么,packet中的pts和dts要用在什么地方呢?我这样按存储顺序进行解码的话,显示之前是不是要自己进行缓存呢?谢谢!
另外,还有个问题,既然解码的时候,不一定是按照pts递增的顺序得到的解码后的画面,那我在编码图像的时候,是应该按照解码出来的帧顺序进行编码吗?还是把帧先缓存起来,最后严格接照图像的显示顺序来编码呢?用代码来表示,就是:
方法一:
while(av_read_frame )
{
解码;
pts+1;
编码;
输出;
}
方法二:
while(av_read_frame )
{
解码;
if( pts<previous )
{
缓存;
}
else
{
编码缓存的帧并写入文件;
}
}
这两个方法,哪个是正确的呢?因为我看到网上的代码都用的是方法一,但是我觉得方法二是对的呀?
答:
the output of decoderis the right order for display because I/P frames will be cacheduntil next I/P
理解:
Decoder 后output的pts 是按正常的顺序,即显示的顺序输出的,如果有B帧,decoder会缓存。
但encoder后,输出的是按dts输出的。
Pts,dts并不是时间戳,而更应该理解为frame的顺序序列号。由于每帧frame的帧率并不一定是一致的,可能会变化的。转换为时间戳的话,应该是(pts*帧率)。为加深理解
可以将pts比做是第pts帧frame,假设每帧的帧率不变的话,则显示的时间戳为(pts*帧率),如果考虑帧率变化的,则要想办法将(pts*当前的帧率)累加到后面。
在tutorial5中在decode 下增加trace后打印情况:
len1 = avcodec_decode_video(is->video_st->codec,pFrame, &frameFinished,
packet->data,packet->size);
printf("-----------------------------------------------------------------------------n");
printf("avcodec_decode_videopacket->pts:%x,packet->dts:%xn",packet->pts,packet->dts);
printf("avcodec_decode_videopFrame->pkt_pts:%x,pFrame->pkt_dts:%x,pFrame->pts:%xn",pFrame->pkt_pts,pFrame->pkt_dts,pFrame->pts);
if(pFrame->opaque)
printf("avcodec_decode_video*(uint64_t *)pFrame->opaque:%xn",*(uint64_t *)pFrame->opaque);
其中播一个mp4文件的打印情况:
-----------------------------------------------------------------------------
avcodec_decode_video packet->pts:1ae,packet->dts:0
avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0
avcodec_decode_video *(uint64_t *)pFrame->opaque:1ae
-----------------------------------------------------------------------------
avcodec_decode_video packet->pts:1af,packet->dts:0
avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0
avcodec_decode_video *(uint64_t *)pFrame->opaque:1af
-----------------------------------------------------------------------------
avcodec_decode_video packet->pts:24c,packet->dts:0
avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0
avcodec_decode_video *(uint64_t *)pFrame->opaque:1ac
-----------------------------------------------------------------------------
avcodec_decode_video packet->pts:24d,packet->dts:0
avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0
avcodec_decode_video *(uint64_t *)pFrame->opaque:24d
-----------------------------------------------------------------------------
avcodec_decode_video packet->pts:24e,packet->dts:0
avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0
avcodec_decode_video*(uint64_t *)pFrame->opaque:24e
以下为播放rm文件的情况:
-----------------------------------------------------------------------------
avcodec_decode_videopacket->pts:1831b,packet->dts:0
avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0
avcodec_decode_video *(uint64_t *)pFrame->opaque:1831b
-----------------------------------------------------------------------------
avcodec_decode_videopacket->pts:18704,packet->dts:0
avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0
avcodec_decode_video *(uint64_t *)pFrame->opaque:18704
-----------------------------------------------------------------------------
avcodec_decode_videopacket->pts:18aed,packet->dts:0
avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0
avcodec_decode_video *(uint64_t *)pFrame->opaque:18aed
-----------------------------------------------------------------------------
avcodec_decode_videopacket->pts:18ed6,packet->dts:0
avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0
avcodec_decode_video *(uint64_t *)pFrame->opaque:18ed6
-----------------------------------------------------------------------------
avcodec_decode_videopacket->pts:192bf,packet->dts:0
avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0
avcodec_decode_video *(uint64_t *)pFrame->opaque:192bf
-----------------------------------------------------------------------------
avcodec_decode_videopacket->pts:196a8,packet->dts:0
avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0
avcodec_decode_video *(uint64_t *)pFrame->opaque:196a8
可以看出有的pts是+1 累加,有的是加了很多,但都是按顺序累加的。当传人decoder前的packet有pts时,则decoder后获取的frame将会赋值packet 的pts;当传人的packet 只是一帧的部分数据或是B帧,由于decoder出来的frame要按正常的pts顺序输出,有可能decoder不会获取到frame ,或decoder内部会缓存也不会输出frame,即frame的pts会为空。Frame pts(即opaque) 为空的话则会看frame->dts,dts都没有的话才认为frame->pts为0.
对于:
pts *= av_q2d(is->video_st->time_base);////即pts*帧率
// Did we get avideo frame?
if(frameFinished) {
pts =synchronize_video(is, pFrame, pts);
///// synchronize_video考虑了3中情况:
1. pts拿到的话就用该pts
2. pts没有拿到的话就用前一帧的pts时间
3. 如果该帧要重复显示,则将显示的数量*帧率,再加到前面的pts中。
if(queue_picture(is, pFrame, pts) < 0) {/////传人decoder后的帧队列中,以便后续去获取show。
static double synchronize_video(VideoState *is, AVFrame*src_frame, double pts) {
doubleframe_delay;
if(pts != 0) {
is->video_clock = pts;
} else {
pts =is->video_clock;
}
/////很关键:前面传进来的pts已经是时间戳了,是当前frame开始播放的时间戳,
/////下面frame_delay是该帧显示完将要花费的时间,(pts+frame_delay)也即是/////预测的下一帧将要播放的时间戳。
frame_delay =av_q2d(is->video_st->codec->time_base);
//////重复多帧的话要累加上
frame_delay +=src_frame->repeat_pict * (frame_delay * 0.5);
is->video_clock += frame_delay;
return pts;/////此时返回的值即为下一帧将要开始显示的时间戳。
}
///////开定时器去显示帧队列中的已经decode过的数据,按前面的分析我们已经知道帧队列中的数据已经是按pts顺序插入到队列中的。 Timer的作用就是有帧率不一致及重复帧的情况造成时间戳不是线性的,有快有慢,从而tutorial5才有timer的方式来播放:追赶
以下是一个网友很直观浅显的例子解释:
ccq(183892517) 17:05:21 if(packet->dts ==AV_NOPTS_VALUE 是不是就是没有获取到dts的情况?
David Cen(3727567) 17:06:44 就是有一把尺子 一只蚂蚁跟着一个标杆走 David Cen(3727567) 17:06:58 标杆是匀速的 蚂蚁或快或慢 DavidCen(3727567) 17:07:18 慢了你就抽它 让他跑起来 快了就拽它 David Cen(3727567) 17:07:38 这样音(标杆)视频(蚂蚁)就能同步了 DavidCen(3727567) 17:08:00 这里最大的问题就是音频是匀速的 视频是非线性的
另外:此时vp–>pts获取到的pts已经转化为时间戳了,这个时间戳为就是当前帧显示结束的时间戳,也即是下一帧将显示的预测时间戳。
static void video_refresh_timer(void *userdata) {
VideoState *is = (VideoState*)userdata;
VideoPicture *vp;
double actual_delay, delay,sync_threshold, ref_clock, diff;
if(is->video_st) {
if(is->pictq_size == 0) {
schedule_refresh(is, 1);
} else {
vp =&is->pictq[is->pictq_rindex];
delay = vp->pts -is->frame_last_pts; ////这是当前要显示的frame和下一 副 //////将要显示的 frame的间隔时间
if(delay <= 0 || delay>= 1.0) {
delay =is->frame_last_delay;
}
is->frame_last_delay =delay;
is->frame_last_pts =vp->pts;
ref_clock = get_audio_clock(is);/////获取到声音当前播放的时间戳。
diff = vp->pts -ref_clock;////// vp->pts实际上是预测的下一帧将要播放的开始时间,
//////////也就是说在diff这段时间中声音是匀速发生的,但是在delay这段时间frame的显示可能就会有快//////////慢的区别。
sync_threshold = (delay >AV_SYNC_THRESHOLD) ? delay : AV_SYNC_THRESHOLD;
if(fabs(diff) < AV_NOSYNC_THRESHOLD) {
if(diff <=-sync_threshold) {
delay = 0;//////下一帧画面显示的时间和当前的声音很近的话加快显示下一帧(即后面video_display显示完当前帧后开启定时器很快去显示下一帧)
} else if(diff >=sync_threshold) {
delay = 2 * delay;//////下一帧开始显示的时间和当前声音的时间隔的比较长则延缓,即两帧画面间话的显示的时间长度大于两帧画面间的声音播放的时间,则我 们将两帧画显示的时候加倍拖长点,比如帧1和帧2的时间显示间隔为40ms,但帧1和帧2的声音播放时间为55ms,怎么办呢?我们不可能去打乱声音的质 量的,则我们采用的方法是:将两帧画面的播放间隔加大,本来是过30ms就要开始播下一帧的,我们改成60ms后才播下一帧。
}
}/////
////当然如果diff大于AV_NOSYNC_THRESHOLD,即快进的模式了,画面跳动太大,不存在音视频同步的问题了。
is->frame_timer += delay;
actual_delay =is->frame_timer - (av_gettime() / 1000000.0);
if(actual_delay < 0.010){
actual_delay = 0.010;
}
schedule_refresh(is,(int)(actual_delay * 1000 + 0.5));////开定时器去显示下一帧
video_display(is);////立马显示当前帧
if(++is->pictq_rindex ==VIDEO_PICTURE_QUEUE_SIZE) {
is->pictq_rindex = 0;
}
SDL_LockMutex(is->pictq_mutex);
is->pictq_size--;
SDL_CondSignal(is->pictq_cond);
SDL_UnlockMutex(is->pictq_mutex);
}
} else {
schedule_refresh(is, 100);
}