VideoToolbox使用說(shuō)明

使用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ù)

  1. CVPixelBuffer

    // CVPixelBuffer 與 CVImageBuffer 類(lèi)型相同
    typealias CVPixelBuffer = CVImageBuffer
    

    CVPixelBuffer 是存儲(chǔ)在內(nèi)存中的一個(gè)未壓縮的光柵圖像 Buffer,包括圖像的寬度、高度等。

  2. CMBlockBuffer

    CMBlockBuffer 是一個(gè)任意的 Buffer,相當(dāng)于 Buffer 中的 Any. 在管道中壓縮視頻的時(shí)候,會(huì)把它包裝成 CMBlockBuffer。相當(dāng)于 CMBlockBuffer 代表著一個(gè)壓縮的數(shù)據(jù)。

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

解碼流程:

  1. 使用 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)
    
  2. 使用 VTSessionSetProperty 設(shè)置會(huì)話(huà)設(shè)置

    VTSessionSetProperty(
      // 解碼會(huì)話(huà)
      CM_NONNULL VTSessionRef       session,
      // 屬性 KEY
      CM_NONNULL CFStringRef        propertyKey,
      // 設(shè)置的屬性值
      CM_NULLABLE CFTypeRef         propertyValue )
    
  3. 使用 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 );
    
  4. 使用 VTCompressionSessionCompleteFrames 強(qiáng)制結(jié)束并完成編碼

  5. 編碼完成后使用 VTCompressionSessionInvalidate 結(jié)束編碼,并釋放內(nèi)存

編碼

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

    設(shè)置一些例如碼率、幀率、分辨率等屬性

    • FPS(Frames PerSecond):每秒刷新的幀數(shù)。幀數(shù)越高,流暢度越高
    • 分辨率
    • 比特率/碼率:表示經(jīng)過(guò)編碼(壓縮)后的視頻數(shù)據(jù)每秒鐘需要用多少個(gè)比特來(lái)表示。比特率越高,視頻的質(zhì)量就越好;但編碼后的文件也就越大。
  3. VTCompressionSessionPrepareToEncodeFrames 準(zhǔn)備編碼

    VTCompressionSessionPrepareToEncodeFrames(self.session);
    
  4. 調(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 )
    
  5. 執(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,);
    }
    
  6. 結(jié)束編碼

    調(diào)用編碼完成函數(shù),將編碼會(huì)話(huà)銷(xiāo)毀,釋放資源

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容