所以需要自研多媒体框架去解决/优化上述问题,以便后续能够更好的支撑业务发展。
支持Android7.0以后,将mediacodec部分从mediaplayerservice里抽离出来,单独开了一条新的binder服务media.codec来开放系统的硬编解码能力,系统要开放,必须要有标准,让各个硬件平台根据标准来开发其硬编解码器,然后集成到media.codec中,这个协议就是OpenMax。
OpenMax分为三层:
OpenMax是多媒体框架标准,目前应用最广泛的是IL层,各个硬件平台只需要依托IL层协议,提供统一的抽象层接口,屏蔽各自在底层适配时存在的差异,最终打包到libstagefrighthw.so,由mediacodecservice加载后,以binder服务的形式,将硬编解码能力开放给有需要的多媒体应用,包括OpenMaxAL,ffmpeg,nuplayer,exoplayer,街猫多媒体组件等等。
街猫多媒体底层依托于ffmpeg,从而具备了覆盖多媒体全应用场景的底层能力,基于ffmpeg4.2.2源码,我们目前主要做了如下定制化:
上图是视频播放器的基本流程,source、demux、decoder、output
市面上绝大多数播放器的基本结构都是如此,不同的是在实现方式上会存在差异。
下面拿ffmpeg/examples/transcoding.c做介绍,这是一个转码的参考工程:
ffmpeg使用AVPacket来存储编码的帧数据,解码后的音视频帧数据,统一使用AVFrame来存储。
音频帧数据存储音频解码后的pcm数据,在ffmpeg内部有Packed和Planar两种存储方式:
RPacked格式,frame.data[0]或frame.extended_data[0]包含AVFrame保存的所有pcm数据
Planar格式,frame.data[i]或者frame.extended_data[i]表示第i个声道的数据
AVFrame.data数组大小固定为8,如果声道数超过8,需要从frame.extended_data获取声道数据,extended_data是为了支持更多的声道数,后期扩展的字段。
Planar是ffmpeg内部的数据格式,常规的都为Packed,从命名上,Planar一般都在Packed命名后+P,比如sampleformat为16bit,Packed命名:AV_SAMPLE_FMT_S16,Planar:AV_SAMPLE_FMT_S16P。
除了data/extended_data,AVFrame保存音频数据其他四个核心字段:format(AVSampleFormat),sample_rate,channel_layout,nb_samples。format-指的是单帧的存储格式,可以通过该值来确定是packed还是planar,以及存储大小,16bit或float等sample_rate-采样率channel_layout-声道数nb_samples-AVFrame中包含的采样数(单声道)
所以,我们通过sample_rate和nb_samples就可以得出AVFrame包含pcm数据的播放时长,通过formatchannel_layoutnb_samples就可以得出AVFrame中buffer的长度
**视频帧数据存储
**视频帧数据采用YUV格式,其中Y指亮度通道,UV指色度通道,主流的采样格式有:YUV444、YUV422、YUV420YUV4::,简单点理解就是,以4个像素为一采样组,每个像素固定有一个Y通道,YUV后面UV对应的数值,以2为单位对应一组UV色度通道,注意,UV是一组,不要将其分开,基于这个去理解YUV4:2:2和YUV4:2:0采样方式,会更容易点对于大分辨率的视频帧,相邻像素的色度通道差异是极小的,所以YUV420格式在保证图像质量的情况下,又大幅的降低了存储,是目前主流的帧格式,接下去重点介绍下YUV420在ffmpeg,即AVFrame里的存储,YUV420根据Y,U,V数据的存储方式,又细分出YUV420P,YUV420SP(NV12)等子格式
YUV420P:YYYYYYYYUUVVY分量、U分量、V分量分别占一个平面空间,4个像素的Y分量共用一个UV分量
YUV420SP:YYYYYYYYUUVVY分量占一个平面空间,UV交差存储占一个平面空间,4个像素的Y分量共用一个UV分量
二者的差异,就是UV分量的存储方式,AVFrame保存YUV数据,主要用
uint8_t*data[AV_NUM_DATA_POINTERS];intlinesize[AV_NUM_DATA_POINTERS];data保存平面对应的向量数据,linesize保存平面对应向量数据的长度,所以yuv420p有三个平面,data数组有效长度为3,yuv420sp只有两个平面,有效长度就只有2。
ffmpeg也提供了辅助计算函数:timestamp(秒)=pts*av_q2d(time_base)
ffmpeg内部filtergraph处理流程
filtergraph建立后,会监听sourcefilter的sourcebuffer,如果有AVFrame塞入,filtergraph就开始运作,通过filterchain处理完后,从sinkfilter的sinkbuffer中取出,格式为AVFrame。
上述工程里initFilter代码流程:
然后在帧处理的时候:
步骤2和3创建了source和sinkfilter,workfilter则是使用avfilter_graph_parse_ptr传入filterspec,ffmpeg会解析spec创建内置的filter,当然,我们也可以实现自定义的filter塞入到filtegraph中。