VideoToolbox硬解碼H264流播放

VideoToolBox硬解碼H264

對于H264和VideoToolBox還不熟悉的童鞋一定下先看下
H264基礎(chǔ)簡介
iOS-VideoToolbox硬編碼H264

我們先看下demo效果
demo效果

整體的流程是:

從攝像頭獲取視頻裸數(shù)據(jù)->使用VideoToolBox編碼成H264->使用VideoToolBox解碼成image->使用openGLES繪制展示

demo中上面的是實時攝像頭的預(yù)覽圖,下方的是解碼后渲染圖,gif不能顯示完整,可以下載demo代碼看詳細(xì)效果。

VideoToolbox解碼主要流程

編碼部分可以看上一篇的文章。這里重點講一下解碼流程

//解碼nalu裸數(shù)據(jù)
-(void) decodeNalu:(uint8_t *)frame size:(uint32_t)frameSize

傳入nalu的裸數(shù)據(jù)和數(shù)據(jù)frameSize

我們知道

一個原始的NALU單元結(jié)構(gòu)如下
[StartCode][NALU Header][NALU Payload]三部分。

StartCode,是一個NALU單元開始,必須是00 00 00 01 或者00 00 01。

對于VideoToolBox的NALU前四個字節(jié)并不是StartCode,而是FrameSize,所以這里我們寫入frameSize到前四個字節(jié)中

//填充nalu size 去掉start code 替換成nalu size
    uint32_t nalSize = (uint32_t)(frameSize - 4);
    uint8_t *pNalSize = (uint8_t*)(&nalSize);
    frame[0] = *(pNalSize + 3);
    frame[1] = *(pNalSize + 2);
    frame[2] = *(pNalSize + 1);
    frame[3] = *(pNalSize);

再次之前我們先讀取NALU Header判斷類型

    //獲取nalu type
    int nalu_type = (frame[4] & 0x1F);

區(qū)分關(guān)鍵幀還是sps和pps,已經(jīng)B,P其他幀

switch (nalu_type)
    {
        case 0x05:
            //關(guān)鍵幀
            if([self initH264Decoder])
            {
                pixelBuffer = [self decode:frame size:frameSize];
            }
            break;
        case 0x07:
            //sps
            _spsSize = frameSize - 4;
            _sps = malloc(_spsSize);
            memcpy(_sps, &frame[4], _spsSize);
            break;
        case 0x08:
        {
            //pps
            _ppsSize = frameSize - 4;
            _pps = malloc(_ppsSize);
            memcpy(_pps, &frame[4], _ppsSize);
            break;
        }
        default:
        {
            // B/P frame
            if([self initH264Decoder])
            {
                pixelBuffer = [self decode:frame size:frameSize];
            }
            break;
        }

這里我們可以看到讀到關(guān)鍵幀或者B/P其他視頻幀的時候我們才去initH264Decoder,初始化VideoToolBox解碼,這是因為sps和pps里面包含了視頻寬高,以及解碼相關(guān)參數(shù),必須先獲取到sps和pps構(gòu)建CMVideoFormatDescriptionRef,才能初始化VideoToolBox解碼session

封裝CMVideoFormatDescriptionRef

@interface H264DecodeTool(){
    
    //解碼session
    VTDecompressionSessionRef _decoderSession;
    
    //解碼format 封裝了sps和pps
    CMVideoFormatDescriptionRef _decoderFormatDescription;
    
    //sps & pps
    uint8_t *_sps;
    NSInteger _spsSize;
    uint8_t *_pps;
    NSInteger _ppsSize;
    
}

 const uint8_t* const parameterSetPointers[2] = { _sps, _pps };
    const size_t parameterSetSizes[2] = { _spsSize, _ppsSize };
    
    //用sps 和pps 實例化_decoderFormatDescription
    OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault,
                                                                          2, //參數(shù)個數(shù)
                                                                          parameterSetPointers,
                                                                          parameterSetSizes,
                                                                          4, //nal startcode開始的size
                                                                          &_decoderFormatDescription);

初始化VideoToolBox Session

NSDictionary* destinationPixelBufferAttributes = @{
                                                           (id)kCVPixelBufferPixelFormatTypeKey : [NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange],
                                                           //硬解必須是 kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange
                                                           //                                                           或者是kCVPixelFormatType_420YpCbCr8Planar
                                                           //因為iOS是  nv12  其他是nv21
                                                           (id)kCVPixelBufferWidthKey : [NSNumber numberWithInt:1280],
                                                           (id)kCVPixelBufferHeightKey : [NSNumber numberWithInt:960],
                                                           //這里寬高和編碼反的 兩倍關(guān)系
                                                           (id)kCVPixelBufferOpenGLCompatibilityKey : [NSNumber numberWithBool:YES]
                                                           };

        
        
        VTDecompressionOutputCallbackRecord callBackRecord;
        callBackRecord.decompressionOutputCallback = didDecompress;
        callBackRecord.decompressionOutputRefCon = (__bridge void *)self;
        status = VTDecompressionSessionCreate(kCFAllocatorDefault,
                                              _decoderFormatDescription,
                                              NULL,
                                              (__bridge CFDictionaryRef)destinationPixelBufferAttributes,
                                              &callBackRecord,
                                              &_decoderSession);
        VTSessionSetProperty(_decoderSession, kVTDecompressionPropertyKey_ThreadCount, (__bridge CFTypeRef)[NSNumber numberWithInt:1]);
        VTSessionSetProperty(_decoderSession, kVTDecompressionPropertyKey_RealTime, kCFBooleanTrue);

iOS下硬解碼只可以使用:
kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange:YUV420P
kCVPixelFormatType_420YpCbCr8Planar:NV12

YUV420P和NV12是兩種不同的圖像數(shù)據(jù)格式,有興趣的童鞋可以自行查閱下資料。

需要注意的是kCVPixelBufferWidthKey,kCVPixelBufferHeightKey這里指定的寬和高,和實際視頻的寬高是反的,兩倍關(guān)系。
我們錄制的視頻是640 * 480,所以這里傳入1280和960

解碼回調(diào)

//解碼回調(diào)
static void didDecompress( void *decompressionOutputRefCon, void *sourceFrameRefCon, OSStatus status, VTDecodeInfoFlags infoFlags, CVImageBufferRef pixelBuffer, CMTime presentationTimeStamp, CMTime presentationDuration ){
    CVPixelBufferRef *outputPixelBuffer = (CVPixelBufferRef *)sourceFrameRefCon;
    
    //持有pixelBuffer數(shù)據(jù),否則會被釋放
    *outputPixelBuffer = CVPixelBufferRetain(pixelBuffer);
    H264DecodeTool *decoder = (__bridge H264DecodeTool *)decompressionOutputRefCon;
    if (decoder.delegate)
    {
        [decoder.delegate gotDecodedFrame:pixelBuffer];
    }
}

這里retain一次回調(diào)的pixelBuffer,也就是圖像裸數(shù)據(jù)。然后回調(diào)。

渲染

渲染部分使用了APPLE的一個demo Layer,渲染CVImageBufferRef,原理是使用opengl。這塊后面在OpenGL專題再做詳解,這里不再累述。

總結(jié)

H264編碼是很復(fù)雜的,但是由于框架的封裝,事實上平時我們項目中使用的現(xiàn)有API硬件編解碼也還是很方便的。理解了流程和原理是最重要的。當(dāng)然demo僅僅是實現(xiàn)了基本編解碼,很多異常處理,例如退到后臺,session報錯異常,前臺恢復(fù)等在實際商業(yè)項目中是必然需要考慮的。

demo下載地址:iOS-VideoToolBox-demo
也來練習(xí)下吧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容