写在前面

最近在做和转码有关的项目,接触到ffmpeg这个神器。从一开始简单的写脚本直接调用ffmpeg的可执行文件做些转码的工作,到后来需要写程序调用ffmpeg的API。虽然上网搜了别人的demo稍微改改顺利完成了工作,但是对于ffmpeg这个黑盒子,还是有些好奇心和担心(项目中使用不了解的代码总是不那么放心),于是抽空翻了翻ffmpeg的源码,整理成文章给大家分享分享。

由于我并非做音频出身,对于音频一窍不通。ffmpeg整个也非常庞大,所以这篇文章从ffmpeg提供的转码的demo开始,侧重于讲清楚整个输入->转码->输出的流程,并学习ffmpeg如何做到通用和可扩展性。

注:本文基于ffmpeg提供的transcode_aac.c样例。

三个重点

转码的过程是怎么样的?简单来说就是从输入读取数据,解析原来的数据格式,转成目标数据格式,再将最终数据输出。这里就涉及到三个数据输入和输出方式数据的编码方式数据的容器格式(容器是用来区分不同文件的数据类型的,而编码格式则由音视频的压缩算法决定,一般所说的文件格式或者后缀名指的就是文件的容器。对于一种容器,可以包含不同编码格式的一种视频和音频)。

ffmpeg是一个非常非常通用的工具,支持非常广的数据输入和输出,包括:hls流,文件,内存等,支持各类数据编码格式,包括:aac,mp3等等,同时支持多种容器格式,包括ts,aac等。另外ffmpeg是通过C语言实现的,如果是C++,我们可以通过继承和多态来实现。定义一个IO的基类,一个Format的基类和一个Codec的基类,具体的输入输出协议继承IO基类实现各自的输入输出方法,具体的容器格式继承Format基类,具体的编码格式继承Codec基类。这篇文章也会简单讲解ffmpeg如何用C语言实现类似C++的继承和多态。

基本数据结构

ffmpeg转码中最基本的结构为AVFormatContext和AVCodecContext。AVCodecContext负责编码,AVFormatContext负责IO和容器格式。

我从AVFormatContext类抽离出三个基本的成员iformat,oformat,pb。分别属于AVInputFormat,AVOutputFormat,AVIOContext类。iformat为输入的数据格式,oformat为输出的数据格式,pb则负责输入输出。

我把这三个类的定义抽离了出来简化了下,可以看出AVInputFormat声明了read_packet方法,AVOutputFormat声明了write_packet方法,AVIOContext声明了read_packet, write_packet方法。同时AVInputFormat和AVOutputFormat还有一个成员变量name用以标识该格式的后缀名。

下一节我们会看到Input/OutputForm的read/write packet方法和IOContext的关系。

输入函数调用图

下面是初始化输入的整个过程的函数调用图。

首先从调用open_input_file开始,首先解析输入的protocol。avio_open2函数会调用一系列helper函数(ffurl_open,ffio_fdopen)分析输入的协议,设置AVFormatContext的pb变量的read_packet方法。而av_probe_input_buffer2函数则会分析输入文件的格式(从文件名解析或输入数据做判断),设置AVFormatContext的iformat的read_packet方法。

两个read_packet有什么关系呢?第二个函数调用图可以看出,iformat的read_packet最终会调用pb的read_packet方法。意思就是数据本身由pb的read_packet方法来读取,而iformat则会在输入的数据上做些格式相关的解析操作(比如解析输入数据的头部,提取出输入数据中真正的音频/视频数据,再加以转码)。

IO相关代码

直接看上面的图不太直观,这一节我把源码中各个步骤截图下来进行分析。

转码开始步骤,调用open_input_file函数,传入文件名。

avformat_open_input函数会调用init_input()来处理输入文件。

init_input函数主要做两个事情,一是解析输入协议(如何读取数据?hls流?文件?内存?),二是解析输入数据的格式(输入数据为aac?ts?m4a?)

avio_open2函数首先调用ffurl_open函数,根据文件名来推断所属的输入协议(URLProtocol)。之后再调用ffio_fdopen设置pb的read_packet方法。

上面几段代码的逻辑为:根据文件名查找对应的URLProtocol->把该URLProtocol赋值给URLContext的prot成员变量->创建AVIOContext实例,赋值给AVFormatContext的pb成员变量。

这里设置了AVIOContext实例的read_packet为ffurl_read方法。

ffurl_read方法其实就是调用URLContext的prot(上面赋值的)的url_read方法。通过函数指针去调用具体的URLContext对象的prot成员变量的url_read方法。

接下来看看解析输入数据格式的代码。av_probe_input_buffer2函数调用av_probe_input_format2函数来推断数据数据的格式。从之前的图我们知道*fmt其实就是&s->iformat。因此这里设置了AVFormatContext的iformat成员变量。

至此AVFormatContext对象的iformat和pb成员变量就设置好了。接下来看看如何读取输入开始转码。

av_read_frame函数调用read_frame_internal函数开始读取数据。

read_frame_internal会调用ff_read_packet,后者最终调用的是iformat成员变量的read_packet方法。

拿aac举例,aac的read_packet方法实际上是ff_raw_read_partial_packet函数。

ff_raw_read_partial_packet会调用ffio_read_partial,后者最终调用的是AVFormatContext的pb成员变量的read_packet方法。而我们知道pb成员的read_packet其实就是ffurl_read,也就是具体输入URLProtocl的read_packet方法。

至此已经走完了整个输入的流程,输出也是类似的代码,这里就不再赘述。

转码函数调用图

上面关于IO的介绍我从输入的角度进行分析。接下来的转码过程我则从输出的角度进行分析。下图是转码过程的函数调用图(做了简化)。load_encode_and_write调用encode_audio_frame, encode_audio_frame调用avcodec_encode_audio2来做实际的编码工作,最后调用av_write_frame将编码完的数据写入输出。

转码相关代码

首先需要设置输出目标编码格式,下面的代码为设置编码格式(aac)的片段:

在这里设置了output_codec_context(AVCodecContext类对象)之后,从前面的函数调用图,我们知道是avcodec_encode_audio2函数执行的转码过程:

这里看到调用了avctx(AVCodecContext类对象)的codec(AVCodec类对象)成员变量的encode2方法去做编码操作。

转码这里专业性比较强,我并没有细读,因此这里简单带过。

总结

可以看出ffmpeg大量使用函数指针来实现类似C++的继承/多态的效果。并且ffmpeg具有非常好的扩展性。如果我需要自定义一个新的输入协议,只需要自己定义一个新的URLProtocol对象,实现read_packet方法即可。如果需要自定义一个新的容器格式,只需要定义一个新的AVInputFormat对象,实现read_packet方法即可。如果需要自定义一个新的编码格式,只需要定义一个新的AVCodec对象,实现encode2方法即可。真是非常赞的代码架构设计!

本文涉及的资料全部打包放到我Github仓: GitHub:2022年,最新 ffmpeg 资料整理,项目(调试可用),命令手册,文章,编解码论文,视频讲解,面试题全套资料 有需要的可以前去下载,或者觉得还不错,请给我Star,感谢支持!

3个重点,20个函数分析,浅析FFmpeg转码过程相关推荐

  1. x264 代码重点详解 详细分析

    eg mplayer x264 代码重点详解 详细分析 分类: ffmpeg 2012-02-06 09:19 4229人阅读 评论(1) 收藏 举报 h.264codecflv优化initializ ...

  2. 【Android 逆向】整体加固脱壳 ( DexClassLoader 加载 dex 流程分析 | DexPathList 构造函数分析 | makeDexElements 函数分析 )

    文章目录 前言 一.DexPathList 构造函数分析 二.DexPathList.makeDexElements 函数分析 三.Element 类分析 前言 上一篇博客 [Android 逆向]整 ...

  3. 【Android 逆向】Android 逆向通用工具开发 ( adb forward 网络端口重定向命令 | PC 端逆向程序主函数分析 )

    文章目录 前言 一.adb forward 网络端口重定向命令 二.PC 端逆向程序主函数分析 前言 本篇博客重点分析 PC 端 hacktool 模块 ; 一.adb forward 网络端口重定向 ...

  4. python函数count_python中count函数知识点浅析

    python中,count函数的作用是进行python中的数量计算.count函数用于统计字符串.列表或元祖中某个字符出现的次数,是一个很好用的统计函数.具体介绍请看本文. 1.count函数 统计列 ...

  5. ucos任务调度函数 OSSched()函数分析 ,任务切换函数

    OS_Sched()分析 在uc/os中总是运行优先级最高的就绪任务,确定哪个任务优先级最高,该由哪个优先级人物运行了,这一工作是由任务调度器完成的,(而具体的任务切换,是任务调度器在调用其他函数来完 ...

  6. ffmpeg mplayer x264 代码重点详解 详细分析

    ffmpeg和mplayer中求平均值得方法 1 ordinary c language level #define avg2(a,b) ((a+b+1)>>1) #define avg4 ...

  7. Darknet函数分析

    Darknet中函数分析 随机打乱数据 代码在data.c源文件中 void randomize_data(data d) {int i;for(i = d.X.rows-1; i > 0; - ...

  8. 全景相机行业重点企业竞争格局分析及市场销售规模前景预测

    全景相机行业重点企业竞争格局分析及市场销售规模前景预测 1.全景相机行业竞争情况:在2015年的VR热潮的推动下,影像行业的传统企业如柯达.三星.理光等都推出相应的全景相机产品,故早期的全景相机行业竞 ...

  9. python中统计函数_python中count函数知识点浅析

    python中,count函数的作用是进行python中的数量计算.count函数用于统计字符串.列表或元祖中某个字符出现的次数,是一个很好用的统计函数.具体介绍请看本文. 1.count函数 统计列 ...

  10. FFmpeg学习 avcodec软解码函数分析

    前言 本文分析ffmpeg软解码流程,相关函数如下,以find_stream_info中的try_decode_frame为例: 相关函数都在libavcodec包下. 基本调用流程如下: const ...

最新文章

  1. 倍福ads通讯软件_软件定义汽车“性感”吗?东软睿驰有自己的答案
  2. 启动 docker 容器报错 (iptables failed: iptables --wait -t filter -A DOCKER ! -i docker0 -o docker0
  3. 深度学总结:RNN训练需要注意地方:pytorch每一个batch训练之前需要把hidden = hidden.data,否者反向传播的梯度会遍历以前的timestep
  4. LintCode 633. 寻找重复的数(这个题要复习)
  5. linux系统函数 utime,utime函数
  6. SQL Server自动备份存储过程和视图的方法
  7. excel 调用表单名称公式_原来Excel自动生成图表报表是这样做出来的?Excel图表制作方法...
  8. C盘空间丢失30G,怎么也找不到
  9. Java实现仿QQ登陆、好友界面(可连接数据库)
  10. 计算机中word音乐符号在哪里找,word音乐符号怎么打出来|word音乐符号怎么打
  11. 浅谈YOLOV2与YOLOV3
  12. java对象转换为map
  13. [个人笔记]FDTD100
  14. 2017-2018 年终总结
  15. ValueError: Format specifier missing precision
  16. 5种经典的Linux桌面系统
  17. 基本类型和包装类型的区别详解
  18. 艾司博讯:拼多多新手如何正确使用多多进宝?
  19. [Zookeeper-3.6.2源码解析系列]-14-Zookeeper使用到的Reactor网络模型原理分析
  20. 使用easygui制作app

热门文章

  1. ssh登录工具 putty 和 生成.ppk文件的puttygen工具 如何使用puttygen生成密钥
  2. 天空U盘装机助理 v1.51正式版(UD版_U盘启动制作工具)
  3. 基于Go调用国密SM2算法
  4. java 查看native方法_Java-如何查看java里的native方法?
  5. matlab数组、矩阵运算
  6. java dtls server_基于tinyDTLS 构建的lwm2m Server
  7. 如何利用OTDR光纤测试仪定位熔接点及诊断排除故障
  8. dmg为什么下载成php,解答:dmg是什么意思,dmg文件如何打开,及怎么把dmg转换成iso
  9. 使用QUARKUS开发JSON REST 服务
  10. CAN通讯、CAN协议、UDS