版本迭代

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. Java中实现多线程的四种方式

    Java多线程实现方式主要有四种:继承Thread类.实现Runnable接口.实现Callable接口通过FutureTask包装器来创建Thread线程.使用ExecutorService.Cal ...

  2. 【转】解决未能加载文件或程序集'WebGrease‘的问题

    在多个视图中,如果有使用共用的样式代码,可以把它们移至CSS文件中去.今天Insus.NET就举例一个例子来说明. 比如前2篇中<ASP.NET MVC图片管理(上传,预览与显示)>htt ...

  3. Mysql使用event,类似oracle job

    MySQL从5.1开始支持event功能,类似oracle的job功能.有了这个功能之后我们就可以让MySQL自动的执行数据汇总等功能,不用像以前需要操作的支持了.如linux crontab功能. ...

  4. thinkphp5路由心得

    路由的作用:1. 简化URL地址,方便大家记忆2. 有利于搜索引擎的优化,比如可以被百度的爬虫抓取到 优化URl1. 前后端分离修改入口文件,在public下新建admin.php文件,将下面的代码添 ...

  5. 基于Xamarin Android实现的简单的浏览器

    最近做了一个Android浏览器,当然功能比较简单,主要实现了自己想要的一些功能……现在有好多浏览器为什么还要自己写?当你使用的时候总有那么一些地方不如意,于是就想自己写一个. 开发环境:Xamari ...

  6. Linux网络基本网络配置方法介绍

    网络信息查看 设置网络地址: cat /etc/sysconfig/network-scripts/ifcfg-eth0 你将会看到: DEVICE=eth0BOOTPROTO=staticsHWAD ...

  7. Python正则表达式很难?一篇文章搞定他,不是我吹!

    1. 正则表达式语法 1.1 字符与字符类 1 特殊字符:.^$?+*{}| 以上特殊字符要想使用字面值,必须使用进行转义 2 字符类 1. 包含在[]中的一个或者多个字符被称为字符类,字符类在匹配时 ...

  8. Android串口开发

    参考资料: https://www.jianshu.com/p/9249ed03e745 GitHUb地址: https://github.com/AIlll/AndroidSerialPort An ...

  9. mac上修改host

    host文件下载地址: https://github.com/highsea/Hosts/blob/master/hosts https://github.com/racaljk/hosts 备份ma ...

  10. 【shell基础】数学计算

    #!/bin/bash #4.4.sh s= #定义一个求和变量,初值为0. t=`**$` #用expr改变运算顺序,求x的y次方. t=$[t*] #t乘以3. s=$[s+t] #结果相加. t ...