【草稿】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)

等内容。

小广告:欢迎加入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)及相关描述信息,如下图所示。

【草稿】iOS VideoToolbox硬编H.265(HEVC)H.264(AVC):2 H264数据写入文件_第1张图片
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才是对的。

【草稿】iOS VideoToolbox硬编H.265(HEVC)H.264(AVC):2 H264数据写入文件_第2张图片
小端字节序表示图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帧写起,否则使用一些播放器等可能不能播放。

你可能感兴趣的:(【草稿】iOS VideoToolbox硬编H.265(HEVC)H.264(AVC):2 H264数据写入文件)