對(duì)捕獲的視頻數(shù)據(jù)進(jìn)行H264解碼

對(duì)視頻數(shù)據(jù)進(jìn)行H264編碼參考http://www.itdecent.cn/p/07c7ad30b70a

接受到的數(shù)據(jù)前4個(gè)字節(jié)是NALU數(shù)據(jù)的開始碼,也就是00 00 00 01,
第5個(gè)字節(jié)是表示數(shù)據(jù)類型,轉(zhuǎn)為10進(jìn)制后,7是sps, 8是pps, 5是IDR(I幀)信息
幀類型有:

NAL_SLICE = 1 非關(guān)鍵幀
NAL_SLICE_DPA = 2
NAL_SLICE_DPB = 3
NAL_SLICE_DPC =4
NAL_SLICE_IDR =5 關(guān)鍵幀
NAL_SEI = 6
NAL_SPS = 7 SPS幀
NAL_PPS = 8 PPS幀
NAL_AUD = 9
NAL_FILLER = 12

解密的時(shí)候需要先獲取sps 和 pps ,這兩個(gè)數(shù)據(jù)是不需要解碼的(之前壓縮的時(shí)候也沒(méi)編碼)。

獲取到sps和pps就可以創(chuàng)建解碼會(huì)話了,我們可以通過(guò)\color{red}{VTDecompressionSession}來(lái)創(chuàng)建會(huì)話,其工作流如下:

  1. 創(chuàng)建一個(gè)會(huì)話通過(guò) \color{blue}{VTDecompressionSessionCreate}
  2. 根據(jù)需要配置會(huì)話屬性通過(guò)調(diào)用\color{blue}{VTSessionSetProperty(_:key:value:) or VTSessionSetProperties(_:propertyDictionary:)}
  3. 解碼視頻幀通過(guò)使用\color{blue}{VTDecompressionSessionDecodeFrame(_:sampleBuffer:flags:frameRefcon:infoFlagsOut:).}
    4.當(dāng)你結(jié)束解碼的時(shí)候調(diào)用\color{blue} {VTDecompressionSessionInvalidate(_:) }

VTDecompressionSessionCreate需要兩個(gè)重要參數(shù)\color{blue}{videoFormatDescription}\color{blue}{destinationImageBufferAttributes}和回調(diào)函數(shù)\color{blue}{VTDecompressionOutputCallbackRecord}

  1. \color{blue}{videoFormatDescription}:是關(guān)于源視頻的描述,也就是對(duì)應(yīng)壓縮方式的解壓參數(shù)配置,將獲取的sps,pps放入其中。通過(guò)\color{blue}{CMVideoFormatDescriptionCreateFromH264ParameterSets}來(lái)創(chuàng)建
  2. \color{blue}{destinationImageBufferAttributes}:用于設(shè)置像素緩沖器的要求,如攝像頭的輸出數(shù)據(jù)格式,視頻源的分辨率,是否開啟零拷貝通道等。
         case 0x07: //sps
            _spsSize = naluSize;
            _sps = malloc(_spsSize);
            memcpy(_sps, &frame[4], _spsSize);
            break;
        case 0x08: //pps
            _ppsSize = naluSize;
            _pps = malloc(_ppsSize);
            memcpy(_pps, &frame[4], _ppsSize);
            break;

初始化解碼器

/**解碼會(huì)話*/
@property (nonatomic) VTDecompressionSessionRef decodeSesion;
- (BOOL)initDecoder {
    if (_decodeSesion) return true;
    //定義一個(gè)指針數(shù)組 ,里面存放的是sps pps的指針變量
    const uint8_t * const parameterSetPointers[2] = {_sps, _pps};
 //定義一個(gè)數(shù)組 ,里面存放的是sps pps的容量大小
    const size_t parameterSetSizes[2] = {_spsSize, _ppsSize};
    int naluHeaderLen = 4;
    
    /**
     根據(jù)sps pps設(shè)置解碼參數(shù)
     param kCFAllocatorDefault 分配器
     param 2 參數(shù)個(gè)數(shù)
     param parameterSetPointers 參數(shù)集指針
     param parameterSetSizes 參數(shù)集大小
     param naluHeaderLen nalu nalu start code 的長(zhǎng)度 4
     param _decodeDesc 解碼器描述
     return 狀態(tài)
     */
    OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault, 2, parameterSetPointers, parameterSetSizes, naluHeaderLen, &_decodeDesc);
    if (status != noErr) {
        NSLog(@"Video hard DecodeSession create H264ParameterSets(sps, pps) failed status= %d", (int)status);
        return false;
    }
    
    /*
     解碼參數(shù):
    * kCVPixelBufferPixelFormatTypeKey:攝像頭的輸出數(shù)據(jù)格式
     kCVPixelBufferPixelFormatTypeKey,已測(cè)可用值為
        kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,即420v
        kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,即420f
        kCVPixelFormatType_32BGRA,iOS在內(nèi)部進(jìn)行YUV至BGRA格式轉(zhuǎn)換
     YUV420一般用于標(biāo)清視頻,YUV422用于高清視頻,這里的限制讓人感到意外。但是,在相同條件下,YUV420計(jì)算耗時(shí)和傳輸壓力比YUV422都小。
     
    * kCVPixelBufferWidthKey/kCVPixelBufferHeightKey: 視頻源的分辨率 width*height
     * kCVPixelBufferOpenGLCompatibilityKey : 它允許在 OpenGL 的上下文中直接繪制解碼后的圖像,而不是從總線和 CPU 之間復(fù)制數(shù)據(jù)。這有時(shí)候被稱為零拷貝通道,因?yàn)樵诶L制過(guò)程中沒(méi)有解碼的圖像被拷貝.
     
     */
    NSDictionary *destinationPixBufferAttrs =
    @{
      (id)kCVPixelBufferPixelFormatTypeKey: [NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange], //iOS上 nv12(uvuv排布) 而不是nv21(vuvu排布)
      (id)kCVPixelBufferWidthKey: [NSNumber numberWithInteger:_config.width],
      (id)kCVPixelBufferHeightKey: [NSNumber numberWithInteger:_config.height],
      (id)kCVPixelBufferOpenGLCompatibilityKey: [NSNumber numberWithBool:true]
      };
    
    //解碼回調(diào)設(shè)置
    /*
     VTDecompressionOutputCallbackRecord 是一個(gè)簡(jiǎn)單的結(jié)構(gòu)體,它帶有一個(gè)指針 (decompressionOutputCallback),指向幀解壓完成后的回調(diào)方法。你需要提供可以找到這個(gè)回調(diào)方法的實(shí)例 (decompressionOutputRefCon)。VTDecompressionOutputCallback 回調(diào)方法包括七個(gè)參數(shù):
            參數(shù)1: 回調(diào)的引用
            參數(shù)2: 幀的引用
            參數(shù)3: 一個(gè)狀態(tài)標(biāo)識(shí) (包含未定義的代碼)
            參數(shù)4: 指示同步/異步解碼,或者解碼器是否打算丟幀的標(biāo)識(shí)
            參數(shù)5: 實(shí)際圖像的緩沖
            參數(shù)6: 出現(xiàn)的時(shí)間戳
            參數(shù)7: 出現(xiàn)的持續(xù)時(shí)間
     */
    VTDecompressionOutputCallbackRecord callbackRecord;
    callbackRecord.decompressionOutputCallback = videoDecompressionOutputCallback;
    callbackRecord.decompressionOutputRefCon = (__bridge void * _Nullable)(self);
    
    //創(chuàng)建session
    
    /*!
     @function    VTDecompressionSessionCreate
     @abstract    創(chuàng)建用于解壓縮視頻幀的會(huì)話。
     @discussion  解壓后的幀將通過(guò)調(diào)用OutputCallback發(fā)出
     @param    allocator  內(nèi)存的會(huì)話。通過(guò)使用默認(rèn)的kCFAllocatorDefault的分配器。
     @param    videoFormatDescription 描述源視頻幀
     @param    videoDecoderSpecification 指定必須使用的特定視頻解碼器.NULL
     @param    destinationImageBufferAttributes 描述源像素緩沖區(qū)的要求 NULL
     @param    outputCallback 使用已解壓縮的幀調(diào)用的回調(diào)
     @param    decompressionSessionOut 指向一個(gè)變量以接收新的解壓會(huì)話
     */
    status = VTDecompressionSessionCreate(kCFAllocatorDefault, _decodeDesc, NULL, (__bridge CFDictionaryRef _Nullable)(destinationPixBufferAttrs), &callbackRecord, &_decodeSesion);
    
    //判斷一下status
    if (status != noErr) {
        NSLog(@"Video hard DecodeSession create failed status= %d", (int)status);
        return false;
    }
    
    //設(shè)置解碼會(huì)話屬性(實(shí)時(shí)編碼)
    status = VTSessionSetProperty(_decodeSesion, kVTDecompressionPropertyKey_RealTime,kCFBooleanTrue);
    
    NSLog(@"Vidoe hard decodeSession set property RealTime status = %d", (int)status);
    
    return true;
}

初始化完會(huì)話后,要處理外部傳入的數(shù)據(jù)進(jìn)行解碼處理,這里要注意傳送給解碼會(huì)話的數(shù)據(jù)要重新包裝成CMBlockBufferRef-》CMSampleBufferRef然后通過(guò)VTDecompressionSessionDecodeFrame進(jìn)行解碼。解碼成功后會(huì)回調(diào)之前設(shè)置的VTDecompressionOutputCallbackRecord

-(void)decodeNaluData:(NSData *)frame{
    dispatch_async(_decodeQueue, ^{
        uint8_t *nalu =(uint8_t *) frame.bytes;
        [self decodeNaluData:nalu size:(uint32_t)frame.length];
    });
}
- (void)decodeNaluData:(uint8_t *)frame size:(uint32_t)size {
    //數(shù)據(jù)類型:frame的前4個(gè)字節(jié)是NALU數(shù)據(jù)的開始碼,也就是00 00 00 01,
    // 第5個(gè)字節(jié)是表示數(shù)據(jù)類型,轉(zhuǎn)為10進(jìn)制后,7是sps, 8是pps, 5是IDR(I幀)信息
    int type = (frame[4] & 0x1F);
    // 將NALU的開始碼轉(zhuǎn)為4字節(jié)大端NALU的長(zhǎng)度信息
    uint32_t naluSize = size - 4;
    uint8_t *pNaluSize = (uint8_t *)(&naluSize);
    CVPixelBufferRef pixelBuffer = NULL;
    frame[0] = *(pNaluSize + 3);
    frame[1] = *(pNaluSize + 2);
    frame[2] = *(pNaluSize + 1);
    frame[3] = *(pNaluSize);
    
    //第一次解析時(shí): 初始化解碼器initDecoder
    /*
     關(guān)鍵幀/其他幀數(shù)據(jù): 調(diào)用[self decode:frame withSize:size] 方法
     sps/pps數(shù)據(jù):則將sps/pps數(shù)據(jù)賦值到_sps/_pps中.
     */
    switch (type) {
        case 0x05: //關(guān)鍵幀
            if ([self initDecoder]) {
                pixelBuffer= [self decode:frame withSize:size];
            }
            break;
        case 0x06:
            //NSLog(@"SEI");//增強(qiáng)信息
            break;
        case 0x07: //sps
            _spsSize = naluSize;
            _sps = malloc(_spsSize);
            memcpy(_sps, &frame[4], _spsSize);
            break;
        case 0x08: //pps
            _ppsSize = naluSize;
            _pps = malloc(_ppsSize);
            memcpy(_pps, &frame[4], _ppsSize);
            break;
        default: //其他幀(1-5)
            if ([self initDecoder]) {
                pixelBuffer = [self decode:frame withSize:size];
            }
            break;
    }
}
/**解碼函數(shù)(private)*/
- (CVPixelBufferRef)decode:(uint8_t *)frame withSize:(uint32_t)frameSize {
    CVPixelBufferRef outputPixelBuffer = NULL;
    CMBlockBufferRef blockBuffer = NULL;
    CMBlockBufferFlags flag0 = 0;
    //創(chuàng)建blockBuffer
    /*!
     參數(shù)1: structureAllocator kCFAllocatorDefault
     參數(shù)2: memoryBlock  frame
     參數(shù)3: frame size
     參數(shù)4: blockAllocator: Pass NULL
     參數(shù)5: customBlockSource Pass NULL
     參數(shù)6: offsetToData  數(shù)據(jù)偏移
     參數(shù)7: dataLength 數(shù)據(jù)長(zhǎng)度
     參數(shù)8: flags 功能和控制標(biāo)志
     參數(shù)9: newBBufOut blockBuffer地址,不能為空
     */
    OSStatus status = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, frame, frameSize, kCFAllocatorNull, NULL, 0, frameSize, flag0, &blockBuffer);
    if (status != kCMBlockBufferNoErr) {
        NSLog(@"Video hard decode create blockBuffer error code=%d", (int)status);
        return outputPixelBuffer;
    }
    
    //創(chuàng)建sampleBuffer
    /*
     參數(shù)1: allocator 分配器,使用默認(rèn)內(nèi)存分配, kCFAllocatorDefault
     參數(shù)2: blockBuffer.需要編碼的數(shù)據(jù)blockBuffer.不能為NULL
     參數(shù)3: formatDescription,視頻輸出格式
     參數(shù)4: numSamples.CMSampleBuffer 個(gè)數(shù).
     參數(shù)5: numSampleTimingEntries 必須為0,1,numSamples
     參數(shù)6: sampleTimingArray.  數(shù)組.為空
     參數(shù)7: numSampleSizeEntries 默認(rèn)為1
     參數(shù)8: sampleSizeArray
     參數(shù)9: sampleBuffer對(duì)象
     */
    CMSampleBufferRef sampleBuffer = NULL;
    const size_t sampleSizeArray[] = {frameSize};
    status = CMSampleBufferCreateReady(kCFAllocatorDefault, blockBuffer, _decodeDesc, 1, 0, NULL, 1, sampleSizeArray, &sampleBuffer);
    if (status != noErr || !sampleBuffer) {
        NSLog(@"Video hard decode create sampleBuffer failed status=%d", (int)status);
        CFRelease(blockBuffer);
        return outputPixelBuffer;
    }
    VTDecodeFrameFlags flag1 = kVTDecodeFrame_1xRealTimePlayback;
    //異步解碼
    VTDecodeInfoFlags  infoFlag = kVTDecodeInfo_Asynchronous;
    //解碼數(shù)據(jù)
    /*
     參數(shù)1: 解碼session
     參數(shù)2: 源數(shù)據(jù) 包含一個(gè)或多個(gè)視頻幀的CMsampleBuffer
     參數(shù)3: 解碼標(biāo)志
     參數(shù)4: 解碼后數(shù)據(jù)outputPixelBuffer
     參數(shù)5: 同步/異步解碼標(biāo)識(shí)
     */
    status = VTDecompressionSessionDecodeFrame(_decodeSesion, sampleBuffer, flag1, &outputPixelBuffer, &infoFlag);
    if (status == kVTInvalidSessionErr) {
           NSLog(@"Video hard decode  InvalidSessionErr status =%d", (int)status);
       } else if (status == kVTVideoDecoderBadDataErr) {
           NSLog(@"Video hard decode  BadData status =%d", (int)status);
       } else if (status != noErr) {
           NSLog(@"Video hard decode failed status =%d", (int)status);
       }
       CFRelease(sampleBuffer);
       CFRelease(blockBuffer);
       
       
       return outputPixelBuffer;
//    return outputPixelBuffer;
}

解碼成功后我們?cè)诨卣{(diào)函數(shù) VTDecompressionOutputCallbackRecord 】中處理解碼后的數(shù)據(jù)

/*
 1.參數(shù)1:decompressionOutputRefCon
 這個(gè)是之前傳人方法的參數(shù)也就是self,這個(gè)是c方法所以沒(méi)有self參數(shù)需要自己傳。
 2.參數(shù)2:sourceFrameRefCon
 回調(diào)函數(shù)會(huì)引用你設(shè)置的這個(gè)幀的參考值
 3.status
 noErr if decompression was successful; an error code if decompression was not successful.
 4.infoFlags
 Information about the decode operation.
 指向一個(gè)VTEncodeInfoFlags來(lái)接受一個(gè)編碼操作.如果使用異步運(yùn)行,kVTEncodeInfo_Asynchronous被設(shè)置;同步運(yùn)行,kVTEncodeInfo_FrameDropped被設(shè)置;設(shè)置NULL為不想接受這個(gè)信息.
 5.imageBuffer
    解壓的幀如果不成功是null
 
 6.presentationTimeStamp
 幀的表示時(shí)間戳
 7.presentationDuration
 幀的表示持續(xù)時(shí)間
 */
void videoDecompressionOutputCallback(
                                      void * CM_NULLABLE decompressionOutputRefCon,
                                      void * CM_NULLABLE sourceFrameRefCon,
                                      OSStatus status,
                                      VTDecodeInfoFlags infoFlags,
                                      CM_NULLABLE CVImageBufferRef imageBuffer,
                                      CMTime presentationTimeStamp,
                                      CMTime presentationDuration ){
    if (status != noErr) {
           NSLog(@"Video hard decode callback error status=%d", (int)status);
           return;
       }
    CVPixelBufferRef *outputPixelBuffer = (CVPixelBufferRef *)sourceFrameRefCon;
    *outputPixelBuffer = CVPixelBufferRetain(imageBuffer);
    
    //獲取self
    SQVideoDecoder *decoder = (__bridge SQVideoDecoder *)(decompressionOutputRefCon);
    
    //調(diào)用回調(diào)隊(duì)列
    dispatch_async(decoder.callbackQueue, ^{
        
        //將解碼后的數(shù)據(jù)給decoder代理.viewController
        [decoder.delegate videoDecodeCallback:imageBuffer];
        //釋放數(shù)據(jù)
        CVPixelBufferRelease(imageBuffer);
    });
       //解碼后的數(shù)據(jù)sourceFrameRefCon -> CVPixelBufferRef
}

全部源碼

//
//  SQVideoDecoder.h
//  CPDemo
//
//  Created by Sem on 2020/8/10.
//  Copyright ? 2020 SEM. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
#import "SQAVConfig.h"
NS_ASSUME_NONNULL_BEGIN
@protocol SQVideoDecoderDelegate <NSObject>
//解碼后H264數(shù)據(jù)回調(diào)
- (void)videoDecodeCallback:(CVPixelBufferRef)imageBuffer;


@end
@interface SQVideoDecoder : NSObject
@property (nonatomic, strong) SQVideoConfig *config;
@property (nonatomic, weak) id<SQVideoDecoderDelegate> delegate;
/**初始化解碼器**/
- (instancetype)initWithConfig:(SQVideoConfig*)config;
-(void)decodeNaluData:(NSData *)frame;
@end

NS_ASSUME_NONNULL_END

//
//  SQVideoDecoder.m
//  CPDemo
//
//  Created by Sem on 2020/8/10.
//  Copyright ? 2020 SEM. All rights reserved.
//

#import "SQVideoDecoder.h"
#import <AVFoundation/AVFoundation.h>
#import <VideoToolbox/VideoToolbox.h>
@interface SQVideoDecoder ()
@property (nonatomic, strong) dispatch_queue_t decodeQueue;
@property (nonatomic, strong) dispatch_queue_t callbackQueue;
/**解碼會(huì)話*/
@property (nonatomic) VTDecompressionSessionRef decodeSesion;

@end
@implementation SQVideoDecoder{
    uint8_t *_sps;
    NSUInteger _spsSize;
    uint8_t *_pps;
    NSUInteger _ppsSize;
    CMVideoFormatDescriptionRef _decodeDesc;
}
- (instancetype)initWithConfig:(SQVideoConfig*)config{
    self = [super init];
    if(self){
        _config = config;
        _decodeQueue = dispatch_queue_create("h264 hard decode queue", DISPATCH_QUEUE_SERIAL);
        _callbackQueue =dispatch_queue_create("h264 hard decode callback queue", DISPATCH_QUEUE_SERIAL);
    }
    return self;
}
/*初始化解碼器**/
/*初始化解碼器**/

-(void)decodeNaluData:(NSData *)frame{
    dispatch_async(_decodeQueue, ^{
        uint8_t *nalu =(uint8_t *) frame.bytes;
        [self decodeNaluData:nalu size:(uint32_t)frame.length];
    });
}
- (void)decodeNaluData:(uint8_t *)frame size:(uint32_t)size {
    //數(shù)據(jù)類型:frame的前4個(gè)字節(jié)是NALU數(shù)據(jù)的開始碼,也就是00 00 00 01,
    // 第5個(gè)字節(jié)是表示數(shù)據(jù)類型,轉(zhuǎn)為10進(jìn)制后,7是sps, 8是pps, 5是IDR(I幀)信息
    int type = (frame[4] & 0x1F);
    // 將NALU的開始碼轉(zhuǎn)為4字節(jié)大端NALU的長(zhǎng)度信息
    uint32_t naluSize = size - 4;
    uint8_t *pNaluSize = (uint8_t *)(&naluSize);
    CVPixelBufferRef pixelBuffer = NULL;
    frame[0] = *(pNaluSize + 3);
    frame[1] = *(pNaluSize + 2);
    frame[2] = *(pNaluSize + 1);
    frame[3] = *(pNaluSize);
    
    //第一次解析時(shí): 初始化解碼器initDecoder
    /*
     關(guān)鍵幀/其他幀數(shù)據(jù): 調(diào)用[self decode:frame withSize:size] 方法
     sps/pps數(shù)據(jù):則將sps/pps數(shù)據(jù)賦值到_sps/_pps中.
     */
    switch (type) {
        case 0x05: //關(guān)鍵幀
            if ([self initDecoder]) {
                pixelBuffer= [self decode:frame withSize:size];
            }
            break;
        case 0x06:
            //NSLog(@"SEI");//增強(qiáng)信息
            break;
        case 0x07: //sps
            _spsSize = naluSize;
            _sps = malloc(_spsSize);
            memcpy(_sps, &frame[4], _spsSize);
            break;
        case 0x08: //pps
            _ppsSize = naluSize;
            _pps = malloc(_ppsSize);
            memcpy(_pps, &frame[4], _ppsSize);
            break;
        default: //其他幀(1-5)
            if ([self initDecoder]) {
                pixelBuffer = [self decode:frame withSize:size];
            }
            break;
    }
}
/**解碼函數(shù)(private)*/
- (CVPixelBufferRef)decode:(uint8_t *)frame withSize:(uint32_t)frameSize {
    CVPixelBufferRef outputPixelBuffer = NULL;
    CMBlockBufferRef blockBuffer = NULL;
    CMBlockBufferFlags flag0 = 0;
    //創(chuàng)建blockBuffer
    /*!
     參數(shù)1: structureAllocator kCFAllocatorDefault
     參數(shù)2: memoryBlock  frame
     參數(shù)3: frame size
     參數(shù)4: blockAllocator: Pass NULL
     參數(shù)5: customBlockSource Pass NULL
     參數(shù)6: offsetToData  數(shù)據(jù)偏移
     參數(shù)7: dataLength 數(shù)據(jù)長(zhǎng)度
     參數(shù)8: flags 功能和控制標(biāo)志
     參數(shù)9: newBBufOut blockBuffer地址,不能為空
     */
    OSStatus status = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, frame, frameSize, kCFAllocatorNull, NULL, 0, frameSize, flag0, &blockBuffer);
    if (status != kCMBlockBufferNoErr) {
        NSLog(@"Video hard decode create blockBuffer error code=%d", (int)status);
        return outputPixelBuffer;
    }
    
    //創(chuàng)建sampleBuffer
    /*
     參數(shù)1: allocator 分配器,使用默認(rèn)內(nèi)存分配, kCFAllocatorDefault
     參數(shù)2: blockBuffer.需要編碼的數(shù)據(jù)blockBuffer.不能為NULL
     參數(shù)3: formatDescription,視頻輸出格式
     參數(shù)4: numSamples.CMSampleBuffer 個(gè)數(shù).
     參數(shù)5: numSampleTimingEntries 必須為0,1,numSamples
     參數(shù)6: sampleTimingArray.  數(shù)組.為空
     參數(shù)7: numSampleSizeEntries 默認(rèn)為1
     參數(shù)8: sampleSizeArray
     參數(shù)9: sampleBuffer對(duì)象
     */
    CMSampleBufferRef sampleBuffer = NULL;
    const size_t sampleSizeArray[] = {frameSize};
    status = CMSampleBufferCreateReady(kCFAllocatorDefault, blockBuffer, _decodeDesc, 1, 0, NULL, 1, sampleSizeArray, &sampleBuffer);
    if (status != noErr || !sampleBuffer) {
        NSLog(@"Video hard decode create sampleBuffer failed status=%d", (int)status);
        CFRelease(blockBuffer);
        return outputPixelBuffer;
    }
    VTDecodeFrameFlags flag1 = kVTDecodeFrame_1xRealTimePlayback;
    //異步解碼
    VTDecodeInfoFlags  infoFlag = kVTDecodeInfo_Asynchronous;
    //解碼數(shù)據(jù)
    /*
     參數(shù)1: 解碼session
     參數(shù)2: 源數(shù)據(jù) 包含一個(gè)或多個(gè)視頻幀的CMsampleBuffer
     參數(shù)3: 解碼標(biāo)志
     參數(shù)4: 解碼后數(shù)據(jù)outputPixelBuffer
     參數(shù)5: 同步/異步解碼標(biāo)識(shí)
     */
    status = VTDecompressionSessionDecodeFrame(_decodeSesion, sampleBuffer, flag1, &outputPixelBuffer, &infoFlag);
    if (status == kVTInvalidSessionErr) {
           NSLog(@"Video hard decode  InvalidSessionErr status =%d", (int)status);
       } else if (status == kVTVideoDecoderBadDataErr) {
           NSLog(@"Video hard decode  BadData status =%d", (int)status);
       } else if (status != noErr) {
           NSLog(@"Video hard decode failed status =%d", (int)status);
       }
       CFRelease(sampleBuffer);
       CFRelease(blockBuffer);
       
       
       return outputPixelBuffer;
//    return outputPixelBuffer;
}
- (BOOL)initDecoder {
    if (_decodeSesion) return true;
    //定義一個(gè)指針數(shù)組 ,里面存放的是sps pps的指針變量
    const uint8_t * const parameterSetPointers[2] = {_sps, _pps};
    //定義一個(gè)數(shù)組 ,里面存放的是sps pps的容量大小
    const size_t parameterSetSizes[2] = {_spsSize, _ppsSize};
    int naluHeaderLen = 4;
    
    /**
     根據(jù)sps pps設(shè)置解碼參數(shù)
     param kCFAllocatorDefault 分配器
     param 2 參數(shù)個(gè)數(shù)
     param parameterSetPointers 參數(shù)集指針
     param parameterSetSizes 參數(shù)集大小
     param naluHeaderLen nalu nalu start code 的長(zhǎng)度 4
     param _decodeDesc 解碼器描述
     return 狀態(tài)
     */
    OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault, 2, parameterSetPointers, parameterSetSizes, naluHeaderLen, &_decodeDesc);
    if (status != noErr) {
        NSLog(@"Video hard DecodeSession create H264ParameterSets(sps, pps) failed status= %d", (int)status);
        return false;
    }
    
    /*
     解碼參數(shù):
    * kCVPixelBufferPixelFormatTypeKey:攝像頭的輸出數(shù)據(jù)格式
     kCVPixelBufferPixelFormatTypeKey,已測(cè)可用值為
        kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,即420v
        kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,即420f
        kCVPixelFormatType_32BGRA,iOS在內(nèi)部進(jìn)行YUV至BGRA格式轉(zhuǎn)換
     YUV420一般用于標(biāo)清視頻,YUV422用于高清視頻,這里的限制讓人感到意外。但是,在相同條件下,YUV420計(jì)算耗時(shí)和傳輸壓力比YUV422都小。
     
    * kCVPixelBufferWidthKey/kCVPixelBufferHeightKey: 視頻源的分辨率 width*height
     * kCVPixelBufferOpenGLCompatibilityKey : 它允許在 OpenGL 的上下文中直接繪制解碼后的圖像,而不是從總線和 CPU 之間復(fù)制數(shù)據(jù)。這有時(shí)候被稱為零拷貝通道,因?yàn)樵诶L制過(guò)程中沒(méi)有解碼的圖像被拷貝.
     
     */
    NSDictionary *destinationPixBufferAttrs =
    @{
      (id)kCVPixelBufferPixelFormatTypeKey: [NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange], //iOS上 nv12(uvuv排布) 而不是nv21(vuvu排布)
      (id)kCVPixelBufferWidthKey: [NSNumber numberWithInteger:_config.width],
      (id)kCVPixelBufferHeightKey: [NSNumber numberWithInteger:_config.height],
      (id)kCVPixelBufferOpenGLCompatibilityKey: [NSNumber numberWithBool:true]
      };
    
    //解碼回調(diào)設(shè)置
    /*
     VTDecompressionOutputCallbackRecord 是一個(gè)簡(jiǎn)單的結(jié)構(gòu)體,它帶有一個(gè)指針 (decompressionOutputCallback),指向幀解壓完成后的回調(diào)方法。你需要提供可以找到這個(gè)回調(diào)方法的實(shí)例 (decompressionOutputRefCon)。VTDecompressionOutputCallback 回調(diào)方法包括七個(gè)參數(shù):
            參數(shù)1: 回調(diào)的引用
            參數(shù)2: 幀的引用
            參數(shù)3: 一個(gè)狀態(tài)標(biāo)識(shí) (包含未定義的代碼)
            參數(shù)4: 指示同步/異步解碼,或者解碼器是否打算丟幀的標(biāo)識(shí)
            參數(shù)5: 實(shí)際圖像的緩沖/Users/sem/Desktop/CPDemo/CPDemo/AudioEncoder/SQAudioEncoder.m
            參數(shù)6: 出現(xiàn)的時(shí)間戳
            參數(shù)7: 出現(xiàn)的持續(xù)時(shí)間
     */
    VTDecompressionOutputCallbackRecord callbackRecord;
    callbackRecord.decompressionOutputCallback = videoDecompressionOutputCallback;
    callbackRecord.decompressionOutputRefCon = (__bridge void * _Nullable)(self);
    
    //創(chuàng)建session
    
    /*!
     @function    VTDecompressionSessionCreate
     @abstract    創(chuàng)建用于解壓縮視頻幀的會(huì)話。
     @discussion  解壓后的幀將通過(guò)調(diào)用OutputCallback發(fā)出
     @param    allocator  內(nèi)存的會(huì)話。通過(guò)使用默認(rèn)的kCFAllocatorDefault的分配器。
     @param    videoFormatDescription 描述源視頻幀
     @param    videoDecoderSpecification 指定必須使用的特定視頻解碼器.NULL
     @param    destinationImageBufferAttributes 描述源像素緩沖區(qū)的要求 NULL
     @param    outputCallback 使用已解壓縮的幀調(diào)用的回調(diào)
     @param    decompressionSessionOut 指向一個(gè)變量以接收新的解壓會(huì)話
     */
    status = VTDecompressionSessionCreate(kCFAllocatorDefault, _decodeDesc, NULL, (__bridge CFDictionaryRef _Nullable)(destinationPixBufferAttrs), &callbackRecord, &_decodeSesion);
    
    //判斷一下status
    if (status != noErr) {
        NSLog(@"Video hard DecodeSession create failed status= %d", (int)status);
        return false;
    }
    
    //設(shè)置解碼會(huì)話屬性(實(shí)時(shí)編碼)
    status = VTSessionSetProperty(_decodeSesion, kVTDecompressionPropertyKey_RealTime,kCFBooleanTrue);
    
    NSLog(@"Vidoe hard decodeSession set property RealTime status = %d", (int)status);
    
    return true;
}
/*
 1.參數(shù)1:decompressionOutputRefCon
 這個(gè)是之前傳人方法的參數(shù)也就是self,這個(gè)是c方法所以沒(méi)有self參數(shù)需要自己傳。
 2.參數(shù)2:sourceFrameRefCon
 回調(diào)函數(shù)會(huì)引用你設(shè)置的這個(gè)幀的參考值
 3.status
 noErr if decompression was successful; an error code if decompression was not successful.
 4.infoFlags
 Information about the decode operation.
 指向一個(gè)VTEncodeInfoFlags來(lái)接受一個(gè)編碼操作.如果使用異步運(yùn)行,kVTEncodeInfo_Asynchronous被設(shè)置;同步運(yùn)行,kVTEncodeInfo_FrameDropped被設(shè)置;設(shè)置NULL為不想接受這個(gè)信息.
 5.imageBuffer
    解壓的幀如果不成功是null
 
 6.presentationTimeStamp
 幀的表示時(shí)間戳
 7.presentationDuration
 幀的表示持續(xù)時(shí)間
 */
void videoDecompressionOutputCallback(
                                      void * CM_NULLABLE decompressionOutputRefCon,
                                      void * CM_NULLABLE sourceFrameRefCon,
                                      OSStatus status,
                                      VTDecodeInfoFlags infoFlags,
                                      CM_NULLABLE CVImageBufferRef imageBuffer,
                                      CMTime presentationTimeStamp,
                                      CMTime presentationDuration ){
    if (status != noErr) {
           NSLog(@"Video hard decode callback error status=%d", (int)status);
           return;
       }
    CVPixelBufferRef *outputPixelBuffer = (CVPixelBufferRef *)sourceFrameRefCon;
    *outputPixelBuffer = CVPixelBufferRetain(imageBuffer);
    
    //獲取self
    SQVideoDecoder *decoder = (__bridge SQVideoDecoder *)(decompressionOutputRefCon);
    
    //調(diào)用回調(diào)隊(duì)列
    dispatch_async(decoder.callbackQueue, ^{
        
        //將解碼后的數(shù)據(jù)給decoder代理.viewController
        [decoder.delegate videoDecodeCallback:imageBuffer];
        //釋放數(shù)據(jù)
        CVPixelBufferRelease(imageBuffer);
    });
       //解碼后的數(shù)據(jù)sourceFrameRefCon -> CVPixelBufferRef
}
//銷毀
- (void)dealloc
{
    if (_decodeSesion) {
        VTDecompressionSessionInvalidate(_decodeSesion);
        CFRelease(_decodeSesion);
        _decodeSesion = NULL;
    }
    
}

@end

@interface SQVideoConfig : NSObject
@property (nonatomic, assign) NSInteger width;//可選,系統(tǒng)支持的分辨率,采集分辨率的寬
@property (nonatomic, assign) NSInteger height;//可選,系統(tǒng)支持的分辨率,采集分辨率的高
@property (nonatomic, assign) NSInteger bitrate;//自由設(shè)置
@property (nonatomic, assign) NSInteger fps;//自由設(shè)置 25
+ (instancetype)defaultConifg;
@end

@implementation SQVideoConfig

+ (instancetype)defaultConifg {
    return [[SQVideoConfig alloc] init];
}
- (instancetype)init
{
    self = [super init];
    if (self) {
        self.width = 480;
        self.height = 640;
        self.bitrate = 640*1000;
        self.fps = 25;
    }
    return self;
}
@end
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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