移动端硬解关键流程梳理

概述

介绍移动端Android/iOS硬解用法的文章也是比较多的,本文将以笔者在实际开发工做中的经验为基础,抽出几个比较关键的部分来跟你们分享,旨在解决实际工做中可能遇到的花屏、(半边)绿屏、播放不完整等问题。
本文将以目前普遍应用的H264编码的视频为例来讲明,主要包含:H264码流数据结构说明、解码器的初始化、seek、先后台切换、无缝分辨率切换、播放结束时的处理以及iOS如何避免下半部分绿屏的问题。
想要阅读更多技术干货、行业洞察,欢迎关注 网易云信博客
了解 网易云信,来自网易核心架构的通讯与视频云服务。

H264码流数据结构说明

理解码流数据结构的重要性

咱们讲支持硬解,提升硬解兼容性,实际上就是对码流数据的结构进行处理以符合平台硬解要求,所以对码流数据结构的理解是必不可少的。

SPS/PPS与IDR帧

SPS(Sequence Parameter Set)序列参数集、PPS(Picture Parameter Set)图像参数集,包含了图像编码的各类参数信息,是做为解码器初始化所必须的参数信息。
IDR(Instantaneous Decoding Refresh)帧,也就是即时解码刷新帧,直观意思就是解码器在接收到IDR帧后会刷新参考帧缓存。IDR帧先后的视频帧不会有任何参考关系,解码器能够从任何一个IDR帧开始解码。

H264的NAL单元

NALU结构图示:
H264标准中,视频流是由NAL(Network Abstraction Layer)单元组成的(简称NALU),每一个NALU中多是IDR图像、SPS、PPS、non-IDR图像等。
上图中示意的NALU单元是以startcode方式分割的,关于NALU的分割方式将在后面说明。 另外,NALU内容中添加了防竞争字节,也就是说在一个NALU中,咱们不可能再找到匹配的startcode.
H264流的NALU组成图示
从上图能够看到,一个视频帧中可能可能包含多个NALU, 此时能够称该视频帧为多slice视频帧(一个NALU中包含该视频帧的一个slice)。
NAL Header的结构说明
其中nal_unit_type是咱们关心的字段,该字段标识了当前NALU的类型,咱们能够经过将NALU中第一个字节&0x1F的方式来获得NALU类型。NALU类型的具体定义以下图所示:
NALU类型定义
其中5表明上面提到的IDR帧数据,七、8分别表明SPS/PPS数据。

AVCC与Annex-B

H264码流分为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格式的H264数据,而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方式。

解码器的初始化及数据输入

初始化解码器,除了配置输入视频流的的编码格式、宽高以及输出格式以外,还须要配置一些额外的信息。 对于H264视频,须要填充的就是咱们前面提到的SPS/PPS信息。

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格式兼容,二是在解码渲染高分辨率的视频时能够有很是明显的效率提高。缓存

iOS平台VideoToolBox接口的初始化

VideoToolBox针对AVCC格式和Annex-B格式的SPS/PPS信息设置,分别提供了两个方法:
  • CMVideoFormatDescriptionCreate: 能够设置AVCC格式的extradata信
  • CMVideoFormatDescriptionCreateFromH264ParameterSets: 用来设置Annex-B格式的SPS/PPS NALU信息(须要去掉startcode)
须要注意,iOS平台不支持隔行H264视频的解码,须要在建立videoToolBox前从SPS中判断当前视频是否隔行编码。

数据格式的转换

如前所述,Android平台只接受Annex-B格式以startcode分割的H264 NALU;iOS平台则相反,只接受AVCC格式以size分割的NALU. 在原视频流格式不匹配时须要进行相应的转换。
iOS还有如下的一些限制须要留意:
  1. 若是源视频流自己已是AVCC格式,但NALU size的大小是3个字节,而非4字节时,须要转为4字节格式。具体的话,须要先更改extradata中标识NALU size的字段,而后每一个视频帧中的NALU size都要改为4个字节。
  2. 若是一个视频帧内有多个NALU(多slice),那必须将这些NALU打包到一个CMSampleBuffer中,一次性送给解码器。

seek时的处理

编码后的视频帧之间存在着参考关系,咱们没法直接从任意一帧开始解码,只能从可随机访问帧开始,在H264中就是IDR帧。

从IDR帧开始解码

对于点播视频,mp4/flv/mkv的头信息中都会保存整个视频的IDR帧索引,seek时须要定位到原seek位置附近的IDR帧再送数据给解码器。 若是要实现短视频中的精确seek逻辑,能够先seek到离目标位置最近的上一个IDR帧开始解码,但不输出图像,直到目标位置的视频被解码出来。

刷新解码器

进行seek操做时,除了要保证从IDR帧开始以外,还须要在送新的IDR帧数据前对解码器进行刷新操做。
  • Android平台能够经过调用MediaCodec的flush()接口来实现。
  • iOS平台则须要从新建立videoToolBox.

先后台切换

对Android、iOS平台,都存在App切后台,播放器渲染View被销毁而致使解码出错的状况。

切回前台的处理

App切到后台时,iOS的videoToolBox session会失效,切回前台后原session也不能继续使用,需从新建立videoToolBox实例;Android平台在配置了Surface的状况下,若是Surface被销毁,则在切回前台时也须要配置新的Surface来从新建立并初始化MediaCodec.
若是咱们要提升用户体验,实现先后台切换时的无缝播放,而不是从新拉流,那么能够在用户切后台的时候暂停播放,切回前台时从新建立解码器,继续从原位置开始播放。
不过参考前面seek章节的说明,咱们恢复播放的位置极可能不是IDR帧,这种状况下就会出现切回前台后画面会先黑一段时间,直到下一个IDR帧被解码。黑屏的时间会跟视频流的IDR帧间隔有关,最差状况下黑屏时间接近IDR帧间隔。 为了尽可能避免黑屏现象的出现,咱们能够参考前面精确seek的处理,在解码过程当中一直缓存当前GOP(Group Of Picture)的视频帧数据,在恢复时从当前GOP的IDR帧开始解码但不输出图像,直到恢复点。
不过上述方案也没法100%解决黑屏问题,解码恢复点前的视频数据自己会有时间消耗,GOP越大,解码恢复可能须要的时间也就越长,黑屏时间也就会越长。

Android平台使用TextureView避免Surface被销毁

对Android平台,咱们也能够经过使用TextureView渲染来尽可能避免Surface被销毁。
具体实现上,能够:
  1. 在TextureView的onSurfaceTextureAvailable回调中保存当前建立的SurfaceTexture;
  2. App切后台时,TextureView的onSurfaceTextureDestroyed回调中返回false,不让系统销毁当前的SurfaceTexture;
  3. 在下一次App切回前台,onSurfaceTextureAvailable回调中,将前面保存的SurfaceTexture经过setSurfaceTexture接口设置给TextureView,并销毁回调参数中传回的surfaceTexture;
  4. 播放器销毁时,须要销毁保存的surfaceTexture.

无缝分辨率切换的处理

考虑到用户网络的差别性,以及不一样时间段的拥堵情况不一样,为了兼顾拉流清晰度与流畅度,咱们能够经过实时检测用户的网络状况,并动态切换视频的分辨率、码率来提升播放体验。
rtmp直播,http/flv直播,hls直播以及hls点播能够支持动态分辨率切换。

分辨率切换时须要拿到新的SPS/PPS并重启解码器

  • 对于rtmp, http/flv直播,以及mp4分片的hls视频,分辨率切换时咱们可以拿到新的AVCC格式的extradata(使用ffmpeg解封装时这个信息是在AVPacket的sidedata中), 此时须要用新的extradata数据从新建立解码器,所需的分辨率信息能够从extradata中解析出来。
  • 而对于ts切片的hls直播点播视频,SPS/PPS信息是以Annex-B格式保存在正常的NALU中,并且每一个IDR帧前都会有SPS/PPS的NALU。对此,咱们须要监控每一个收到的视频包,获取其NALU类型,若是是SPS/PPS, 则从中解析出分辨率等信息,若是有变化,则用新的SPS/PPS从新建立解码器。

播放完成时避免遗漏最后几帧

前面咱们提到过,编码后的视频帧之间存在着参考关系,并且存在双向参考帧(B帧)的视频流其解码输出顺序和输入的顺序是不一样的,同时解码器在异步模式下也不会当即返回解码后的视频帧,这就致使咱们在输入最后一帧数据给解码器后,可能还会有一些视频帧没有输出。
为了不遗漏最后几帧的状况,咱们须要作一些处理:
  • Android平台须要给MediaCodec送入一个带有BUFFER_FLAG_END_OF_STREAM标记的buffer数据(能够是空buffer),而后等待MediaCodec输出带有该标记的内容,再销毁解码器,结束播放。
  • iOS平台须要在送完最后一帧数据后,调用VTDecompressionSessionWaitForAsynchronousFrames接口,该接口会等待全部未输出的视频帧输出结束后再返回。

VideoToolBox兼容不标准的多slice视频

在iOS平台的硬解的实践中,咱们可能会遇到以下图的这种状况(上面一部分有画面,下面部分是绿屏):
这种现象实际上就是多slice视频的组织格式不符合VideoToolBox的要求引发的。
以上图的视频为例,该视频流的每一帧是由3个slice构成的,对于VideoToolBox能够正常解码的组织格式应该以下图所示:
而该视频的帧组织方式则以下图所示:
能够看出,该视频混用了AVCC与Annex-B格式的分隔符,致使iOS VideoToolBox只能解码第一个slice单元,从而出现下半部分绿屏的状况。
  • 对于这类问题视频的处理: 若是是源视频流可控,能够调整源视频流的打包方式,按第一种图示的方式打包。
  • 对于不可控的场景,播放器也能够作下兼容:由于一个NALU中的内容必定是不包含startcode的,因此若是在一个NALU中找到了startcode,就能够将其处理成第一种图示中的格式。

想要阅读更多技术干货、行业洞察,欢迎关注 网易云信博客
了解 网易云信,来自网易核心架构的通讯与视频云服务。

网易云信(NeteaseYunXin)是集网易18年IM以及音视频技术打造的PaaS服务产品,来自网易核心技术架构的通讯与视频云服务,稳定易用且功能全面,致力于提供全球领先的技术能力和场景化解决方案。开发者经过集成客户端SDK和云端OPEN API,便可快速实现包含IM、音视频通话、直播、点播、互动白板、短信等功能。
相关文章
相关标签/搜索