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