VideoToolbox使用說明

使用VideoToolbox硬編&硬解

VideoToolbox簡介

VideoToolbox 是一個低級的框架,可直接訪問硬件的編解碼器。能夠爲視頻提供壓縮和解壓縮的服務,同時也提供存儲在 CoreVideo 像素緩衝區的圖像進行格式的轉換。

優點

  • 利用GPU或者專用處理器對視頻流進行編解碼,不用大量佔用CPU資源。性能高,很好的實時性。

缺點

  • 低碼率下通常質量低於軟編

VideoToolbox數據

  1. CVPixelBuffer

    // CVPixelBuffer 與 CVImageBuffer 類型相同
    typealias CVPixelBuffer = CVImageBuffer
    

    CVPixelBuffer 是存儲在內存中的一個未壓縮的光柵圖像 Buffer,包括圖像的寬度、高度等。

  2. CMBlockBuffer

    CMBlockBuffer 是一個任意的 Buffer,相當於 Buffer 中的 Any. 在管道中壓縮視頻的時候,會把它包裝成 CMBlockBuffer。相當於 CMBlockBuffer 代表着一個壓縮的數據。

  3. 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

解碼流程:

  1. 使用 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)
    
  2. 使用 VTSessionSetProperty 設置會話設置

    VTSessionSetProperty(
      // 解碼會話
      CM_NONNULL VTSessionRef       session,
      // 屬性 KEY
      CM_NONNULL CFStringRef        propertyKey,
      // 設置的屬性值
      CM_NULLABLE CFTypeRef         propertyValue )
    
  3. 使用 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 );
    
  4. 使用 VTCompressionSessionCompleteFrames 強制結束並完成編碼

  5. 編碼完成後使用 VTCompressionSessionInvalidate 結束編碼,並釋放內存

編碼

  1. 使用 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) 
    
  2. VTSessionSetProperty 配置相關屬性

    設置一些例如碼率、幀率、分辨率等屬性

    • FPS(Frames PerSecond):每秒刷新的幀數。幀數越高,流暢度越高
    • 分辨率
    • 比特率/碼率:表示經過編碼(壓縮)後的視頻數據每秒鐘需要用多少個比特來表示。比特率越高,視頻的質量就越好;但編碼後的文件也就越大。
  3. VTCompressionSessionPrepareToEncodeFrames 準備編碼

    VTCompressionSessionPrepareToEncodeFrames(self.session);
    
  4. 調用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 )
    
  5. 執行編碼回調函數 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,);
    }
    
  6. 結束編碼

    調用編碼完成函數,將編碼會話銷燬,釋放資源

    VTCompressionSessionCompleteFrames(session, KCMTimeInvalid);
    VTCompressionSessionInvalidate(session);
    CFRelease(session);
    session = NULL;
    frameID = 0;
    

讀取H264文件,解碼然後編碼的Demo

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章