[ffmpeg] 解码API

版本迭代

ffmpeg解码API通过了好几个版本的迭代,上一个版本的API是html

咱们如今能看到的不少解码例子用的都是这两个,不过如今ffmpeg更推荐用新一代的APIide

 

一般来讲,一个packet会被解码出一个frame,不过也存在一个packet被解码出多个frame或者多个packet才能解码出一个frame的状况,甚至也有些解码器在输入以及输出端上可能会有延迟。所以原来的API在某种程度上存在对调用者误导的可能,使得调用者认为输入的一个或者多个Packet就对应着解码器所输出的一个frame,但实际上可能并不是如此。函数

新的API彻底隐藏了“解码”这一律念,只提供一个输入packet的接口以及输出frame的接口,如此一来调用者能够没必要了解解码器的具体细节,只须要了解这两个接口的调用规则就能写出适用于全部解码器的代码。编码

 

 

状态机

新一代API是一个状态机。调用API是一种动做,API的返回值就是一种状态,经过动做能够进行状态的转换。正常状况下,状态机有6种状态:spa

  • send 0                :send_packet返回值为0,正常状态,意味着输入的packet被解码器正常接收。
  • send EAGAIN    :send_packet返回值为EAGAIN,输入的packet未被接收,须要输出一个或多个的frame后才能从新输入当前packet。
  • send EOF           :send_packet返回值为EOF,当send_packet输入为NULL时才会触发该状态,用于通知解码器输入packet已结束。
  • receive 0            :receive_frame返回值为0,正常状态,意味着已经输出一帧。
  • receive EAGAIN:receive_frame返回值为EAGAIN,未能输出frame,须要输入更多的packet才能输出当前frame。
  • receive EOF       :receive_frame返回值为EOF,当处于send EOF状态后,调用一次或者屡次receive_frame后就能获得该状态,表示全部的帧已经被输出。

image

如上图所示,尽管状态转换稍微有些繁琐,但该状态转换图实际上包含了两种策略,对两种策略分别进行分析能对状态机有一个更为清晰的了解。3d

 

以消耗packet为主的策略

虽然咱们前面说过输入的packet并不必定对应于所输出的frame,不过在这里为了方便语言上的描述,在这里咱们能够认为receive_frame是对输入的packet的一种消耗,当receive_frame返回EAGAIN时就认为所输入的packet被彻底消耗。这里的策略就是对每次所输入的一个packet,都循环调用receive_frame对该packet进行消耗,直到所输入的packet消耗完成。code

image

在消耗完一个packet后输入下一个packet视频

image

当全部的packet都消耗完成后,调用send_packet输入NULL,把状态转换为send EOF,最后调用receive_frame把状态转换为receive EOF即完成全部解码任务。htm

image

 

 

以获取frame为主的策略

本策略是先循环调用send_packet直到返回EAGAIN,此时确定能够输出frame了blog

image

而后调用receive_frame输出一帧

image

当全部的packet都输入完成后,调用send_packet输入NULL,把状态转换为send EOF,最后调用receive_frame把状态转换为receive EOF即完成全部解码任务。

image

 

 

API代码分析

avcodec_send_packet

avcodec_send_packet有以下结构:

image

首先粗略了解一下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是实际的解码入口,它有以下结构

image

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经历了以下流程。

image

 

avcodec_receive_frame

avcodec_receive_frame有以下结构:

image

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经历了以下流程。

image

 

关于EAGAIN

咱们前面讨论过EAGAIN状态:

  • avcodec_send_packet返回EAGAIN代表没法输入当前packet,须要调用avcodec_receive_frame进行消耗上一个packet。
  • avcodec_receive_packet返回EAGAIN代表没法获取当前frame,须要调用avcodec_send_packet输入更多的packet。

 

通常来讲,在实际的实现中,EAGAIN是由bsf相关的函数返回的。

  • 调用avcodec_send_packet时,会先调用av_bsf_send_packet,此时若是用于暂存packet的buffer_pkt中含有有效packet时,av_bsf_send_packet会返回EAGAIN,这会致使avcodec_send_packet也返回EAGAIN。
  • 调用avcodec_receive_frame时,若是没有可输出的frame,则会进入decode_receive_frame_internal分支。此时若是用于暂存packet的buffer_pkt中不含有效packet时,ff_bsf_get_packet_ref会返回EAGAIN,这会致使decode_receive_frame_internal返回EAGAIN,从而也使得avcodec_receive_frame也返回EAGAIN。

不过咱们注意到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;
相关文章
相关标签/搜索