使用VideoToolbox硬解碼H.264

前言

使用VideoToolbox硬編碼H.264
在上一篇的硬編碼簡單介紹了H.264和VideoToolbox以及如何使用VideoToolbox硬編碼從攝像頭采集到的數據為H.264文件,這次使用VideoToolbox硬解碼上一篇生成的H.264文件并渲染顯示到屏幕。

概念介紹

  • CVPixelBuffer

包含未壓縮的像素數據,包括圖像寬度、高度等;

  • CVPixelBufferPool

CVPixelBuffer的緩沖池,因為CVPixelBuffer的創建和銷毀代價很大;

  • pixelBufferAttributes

CFDictionary包括寬高、像素格式(RGBA、YUV)、使用場景(OpenGL ES、Core Animation)

  • CMTime

64位的value,32位的scale,media的時間格式;

  • CMVideoFormatDescription

video的格式,包括寬高、顏色空間、編碼格式等;對于H.264的視頻,PPS和SPS的數據也在這里;

  • CMBlockBuffer

未壓縮的圖像數據;

  • CMSampleBuffer

存放一個或者多個壓縮或未壓縮的媒體文件;

  • CMClock

時間源:A timing source object.

  • CMTimebase

時間控制器,可以設置rate和time:A timebase represents a timeline that clients can control by setting the rate and time. Each timebase has either a master clock or a master timebase. The rate of the timebase is expressed relative to its master.

核心思路

用NSInputStream讀入原始H.264碼流,用CADisplayLink控制顯示速率,用NALU的前四個字節識別SPS和PPS并存儲,當讀入IDR幀的時候初始化VideoToolbox,并開始同步解碼;解碼得到的CVPixelBufferRef會傳入OpenGL ES類進行解析渲染。

效果展示

H.264的清晰度受碼率和關鍵幀間隔影響,GIF清晰度有限。


全文僅此GIF

具體細節

1、把原始碼流包裝成CMSampleBuffer

  • 1、替換頭字節長度;
            uint32_t nalSize = (uint32_t)(packetSize - 4);
            uint32_t *pNalSize = (uint32_t *)packetBuffer;
            *pNalSize = CFSwapInt32HostToBig(nalSize);
  • 2、用CMBlockBuffer把NALUnit包裝起來;
        CMBlockBufferRef blockBuffer = NULL;
        OSStatus status  = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault,
                                                              (void*)packetBuffer, packetSize,
                                                              kCFAllocatorNull,
                                                              NULL, 0, packetSize,
                                                              0, &blockBuffer);
  • 3、把SPS和PPS包裝成CMVideoFormatDescription;

        const uint8_t* parameterSetPointers[2] = {mSPS, mPPS};
        const size_t parameterSetSizes[2] = {mSPSSize, mPPSSize};
        OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault,
                                                                              2, //param count
                                                                              parameterSetPointers,
                                                                              parameterSetSizes,
                                                                              4, //nal start code size
                                                                              &mFormatDescription);
  • 4、添加CMTime時間;

(WWDC視頻上說有,但是我在實現過程沒有找到添加的地方,可能是我遺漏了)

  • 5、創建CMSampleBuffer;
            CMSampleBufferRef sampleBuffer = NULL;
            const size_t sampleSizeArray[] = {packetSize};
            status = CMSampleBufferCreateReady(kCFAllocatorDefault,
                                               blockBuffer,
                                               mFormatDescription,
                                               1, 0, NULL, 1, sampleSizeArray,
                                               &sampleBuffer);

2、解碼并顯示

  • 1、傳入CMSampleBuffer
                VTDecodeFrameFlags flags = 0;
                VTDecodeInfoFlags flagOut = 0;
                // 默認是同步操作。
                // 調用didDecompress,返回后再回調
                OSStatus decodeStatus = VTDecompressionSessionDecodeFrame(mDecodeSession,
                                                                          sampleBuffer,
                                                                          flags,
                                                                          &outputPixelBuffer,
                                                                          &flagOut);
  • 2、回調didDecompress
void didDecompress(void *decompressionOutputRefCon, void *sourceFrameRefCon, OSStatus status, VTDecodeInfoFlags infoFlags, CVImageBufferRef pixelBuffer, CMTime presentationTimeStamp, CMTime presentationDuration ){
    CVPixelBufferRef *outputPixelBuffer = (CVPixelBufferRef *)sourceFrameRefCon;
    *outputPixelBuffer = CVPixelBufferRetain(pixelBuffer);
}
  • 3、顯示解碼的結果
[self.mOpenGLView displayPixelBuffer:pixelBuffer];

仔細對比硬編碼和硬解碼的圖像,會發現硬編碼的圖像被水平鏡像過。

當遇到IDR幀時,更合適的做法是通過
VTDecompressionSessionCanAcceptFormatDescription判斷原來的session是否能接受新的SPS和PPS,如果不能再新建session。

總結

WWDC的視頻適合先學再看(個人體悟),并不是很適合沒基礎的時候看。在寫完硬編碼和硬解碼的demo之后,再完整的看一遍WWDC的視頻,對VideoToolbox的印象更加深刻,同時明白MPEG-4格式下的H.264碼流與原始H.264碼流的不同。
如果有不了解的,可以查看代碼
對OpenGL ES有興趣的,看看的OpenGL ES文集

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容