本人最近在做一个可视化工具,可以输入视频文件,对视频文件进行转码转封装,其中需要显示处理进度,本人以视频帧总数为基准进行进度显示,比如文件中视频帧总数为1000时,现在处理到第100帧,此时进度条显示10%。
本人使用AVStream中nb_frames作为视频帧总数,数值为1200,但是本人在一帧一帧读取,解码处理时,最终得到的视频帧数是1199;比1200少1。
为此,本人使用ffprobe探帧命令行测试,得到的结果也是1199,命令行如下:
ffprobe -v error -count_frames -select_streams v:0 -show_entries stream=nb_read_frames -of default=nokey=1:noprint_wrappers=1 2022-01-08T22-32-58.mp4
本人然后又用ffprobe打印每一帧的具体情况,最终也是1199帧。
问题出在哪里,为此本人调试了ffprobe,关于ffprobe的调试工程搭建,大家可以参看我的博客vs2017调试ffprobe源码
很快,源码调试就是快,本人很快找到了原因,在ffprobe.c里面,有一个函数,如下:
static av_always_inline int process_frame(WriterContext *w,
InputFile *ifile,
AVFrame *frame, AVPacket *pkt,
int *packet_new)
{
AVFormatContext *fmt_ctx = ifile->fmt_ctx;
AVCodecContext *dec_ctx = ifile->streams[pkt->stream_index].dec_ctx;
AVCodecParameters *par = ifile->streams[pkt->stream_index].st->codecpar;
AVSubtitle sub;
int ret = 0, got_frame = 0;
clear_log(1);
if (dec_ctx && dec_ctx->codec) {
switch (par->codec_type) {
case AVMEDIA_TYPE_VIDEO:
case AVMEDIA_TYPE_AUDIO:
if (*packet_new) {
ret = avcodec_send_packet(dec_ctx, pkt);
if (ret == AVERROR(EAGAIN)) {
ret = 0;
} else if (ret >= 0 || ret == AVERROR_EOF) {
ret = 0;
*packet_new = 0;
}
}
if (ret >= 0) {
ret = avcodec_receive_frame(dec_ctx, frame);
if (ret >= 0) {
got_frame = 1;
} else if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
ret = 0;
}
}
break;
case AVMEDIA_TYPE_SUBTITLE:
if (*packet_new)
ret = avcodec_decode_subtitle2(dec_ctx, &sub, &got_frame, pkt);
*packet_new = 0;
break;
default:
*packet_new = 0;
}
} else {
*packet_new = 0;
}
if (ret < 0)
return ret;
if (got_frame) {
int is_sub = (par->codec_type == AVMEDIA_TYPE_SUBTITLE);
nb_streams_frames[pkt->stream_index]++;
if (do_show_frames)
if (is_sub)
show_subtitle(w, &sub, ifile->streams[pkt->stream_index].st, fmt_ctx);
else
show_frame(w, frame, ifile->streams[pkt->stream_index].st, fmt_ctx);
if (is_sub)
avsubtitle_free(&sub);
}
return got_frame || *packet_new;
}
大家可以看到nb_streams_frames[pkt->stream_index]++;
这个nb_streams_frames里面记录的是ffprobe打印出来的帧数,而这个++执行的条件是got_frame为真。
现在我在将里面的关键点摘抄出来,如下所示:
if (ret >= 0) {
ret = avcodec_receive_frame(dec_ctx, frame);
if (ret >= 0) {
got_frame = 1;
} else if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
ret = 0;
}
}
这个是个解码的过程,只有成功解码出一帧(ret >= 0),才被计算在总数内。
2023年2月11号:本人发现,在编码的时候,如果AVPacket里面的duration写为0,则将导致结尾某处的一帧不可解码,所以后面本人在编码时,duration不设置为0就行了。