版本迭代

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. 试试使用 eolinker 扫描 GitLab 代码注释自动生成 API 文档?

    前言: 一般写完代码之后,还要将各类参数注解写入API文档,方便后续进行对接和测试,这个过程通常都很麻烦,如果有工具可以读取代码注释直接生成API文档的话,那会十分方便. 此前一直都是在使用eolin ...

  2. python学习第九讲,python中的数据类型,字符串的使用与介绍

    目录 python学习第九讲,python中的数据类型,字符串的使用与介绍 一丶字符串 1.字符串的定义 2.字符串的常见操作 3.字符串操作 len count index操作 4.判断空白字符,判 ...

  3. Spring Cloud Alibaba基础教程:使用Sentinel实现接口限流

    最近管点闲事浪费了不少时间,感谢网友libinwalan的留言提醒.及时纠正路线,继续跟大家一起学习Spring Cloud Alibaba. Nacos作为注册中心和配置中心的基础教程,到这里先告一 ...

  4. fuzzing学习

    1.简介 1.1 fuzzing 模糊测试(fuzzing)是一种通过向程序提供非预期的输入并监控输出中的异常来发现软件中的故障的方法. 用于模糊测试的模糊测试器(fuzzer)可以按照以下3种方式进 ...

  5. .Net—反射

    在学习反射之前,先来理解以下几个概念. 1.什么是程序集? 程序集是.net中的概念. .Net中的dll文件与exe文件都是程序集.(exe与dll的区别?) exe文件是可执行文件,有执行入口.可 ...

  6. JavaSE面试题收集【仅有题目,答案自备】

    一共有140个题目,可以参考下.一个“.java”源文件中是否可以包含多个类(不是内部类)?有什么限制?说说&和&&的区别在Java中如何跳出当前的多重嵌套循环?IO流的分类S ...

  7. dubbo-源码分析Consumer

    counsumer使用服务的时候会在xml中配置<dubbo:reference> dubbo在spring.handles里的NamespaceHandle又有如下配置: registe ...

  8. vue echarts map的使用,页面多图动态自适应

    最近在vue中使用echarts时,遇到了一些坑,在此记录一下. 1:echarts map的使用 2:页面多图自适应,只有一个图生效 3:根据设备的dpr,动态的修改了meta标签中的initial ...

  9. LEDE 虚拟机安装

    虽然我对路由器没什么兴趣,但是紧跟潮流还是有必要的,现在因为网络闭关锁国政策,很多人都想自己搭配一台私人的服务器,不想被商业公司左右数据安全.我感觉这个是一个商机,建议大家可以朝这个方向发展. 这里最 ...

  10. Python3 小技巧

    完全个人总接 每个文件头部都可以加入这个,或者放到用单独一个文件,再import *.其实都一样,只需要一行false=False;true=True;none=null=None;hid=lambd ...