zoukankan      html  css  js  c++  java
  • iOS VideoToolbox硬编H.265(HEVC)H.264(AVC):2 H264数据写入文件

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

    • CMSampleBufferRef读取实际数据

    • 序列参数集(Sequence Parameter Set, SPS)

    • 图像序列参数(Picture Parameter Set, PPS)

    等内容。

    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 长度 = 738700 00 1C DB 25 B8 20 06// P帧 长度 = 496600 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 长度 = 1256800 00 31 18 25 B8 10// P帧 长度 = 650300 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>// IDR00 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 长度 = 1201000 00 2E EA 
    25 B8 20 06// P帧 长度 = 1623000 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 长度 = 767500 00 1D FB25 B8 20 06// P帧 长度 = 5238900 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 长度 = 1162000 00 2D 64 25 B8 20 06// P帧 长度 = 1595600 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 长度 = 1186000 00 2E 54 25 B8 20 06// P帧 长度 = 5397600 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 长度 = 738300 00 1C D7 
    25 B8 20 06// P帧 长度 = 5473100 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 长度 = 558700 00 15 D3 
    25 B8 20 06// P帧 长度 = 344000 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 长度 = 390100 00 0F 3D 
    25 B8 20 06// P帧 长度 = 268400 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帧写起,否则使用一些播放器等可能不能播放。

  • 相关阅读:
    Android 仿淘宝头条竖直跑马灯式新闻标题及“分页思想
    Android setOnPageChangeListener 过时了怎么办?
    Android requestWindowFeature(Window.FEATURE_NO_TITLE)无效解决方法
    Android ViewPager+HorizontalScrollView实现标题栏滑动(腾讯新闻)
    Android OKHttp网络框架
    安卓开发常用网络请求框架OkHttp、Volley、XUtils、Retrofit对比
    Android 修改源码自定义SwipeRefreshLayout样式——高仿微信朋友圈下拉刷新
    Android 比SwipeRefreshLayout更漂亮和强大的下拉刷新控件:Android-MaterialRefreshLayout
    Android Error:Unable to find method 'com.android.build.gradle.api.BaseVariant.getOutputs()Ljava/util/List;'.
    Android 浅谈 RxAndroid + Retrofit + Databinding
  • 原文地址:https://www.cnblogs.com/isItOk/p/5964644.html
Copyright © 2011-2022 走看看