版本迭代

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

我们现在能看到的很多解码例子用的都是这两个,不过现在ffmpeg更推荐用新一代的API

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

新的API完全隐藏了“解码”这一概念,只提供一个输入packet的接口以及输出frame的接口,如此一来调用者可以不必了解解码器的具体细节,只需要了解这两个接口的调用规则就能写出适用于所有解码器的代码。

状态机

新一代API是一个状态机。调用API是一种动作,API的返回值就是一种状态,通过动作可以进行状态的转换。正常情况下,状态机有6种状态:

  • 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后就能得到该状态,表示所有的帧已经被输出。

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

以消耗packet为主的策略

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

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

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

以获取frame为主的策略

本策略是先循环调用send_packet直到返回EAGAIN,此时肯定可以输出frame了

然后调用receive_frame输出一帧

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

API代码分析

avcodec_send_packet

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有如下结构:

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状态:

  • 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;

[ffmpeg] 解码API的更多相关文章

  1. FFmpeg 学习(五):FFmpeg 编解码 API 分析

    在上一篇文章 FFmpeg学习(四):FFmpeg API 介绍与通用 API 分析 中,我们简单的讲解了一下FFmpeg 的API基本概念,并分析了一下通用API,本文我们将分析 FFmpeg 在编 ...

  2. FFmpeg编解码处理2-编解码API详解

    本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10584925.html FFmpeg编解码处理系列笔记: [0]. FFmpeg时间戳详 ...

  3. FFmpeg开发笔记(五):ffmpeg解码的基本流程详解(ffmpeg3新解码api)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  4. 最新FFMPEG解码流程

    FFMPEG解码流程: 1. 注册所有容器格式和CODEC:  av_register_all() 2. 打开文件:                    av_open_input_file() 3 ...

  5. 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——解码篇:(二)用ffmpeg解码音频

    其实这篇的内容和(一)用ffmpeg解码视频基本是一样的,重点还是给ffmpeg指定callback函数,而这个函数是从RTSP服务端那里获取音频数据的. 这里,解码音频的示例代码量之所以比解码视频的 ...

  6. 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——解码篇:(一)用ffmpeg解码视频

    一.概述 myRTSPClient(RTSPClient)获取音视频数据之后,接下来的工作便是将音视频数据交给解码器去解码(ffmpeg),ffmpeg解码之后于是便有了呈现在终端用户(USER)面前 ...

  7. (转)引用---FFMPEG解码过程

    视频播放过程 首先简单介绍以下视频文件的相关知识.我们平时看到的视频文件有许多格式,比如 avi, mkv, rmvb, mov, mp4等等,这些被称为容器(Container), 不同的容器格式规 ...

  8. FFmpeg再学习 -- FFmpeg解码知识

    继续看雷霄骅的 课程资料 - 基于FFmpeg+SDL的视频播放器的制作 前面用了五个篇幅来讲 FFmpeg,其主要目的是为实现将图片转视频的功能. 总的来说,对于 FFmepg 多少有一些了解了.但 ...

  9. ffmpeg解码RTSP/TCP视频流H.264(QT界面显示视频画面)

    源码下载地址: http://download.csdn.net/detail/liukang325/9489952 我用的ffmpeg版本为 ffmpeg-2.1.8.tar.bz2 版本低了恐怕有 ...

随机推荐

  1. Virtual Box虚拟机Ubuntu系统安装及基本配置

    Linux简介 什么是 Linux? Linux:世界上不仅只有一个 Windows 操作系统,还有 Linux.mac.Unix 等操作系统.桌面操作系统下 Windows 是霸主,而 Linux ...

  2. 并发concurrent---1

    背景:并发知识是一个程序员段位升级的体现,同样也是进入BAT的必经之路,有必要把并发知识重新梳理一遍. 并发concurrent: 说到并发concurrent,肯定首先想到了线程,创建线程有两种方法 ...

  3. kali linux 2019.1 替换burpsuite pro 1.7.37

    下面全部实操: 先切换JDK版本为1.8.执行 update-alternatives --config java 然后会显示多个jdk版本,1.8选3 输入java –version会显示jdk为1 ...

  4. html 微信video放大后无法返回问题

    android  video播放视频放大后无法返回,先debug下debugx5.qq.com 发现用的不是X5内核 直接激活  debugmm.qq.com/?forcex5=true  问题解决 ...

  5. 关于computed使用时报no-side-effects-in-computed-properties错误

    不要在计算属性内直接修改data里面的数据,eslint会报 no-side-effects-in-computed-properties 错误,如果非要改可以写在一个函数里,然后在计算属性里调用该函 ...

  6. jmeter使用TCP请求时,乱码问题,字符集设置

    不墨迹,直接上干货.(提示:UTF-8一个汉字占3个字节) TCP请求默认发的是GBK字符集,要想修改成UTF-8,只需要修改bin目录下的jmeter.properties文件,其中tcp.char ...

  7. 周一04.3流程控制while循环

    #循环就是重复做某件事 1.条件循环:while,语法如下 while 条件: # 循环体 # 如果条件为真,那么循环体则执行,执行完毕后再次循环,重新判断条件... # 如果条件为假,那么循环体不执 ...

  8. 随机排序std::vector,扑克牌,麻将类尤其合用

    有些需要重新对std::vector对象重新排序,特别是游戏,例如说:扑克牌,麻将,抽奖等,C++标准已经为std::vector写好了随机排序的方式,这里做个笔记: #include <alg ...

  9. IBM developer:Kafka ACLs

    Overview In Apache Kafka, the security feature is supported from version 0.9. When Kerberos is enabl ...

  10. ckeditor django admin 中使用

    ckeditor settings配置 ############ # CKEDITOR # ############ MEDIA_ROOT = os.path.join(BASE_DIR, 'medi ...