Android与IOS端h264硬解关键流程梳理

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平台的硬解的实践中,我们可能会遇到如下图的这种情况(上面一部分有画面,下面部分是绿屏):

v2-62cbcb1506428c62b21d9cfd5f421992_hd.png

这种现象实际上就是多slice视频的组织格式不符合VideoToolBox的要求引起的。

以上图的视频为例,该视频流的每一帧是由3个slice构成的,对于VideoToolBox可以正常解码的组织格式应该如下图所示:

1.png

而该视频的帧组织方式则如下图所示:
2.png

可以看出,该视频混用了AVCC与Annex-B格式的分隔符,导致iOS VideoToolBox只能解码第一个slice单元,从而出现下半部分绿屏的情况。

  • 对于这类问题视频的处理: 如果是源视频流可控,可以调整源视频流的打包方式,按第一种图示的方式打包。
  • 对于不可控的场景,播放器也可以做下兼容:因为一个NALU中的内容一定是不包含startcode的,所以如果在一个NALU中找到了startcode,就可以将其处理成第一种图示中的格式。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342