iOS VideoToolbox硬解H.264数据说明

一. 概述

苹果从iOS 8开始,开放了硬编码和硬解码的api,所以,从iOS 8开始,需要解码H.264视频时,推荐使用系统提供的VideoToolbox来进行硬解

因为VideoToolbox解码时的输入是H.264数据,而通常看到的视频流或者文件都是经过复用封装之后的类似MP4格式的,所以在将数据交由VideoToolbox处理之前需要先进行解复用的操作来将H264数据抽取出来。目前比较通用的做法是使用FFmpeg来进行这个解复用的操作。

此处介绍通过FFmpeg读取到视频数据之后,再交由VideoToolbox解码之前需要进行的一些操作。

1.1 H.264数据的结构

通常所说的H.264裸流,指的是由StartCode分割开来的一个个NALU组成的二进制序列,每个NALU一般来说就是一帧视频图像的数据(也有可能是多个NALU组成一帧图像,或者该NALU是SPS、PPS等数据)

图1 - H.264格式

如上图所示,0x00 00 00 01四个字节为StartCode,在两个StartCode之间的内容即为一个完整的NALU。
每个NALU的第一个字节包含了该NALU的类型信息,该字节的8个bit将其转为二进制数据后,解读顺序为从左往右算,如下:
(1)第1位禁止位,值为1表示语法出错
(2)第2~3位为参考级别
(3)第4~8为是nal单元类型
由此可知计算NALU类型时,只需将该字节的值与0x1F(二进制的0001 1111)相与,结果即为该NALU类型。
NALU类型有一下几种:

图2 - NALU类型

其中常见的有1、5、7、8、9几种类型。

1.2 VideoToolbox可接收的数据格式

与通常所说的H.264数据格式有区别,VideoToolbox解码时需要数据的H.264数据格式为AVC1格式,开始的4个字节不是StartCode,而是后面NALU的数据长度。
常见的封装格式中mp4和flv格式封装的是AVC1格式的H.264, ts格式中是以StartCode为起始的H.264。
如果原始数据是StartCode的格式,则需要将其转换为AVC1的格式才能交给VideoToolbox进行解码

1.3 SPS和PPS

SPS(序列参数集Sequence Parameter Set)和 PPS(图像参数集Picture Parameter Set)是图2中NALU类型为7、8的两种NALU,其中包含了图像编码的各种参数信息,为解码时必须的输入。

二. 实践

实践过程中遇到过不少坑,大致如下。

2.1 SPS和PPS的获取

如果是自己实现解复用来提取音视频流中H.264数据,可以通过分析H.264数据中的NALU类型来获取SPS和PPS。
但通常的做法是使用FFmpeg来实现解复用,此时调用avformat_open_input和avformat_find_stream_info函数后,可以在AVCodecContext结构中的extradata数据中获取SPS和PPS数据
extradata为一个数据块的地址指针,extradata_size指明了其长度,其中存储的数据有两种格式:
(1) 直接存储SPS和PPS两个NALU
(2) 存储一个AVCDecoderConfigurationRecord格式的数据,该结构在标准文档“ISO-14496-15 AVC file format”中有详细说明,如下图:

图3 - AVCDecoderConfigurationRecord

实际使用过程中可以通过extradata中的开始几个字节来判断是其中存储的是那种类型的数据(起始数据为00 00 00 01或00 00 01的为两个NALU)

2.2 4字节还是3字节的StartCode

StartCode可以是4个字节的00 00 00 01,也可以是3个字节的00 00 01, 有资料说当一帧图像被编码为多个slice(即需要有多个NALU)时,每个NALU的StartCode为3个字节,否则为4个字节。但实际并不是所有编码器都按照这个规定实现,所以在实际使用过程中,要对4个字节和3个字节的StartCode都进行一次判断,包括上面说的extradata中的SPS和PPS,还有实际图像的NALU。

2.3 FFmpeg中side_data的影响

如果使用FFmpeg对ts格式进行解复用操作,在av_read_frame读取到一帧视频数据之后,需要将数据转换为AVC1的格式,但如果在FFmpeg中没有对AVFormatContext结构的flags变量设置AVFMT_FLAG_KEEP_SIDE_DATA,那么获取的AVPacket结构中的data地址中,保存的将不仅仅只有原始数据,它还将在末尾包含一个叫做side_data的数据(其实存储的是MEPG2标准中定义的StreamID),这个数据会导致计算的NALU长度比实际要长,从而可能导致VideoToolbox无法解码。
避免VideoToolbox解码失败的方法有两种,任选其一即可:

  • 设置AVFormatContext的AVFMT_FLAG_KEEP_SIDE_DATA;
  • 调用av_packet_split_side_data将side_data从data数据中分离。

2.4 分辨率的变化

flv和ts格式的流都支持中途改变分辨率,所以在分辨率变化后需要重新初始化VideoToolbox的session,否则将会产生解码错误。
码流的分辨率发生变化(或者说编码参数发生变化时),都会有SPS和PPS数据的更新,需要根据新的SPS和PPS信息重新建立解码session。
ts流中更新的SPS和PPS数据和普通视频数据一样,正常解析数据即可获取到新的SPS和PPS数据。
flv流或者rtmp流中的SPS和PPS数据的更新,位于FLV结构中一个叫做AVC sequence header的tag中,其中存储的为图3所示的一个AVCDecoderConfigurationRecord结构,需要从中提取出SPS和PPS数据。该数据在使用FFmpeg的av_read_frame获取数据时,依然保存在AVPacket的side_data中。
获取到SPS和PPS数据后,可以创建一个CMFormatDescriptionRef结构,然后使用VTDecompressionSessionCanAcceptFormatDescription函数判定原有session是否能解码新的数据,如果返回值为假,则需要重建解码session。
而新的视频分辨率可以通过解析SPS数据来获取。

三. SPS解析

SPS结构如下图所示:


图4 - SPS

根据SPS信息计算视频分辨率如下

width = ((pic_width_in_mbs_minus1 +1)*16) - frame_crop_left_offset*2 - frame_crop_right_offset*2;
height = ((2 - frame_mbs_only_flag)* (pic_height_in_map_units_minus1 +1) * 16) - (frame_crop_top_offset * 2) - (frame_crop_bottom_offset * 2);

图4中descriptor中的描述意义如下

static int64_t
nal_bs_read_se(nal_bitstream *bs)
{
    int64_t ueVal = nal_bs_read_ue(bs);
    double k = ueVal;
    int64_t nValue = ceil(k/2);
    if(ueVal%2 == 0)
    {
        nValue = -nValue;
    }
    return nValue;
}

或者参考ffmpeg中对SPS解析的实现https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/h264_ps.c#L334

转载请注明:
作者金山视频云,首发简书 Jianshu.com


如果准备使用多媒体移动端SDK,请参考https://github.com/ksvc

金山云SDK相关的QQ交流群:

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