本文主要是介绍avcodec send_packet和receive_frame,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
下面是解码的过程代码,对输入给解码器的pkt桢类型进行判断,关键桢打印出is key frame
,解码出来的桢根据pict_type打印桢类型出I/P/B桢类型,从这里也可以看出来,没解码之前,AVPacket只能得到是否关键帧,要知道桢类型,必须在解码后。
完整代码可以从github上获取
/* Read packets from input file and decode them */while (av_read_frame(fmt_ctx, pkt) >= 0) {if (pkt->stream_index == video_stream_idx) {if (pkt->flags & AV_PKT_FLAG_KEY) {av_log(NULL, AV_LOG_INFO, " in is key frame!\n");} else {av_log(NULL, AV_LOG_INFO, " in is't key frame!\n");}/* Send packet to decoder */ret = avcodec_send_packet(codec_ctx, pkt);if (ret < 0) {fprintf(stderr, "Error sending packet to decoder\n");exit(1);}/* Receive frame from decoder */while (ret >= 0) {ret = avcodec_receive_frame(codec_ctx, frame);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {break;} else if (ret < 0) {fprintf(stderr, "Error receiving frame from decoder\n");exit(1);}if (frame->pict_type == AV_PICTURE_TYPE_I) {av_log(NULL, AV_LOG_INFO, " out is I frame!\n");} else if (frame->pict_type == AV_PICTURE_TYPE_P) {av_log(NULL, AV_LOG_INFO, " out is P frame!\n");} else if (frame->pict_type == AV_PICTURE_TYPE_B) {av_log(NULL, AV_LOG_INFO, " out is B frame!\n");}/* Write YUV data to output file */fwrite(frame->data[0], 1, codec_ctx->width * codec_ctx->height, outfile);fwrite(frame->data[1], 1, codec_ctx->width * codec_ctx->height / 4, outfile);fwrite(frame->data[2], 1, codec_ctx->width * codec_ctx->height / 4, outfile);}}av_packet_unref(pkt);}
FFmpeg解码流程
avcodec_send_packet
先对avpkt进行ref操作,然后发送给bsf,然后判断avci->buffer_frame->buf
为NULL,就调用decode_receive_frame_internal
进行解码,进入decode_simple_internal
后,ff_decode_get_packet
会先从bsf中获取packet,然后调用解码器解码函数进行解码。
avcodec_receive_frame
中也会先判断avci->buffer_frame->buf[0]
,如果不为NULL,说明前面send_packet的时候已经解码出来了,这次调用只需要进行ref操作。如果avci->buffer_frame->buf[0]
为NULL,调用decode_receive_frame_internal解码,和前面send_packet中的调用流程一样。
avcodec_send_packet-> av_packet_ref(avci->buffer_pkt, avpkt)-> av_bsf_send_packet(avci->bsf, avci->buffer_pkt)-> !avci->buffer_frame->buf[0]-> decode_receive_frame_internal-> decode_simple_receive_frame(avctx, frame)-> decode_simple_internal-> ff_decode_get_packet-> av_bsf_receive_packet-> ff_bsf(ctx->filter)->filter(ctx, pkt)-> h264_decode_framegot_frame -> returnavcodec_receive_frame # 循环receive,直到返回EAGAIN-> avci->buffer_frame->buf[0] # 前面已经解码出来-> av_frame_move_ref(frame, avci->buffer_frame) #返回继续解码-> !avci->buffer_frame->buf[0] #前面没有解码-> decode_receive_frame_internal-> decode_simple_receive_frame(avctx, frame)-> decode_simple_internal-> ff_decode_get_packet-> av_bsf_receive_packet-> ff_bsf(ctx->filter)->filter(ctx, pkt)-> h264_decode_framegot_frame -> return
解码含B桢的视频
解码含B桢的视频,通过ffprobe把前面几桢的frame信息输出到xml,可以看到桢的分布如下:
ffprobe -show_frames -select_streams v -of xml ~/video/b-frame.mp4 > videoframes.info
<frame key_frame="1" pts="0" pkt_dts="0" pkt_dts_time="0.000000" width="1920" height="1080" pix_fmt="yuv420p" pict_type="I"><side_data_list><side_data type="H.26[45] User Data Unregistered SEI message"><side_datum key="side_data_type" value="H.26[45] User Data Unregistered SEI message"/></side_data></side_data_list></frame><frame key_frame="0" pkt_dts="512" pkt_dts_time="0.040000" pkt_size="921" width="1920" height="1080" pix_fmt="yuv420p" pict_type="B" /><frame key_frame="0" pkt_dts="1024" pkt_dts_time="0.080000" pkt_size="250" width="1920" height="1080" pix_fmt="yuv420p" pict_type="B" /><frame key_frame="0" pkt_dts="1536" pkt_dts_time="0.120000" pkt_size="2663" width="1920" height="1080" pix_fmt="yuv420p" pict_type="P" /><frame key_frame="0" pkt_dts="2048" pkt_dts_time="0.160000" pkt_size="400" width="1920" height="1080" pix_fmt="yuv420p" pict_type="B" /><frame key_frame="0" pkt_dts="2560" pkt_dts_time="0.200000" pkt_size="247" width="1920" height="1080" pix_fmt="yuv420p" pict_type="B" /><frame key_frame="0" pkt_dts="3072" pkt_dts_time="0.240000" pkt_size="774" width="1920" height="1080" pix_fmt="yuv420p" pict_type="P" /><frame key_frame="0" pkt_dts="3584" pkt_dts_time="0.280000" pkt_size="353" width="1920" height="1080" pix_fmt="yuv420p" pict_type="B" /><frame key_frame="0" pkt_dts="4096" pkt_dts_time="0.320000" pkt_size="197" width="1920" height="1080" pix_fmt="yuv420p" pict_type="B" /><frame key_frame="0" pkt_dts="4608" pkt_dts_time="0.360000" pkt_size="206" width="1920" height="1080" pix_fmt="yuv420p" pict_type="P" /><frame key_frame="0" pkt_dts="5120" pkt_dts_time="0.400000" pkt_size="332" width="1920" height="1080" pix_fmt="yuv420p" pict_type="B" /><frame key_frame="0" pkt_dts="5632" pkt_dts_time="0.440000" pkt_size="15012" width="1920" height="1080" pix_fmt="yuv420p" pict_type="B" /><frame key_frame="0" pkt_dts="6144" pkt_dts_time="0.480000" pkt_size="20861" width="1920" height="1080" pix_fmt="yuv420p" pict_type="P" />
从前面的xml中可以看到输入桢的次序是:
I B B P B B P B B P
in is key frame! # in Iin is't key frame! # in Bin is't key frame! # in B# 进三桢I B B解码出一帧Iout is I frame! # out I# 然后进一帧P解码出一帧Bin is't key frame! # in Pout is B frame! # out B# 然后进一帧B解码出一帧Bin is't key frame! # in Bout is B frame! # out B# 然后进一帧B解码出一帧Pin is't key frame! # in Bout is P frame! # out P# 然后进一帧P解码出一帧Bin is't key frame! # in Pout is B frame! # out B# 然后进一帧B解码出一帧Bin is't key frame! # in Bout is B frame! # out B
解码不含B桢的视频
通过debug和log可以看出,不含B桢的视频是进一帧出一帧,avcodec_send_packet
之后,decoder实际上会被调用一次解码出来一帧,avcodec_receive_frame
中调用ff_decode_receive_frame
也是判断avci->buffer_frame->buf[0]
是否为NULL,不为NULL时,av_frame_move_ref
返回,不会再走decode_receive_frame_internal
解码流程。
in is key frame!out is I frame!in is key frame!out is I frame!in is key frame!out is I frame!in is key frame!
所以使用avcodec_send_packet
/avcodec_receive_frame
,avcodec_send_packet
之后需要将frame及时取出来,不然第三次avcodec_send_packet
之后,判断avci->buffer_pkt
的地方就会返回EAGAIN。
if (!AVPACKET_IS_EMPTY(avci->buffer_pkt))return AVERROR(EAGAIN);
原因是这样的:
第一个avcodec_send_packet
进来,av_packet_ref(avci->buffer_pkt, avpkt)
后,调用decode_receive_frame_internal
解码,正常返回。
第二个avcodec_send_packet
进来,av_packet_ref(avci->buffer_pkt, avpkt)
后,因为avci->buffer_frame->buf[0]
不为NULL,没有调用decode_receive_frame_internal
解码消耗掉。
if (consumed >= pkt->size || ret < 0) {av_packet_unref(pkt);
消耗掉以后就会在
decode_simple_internal
中调用av_packet_unref
。
第三个avcodec_send_packet
进来,因为前面没有解码消耗,avci->buffer_pkt->data
不为NULL,AVPACKET_IS_EMPTY
判断失败,返回EAGAIN。
所以在使用FFmpeg解码时候,avcodec_send_packet
和avcodec_receive_frame
得在一起出现,receive_frame需要及时的将frame从avci->buffer_frame
中取出。
- 对于没有B桢的视频,没有及时取走后,因为第二个packet没有被解码,第三个以上的buf进来都会报
EAGAIN
。 - 对于有B桢的视频,进去几桢之后,没有及时取走,后面就不会再调用decode解码,前面的IBBPBB的桢排布,在第五个buf进来的时候也会报
EAGAIN
。
这篇关于avcodec send_packet和receive_frame的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!