使用VideoToolbox硬編&硬解
VideoToolbox簡(jiǎn)介
VideoToolbox 是一個(gè)低級(jí)的框架,可直接訪問(wèn)硬件的編解碼器。能夠?yàn)橐曨l提供壓縮和解壓縮的服務(wù),同時(shí)也提供存儲(chǔ)在 CoreVideo 像素緩沖區(qū)的圖像進(jìn)行格式的轉(zhuǎn)換。
優(yōu)點(diǎn)
- 利用GPU或者專(zhuān)用處理器對(duì)視頻流進(jìn)行編解碼,不用大量占用CPU資源。性能高,很好的實(shí)時(shí)性。
缺點(diǎn)
- 低碼率下通常質(zhì)量低于軟編
VideoToolbox數(shù)據(jù)
-
CVPixelBuffer
// CVPixelBuffer 與 CVImageBuffer 類(lèi)型相同 typealias CVPixelBuffer = CVImageBuffer
CVPixelBuffer 是存儲(chǔ)在內(nèi)存中的一個(gè)未壓縮的光柵圖像 Buffer,包括圖像的寬度、高度等。
-
CMBlockBuffer
CMBlockBuffer 是一個(gè)任意的 Buffer,相當(dāng)于 Buffer 中的 Any. 在管道中壓縮視頻的時(shí)候,會(huì)把它包裝成 CMBlockBuffer。相當(dāng)于 CMBlockBuffer 代表著一個(gè)壓縮的數(shù)據(jù)。
-
CMSampleBuffer
CMSampleBuffer 可能是一個(gè)壓縮的數(shù)據(jù),也可能是一個(gè)未壓縮的數(shù)據(jù)。取決于 CMSampleBuffer 里面是 CMBlockBuffer(壓縮后) 還是 CVPixelBuffer(未壓縮)
對(duì)于VideoToolbox,可以通過(guò)直接訪問(wèn)硬編解碼器,將 H.264 文件或傳輸流轉(zhuǎn)換為 iOS上的 CMSampleBuffer
并解碼成 CVPixelBuffer
, 或?qū)⑽磯嚎s的 CVPixelBuffer
編碼成 CMSampleBuffer
(將未編碼的CMSampleBuffer(CVPixelBuffer)與已編碼的CMSampleBuffer(CMBlockBuffer)的相互轉(zhuǎn)換):
解碼
- H.264 -> CMSampleBuffer -> CVPixelBuffer
編碼:
- CVPixelBuffer -> CMSampleBuffer -> H.264
解碼
把原始碼流包裝成 CMSampleBuffer
解碼前的原始數(shù)據(jù)為H264碼流,iOS可以使用 NSInputStream
讀取H264文件。
H264 有兩種封裝格式,一種為 MP4 格式,一種是annexb格式。MP4格式是以NALU的長(zhǎng)度分割;annexb格式是以 0x00000001 或 0x0000000001 分割。
VideoToolbox解碼使用的 H264 為MP4格式,因此需要替換NALU的Header
-
使用 CMVideoFormatDescriptionCreateFromH264ParameterSets 將 SPS 和 PPS 封裝成 CMVideoFormatDescription
typealias CMVideoFormatDescription = CMFormatDescription
-
修改 NALU 的 Header
NALU 只要有兩種格式:Annex B 和 AVCC。Annex B 格式以 0x 00 00 01 或 0x 00 00 00 01 開(kāi)頭, AVCC 格式以所在 NALU 的長(zhǎng)度開(kāi)頭。
替換掉NALU 的 StartCode
使用 CMBlockBufferCreateWithMemoryBlock 接口將 NALU unit 封裝成 CMBlockBuffer
通過(guò) CMSampleBufferCreate 將 CMBlockBuffer + CMVideoFormatDescription + CMTime 創(chuàng)建成 CMSampleBuffer
解碼流程:
-
使用
VTDecompressionSessionCreate
創(chuàng)建解碼會(huì)話(huà)VT_EXPORT OSStatus VTDecompressionSessionCreate( // 會(huì)話(huà)的分配器,默認(rèn)使用kCFAllocatorDefault CM_NULLABLE CFAllocatorRef allocator, // 源視頻幀的描述(包含SPS & PPS 信息) CM_NONNULL CMVideoFormatDescriptionRef videoFormatDescription, // 視頻解碼器(默認(rèn)為空,由 VideoToolbox 選擇) CM_NULLABLE CFDictionaryRef videoDecoderSpecification, // 包含解碼配置信息的數(shù)組 CM_NULLABLE CFDictionaryRef destinationImageBufferAttributes, // 回調(diào)函數(shù) const VTDecompressionOutputCallbackRecord * CM_NULLABLE outputCallback, // 解碼會(huì)話(huà)對(duì)象的指針 CM_RETURNS_RETAINED_PARAMETER CM_NULLABLE VTDecompressionSessionRef * CM_NONNULL decompressionSessionOut)
-
使用
VTSessionSetProperty
設(shè)置會(huì)話(huà)設(shè)置VTSessionSetProperty( // 解碼會(huì)話(huà) CM_NONNULL VTSessionRef session, // 屬性 KEY CM_NONNULL CFStringRef propertyKey, // 設(shè)置的屬性值 CM_NULLABLE CFTypeRef propertyValue )
-
使用
VTDecompressionSessionDecodeFrame
編碼視頻幀,在之前設(shè)置的回調(diào)函數(shù)中獲取編碼后的結(jié)果VT_EXPORT OSStatus VTDecompressionSessionDecodeFrame( // 解碼會(huì)話(huà) CM_NONNULL VTDecompressionSessionRef session, // 要解碼的視頻數(shù)據(jù)(包含一個(gè)或多個(gè)視頻幀) CM_NONNULL CMSampleBufferRef sampleBuffer, // 解碼器和解碼會(huì)話(huà)的指令 VTDecodeFrameFlags decodeFlags, // 解碼后的數(shù)據(jù) void * CM_NULLABLE sourceFrameRefCon, VTDecodeInfoFlags * CM_NULLABLE infoFlagsOut)
回調(diào)函數(shù)返回?cái)?shù)據(jù)
typedef void (*VTDecompressionOutputCallback)( // VTDecompressionOutputCallbackRecord 的 decompressionOutputRefCon字段值 void * CM_NULLABLE decompressionOutputRefCon, // 解碼返回的數(shù)據(jù) void * CM_NULLABLE sourceFrameRefCon, // 錯(cuò)誤碼 OSStatus status, // 解碼操作的信息 VTDecodeInfoFlags infoFlags, // 包含解壓縮的幀數(shù)據(jù) CM_NULLABLE CVImageBufferRef imageBuffer, // 幀數(shù)據(jù)的時(shí)間戳 CMTime presentationTimeStamp, // 幀數(shù)據(jù)的表示時(shí)間 CMTime presentationDuration );
使用
VTCompressionSessionCompleteFrames
強(qiáng)制結(jié)束并完成編碼編碼完成后使用
VTCompressionSessionInvalidate
結(jié)束編碼,并釋放內(nèi)存
編碼
-
使用
VTDecompressionSessionCreate
創(chuàng)建 session(編碼會(huì)話(huà))VTCompressionSessionCreate( // 分配器,傳NULL或KCFAllocatorDefault CM_NULLABLE CFAllocatorRef allocator, // 寬度 int32_t width, // 高度 int32_t height, // 編碼類(lèi)型 CMVideoCodecType codecType, // 編碼規(guī)范 傳NULL,videotoolbox自行選擇 CM_NULLABLE CFDictionaryRef encoderSpecification, // 源像素緩沖區(qū) CM_NULLABLE CFDictionaryRef sourceImageBufferAttributes, // 壓縮數(shù)據(jù)分配器 CM_NULLABLE CFAllocatorRef compressedDataAllocator, // 回調(diào)函數(shù) CM_NULLABLE VTCompressionOutputCallback outputCallback, // 回調(diào)函數(shù)的引用 void * CM_NULLABLE outputCallbackRefCon, // 編碼會(huì)話(huà)對(duì)象指針 CM_RETURNS_RETAINED_PARAMETER CM_NULLABLE VTCompressionSessionRef * CM_NONNULL compressionSessionOut)
-
VTSessionSetProperty
配置相關(guān)屬性設(shè)置一些例如碼率、幀率、分辨率等屬性
- FPS(Frames PerSecond):每秒刷新的幀數(shù)。幀數(shù)越高,流暢度越高
- 分辨率
- 比特率/碼率:表示經(jīng)過(guò)編碼(壓縮)后的視頻數(shù)據(jù)每秒鐘需要用多少個(gè)比特來(lái)表示。比特率越高,視頻的質(zhì)量就越好;但編碼后的文件也就越大。
-
VTCompressionSessionPrepareToEncodeFrames
準(zhǔn)備編碼VTCompressionSessionPrepareToEncodeFrames(self.session);
-
調(diào)用
VTCompressionSessionEncodeFrame
傳入需要編碼的視頻幀VTCompressionSessionEncodeFrame( // 編碼會(huì)話(huà) CM_NONNULL VTCompressionSessionRef session, // 要編碼的數(shù)據(jù) CM_NONNULL CVImageBufferRef imageBuffer, // 時(shí)間戳 CMTime presentationTimeStamp, // 表示時(shí)間(may be kCMTimeInvalid) CMTime duration, // 數(shù)據(jù)的其他屬性(key-value) CM_NULLABLE CFDictionaryRef frameProperties, // 幀數(shù)據(jù)的引用,將被傳遞給回調(diào)函數(shù) void * CM_NULLABLE sourceFrameRefCon, VTEncodeInfoFlags * CM_NULLABLE infoFlagsOut )
-
執(zhí)行編碼回調(diào)函數(shù)
VTCompressionOutputCallback
如果是關(guān)鍵幀調(diào)用
CMSampleBufferGetFormatDescription
獲取CMFormatDescriptionRef
,;然后用
CMVideoFormatDescriptionGetH264ParameterSetAtIndex
取得PPS和SPS;最后把每一幀的所有NALU數(shù)據(jù)前四個(gè)字節(jié)變成 0X00,00,00,01 之后再寫(xiě)入文件
void didCompressionOutputCallback(void *outputCallbackRefCon, void *sourceFrameRefCon, OSStatus status, VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer) { //獲取傳入的參數(shù) VideoEncode *encode = (__bridge VideoEncode *)outputCallbackRefCon; //判斷是否是關(guān)鍵幀 CFArrayRef arrayRef = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true,); }
-
結(jié)束編碼
調(diào)用編碼完成函數(shù),將編碼會(huì)話(huà)銷(xiāo)毀,釋放資源
VTCompressionSessionCompleteFrames(session, KCMTimeInvalid); VTCompressionSessionInvalidate(session); CFRelease(session); session = NULL; frameID = 0;