移动端硬解关键流程梳理
介绍移动端Android/iOS硬解用法的文章有很多,本文将以笔者在实际开发工作中的经验为基础,抽出几个比较关键的部分来跟大家分享,旨在解决实际工作中可能遇到的花屏、(半边)绿屏、播放不完整等问题。本文将以目前广泛应用的H.264编码的视频为例来说明,主要包含:H.264码流数据结构说明、解码器的初始化、seek、前后台切换、无缝分辨率切换、播放结束时的处理以及iOS如何避免下半部分绿屏的问题。 一、H.264码流数据结构说明1. 理解码流数据结构的重要性我们讲支持硬解,提高硬解兼容性,实际上就是对码流数据的结构进行处理以符合平台硬解要求,因此对码流数据结构的理解是必不可少的。2. SPS/PPS与IDR帧SPS(Sequence Parameter Set)序列参数集、PPS(Picture Parameter Set)图像参数集,包含了图像编码的各种参数信息,是作为解码器初始化所必须的参数信息。IDR(Instantaneous Decoding Refresh)帧,也就是即时解码刷新帧,直观意思就是解码器在接收到IDR帧后会刷新参考帧缓存。IDR帧前后的视频帧不会有任何参考关系,解码器可以从任何一个IDR帧开始解码。3. H.264的NAL单元NALU结构图示: H.264标准中,视频流是由NAL(Network Abstraction Layer)单元组成的(简称NALU),每个NALU中可能是IDR图像、SPS、PPS、non-IDR图像等。上图中示意的NALU单元是以startcode方式分割的,关于NALU的分割方式将在后面说明。 另外,NALU内容中添加了防竞争字节,也就是说在一个NALU中,我们不可能再找到匹配的startcode.H.264流的NALU组成图示 从上图可以看到,一个视频帧中可能可能包含多个NALU, 此时可以称该视频帧为多slice视频帧(一个NALU中包含该视频帧的一个slice)。NAL Header的结构说明 其中nal_unit_type是我们关心的字段,该字段标识了当前NALU的类型,我们可以通过将NALU中第一个字节&0x1F的方式来得到NALU类型。NALU类型的具体定义如下图所示:NALU类型定义 其中5代表上面提到的IDR帧数据,7、8分别代表SPS/PPS数据。 AVCC与Annex-BH.264码流分为AVCC与Annex-B两种组织格式。• AVCC格式 也叫AVC1格式,MPEG-4格式,字节对齐,因此也叫Byte-Stream Format。用于mp4/flv/mkv等封装中。 • Annex-B格式 也叫MPEG-2 transport stream format格式(ts格式), ElementaryStream格式。用于TS流中(以及使用TS作为切片的hls格式中)。这两种格式的区别有两点: (1)NALU的分割方式不同; (2)SPS/PPS的数据结构不同。• AVCC格式使用NALU长度(固定字节,字节数由extradata中的信息给定)进行分割,在封装文件或者直播流的头部包含extradata信息(非NALU),extradata中包含NALU长度的字节数以及SPS/PPS信息。• Annex-B格式使用start code进行分割,start code为0x000001或0x00000001,SPS/PPS作为一般NALU单元以start code作为分隔符的方式放在文件或者直播流的头部。AVCC格式的extradata格式定义在“ISO_IEC_14496-15"文档中,Annex-B格式的SPS/PPS定义可以在"ISO_IEC_14496-10"文档中找到。MediaCodec与VideoToolBox使用的数据格式Android的硬解码接口MediaCodec只能接收Annex-B格式的H.264数据,而iOS平台的VideoToolBox则相反,只支持AVCC格式。这就导致:• 在Android平台硬解播放flv/mp4/mkv等封装的视频时,需要将AVCC格式的extradata以及NALU数据转为Annex-B格式;• 在iOS平台播放ts或ts切片的hls视频时,需要将Annex-B格式的SPS/PPS NALU转为AVCC格式的extradata,以及将其他以size方式分割的NALU转为start code方式。 二、解码器的初始化及数据输入初始化解码器,除了配置输入视频流的的编码格式、宽高以及输出格式之外,还需要配置一些额外的信息。 对于H.264视频,需要填充的就是我们前面提到的SPS/PPS信息。1. Android平台MediaCodec的初始化我们需要将Annex-B格式的两个SPS/PPS NALU单元通过setByteBuffer方法,以"csd-0"为名称(或SPS设为"csd-0", PPS设为"csd-1")设置到MediaFormat对象中,并调用configure接口配置到MediaCodec中去。MediaCodec设置SPS/PPS信息的示例代码MediaCodec mediaCodec = MediaCodec.createDecoderByType("video/avc");MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", width, height);// extradata中是Annex-B格式的SPS、PPS NALU数据mediaFormat.setByteBuffer("csd-0", extradata);// ...mediaCodec.configure(mediaFormat, surface, 0, 0);// ...如上节所述,对于mp4/flv/mkv等封装,我们得到的是AVCC格式的extradata,需要先将该extradata转换为Annex-B格式的两个NALU, 然后用startcode进行分割。 Android平台在配置解码方式时,最好使用MediaCodec直接渲染到Surface的方式,一是可以避免不同硬件平台繁杂的YUV格式兼容,二是在解码渲染高分辨率的视频时可以有非常明显的效率提升。2. iOS平台VideoToolBox接口的初始化VideoToolBox针对AVCC格式和Annex-B格式的SPS/PPS信息设置,分别提供了两个方法:• CMVideoFormatDescriptionCreate: 可以设置AVCC格式的extradata信• CMVideoFormatDescriptionCreateFromH264ParameterSets: 用来设置Annex-B格式的SPS/PPS NALU信息(需要去掉startcode)需要注意,iOS平台不支持隔行H.264视频的解码,需要在创建videoToolBox前从SPS中判断当前视频是否隔行编码。3. 数据格式的转换如前所述,Android平台只接受Annex-B格式以startcode分割的H.264 NALU;iOS平台则相反,只接受AVCC格式以size分割的NALU. 在原视频流格式不匹配时需要进行相应的转换。iOS还有以下的一些限制需要留意:(1)如果源视频流本身已经是AVCC格式,但NALU size的大小是3个字节,而非4字节时,需要转为4字节格式。具体的话,需要先更改extradata中标识NALU size的字段,然后每个视频帧中的NALU size都要改成4个字节。(2)如果一个视频帧内有多个NALU(多slice),那必须将这些NALU打包到一个CMSampleBuffer中,一次性送给解码器。 ...