ffmpeg解码API通过了好几个版本的迭代,上一个版本的API是html
咱们如今能看到的不少解码例子用的都是这两个,不过如今ffmpeg更推荐用新一代的APIide
一般来讲,一个packet会被解码出一个frame,不过也存在一个packet被解码出多个frame或者多个packet才能解码出一个frame的状况,甚至也有些解码器在输入以及输出端上可能会有延迟。所以原来的API在某种程度上存在对调用者误导的可能,使得调用者认为输入的一个或者多个Packet就对应着解码器所输出的一个frame,但实际上可能并不是如此。函数
新的API彻底隐藏了“解码”这一律念,只提供一个输入packet的接口以及输出frame的接口,如此一来调用者能够没必要了解解码器的具体细节,只须要了解这两个接口的调用规则就能写出适用于全部解码器的代码。编码
新一代API是一个状态机。调用API是一种动做,API的返回值就是一种状态,经过动做能够进行状态的转换。正常状况下,状态机有6种状态:spa
如上图所示,尽管状态转换稍微有些繁琐,但该状态转换图实际上包含了两种策略,对两种策略分别进行分析能对状态机有一个更为清晰的了解。3d
虽然咱们前面说过输入的packet并不必定对应于所输出的frame,不过在这里为了方便语言上的描述,在这里咱们能够认为receive_frame是对输入的packet的一种消耗,当receive_frame返回EAGAIN时就认为所输入的packet被彻底消耗。这里的策略就是对每次所输入的一个packet,都循环调用receive_frame对该packet进行消耗,直到所输入的packet消耗完成。code
在消耗完一个packet后输入下一个packet视频
当全部的packet都消耗完成后,调用send_packet输入NULL,把状态转换为send EOF,最后调用receive_frame把状态转换为receive EOF即完成全部解码任务。htm
本策略是先循环调用send_packet直到返回EAGAIN,此时确定能够输出frame了blog
而后调用receive_frame输出一帧
当全部的packet都输入完成后,调用send_packet输入NULL,把状态转换为send EOF,最后调用receive_frame把状态转换为receive EOF即完成全部解码任务。
avcodec_send_packet有以下结构:
首先粗略了解一下bsf,即bitstream filter。音频与视频编码后数据会以必定的语法结构进行构建,除了编码后的数据以外还有一些并不是解码所必须的语法元素,这些语法元素一般只是在解码、显示等过程起到辅助做用,这些语法元素不多使用到,它们的位置通常是位于在编码后的数据以前,如h264中的SEI。bitstream filter就是对这些语法元素进行调整。
av_bsf_send_packet会把packet输送到bitstream filter中,在av_bsf_send_packet当中,会判断用于暂存输入packet的buffer_pkt是否为有效packet,若是是有效packet,则代表上次传入的packet仍未被解码器消耗,所以没法接收此次传入的packet,返回EAGAIN。
if (ctx->internal->buffer_pkt->data || ctx->internal->buffer_pkt->side_data_elems) return AVERROR(EAGAIN);
不然就把当前packet移动到用于暂存的buffer_pkt
av_packet_move_ref(ctx->internal->buffer_pkt, pkt);
decode_receive_frame_internal是实际的解码入口,它有以下结构
decode_receive_frame_internal须要先从用于暂存的buffer_pkt中取出输入的packet,这是调用bsfs_poll来实现的。bsfs_poll会执行全部的bitstream filter,最终会调用到ff_bsf_get_packet_ref,在该函数内,会先判断用于暂存packet的buffer_pkt是否为有效packet,不是则返回EAGAIN
if (!ctx->internal->buffer_pkt->data && !ctx->internal->buffer_pkt->side_data_elems) return AVERROR(EAGAIN);
有效则取出该packet
av_packet_move_ref(pkt, ctx->internal->buffer_pkt);
取出该packet后就能够调用codec的decode函数来进行解码。
整体来看avcodec_send_packet经历了以下流程。
avcodec_receive_frame有以下结构:
avcodec_receive_frame会先进行判断,若是解码器解码出了一帧,则会调用av_frame_move_ref输出这一帧,不然继续调用decode_receive_frame_internal继续进行解码。
if (avci->buffer_frame->buf[0]) { av_frame_move_ref(frame, avci->buffer_frame); } else { ret = decode_receive_frame_internal(avctx, frame); if (ret < 0) return ret; }
整体来讲avcodec_receive_frame经历了以下流程。
咱们前面讨论过EAGAIN状态:
通常来讲,在实际的实现中,EAGAIN是由bsf相关的函数返回的。
不过咱们注意到avcodec_send_packet中也调用了decode_receive_frame_internal,不过avcodec_send_packet会忽视decode_receive_frame_internal所返回的EAGAIN。
ret = decode_receive_frame_internal(avctx, avci->buffer_frame); if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) return ret;