美文网首页视频开发移动直播扩展眼界
【草稿】iOS VideoToolbox硬编H.265(HEVC

【草稿】iOS VideoToolbox硬编H.265(HEVC

作者: 熊皮皮 | 来源:发表于2016-03-13 19:10 被阅读2751次

    本文档为iOS VideoToolbox硬编H.265(HEVC)H.264(AVC):1 概述续篇,主要描述:

    • CMSampleBufferRef读取实际数据
    • 序列参数集(Sequence Parameter Set, SPS)
    • 图像序列参数(Picture Parameter Set, PPS)

    等内容。

    小广告:欢迎加入iOS Android 直播、全景播放技术讨论群:459486016

    1、视频实际内容数据持久化

    1.1、可攻可受的CMSampleBufferRef

    文档1 概述中回调函数

    static void compressionOutputCallback(
                                          void * CM_NULLABLE outputCallbackRefCon,
                                          void * CM_NULLABLE sourceFrameRefCon,
                                          OSStatus status,
                                          VTEncodeInfoFlags infoFlags,
                                          CM_NULLABLE CMSampleBufferRef sampleBuffer)
    

    参数sampleBuffer为已编码的视频图像数据结构。CMSampleBufferRef是一个容易让人误解的数据结构,它可以包含已压缩数据(CMBlockBuffer)或未压缩数据(CVPixelBuffer)及相关描述信息,如下图所示。

    CMSampleBufferRef两种形态

    1.2、读取CMSampleBufferRef的BlockBuffer数据

    这里的CMSampleBufferRef装载了已压缩数据,无法直接让它生成图片。解码回调函数的CMSampleBufferRef因为装载了未压缩数据,才能创建CGImage或UIImage。为方便调试,我们可以将视频数据写成文件,用VLC等工具分析生成的内容。不写文件,直接推流也行,视具体业务而定。

    1.2.1、访问BlockBuffer数据

    从前面的图片可知,CMSampleBufferRef在其CMBlockBufferRef字段中存放实际已压缩图像数据,那么我们需要访问它。

    CMBlockBufferRef dataBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
    size_t totalLength;
    char *dataPointer;
    OSStatus statusCodeRet = CMBlockBufferGetDataPointer(dataBuffer, 0, NULL, &totalLength, &dataPointer);
    

    dataPointer指向图像数据的起始位置,图像数据总长度为totalLength,示例如下。

    已压缩的图像数据

    1.2.2、读取图像数据

    上图的开始数据是00 00 00 29 06 05 23 47 56,那么这段slice长度是前四个字节:00 00 00 29 => 0x29 = 41,以大端字节序表示。直接赋值给整型变量,则iOS以小端字节序读取,结果数值为687865856,显然与预期不符,需要转换。转换思路就是逐字节读取,数组或移位操作都可实现,甚至还能偷懒,调用Core Foundation的接口CFSwapInt32BigToHost。转换完,正确的数据是29 00 00 00,在Xcode中查看内存也是如此。有些反直觉,比如00 00 3E 70 => 0x3E70 = 15984 => 70 3E 00 00。然而,在计算器中输入70 3E得不到15984,输入0x3E70才是对的。

    小端字节序表示图1 小端字节序表示图2

    有关大小端字节序、网络字节序与本地字节序问题,操作系统或计算机网络等课程有详细描述。

    为方便阅读,重新排版上图数据。

    01 02 03 04 05 // 列序
    ---------------
    // slice 1(长度:0x29 = 41),SEI帧
    00 00 00 29
    06 05 23 47 56 
    4A DC 5C 4C 43 
    3F 94 EF C5 11 
    3C D1 43 A8 00 
    00 03 00 00 03 
    00 05 1D BC A9 
    01 FF CC CC FF 
    02 00 4C 4B 40 
    80
    // slice 2(长度:0x3E70 = 15984),I帧
    00 00 3E 70
    25 B8 20 06 FF 
    FF F8 68 48 A0
    // ...
    

    从上面可清晰看出分割slice的思路,按长度逐一读取NALU。

    现在,回到写入文件这一主题。如果要按Annex-B格式写成文件,则需要将每个slice的长度替换成起始码(start code),追加写入。如果还用iOS解码,则直接给Video Toolbox解码即可。

    2、序列参数集(Sequence Parameter Set, SPS)、图像参数集(Picture Parameter Set, PPS)

    以下测试中的长度单位为字节(bytes)个数。

    2.1、Profile、Level的编码输出

    这里以iPhone 6p为例,图像大小为1080P,测试各个Profile、Level输出的数据变化。

    2.1.1、Baseline

    经测试,

    • kVTProfileLevel_H264_Baseline_1_3
    • kVTProfileLevel_H264_Baseline_3_0
    • kVTProfileLevel_H264_Baseline_3_1

    都返回status = -12902,kVTParameterErr参数错误。可知,Video Toolbox可解码这几个规格的H.264压缩数据,但不支持编码1080P图像,因为超出Profile限制。

    2.1.1.1、kVTProfileLevel_H264_Baseline_3_2

    // SPS 长度 = 17
    <27640028 ac56c078 0227e59b 81010152 04>
    // PPS 长度 = 4
    <28ee3cb0>
    // SEI 长度 = 41
    <06052347 564adc5c 4c433f94 efc5113c d143a800 00030000 0300028f 5c2801dd ccccdd02 004c4b40 80>
    // IDR 长度 = 7387
    00 00 1C DB 
    25 B8 20 06
    // P帧 长度 = 4966
    00 00 13 66 
    21 E1 08 46
    

    2.1.1.2、kVTProfileLevel_H264_Baseline_4_0

    // SPS 长度 = 17
    <27640028 ac56c078 0227e59b 81010152 04>
    // PPS 长度 = 4
    <28ee3cb0>
    // SEI 长度 = 41
    <06052347 564adc5c 4c433f94 efc5113c d143a800 00030000 0300051d bca901dd ccccdd02 004c4b40 80>
    // IDR 长度 = 12568
    00 00 31 18 
    25 B8 10
    // P帧 长度 = 6503
    00 00 19 67 
    21 E1 08 46
    

    2.1.1.3、kVTProfileLevel_H264_Baseline_4_1

    // SPS
    <27420029 ab403c01 13f2cdc0 8080a902>
    // PPS
    <28ce3c30>
    // SEI
    <06052347 564adc5c 4c433f94 efc5113c d143a800 00030000 0300028f 5c2801ff ccccff02 004c4b40 80>
    // IDR
    00 00 23 8E 
    25 B8 20 06
    // P帧
    00 00 0D 9C 
    21 E3 18 06
    

    SPS比kVTProfileLevel_H264_Baseline_4_0少了末尾的80,一个字节。PPS长度不变,内容不同。

    2.1.1.4、kVTProfileLevel_H264_Baseline_4_2

    // SPS 长度 = 16
    <2742002a ab403c01 13f2cdc0 8080a902>
    // PPS 长度 = 4
    <28ce3c30>
    // SEI 长度 = 41
    <06052347 564adc5c 4c433f94 efc5113c d143a800 00030000 0300051d bca901ff ccccff02 004c4b40 80>
    // IDR 长度 = 12010
    00 00 2E EA 
    25 B8 20 06
    // P帧 长度 = 16230
    00 00 3F 66 
    21 E1 08 0C
    

    经多次测试,发现几乎每个P帧都比I帧大。

    2.1.1.5、kVTProfileLevel_H264_Baseline_5_0

    // SPS 长度 = 16
    <27420032 ab403c01 13f2cdc0 8080a902>
    // PPS 长度 = 4
    <28ce3c30>
    // SEI 长度 = 41
    <06052347 564adc5c 4c433f94 efc5113c d143a800 00030000 0300028f 5c2801ff ccccff02 004c4b40 80>
    // IDR 长度 = 7675
    00 00 1D FB
    25 B8 20 06
    // P帧 长度 = 52389
    00 00 39 33 
    21 E1 08 0C
    

    经多次测试,发现P帧有时比I帧大。

    2.1.1.6、kVTProfileLevel_H264_Baseline_5_1

    // SPS 长度 = 16
    <27420033 ab403c01 13f2cdc0 8080a902>
    // PPS 长度 = 4
    <28ce3c30>
    // SEI 长度 = 41
    <06052347 564adc5c 4c433f94 efc5113c d143a800 00030000 0300051d bca901ff ccccff02 004c4b40 80>
    // IDR 长度 = 11620
    00 00 2D 64 
    25 B8 20 06
    // P帧 长度 = 15956
    00 00 3E 50 
    21 E1 08 0C
    

    经多次测试,发现多数时候P帧都比I帧大。

    2.1.1.7、kVTProfileLevel_H264_Baseline_5_2

    // SPS 长度 = 16
    <27420034 ab403c01 13f2cdc0 8080a902>
    // PPS 长度 = 4
    <28ce3c30>
    // SEI 长度 = 41
    <06052347 564adc5c 4c433f94 efc5113c d143a800 00030000 0300051d bca901ff ccccff02 004c4b40 80>
    // IDR 长度 = 11860
    00 00 2E 54 
    25 B8 20 06
    // P帧 长度 = 53976
    00 00 D2 D8 
    21 E2 10 04
    

    经多次测试,发现多数时候P帧都比I帧大。

    2.1.1.8、kVTProfileLevel_H264_Baseline_AutoLevel

    // SPS 长度 = 16
    <27420028 ab403c01 13f2cdc0 8080a902>
    // PPS 长度 = 4
    <28ce3c30>
    // SEI 长度 = 41
    <06052347 564adc5c 4c433f94 efc5113c d143a800 00030000 0300028f 5c2801ff ccccff02 004c4b40 80>
    // IDR 长度 = 7383
    00 00 1C D7 
    25 B8 20 06
    // P帧 长度 = 54731
    00 00 D5 C7 
    21 E2 10 04
    

    2.1.2、Baseline总结

    • kVTProfileLevel_H264_Baseline_3_2
    • kVTProfileLevel_H264_Baseline_4_0

    输出的SPS、PPS相同,SEI略有区别。

    • kVTProfileLevel_H264_Baseline_AutoLevel

    2.2.1、Main

    经测试,

    • kVTProfileLevel_H264_Main_3_0
    • kVTProfileLevel_H264_Main_3_1
    • kVTProfileLevel_H264_Main_3_2

    都返回status = -12902,kVTParameterErr参数错误。可知,Video Toolbox可解码这几个规格的H.264压缩数据,但不支持编码1080P图像,因为超出Profile限制。

    与Baseline不同的是,kVTProfileLevel_H264_Main_3_2不能编码1080P图像。

    2.2.1.1、kVTProfileLevel_H264_Main_4_0

    // SPS 长度 = 16
    <274d0028 ab603c01 13f2cdc0 8080a902>
    // PPS 长度 = 4
    <28ee3c30>
    // SEI 长度 = 41
    <06052347 564adc5c 4c433f94 efc5113c d143a800 00030000 0300051d bca901dd ccccdd02 004c4b40 80>
    // IDR 长度 = 5587
    00 00 15 D3 
    25 B8 20 06
    // P帧 长度 = 3440
    00 00 0D 70 
    21 E1 08 46
    

    多次测试,静止画面P帧平均长度为:
    Pavg = (3440 + 3887 + 4326 + 626 + 846 + 956 + 522 + 262 + 149 + 91) / 10 = 15105

    2.2.1.2、kVTProfileLevel_H264_Main_4_1

    // SPS 长度 = 16
    <274d0029 ab603c01 13f2cdc0 8080a902>
    // PPS 长度 = 4
    <28ee3c30>
    // SEI 长度 = 41
    <06052347 564adc5c 4c433f94 efc5113c d143a800 00030000 0300051d bca901dd ccccdd02 004c4b40 80>
    // IDR 长度 = 3901
    00 00 0F 3D 
    25 B8 20 06
    // P帧 长度 = 2684
    00 00 0A 7C 
    21 E1 08 46
    

    多次测试,静止画面P帧平均长度为:
    Pavg = (2684 + 5435 + 322 + 3673 + 615 + 285 + 265 + 197 + 292 + 87) / 10 = 1385.5

    2.2.1.3、kVTProfileLevel_H264_Main_4_2

    2.2.1.4、kVTProfileLevel_H264_Main_5_0

    // SPS
    
    // PPS
    
    // SEI
    
    // IDR
    
    
    // P帧
    
    
    
    // SPS
    
    // PPS
    
    // SEI
    
    // IDR
    
    
    // P帧
    
    
    
    // SPS
    
    // PPS
    
    // SEI
    
    // IDR
    
    
    // P帧
    
    
    

    2.2.2、Main总结

    2.3.1、Hight

    // SPS
    
    // PPS
    
    // SEI
    
    // IDR
    
    
    // P帧
    
    
    
    // SPS
    
    // PPS
    
    // SEI
    
    // IDR
    
    
    // P帧
    
    
    
    // SPS
    
    // PPS
    
    // SEI
    
    // IDR
    
    
    // P帧
    
    
    
    // SPS
    
    // PPS
    
    // SEI
    
    // IDR
    
    
    // P帧
    
    
    
    // SPS
    
    // PPS
    
    // SEI
    
    // IDR
    
    
    // P帧
    
    
    
    // SPS
    
    // PPS
    
    // SEI
    
    // IDR
    
    
    // P帧
    
    
    
    // SPS
    
    // PPS
    
    // SEI
    
    // IDR
    
    
    // P帧
    
    
    
    // SPS
    
    // PPS
    
    // SEI
    
    // IDR
    
    
    // P帧
    
    
    
    // SPS
    
    // PPS
    
    // SEI
    
    // IDR
    
    
    // P帧
    
    
    
    // SPS
    
    // PPS
    
    // SEI
    
    // IDR
    
    
    // P帧
    
    
    
    // SPS
    
    // PPS
    
    // SEI
    
    // IDR
    
    
    // P帧
    
    
    
    // SPS
    
    // PPS
    
    // SEI
    
    // IDR
    
    
    // P帧
    
    
    

    2.3.2、Hight总结

    讨论

    问题:实时接收的H264数据写入文件时,是不是最开始是要写文件头?
    分析:
    可以直接写文件。写文件头是为了标注视频的一些识别信息,好方便标记。最好从接受到的第一个I帧写起,否则使用一些播放器等可能不能播放。

    相关文章

      网友评论

      • CharlyZheng:谢谢博主分享,想请教一下怎么查看编码后的视频格式(码率,帧率,关键帧间隔),Demo等发我一份吗1421723343@qq.com,学习一下
      • 大尾巴熊Johnny:期待楼主写完这篇博客呀!
      • ff60a12390d8:请问作者,对于每一帧含有多个slices的h264流怎么硬件解码?硬件解码好像只能解一整帧的数据
        ff60a12390d8:@赖__82084788 请问多个slice怎么转化为一个CMSampleBufferRef
      • 安然丶永远:飞雄,我使用kVTProfileLevel_H264_Extended_5_0 和kVTProfileLevel_H264_Extended_AutoLevel 设置kVTCompressionPropertyKey_ProfileLevel, 都返回了-12900 , 我使用kVTProfileLevel_H264_Baseline_3_2,是返回0了,但是我查看CMBlockBufferRef的数据时,并没有sps和pps的帧哇,这是怎么回事呢,我看你图片是有的。
      • 子达如何:被小广告吸引了
      • 子达如何:满怀期待,失望而回
        子达如何:@飞雄_82084788 期待写完~~

      本文标题:【草稿】iOS VideoToolbox硬编H.265(HEVC

      本文链接:https://www.haomeiwen.com/subject/qmxflttx.html