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幀寫起,否則使用一些播放器等可能不能播放。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM