此文章分析的是旧的ffmpeg。
一般fps在代码里这样表示
Fps = den/num
如果den = 15,num=1,则fps = 15。
如果帧率固定,pts*fps 就表示当前是第几帧。
当输入视频流的帧率不固定,如rmvb ,而输出视频流的帧率固定,ffmpeg作如下处理(参考ffmpeg代码版本0.6.1):
1、 记录和输出视频流ost相对应的输入视频流ist,变量为ost->sync_ist。这是在av_transcode函数进行输出流初始化时进行的。代码分别为:
if (ist->discard && ist->st->discard != AVDISCARD_ALL && !skip &&
ist->st->codec->codec_type == ost->st->codec->codec_type) {
if(best_nb_frames < ist->st->codec_info_nb_frames){
best_nb_frames= ist->st->codec_info_nb_frames;
ost->source_index = j;
found = 1;
}
}
if (!found) {
if(! opt_programid) {
/* try again and reuse existing stream */
for(j=0;j<nb_istreams;j++) {
ist = ist_table[j];
if ( ist->st->codec->codec_type == ost->st->codec->codec_type
&& ist->st->discard != AVDISCARD_ALL) {
ost->source_index = j;
found = 1;
}
}
}
ist = ist_table[ost->source_index];
ist->discard = 0;
ost->sync_ist = (nb_stream_maps > 0) ?
ist_table[file_table[stream_maps[n].sync_file_index].ist_index +
stream_maps[n].sync_stream_index] : ist;
2、 记录输出视频流ost的时间戳。输出为固定帧率,故可以简化为记录帧数,变量为ost->sync_opts。
3、 将ost对应的ist的pts转换成固定帧率的帧数形式。代码为
sync_ipts = get_sync_ipts(ost) / av_q2d(enc->time_base);
get_sync_ipts计算ost对应的ist的pts,
av_q2d返回enc->time_base.num/ enc->time_base.den,即1/fps.
4、 将sync_ipts和ost->sync_opts进行求差。
double vdelta = sync_ipts - ost->sync_opts;
5、 根据vdelta来判断不同的情况。
l 情况一:Vdelta<-1.1,表示当前输入帧的播放时间在当前输出帧的前一帧之前,故舍弃该帧,nb_frames = 0。
l 情况二: (video_sync_method == 2 || (video_sync_method<0 && (s->oformat->flags & AVFMT_VARIABLE_FPS))){
if(vdelta<=-0.6){
nb_frames=0;
}else if(vdelta>0.6)
ost->sync_opts= lrintf(sync_ipts);}
这里video_sync_method==2 和video_sync_method < 0 表示什么意义,不是很清楚。貌似ffmpeg里video_sync_method一直设为-1。AVFMT_VARIABLE_FPS应该是变帧率的意思。这种情况下,vdelta<=0.6,表示位于当前帧之前,也舍弃该帧,nb_frames = 0;vdelta>0.6表示位于当前帧之后,直接把该帧的时间戳作为输出的时间出来输出该帧;0.6<vdelta<=0.6时,不做任何处理,nb_frames 根据默认值为1。
l 情况三:vdelta > 1.1
此时nb_frames = lrintf(vdelta),需要做插帧操作。
Ffmpeg的插帧操作,貌似是把当前输出帧重复输出nb_frames次。
AVFrame* old_frame = enc->coded_frame;
enc->coded_frame = dec->coded_frame; //FIXME/XXX remove this hack
pkt.data= (uint8_t *)final_picture;
pkt.size= sizeof(AVPicture);
pkt.pts= av_rescale_q(ost->sync_opts, enc->time_base, ost->st->time_base);
pkt.flags |= AV_PKT_FLAG_KEY;
write_frame(s, &pkt, ost->st->codec, bitstream_filters[ost->file_index][pkt.stream_index]);
enc->coded_frame = old_frame;
输出的数据在pkt.data里,final_picture即为经过处理的输入Pic。
6、输出视频流的帧率,是从输入视频流的包头数据中获得的。Rmvb的vedio MDPR块里,保存有fps和fps2信息。Ffmpeg取fps作为帧率,fps2丢弃了。Fps2有什么用,还不清楚。介绍rmvb格式的文章里也没有看到过关于fps的任何介绍。
一些调试
以为313版本为例:
delta0 = sync_ipts - ost->sync_opts; // delta0 is the "drift" between the input frame (next_picture) and where it would fall in the output.
delta = delta0 + duration;
printf("delta0:%f,sync_ipts:%f-%ld,duration:%f\n",delta0,sync_ipts,ost->sync_opts,duration);
nb_frames = FFMIN(nb_frames, ost->max_frames - ost->frame_number);
nb0_frames = FFMIN(nb0_frames, nb_frames);
printf("nb_frames:%d,frame_number:%d\n",nb_frames, ost->frame_number);
以-i /home/otvcloud/rec/t.mp4 -loglevel trace -vcodec h264 -r 10 /home/otvcloud/rec/1.mp4 -y 为例
delta0:-0.000008,sync_ipts:-0.000008-0,duration:0.400000
nb_frames:1,frame_number:0
dup frame,framenum:1
delta0:-0.599998,sync_ipts:0.400002-1,duration:0.400000
nb_frames:1,frame_number:1
dup frame,framenum:2
delta0:-1.199989,sync_ipts:0.800011-2,duration:0.400000
nb_frames:1,frame_number:2
dup frame,framenum:3
delta0:-1.799995,sync_ipts:1.200005-3,duration:0.400000
nb_frames:0,frame_number:3
delta0:-1.399986,sync_ipts:1.600014-3,duration:0.400000
nb_frames:1,frame_number:3
dup frame,framenum:4
delta0:-1.999992,sync_ipts:2.000008-4,duration:0.400000
nb_frames:0,frame_number:4
delta0:-1.599998,sync_ipts:2.400002-4,duration:0.400000
nb_frames:0,frame_number:4
delta0:-1.199989,sync_ipts:2.800011-4,duration:0.400000
nb_frames:1,frame_number:4
dup frame,framenum:5
delta0:-1.799995,sync_ipts:3.200005-5,duration:0.400000
nb_frames:0,frame_number:5
delta0:-1.399986,sync_ipts:3.600014-5,duration:0.400000
nb_frames:1,frame_number:5
dup frame,framenum:6
delta0:-1.999992,sync_ipts:4.000008-6,duration:0.400000
nb_frames:0,frame_number:6
可以看出输出帧是0.4的增量在加,当输入时间跟输出时间相差很多的时候,跳过输入帧;当接近时,使用输入帧率。
case VSYNC_CFR:
// FIXME set to 0.5 after we fix some dts/pts bugs like in avidec.c
if (frame_drop_threshold && delta < frame_drop_threshold && ost->frame_number) {
nb_frames = 0;
} else if (delta < -1.1)
nb_frames = 0; //输入时间小于输出时间,跳过帧。
else if (delta > 1.1) { //输入时间大于输出时间,使用帧。
nb_frames = lrintf(delta);
if (delta0 > 1.1)
nb0_frames = lrintf(delta0 - 0.6);
}
break;
/* duplicates frame if needed */
for (i = 0; i < nb_frames; i++) {
。。。。。。。 //此处即使使用帧,每使用一帧,ost->frame_number++。
}
以-i /home/otvcloud/rec/t.mp4 -loglevel trace -vcodec h264 -r 20 /home/otvcloud/rec/1.mp4 -y 为例
delta0:-0.199989,sync_ipts:0.800011-1,duration:1.000000
nb_frames:1,frame_number:1
dup frame,framenum:2
delta0:-0.399986,sync_ipts:1.600014-2,duration:1.000000
nb_frames:1,frame_number:2
dup frame,framenum:3
delta0:-0.599998,sync_ipts:2.400002-3,duration:1.000000
nb_frames:1,frame_number:3
dup frame,framenum:4
delta0:-0.799995,sync_ipts:3.200005-4,duration:1.000000
nb_frames:1,frame_number:4
dup frame,framenum:5
delta0:-0.999992,sync_ipts:4.000008-5,duration:1.000000
nb_frames:1,frame_number:5
dup frame,framenum:6
delta0:-1.199989,sync_ipts:4.800011-6,duration:1.000000
nb_frames:1,frame_number:6
dup frame,framenum:7
delta0:-1.399986,sync_ipts:5.600014-7,duration:1.000000
nb_frames:1,frame_number:7
dup frame,framenum:8
delta0:-1.599998,sync_ipts:6.400002-8,duration:1.000000
nb_frames:1,frame_number:8
dup frame,framenum:9
delta0:-1.799995,sync_ipts:7.200005-9,duration:1.000000
nb_frames:1,frame_number:9
dup frame,framenum:10