版本記錄
| 版本號 | 時間 |
|---|---|
| V1.0 | 2017.12.23 |
前言
對于做過音視頻的開發(fā)者,編解碼都不陌生,接下來這幾篇就詳細(xì)的看一下音視頻編解碼相關(guān)知識。感興趣的可以看這幾篇文章。
1. 音視頻編解碼(一) —— H264基本概覽(一
視頻H264編解碼數(shù)據(jù)結(jié)構(gòu)
下面我們看一下H264編碼前后數(shù)據(jù)結(jié)構(gòu),如下圖所示。

下圖為H264解碼前后數(shù)據(jù)結(jié)構(gòu)示意圖,這里有幾個對象需要說明一下。
-
CVPixelBuffer- 解碼后的圖像數(shù)據(jù)結(jié)構(gòu)。
-
CMTime、CMClock和CMTimebase- 這個和時間戳相關(guān),可能是32或者64位的形式。
-
CMBlockBuffer- 編碼后的圖像數(shù)據(jù)結(jié)構(gòu)
-
CMVideoFormatDescription- 這里面存放的就是圖像存儲方式,編解碼器等格式描述。
-
CMSampleBuffer- 這里面存放編解碼前后的視頻圖像的容器數(shù)據(jù)結(jié)構(gòu)。
從上圖中可以看出來:
- 編解碼前后的視頻數(shù)據(jù)封裝在
CMSampleBuffer中。 - 編碼后的圖像存儲方式為
CMBlockBuffer - 解碼后的圖像存儲方式為
CVPixelBuffer -
CMSampleBuffer中還存儲和時間已經(jīng)描述相關(guān)的信息。
具體上面幾個對象怎么在代碼中使用,后續(xù)會加上使用方法的Demo。
硬編碼和軟編碼優(yōu)缺點(diǎn)
利用CPU做視頻的編碼和解碼,稱為軟編軟解。該方法比較通用,但是占用CPU資源,編解碼效率不高。
一般系統(tǒng)都會提供GPU或者專用處理器來對視頻流進(jìn)行編解碼,也就是硬件編碼和解碼。蘋果在iOS 8.0系統(tǒng)之前,沒有開放系統(tǒng)的硬件編碼解碼功能,不過Mac OS系統(tǒng)一直有,被稱為Video ToolBox的框架來處理硬件的編碼和解碼,終于在iOS 8.0后,蘋果將該框架引入iOS系統(tǒng)。
硬編碼具有很大的優(yōu)勢,它不像軟編碼大量占用CPU資源。可以更好的利用GPU以及專門的視頻編解碼芯片的高性能,可以實(shí)現(xiàn)很好的實(shí)時性。其實(shí),對于VFoundation也使用硬件對視頻進(jìn)行硬件編解碼,但是編碼后直接寫入文件,解碼后就直接顯示了。而使用Video Toolbox框架可以得到編碼后的幀結(jié)構(gòu),也可以得到解碼后的原始圖像,因此具有更大的靈活性做一些視頻圖像處理。也就是說使用Video Toolbox框架更加靈活,方便進(jìn)一步進(jìn)行視頻處理。
硬解碼
硬解碼其實(shí)就是從服務(wù)端下載視頻數(shù)據(jù),但是是編碼后的,在客戶端呈現(xiàn)出來視頻數(shù)據(jù)之前,需要進(jìn)行解碼,然后才可以拿出來圖像像素?cái)?shù)據(jù)進(jìn)行顯示。下面我們先看一下硬編碼相關(guān)原理及理論,先看一張圖。

1. 將H264碼流轉(zhuǎn)換為解碼前CMSampleBuffer對象
由前面的內(nèi)容我們知道,解碼前的CMSampleBuffer對象,包括CMTime、CMVideoFormatDesc、CMBlockBuffer等,我們解碼的任務(wù)就是從H264碼流里面提取上面三處的信息,合成解碼后的CMSampleBuffer對象,提供給硬解碼接口進(jìn)行解碼工作。
H264碼流由NALU單元組成,NALU單元包含視頻圖像數(shù)據(jù)CMBlockBuffer和H264的參數(shù)信息則可以組合成FormatDesc,具體參數(shù)信息包含SPS(Sequence Parameter Set)和PPS(Picture Parameter Set),如下圖所示為H264的碼流結(jié)構(gòu)。

還可以看下面這個示意圖

下面我們就看一下這個解析過程。
- 提取
sps和pps生成format description- 每個NALU開始碼位0x000001,按照開始碼定位NALU
- 通過類型信息找到sps和pps,開始碼后的第一個byte的后5位,7代表sps,8代表pps。
//sps
_spsSize =format.getCsd_0_size()-4;_sps = (uint8_t *)malloc(_spsSize);memcpy(_sps,format.getCsd_0()+4, _spsSize);
//pps
_ppsSize =format.getCsd_1_size()-4;_pps = (uint8_t *)malloc(_ppsSize);memcpy(_pps,format.getCsd_1()+4, _ppsSize);
利用函數(shù)
CMVideoFormatDescriptionCreateFromH264ParameterSets來構(gòu)建CMVideoFormatDescriptionRef,以獲取描述信息。-
提取視頻數(shù)據(jù)生成待解碼對象
CMBlockBuffer- 通過上面提到的開始碼,定位到NALU
- 確定類型為數(shù)據(jù)后,將開始碼替換成NALU的長度信息(4Bytes)
- 利用函數(shù)
CMBlockBufferCreateWithMemoryBlock構(gòu)造CMBlockBufferRef對象
CMBlockBufferRef blockBuffer=NULL;
CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault,
(void*)frame.bytes,
frame.length,
kCFAllocatorNull,NULL,0, frame.length,0,&blockBuffer);
- 根據(jù)需要,生成
CMTime信息。不過加入time信息可能產(chǎn)生不穩(wěn)定的圖像,如果不是特別需要,不建議加入time信息。
根據(jù)前面產(chǎn)生的CMVideoFormatDescriptionRef、CMBlockBufferRef和可選的時間信息,使用函數(shù)CMSampleBufferCreate得到CMSampleBuffer這個待解碼的原始數(shù)據(jù)。
CMSampleBufferRef sampleBuffer =NULL;
CMSampleBufferCreateReady(kCFAllocatorDefault,
blockBuffer,
_decoderFormatDescription,1,0,NULL,1, sampleSizeArray,
&sampleBuffer);
具體如下所示,為H264解碼數(shù)據(jù)轉(zhuǎn)換圖。

2. 硬解碼后的圖像顯示
下面我們就看一下硬解碼后的圖像顯示,具體的顯示方式有兩種:
- 通過系統(tǒng)提供的
AVSampleBufferDisplayLayer來解碼并顯示。 - 通過
VTDecompression接口來,將CMSampleBuffer解碼成圖像,將圖像通過UIImageView或者OpenGL上顯示。
通過系統(tǒng)提供的AVSampleBufferDisplayLayer來解碼并顯示
AVSampleBufferDisplayLayer是蘋果提供的一個專門顯示解碼后的H264數(shù)據(jù)的顯示層,它是CALayer的子類,因此使用方式和其它CALayer類似。使用方法enqueueSampleBuffer :進(jìn)行顯示該層內(nèi)置了硬件解碼功能,將原始的CMSampleBuffer解碼后的圖像直接顯示在屏幕上面,如下圖所示。

下面看一下實(shí)例代碼
CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer,YES);
CFMutableDictionaryRef dict = (CFMutableDictionaryRef)CFArrayGetValueAtIndex(attachments,0);
CFDictionarySetValue(dict, kCMSampleAttachmentKey_DisplayImmediately, kCFBooleanTrue);
if(status == kCMBlockBufferNoErr) {
if([_avslayer isReadyForMoreMediaData]) {dispatch_sync(dispatch_get_main_queue(),^{
[_avslayer enqueueSampleBuffer:sampleBuffer];
});
}
CFRelease(sampleBuffer);
}
下面看一下這個顯示方式的解碼流程。

通過VTDecompression接口來,將CMSampleBuffer解碼成圖像,將圖像通過UIImageView或者OpenGL上顯示
- 初始化
VTDecompressionSession,設(shè)置解碼器的相關(guān)信息,初始化需要CMSampleBuffer里面的FormatDescription,以及設(shè)置解碼后的圖像存儲方式。編碼后的圖像解碼后,會調(diào)用一個回調(diào)函數(shù),在這個回調(diào)函數(shù)里面,你可以獲得解碼后的圖像。我們將解碼后的圖像發(fā)給control來顯示,初始化的時候需要回調(diào)指針作為參數(shù)傳給create接口函數(shù),同時利用create接口函數(shù)對session進(jìn)行初始化。
VTDecompressionSessionRef _deocderSession;
VTDecompressionSessionCreate(kCFAllocatorDefault,
_decoderFormatDescription,NULL, attrs,
&callBackRecord,
&_deocderSession);
- 上面的回調(diào)函數(shù)可以完成由
CGBitmap到UIImage之間的轉(zhuǎn)換,將圖像通過隊(duì)列發(fā)送到control來處理顯示。
CIImage *ciImage= [CIImage imageWithCVPixelBuffer:outputPixelBuffer];
UIImage *uiImage= [UIImage imageWithCIImage:ciImage];
- 通過接口
VTDecompresSessionDecodeFrame進(jìn)行解碼操作,并將解碼后的圖像交給上面兩個步驟的回調(diào)函數(shù),以便進(jìn)一步處理。具體如下圖所示。
// 使用VTDecompressionSessionDecodeFrame接口解碼成CVPixelBufferRef數(shù)據(jù):
CVPixelBufferRef outputPixelBuffer=NULL;
VTDecompressionSessionDecodeFrame(_deocderSession,
sampleBuffer,
flags
&outputPixelBuffer,
&flagOut);

下面看一下這種解碼方式和顯示流程。

下面看一下這兩種解碼方式的優(yōu)缺點(diǎn)。
-
解碼方式一
優(yōu)點(diǎn): 該方式通過系統(tǒng)提供的
AVSampleBufferDisplayLayer顯示層來解碼并顯示。該層內(nèi)置了硬件解碼功能,將原始的CMSampleBuffer解碼后的圖像直接顯示在屏幕上,非常的簡單方便,且執(zhí)行效率高,占用內(nèi)存相對較少。缺點(diǎn): 從解碼的數(shù)據(jù)中不能直接獲取圖像數(shù)據(jù)并對其做相應(yīng)處理,解碼后的數(shù)據(jù)不能直接進(jìn)行其他方面的應(yīng)用(一般要做較復(fù)雜的轉(zhuǎn)換)。
-
解碼方式二
優(yōu)點(diǎn): 該方式通過
VTDecompressionSessionDecodeFrame接口,得到CVPixelBufferRef數(shù)據(jù),我們可以直接從CVPixelBufferRef數(shù)據(jù)中獲取圖像數(shù)據(jù)并對其做相應(yīng)處理,方便于其他應(yīng)用。缺點(diǎn): 解碼中執(zhí)行效率相對降低,占用的內(nèi)存也會相對較大。
硬編碼
硬編碼我們也經(jīng)常見,比如說,我們直播錄制視頻,就要先通過攝像頭采集圖像,然后進(jìn)行硬編碼,最后將硬編碼后的數(shù)據(jù)組合成H264碼流通過網(wǎng)絡(luò)傳播。
1. 視頻采集
這個硬件設(shè)備就是攝像頭了,通過AVFoundation框架中的AVCaptureSession類來采集圖像,并設(shè)定好input和output,同時設(shè)定deleagte代理和輸出隊(duì)列,在代理delegate方法中,處理采集好的圖像。圖像輸出的格式是未編碼的CMSampleBuffer形式。
2. 使用VTCompressionSession進(jìn)行硬編碼
獲取采集后的圖像,我們需要使用VTCompressionSession進(jìn)行硬編碼。
-
初始化
VTCompressionSession。- 在初始化
VTCompressionSession的時候,我們需要給出width和height,還有編碼器類型kCMVideoCodecType_H264等。然后,通過VTSessionSetProperty接口設(shè)置幀率等屬性。最后,需要設(shè)定一個回調(diào)函數(shù),這個回調(diào)是視頻編碼成功后調(diào)用,全部準(zhǔn)備好后,調(diào)用VTCompressionSessionCreate創(chuàng)建session。
- 在初始化
-
提取攝像頭采集的原始圖像數(shù)據(jù)給
VTCompressionSession來硬編碼- 攝像頭采集后的圖像是未編碼的
CMSampleBuffer形式,利用給定的接口函數(shù)CMSampleBufferGetImageBuffer從中提取出CVPixelBufferRef,使用硬編碼接口VTCompressionSessionEncodeFrame來對該幀進(jìn)行硬編碼,編碼成功后,會自動調(diào)用session初始化時設(shè)置的回調(diào)函數(shù)。
- 攝像頭采集后的圖像是未編碼的
-
利用回調(diào)函數(shù),將因編碼成功的
CMSampleBuffer轉(zhuǎn)換成H264碼流,通過網(wǎng)絡(luò)傳播。- 解析成SPS和PPS參數(shù),加上開始碼后組裝成NALU,提取出視頻數(shù)據(jù),將長度碼轉(zhuǎn)換成開始碼,組長成NALU,并將NALU發(fā)送出去。

參考文章
1. iOS8系統(tǒng)H264視頻硬件編解碼說明
2. iOS-H264 硬解碼
3. 一輪圓月作者關(guān)于硬編解碼的GitHub Demo
后記
未完,待續(xù)~~~
