直播1


视频流媒体中视频数据的传输占据了绝大部分的带宽,如何提升编码效率,使用更少的带宽,提供更优质的画面质量,是音视频开发人员努力的重点。

HEVC(High Efficiency Video Coding,也叫H.265)编码格式的推出,给这一方向带来了突破点,但由于其算法复杂度较高,前期未曾得到普遍应用,而随着移动设备计算能力的提高和越来越多的设备开始支持HEVC的硬件编/解码,直播平台也开始逐渐引入HEVC视频格式。


HEVC属视频编码层面标准,如果在视频流媒体中进行应用,还需要相应的封装格式和流媒体协议的支持。鉴于直播的大部分推拉流协议是基于RTMP的,本文主要介绍如何在RTMP协议中增加对HEVC视频编码格式的支持,其他协议或私有协议,可参考本文自行添加。此外,除推流端和播放端要做出修改,服务端也要同步进行相应修改,才能够保证HEVC在直播中的正常使用。



典型的直播框架通常包括三大部分,如下图所示


1.  推流端:负责音视频数据的采集、处理、编码及封     装后将数据推送至源站;

2.  服务端:涵盖源站和CDN,接收来自推流端的音视     频数据,然后将数据分发至各播放端;

3.  播放端:从CDN拉取直播数据,解复用、解码后渲     染音视频数据;

引入HEVC编码,涉及到的变动部分如上图中红色字体所标注:

1.  编码模块:需要支持HEVC格式的编解码,该部分不     属于本文的介绍范畴,我们有在其它文章中介绍如     何在iOS11上进行HEVC的硬编硬解,感兴趣的朋     友可自行查阅;

2.  封装/传输模块:RTMP、HTTP-FLV流媒体协议需     要增加对HEVC视频编码格式的支持,该部分是本     文介绍的重点。

02

FFmpeg 简 析


FFmpeg从无到有,发展至今,功能日益强大,代码也越来越多,很多初学者都被其众多的源文件、庞大的结构体和复杂的算法打消了继续学习的念头。本章节将从总体对FFmpeg进行简单的解析,教您如何阅读FFmpeg源码。



2.1  总体说明

FFmpeg包含如下类库:

•  libavformat - 用于各种音视频封装格式的生成和解    析,包括获取解码所需信息、读取音视频数据等功    能。各种流媒体协议代码(如rtmpproto.c等)以及音    视频格式的(解)复用代码(如flvdec.c、flvenc.c等)都    位于该目录下。

•  libavcodec - 音视频各种格式的编解码。各种        格式的编解码代码(如aacenc.c、aacdec.c等)都位    于该目录下。

•  libavutil - 包含一些公共的工具函数的使用库,包括    算数运算,字符操作等。

•  ibswscale - 提供原始视频的比例缩放、色彩映射    转换、图像颜色空间或格式转换的功能。

•  libswresample - 提供音频重采样,采样格式转换和    混合等功能。

•  libavfilter - 各种音视频滤波器。

•  libpostproc - 用于后期效果处理,如图像的去块效    应等。

•  libavdevice - 用于硬件的音视频采集、加速和显      示。


如果您之前没有阅读FFmpeg代码的经验,建议优先阅读libavformat、libavcodec以及libavutil下面的代码,它们提供了音视频开发的最基本功能,应用范围也是最广的。


2.2  常用结构

FFmpeg里面最常用的数据结构,按功能可大致分为以下几类(以下代码行数,以branch: origin/release/3.4为准):


1.  封装格式

•  AVFormatContext - 描述了媒体文件的构成及基本    信息,是统领全局的基本结构体,贯穿程序始终,很    多函数都要用它作为参数;

•  AVInputFormat - 解复用器对象,每种作为输入的    封装格式(例如FLV、MP4、TS等)对应一个该结构    体,如libavformat/flvdec.c的ff_flv_demuxer;

•  AVOutputFormat - 复用器对象,每种作为输出的    封装格式(例如FLV, MP4、TS等)对应一个该结构    体,如libavformat/flvenc.c的ff_flv_muxer;

•  AVStream - 用于描述一个视频/音频流的相关数据    信息。


2.  编解码

•  AVCodecContext - 描述编解码器上下文的数据结    构,包含了众多编解码器需要的参数信息;

•  AVCodec - 编解码器对象,每种编解码格式(例如    H.264、AAC等)对应一个该结构     体,如                libavcodec/aacdec.c的ff_aac_decoder。每个        AVCodecContext中含有一个AVCodec;

•  AVCodecParameters - 编解码参数,每个            AVStream中都含有一个                              AVCodecParameters,用来存放当前流的编解码参    数。


3.  网络协议

•  AVIOContext - 管理输入输出数据的结构体;

•  URLProtocol - 描述了音视频数据传输所使用的协    议,每种传输协议(例如HTTP、RTMP)等,都会对    应一个URLProtocol结构,如libavformat/http.c中的    ff_http_protocol;

•  URLContext - 封装了协议对象及协议操作对象。


4.  数据存放

•  AVPacket - 存放编码后、解码前的压缩数据,即ES数据;

•  AVFrame - 存放编码前、解码后的原始数据,如      YUV格式的视频数据或PCM格式的音频数据等

上述结构体的关系图如下所示(箭头表示派生出):


图2. FFmpeg结构体关系图



2.3  代码结构

下面这段代码完成了读取媒体文件中音视频数据的基本功能,本节以此为例,分析FFmpeg内部代码的调用逻辑。


2.3.1  注册

av_register_all函数的作用是注册一系列的(解)复用器、编/解码器等。它在所有基于FFmpeg的应用程序中几乎都是第一个被调用的,只有调用了该函数,才能使用复用器、编码器等。

REGISTER_MUXDEMUX实际上调用的是av_register_input_format和av_register_output_format,通过这两个方法,将(解)复用器分别添加到了全局变量first_iformat与first_oformat链表的最后位置。

编/解码其注册过程相同,此处不再赘述。


2.3.2  文件打开

FFmpeg读取媒体数据的过程始于avformat_open_input,该方法中完成了媒体文件的打开和格式探测的功能。但FFmpeg是如何找到正确的流媒体协议和解复用器呢?可以看到avformat_open_input方法中调用了init_input函数,在这里面完成了查找流媒体协议和解复用器的工作。

1. s->io_open实际上调用的就是io_open_default,它最终调用到url_find_protocol方法。

ffurl_get_protocols可以得到当前编译的FFmpeg支持的所有流媒体协议,通过url的scheme和protocol->name相比较,得到正确的protocol。例如本例中URLProtocol最终指向了libavformat/http.c中的ff_http_protocol。


2. av_probe_input_buffer2最终调用到av_probe_input_format3,该方法遍历所有的解复用器,即first_iformat链表中的所有节点,调用它们的read_probe()函数计算匹配得分,函数最终返回计算找到的最匹配的解复用器。本例中AVInputFormat最终指向了libavformat/flvdec.c中的ff_flv_demuxer。


2.3.3  数据读取

av_read_frame作用是读取媒体数据中的每个音视频帧,该方法中最关键的地方就是调用了AVInputFormat的read_packet()方法。AVInputFormat的read_packet()是一个函数指针,指向当前的AVInputFormat的读取数据的函数。在本例中,AVInputFormat为ff_flv_demuxer,也就是说read_packet最终指向了flv_read_packet。




2.1  总体说明

FFmpeg包含如下类库:

•  libavformat - 用于各种音视频封装格式的生成和解    析,包括获取解码所需信息、读取音视频数据等功    能。各种流媒体协议代码(如rtmpproto.c等)以及音    视频格式的(解)复用代码(如flvdec.c、flvenc.c等)都    位于该目录下。

•  libavcodec - 音视频各种格式的编解码。各种        格式的编解码代码(如aacenc.c、aacdec.c等)都位    于该目录下。

•  libavutil - 包含一些公共的工具函数的使用库,包括    算数运算,字符操作等。

•  ibswscale - 提供原始视频的比例缩放、色彩映射    转换、图像颜色空间或格式转换的功能。

•  libswresample - 提供音频重采样,采样格式转换和    混合等功能。

•  libavfilter - 各种音视频滤波器。

•  libpostproc - 用于后期效果处理,如图像的去块效    应等。

•  libavdevice - 用于硬件的音视频采集、加速和显      示。


如果您之前没有阅读FFmpeg代码的经验,建议优先阅读libavformat、libavcodec以及libavutil下面的代码,它们提供了音视频开发的最基本功能,应用范围也是最广的。


2.2  常用结构

FFmpeg里面最常用的数据结构,按功能可大致分为以下几类(以下代码行数,以branch: origin/release/3.4为准):


1.  封装格式

•  AVFormatContext - 描述了媒体文件的构成及基本    信息,是统领全局的基本结构体,贯穿程序始终,很    多函数都要用它作为参数;

•  AVInputFormat - 解复用器对象,每种作为输入的    封装格式(例如FLV、MP4、TS等)对应一个该结构    体,如libavformat/flvdec.c的ff_flv_demuxer;

•  AVOutputFormat - 复用器对象,每种作为输出的    封装格式(例如FLV, MP4、TS等)对应一个该结构    体,如libavformat/flvenc.c的ff_flv_muxer;

•  AVStream - 用于描述一个视频/音频流的相关数据    信息。


2.  编解码

•  AVCodecContext - 描述编解码器上下文的数据结    构,包含了众多编解码器需要的参数信息;

•  AVCodec - 编解码器对象,每种编解码格式(例如    H.264、AAC等)对应一个该结构     体,如                libavcodec/aacdec.c的ff_aac_decoder。每个        AVCodecContext中含有一个AVCodec;

•  AVCodecParameters - 编解码参数,每个            AVStream中都含有一个                              AVCodecParameters,用来存放当前流的编解码参    数。


3.  网络协议

•  AVIOContext - 管理输入输出数据的结构体;

•  URLProtocol - 描述了音视频数据传输所使用的协    议,每种传输协议(例如HTTP、RTMP)等,都会对    应一个URLProtocol结构,如libavformat/http.c中的    ff_http_protocol;

•  URLContext - 封装了协议对象及协议操作对象。


4.  数据存放

•  AVPacket - 存放编码后、解码前的压缩数据,即ES数据;

•  AVFrame - 存放编码前、解码后的原始数据,如      YUV格式的视频数据或PCM格式的音频数据等

上述结构体的关系图如下所示(箭头表示派生出):


图2. FFmpeg结构体关系图



2.3  代码结构

下面这段代码完成了读取媒体文件中音视频数据的基本功能,本节以此为例,分析FFmpeg内部代码的调用逻辑。


2.3.1  注册

av_register_all函数的作用是注册一系列的(解)复用器、编/解码器等。它在所有基于FFmpeg的应用程序中几乎都是第一个被调用的,只有调用了该函数,才能使用复用器、编码器等。

REGISTER_MUXDEMUX实际上调用的是av_register_input_format和av_register_output_format,通过这两个方法,将(解)复用器分别添加到了全局变量first_iformat与first_oformat链表的最后位置。

编/解码其注册过程相同,此处不再赘述。


2.3.2  文件打开

FFmpeg读取媒体数据的过程始于avformat_open_input,该方法中完成了媒体文件的打开和格式探测的功能。但FFmpeg是如何找到正确的流媒体协议和解复用器呢?可以看到avformat_open_input方法中调用了init_input函数,在这里面完成了查找流媒体协议和解复用器的工作。

1. s->io_open实际上调用的就是io_open_default,它最终调用到url_find_protocol方法。

ffurl_get_protocols可以得到当前编译的FFmpeg支持的所有流媒体协议,通过url的scheme和protocol->name相比较,得到正确的protocol。例如本例中URLProtocol最终指向了libavformat/http.c中的ff_http_protocol。


2. av_probe_input_buffer2最终调用到av_probe_input_format3,该方法遍历所有的解复用器,即first_iformat链表中的所有节点,调用它们的read_probe()函数计算匹配得分,函数最终返回计算找到的最匹配的解复用器。本例中AVInputFormat最终指向了libavformat/flvdec.c中的ff_flv_demuxer。


2.3.3  数据读取

av_read_frame作用是读取媒体数据中的每个音视频帧,该方法中最关键的地方就是调用了AVInputFormat的read_packet()方法。AVInputFormat的read_packet()是一个函数指针,指向当前的AVInputFormat的读取数据的函数。在本例中,AVInputFormat为ff_flv_demuxer,也就是说read_packet最终指向了flv_read_packet。


猜你喜欢

转载自blog.csdn.net/ssllkkyyaa/article/details/79494712