音視頻-視頻編/解碼 實戰(zhàn)

本文主要介紹如何利用 VideoToolBox 實現(xiàn)對視頻的硬編/解碼冗恨。

先來簡單看下 音視頻的采集 咖为。

一缔莲、音視頻的采集

音視頻采集的核心流程:
音/視頻采集
  • 用到的視頻輸出的類是AVCaptureVideoDataOutput动壤,音頻輸出的類是AVCaptureAudioDataOutput零截。
  • 采集成功后的代理方法輸出的音視頻對象為CMSampleBufferRef類型的sampleBuffer岖瑰。這里我們可以使用AVCaptureConnection來判斷是音頻還是視頻杀怠。
- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
    if (connection == self.audioConnection) { //音頻
    }else if (connection == self.videoConnection) { //視頻
    }
}

采集的核心流程跟 AVFoundation 拍照/錄制視頻AVFoundation 人臉識別 的采集流程基本一致排龄,大家可以了解下阀坏。

二如暖、視頻的編解碼

2.1 視頻的編碼

1.首先需要初始化編碼器,看代碼:

- (instancetype)initWithConfigure:(CQVideoCoderConfigure *)configure {
    self = [super init];
    if (self) {
        self.configure = configure;
        self.encodeQueue = dispatch_queue_create("h264 hard encode queue", DISPATCH_QUEUE_SERIAL);
        self.callbackQueue = dispatch_queue_create("h264 hard encode callback queue", DISPATCH_QUEUE_SERIAL);
        
        //1.創(chuàng)建編碼session 
        OSStatus status = VTCompressionSessionCreate(kCFAllocatorDefault, (int32_t)self.configure.width, (int32_t)self.configure.height, kCMVideoCodecType_H264, NULL, NULL, NULL, compressionOutputCallback, (__bridge void * _Nullable)(self), &_encodeSesion);
        if (status != noErr) {
           NSLog(@"VTCompressionSessionCreate error status: %d", (int)status);
            return self;
        }
        
        //2忌堂、設(shè)置編碼器參數(shù)
        //是否實時執(zhí)行
        status = VTSessionSetProperty(self.encodeSesion, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue);
        NSLog(@"VTSessionSetProperty RealTime status: %d", (int)status);
        //指定編碼比特流的配置文件和級別盒至。直播一般使用baseline,可減少由b幀減少帶來的延遲士修。
        status = VTSessionSetProperty(self.encodeSesion, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_Baseline_AutoLevel);
NSLog(@"VTSessionSetProperty ProfileLevel status: %d", (int)status);
        //設(shè)置比特率均值(比特率可以高于此枷遂。默認(rèn)比特率為零,表示視頻編碼器棋嘲。應(yīng)該確定壓縮數(shù)據(jù)的大小酒唉。)
        //注意:比特率設(shè)置只在定時的時候有效
        status = VTSessionSetProperty(self.encodeSesion, kVTCompressionPropertyKey_AverageBitRate, (__bridge CFNumberRef)@(self.configure.bitrate));
        NSLog(@"VTSessionSetProperty AverageBitRate status: %d", (int)status);
        //碼率限制
        CFArrayRef limits = (__bridge CFArrayRef)@[@(self.configure.bitrate / 4),@(self.configure.bitrate * 4)];
        status = VTSessionSetProperty(_encodeSesion, kVTCompressionPropertyKey_DataRateLimits,limits);
        NSLog(@"VTSessionSetProperty DataRateLimits status: %d", (int)status);
        //設(shè)置關(guān)鍵幀間隔
        status = VTSessionSetProperty(self.encodeSesion, kVTCompressionPropertyKey_MaxKeyFrameInterval, (__bridge CFNumberRef)@(self.configure.fps * 2));
        NSLog(@"VTSessionSetProperty MaxKeyFrameInterval status: %d", (int)status);
        //設(shè)置預(yù)期的fps
        CFNumberRef expectedFrameRate = (__bridge CFNumberRef)@(self.configure.fps);
        status = VTSessionSetProperty(_encodeSesion, kVTCompressionPropertyKey_ExpectedFrameRate, expectedFrameRate);
        NSLog(@"VTSessionSetProperty ExpectedFrameRate status: %d", (int)status);

        //3、準(zhǔn)備編碼
        status = VTCompressionSessionPrepareToEncodeFrames(self.encodeSesion);
        NSLog(@"VTSessionSetProperty: set PrepareToEncodeFrames return: %d", (int)status);
        
    }
    return self;
}
  • 1沸移、VTCompressionSessionCreate:創(chuàng)建壓縮會話痪伦,并且添加了編碼成功后的回調(diào)函數(shù)compressionOutputCallback侄榴。
    參數(shù)1:會話的分配器。傳遞NULL使用默認(rèn)分配器网沾。
    參數(shù)2:幀的寬度癞蚕,以像素為單位。如果視頻編碼器不支持所提供的寬度和高度辉哥,系統(tǒng)可能會自動修改桦山。
    參數(shù)3:幀的高度。
    參數(shù)4:編碼類型醋旦。
    參數(shù)5:編碼規(guī)范度苔。NULLvideoToolbox自己選擇。
    參數(shù)6:源像素緩沖區(qū)屬性浑度,NULL不讓videToolbox創(chuàng)建寇窑,自己創(chuàng)建。
    參數(shù)7: 壓縮數(shù)據(jù)分配器箩张。NULL默認(rèn)的分配甩骏。
    參數(shù)8:回調(diào)函數(shù)。異步調(diào)用先慷。
    參數(shù)9:客戶端為輸出回調(diào)定義的引用值饮笛。這里傳的事我們自定義的編碼器,也就是self论熙。
    參數(shù)10: 要創(chuàng)建的編碼會話對象福青。
  • 2、VTSessionSetProperty屬性配置脓诡。
  • 3无午、VTCompressionSessionPrepareToEncodeFrames:準(zhǔn)備編碼。

2祝谚、進(jìn)行編碼宪迟,看代碼:

- (void)encoderSampleBuffers:(CMSampleBufferRef)sampleBuffer {
        CFRetain(sampleBuffer);
    dispatch_async(self.encodeQueue, ^{
        CVImageBufferRef imageBuffer = (CVImageBufferRef)CMSampleBufferGetImageBuffer(sampleBuffer);//幀數(shù)據(jù)
        self->frameID++;
        CMTime timeStamp = CMTimeMake(self->frameID, 1000);//該幀的時間戳
        CMTime duration = kCMTimeInvalid;//持續(xù)時間
        VTEncodeInfoFlags flags;
        OSStatus status = VTCompressionSessionEncodeFrame(self.encodeSesion, imageBuffer, timeStamp, duration, NULL, NULL, &flags);//編碼
        if (status != noErr) {
            NSLog(@"VTCompressionSessionEncodeFrame error status: %d",(int)status);
        }
        CFRelease(sampleBuffer);
    });
}
  • 1、CMSampleBufferGetImageBuffer從采集到的視頻CMSampleBufferRef中獲取CVImageBufferRef交惯。
  • 2次泽、VTCompressionSessionEncodeFrame壓縮編碼:
    參數(shù)1:編碼會話encodeSesion
    參數(shù)2:CVImageBuffer對象,包含視頻幀數(shù)據(jù)
    參數(shù)3:對應(yīng)該幀的時間戳席爽,每個示例的時間戳必須大于前一個意荤。
    參數(shù)4:該演示幀的持續(xù)時間,沒有可填kCMTimeInvalid
    參數(shù)5:編碼該幀的鍵/值對屬性信息只锻。注意玖像,某些會話屬性也可能在幀之間更改。這種變化對隨后編碼的幀有影響炬藤。
    參數(shù)6:將要傳遞給回調(diào)函數(shù)的幀的引用值御铃。
    參數(shù)7:VTEncodeInfoFlags接收有關(guān)編碼操作的信息.

3碴里、編碼成功后回調(diào)處理:

// startCode 長度 4
const Byte startCode[] = "\x00\x00\x00\x01";
void compressionOutputCallback(void * CM_NULLABLE outputCallbackRefCon, void * CM_NULLABLE sourceFrameRefCon, OSStatus status, VTEncodeInfoFlags infoFlags, CM_NULLABLE CMSampleBufferRef sampleBuffer ) {
    if (status != noErr) {
        NSLog(@"compressionOutputCallback error status: %d", (int)status);
        return;
    }
    if (!CMSampleBufferDataIsReady(sampleBuffer)) {
        NSLog(@"CMSampleBufferDataIsReady is not ready");
        return;
    }
    
    CQVideoEncoder *encoder =  (__bridge CQVideoEncoder *)outputCallbackRefCon;
    BOOL keyFrame = NO;
    CFArrayRef attachmentsArray = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true);
    keyFrame = !CFDictionaryContainsKey(CFArrayGetValueAtIndex(attachmentsArray, 0), kCMSampleAttachmentKey_NotSync);

    //是否為關(guān)鍵幀,并且有沒有獲取過sps 和 pps 數(shù)據(jù)上真。
    if (keyFrame && !encoder->hasSpsPps) {
        size_t spsSize, spsCount, ppsSize, ppsCount;
        const uint8_t *spsData, *ppsData;
        //獲取圖像原格式
        CMFormatDescriptionRef formatDes = CMSampleBufferGetFormatDescription(sampleBuffer);
        OSStatus status1 = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(formatDes, 0, &spsData, &spsSize, &spsCount, 0);
        OSStatus status2 = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(formatDes, 1, &ppsData, &ppsSize, &ppsCount, 0);
        
        if (status1 == noErr & status2 == noErr) {//sps/pps獲取成功
            NSLog(@"Get sps and pps successRб浮!睡互!");
            //sps 和 pps 數(shù)據(jù)只需保存在H264文件開頭即可根竿。
            encoder->hasSpsPps = true;
            NSMutableData *spsDataM = [NSMutableData dataWithCapacity:4 + spsSize];
            [spsDataM appendBytes:startCode length:4];
            [spsDataM appendBytes:spsData length:spsSize];
            NSMutableData *ppsDataM = [NSMutableData dataWithCapacity:4 + ppsSize];
            [ppsDataM appendBytes:startCode length:4];
            [ppsDataM appendBytes:ppsData length:ppsSize];
            dispatch_async(encoder.encodeQueue, ^{
                if ([encoder.delegate respondsToSelector:@selector(encodeCallbackWithSps:pps:)]) {
                    [encoder.delegate encodeCallbackWithSps:spsDataM pps:ppsDataM];
                }
            });
        } else {
             NSLog(@"Get sps and pps failed, spsStatus:%d, ppsStatus:%d", (int)status1, (int)status2);
        }
    }
    
    //獲取NAL Unit數(shù)據(jù)
    size_t lengthAtOffset, totalLength;
    char *dataPoint;
    //將數(shù)據(jù)復(fù)制到dataPoint
    CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
    OSStatus error = CMBlockBufferGetDataPointer(blockBuffer, 0, &lengthAtOffset, &totalLength, &dataPoint);
    if (error != kCMBlockBufferNoErr) {
        NSLog(@"VideoEncodeCallback: get datapoint failed, status = %d", (int)error);
        return;
    }
     //循環(huán)獲取NAL Unit數(shù)據(jù)
    size_t offet = 0;
    //返回的NAL Unit數(shù)據(jù)前四個字節(jié)不是系統(tǒng)端的startCode(0001)
    //而是大端模式的幀長度
    const int lengthStartCode = 4;
    const int lengthBigFrame = 4;
    while (offet < totalLength - lengthBigFrame) {
        //獲取NAL Unit數(shù)據(jù)長度
        uint32_t lengthNALU = 0;
        memcpy(&lengthNALU, dataPointerOut + offet, lengthBigFrame);
        lengthNALU = CFSwapInt32BigToHost(lengthBigFrame);//大端轉(zhuǎn)系統(tǒng)端
        //獲取到編碼好的視頻startCode + NAL Uint
        NSMutableData *data = [NSMutableData dataWithCapacity:lengthStartCode + lengthNALU];
        [data appendBytes:startCode length:lengthStartCode];
        [data appendBytes:dataPointerOut + offet + lengthBigFrame length:lengthNALU];
        
        dispatch_async(encoder.encodeQueue, ^{
            if ([encoder.delegate respondsToSelector:@selector(encodeVideoCallback:)]) {
                [encoder.delegate encodeVideoCallback:data];
            }
        });
        offet += lengthStartCode + lengthNALU;
    }
}
  • 1、通過編碼成功后的CMSampleBufferRef獲取到當(dāng)前幀的相關(guān)屬性就珠,判斷是否是關(guān)鍵幀寇壳。編碼前后的視頻數(shù)據(jù)都是CMSampleBufferRef類型。
  • 2妻怎、是關(guān)鍵幀并且沒有設(shè)置過sps壳炎、pps
    ?2.1逼侦、獲取圖像原格式和圖像的sps匿辩、pps
    ?2.2、將二進(jìn)制格式的sps榛丢、pps拼接到NSData中铲球,并且開頭加上startCode(00 00 00 01)NAL Unit之間使用startCode(00 00 00 01)進(jìn)行分割的晰赞。
    ?2.3稼病、將NSData格式的sps、pps回調(diào)出去掖鱼。
  • 3然走、將數(shù)據(jù)復(fù)制到dataPointerOut
    ?3.1、CMSampleBufferGetDataBuffer:從CMSampleBufferRef中獲取CMBlockBufferRef锨用。
    ?3.2丰刊、CMBlockBufferGetDataPointer:將數(shù)據(jù)復(fù)制到dataPointerOut,獲取到數(shù)據(jù)的總長度增拥。
  • 4、循環(huán)獲取NAL Unit數(shù)據(jù)寻歧。
    ?4.1掌栅、memcpy:獲取NAL Unit數(shù)據(jù)長度。
    ?4.2码泛、獲取到編碼好的視頻猾封,開頭加上startCode(00 00 00 01)
    ?4.3噪珊、將視頻數(shù)據(jù)回調(diào)出去晌缘。

2.2視頻的解碼
解析H264格式數(shù)據(jù):

- (void)decodeH264Data:(NSData *)frame {
    dispatch_async(self.decodeQueue, ^{
        uint8_t *frameNALU = (uint8_t *)frame.bytes;
        uint32_t lengthFrame = (uint32_t)frame.length;

        int type = (frameNALU[4] & 0x1F);
        //0 01 00111 &   39
        //0 00 11111       31
        //0 00 00111       7
        //NSLog(@"type: %hhu, %d", frame[4], type);
        
        //將NAL Unit開始碼轉(zhuǎn)為4字節(jié)大端NAL Unit的長度信息齐莲。
        uint32_t naluSize = lengthFrame - 4;
        uint8_t *pNaluSize = (uint8_t *)(&naluSize);
        frameNALU[0] = *(pNaluSize + 3);
        frameNALU[1] = *(pNaluSize + 2);
        frameNALU[2] = *(pNaluSize + 1);
        frameNALU[3] = *(pNaluSize);
        
        CVPixelBufferRef pixelBuffer = NULL;
        switch (type) {
            case 0x05://I幀(關(guān)鍵幀)
                if ([self createDecoder]) {
                    pixelBuffer = [self decodeNALUFrame:frameNALU withFrameLength:lengthFrame];
                }
                break;
            case 0x06://增強信息
                break;
            case 0x07://sps
                self->_spsSize = naluSize;
                self->_sps = malloc(self->_spsSize);
                memcpy(self->_sps, &frameNALU[4], self->_spsSize);
                break;
            case 0x08://pps
                self->_ppsSize = naluSize;
                self->_pps = malloc(self->_ppsSize);
                memcpy(self->_pps, &frameNALU[4], self->_ppsSize);
                break;
            default://其他幀(0x01到 0x05)
                if ([self createDecoder]) {
                    pixelBuffer = [self decodeNALUFrame:frameNALU withFrameLength:lengthFrame];
                }
                break;
        }
    });
}
  • 1、獲取幀的二進(jìn)制數(shù)據(jù)磷箕,這里的NSData數(shù)據(jù)就是我門上面編碼回調(diào)過來的編碼后的視頻數(shù)據(jù)选酗。
  • 2、int type = (frameNALU[4] & 0x1F);:獲取該數(shù)據(jù)類型岳枷。type為7是sps, 8是pps芒填。
    ?注意:因為前4個字節(jié)是NAL Unit數(shù)據(jù)的分割碼也就是前面說的startCode(\x00\x00\x00\x01),所以這里取第5個字節(jié)的數(shù)據(jù)空繁,frameNALU[4]也就是NAL Unitheader殿衰。
    ?例如:
    0 01 00111(frameNALU[4]) & 0 00 11111(0x1F) = 0 00 00111 轉(zhuǎn)為十進(jìn)制就是7代表sps
    ?我們將header分為三部分0-01-00111
    ?第一部分占一位0代表 禁止位盛泡,用以檢查傳輸過程中是否發(fā)生錯誤闷祥,0表示正常,1表示違反語法傲诵。
    ?第二部分占兩位01 用來表示當(dāng)前NAL單元的優(yōu)先級蜀踏。非0值表示參考字段/幀/圖片數(shù)據(jù),其他不那么重要的數(shù)據(jù)則為0掰吕。對于非0值果覆,值越大表示NAL Unit重要性越高。
    ?第三部分占五位00111 指定NAL Unit類型殖熟,這就是為什么按位與運算用0 00 11111(0x1F)局待。
  • 3、當(dāng)type0x05時代表時I幀(關(guān)鍵幀)這時我們需要先創(chuàng)建解碼器菱属。0x07時創(chuàng)建sps數(shù)據(jù)钳榨。0x08時創(chuàng)建pps數(shù)據(jù)。

創(chuàng)建解碼器:

- (BOOL)createDecoder {
    if (self.decodeSesion) return YES;
    const uint8_t * const parameterSetPointers[2] = {_sps, _pps};
    const size_t parameterSetSize[2] = {_spsSize, _ppsSize};
    int lengthStartCode = 4;
    OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault, 2, parameterSetPointers, parameterSetSize, lengthStartCode, &_decoderDesc);
    if (status != noErr) {
       NSLog(@"CMVideoFormatDescriptionCreateFromH264ParameterSets error status: %d", (int)status);
        return NO;
    }
    NSDictionary *decoderAttachments =
    @{
        (id)kCVPixelBufferPixelFormatTypeKey: [NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange], //攝像頭的輸出數(shù)據(jù)格式
        (id)kCVPixelBufferWidthKey: [NSNumber numberWithInteger:self.configure.width],
        (id)kCVPixelBufferHeightKey: [NSNumber numberWithInteger:self.configure.height],
        (id)kCVPixelBufferOpenGLCompatibilityKey: [NSNumber numberWithBool:YES]
    };
    
    //解碼回調(diào)設(shè)置
    VTDecompressionOutputCallbackRecord decompressionCallback;
    decompressionCallback.decompressionOutputCallback = decoderVideoOutputCallback;
    decompressionCallback.decompressionOutputRefCon = (__bridge void * _Nullable)self;

    VTDecompressionSessionCreate(kCFAllocatorDefault, _decoderDesc, NULL, (__bridge CFDictionaryRef _Nullable)decoderAttachments, &decompressionCallback, &_decodeSesion);
    if (status != noErr) {
       NSLog(@"VTDecompressionSessionCreate error status: %d", (int)status);
        return NO;
    }
    //實時編碼
    status = VTSessionSetProperty(_decodeSesion, kVTDecompressionPropertyKey_RealTime,kCFBooleanTrue); 
    if (status != noErr) {
        NSLog(@"VTSessionSetProperty RealTime error status:%d", (int)status);
    }
    return YES;
}
  • 1纽门、CMVideoFormatDescriptionCreateFromH264ParameterSets設(shè)置解碼參數(shù):
    參數(shù)1: kCFAllocatorDefault 使用默認(rèn)的內(nèi)存分配
    參數(shù)2: 參數(shù)個數(shù)
    參數(shù)3: 參數(shù)集指針
    參數(shù)4: 參數(shù)集大小
    參數(shù)5: startCode的長度 4
    參數(shù)6: 解碼格式器描述對象
  • 2薛耻、VTDecompressionOutputCallbackRecord解碼回調(diào)設(shè)置。
  • 3赏陵、VTDecompressionSessionCreate創(chuàng)建解碼器饼齿。
    參數(shù)1: kCFAllocatorDefault 使用默認(rèn)的內(nèi)存分配。
    參數(shù)2: 解碼格式器描述對象蝙搔。
    參數(shù)3: 指定必須使用的特定視頻解碼器缕溉。NULLvideo toolbox選擇解碼器。
    參數(shù)4: 描述源像素緩沖區(qū)的要求, NULL無要求吃型。
    參數(shù)5: 已解壓縮的幀調(diào)用的回調(diào)函數(shù)证鸥。
    參數(shù)6: 指向一個變量以接收新的解壓會話。
  • 4、VTSessionSetProperty設(shè)置解碼會話屬性枉层。

解碼:

- (CVPixelBufferRef)decodeNALUFrame:(uint8_t *)frameNALU withFrameLength:(uint32_t)lengthFrame {
    CVPixelBufferRef outputPixelBuffer = NULL;
    CMBlockBufferRef blockBufferOut = NULL;
    CMBlockBufferFlags flag0 = 0;
  //1.
    OSStatus status = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, frameNALU, lengthFrame, kCFAllocatorNull, NULL, 0, lengthFrame, flag0, &blockBufferOut);
    if (status != kCMBlockBufferNoErr) {
        NSLog(@"CMBlockBufferCreateWithMemoryBlock error status:%d", (int)status);
        return outputPixelBuffer;
    }
    
    CMSampleBufferRef sampleBuffer = NULL;
    const size_t sampleSizeArray[] = {lengthFrame};
    //2.創(chuàng)建sampleBuffer
    status = CMSampleBufferCreateReady(kCFAllocatorDefault, blockBufferOut, _decoderDesc, 1, 0, NULL, 1, sampleSizeArray, &sampleBuffer);
    if (status != noErr || !sampleBuffer) {
        NSLog(@"CMSampleBufferCreateReady error status:%d", (int)status);
        CFRelease(blockBufferOut);
        return outputPixelBuffer;
    }
    
    //解碼
    VTDecodeFrameFlags decodeFrameFlags = kVTDecodeFrame_1xRealTimePlayback;
    VTDecodeInfoFlags decodeInfoFlags = kVTDecodeInfo_Asynchronous; //異步解碼
 
    status = VTDecompressionSessionDecodeFrame(_decodeSesion, sampleBuffer, decodeFrameFlags, &outputPixelBuffer, &decodeInfoFlags);
        if (status == kVTInvalidSessionErr) {
        NSLog(@"VTDecompressionSessionDecodeFrame  InvalidSessionErr status:%d", (int)status);
    } else if (status == kVTVideoDecoderBadDataErr) {
        NSLog(@"VTDecompressionSessionDecodeFrame  BadData status:%d", (int)status);
    } else if (status != noErr) {
        NSLog(@"VTDecompressionSessionDecodeFrame status:%d", (int)status);
    }
    CFRelease(sampleBuffer);
    CFRelease(blockBuffer);
    
    return outputPixelBuffer;
}
  • 1锉试、CMBlockBufferCreateWithMemoryBlock創(chuàng)建CMBlockBufferRef:
    參數(shù)1: kCFAllocatorDefault使用默認(rèn)內(nèi)存分配
    參數(shù)2: 幀的內(nèi)存塊窘奏,這里就用frameNALU
    參數(shù)3: 幀大小
    參數(shù)4: 管理 內(nèi)存塊 的分配器熄云,參數(shù)2為NULL時用于分配內(nèi)存塊席揽,參數(shù)2不為NULL時用于釋放內(nèi)存塊,kCFAllocatorNull不需要釋放內(nèi)存塊矩欠。
    參數(shù)5: 如果非空财剖,則用于內(nèi)存塊的分配和釋放(參數(shù)4blockAllocator會被忽略)。如果 參數(shù)2內(nèi)存塊 為NULL癌淮,則其Allocate()必須為非NULL躺坟。如果分配成功,將在分配內(nèi)存塊時調(diào)用一次Allocate乳蓄。釋放CMBlockBuffer時將調(diào)用Free()咪橙。
    參數(shù)6: 數(shù)據(jù)偏移量
    參數(shù)7: 數(shù)據(jù)長度
    參數(shù)8: 功能和控制標(biāo)志
    參數(shù)9: 接收新創(chuàng)建的CMBlockBuffer地址,不能為空虚倒。
  • 2美侦、CMSampleBufferCreateReady創(chuàng)建CMSampleBufferRef
    參數(shù)1: kCFAllocatorDefault使用默認(rèn)內(nèi)存分配
    參數(shù)2:需要編碼的數(shù)據(jù)blockBufferOut.不能為NULL
    參數(shù)3:視頻輸出格式
    參數(shù)4: CMSampleBuffer 個數(shù).
    參數(shù)5: 必須為0、1 或numSamples
    參數(shù)6: 數(shù)組.為空
    參數(shù)7: 必須為0魂奥、1 或numSamples, 默認(rèn)為1
    參數(shù)8: 幀大小的數(shù)組菠剩。
    參數(shù)9: 新的CMSampleBufferRef對象
  • 3、VTDecompressionSessionDecodeFrame解碼:
    參數(shù)1: 解碼會話對象耻煤。
    參數(shù)2: CMSampleBufferRef對象具壮,包含一個或多個視頻幀。
    參數(shù)3: 解碼標(biāo)志
    參數(shù)4: 解碼后數(shù)據(jù)CVPixelBufferRef哈蝇。
    參數(shù)5: 同步還是異步解碼棺妓。

解碼成功后回調(diào)函數(shù):

void decoderVideoOutputCallback(void * CM_NULLABLE decompressionOutputRefCon,
                                      void * CM_NULLABLE sourceFrameRefCon,
                                      OSStatus status,
                                      VTDecodeInfoFlags infoFlags,
                                      CM_NULLABLE CVImageBufferRef imageBuffer,
                                      CMTime presentationTimeStamp,
                                      CMTime presentationDuration ) {
    if (status != noErr) {
        NSLog(@"decoderVideoOutputCallback error status:%d", (int)status);
        return;
    }
    CVPixelBufferRef *outputPixelBuffer = (CVPixelBufferRef *)sourceFrameRefCon;
    *outputPixelBuffer = CVPixelBufferRetain(imageBuffer);
        
    CQVideoDecoder *decoder = (__bridge CQVideoDecoder *)decompressionOutputRefCon;
    dispatch_async(decoder.callbackQueue, ^{
        if ([decoder.delegate respondsToSelector:@selector(videoDecodeCallback:)]) {
            [decoder.delegate videoDecodeCallback:imageBuffer];
        }
        //釋放數(shù)據(jù)
        CVPixelBufferRelease(imageBuffer);
    });
    
}
  • 1、回調(diào)函數(shù)的參數(shù):
    參數(shù)1:回調(diào)的引用值炮赦。
    參數(shù)2:幀的引用值怜跑。
    參數(shù)3:壓縮失敗/成功的狀態(tài)碼。
    參數(shù)4:如果設(shè)置了kVTDecodeInfo_Asynchronous表示異步解碼吠勘,
    如果設(shè)置了kVTDecodeInfo_FrameDropped可以丟幀性芬,
    如果設(shè)置了kVTDecodeInfo_ImageBufferModifiable可以安全地修改imageBuffer(實際圖像的緩沖).
    參數(shù)5:實際圖像的緩沖。如果未設(shè)置kVTDecodeInfo_ImageBufferModifiable標(biāo)志看幼,則視頻解壓縮器可能仍在引用此回調(diào)中返回的imageBuffer批旺,此時修改返回的imageBuffer是不安全的。
    參數(shù)6:幀的時間戳诵姜。
    參數(shù)7:幀的持續(xù)時間。

  • 2、將指針*outputPixelBuffer指向?qū)嶋H圖像緩沖區(qū)imageBuffer

  • 3棚唆、將圖像緩沖區(qū)imageBuffer回調(diào)出去用來展示暇赤。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市宵凌,隨后出現(xiàn)的幾起案子鞋囊,更是在濱河造成了極大的恐慌,老刑警劉巖瞎惫,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件溜腐,死亡現(xiàn)場離奇詭異,居然都是意外死亡瓜喇,警方通過查閱死者的電腦和手機挺益,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來乘寒,“玉大人望众,你說我怎么就攤上這事∩⌒粒” “怎么了烂翰?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蚤氏。 經(jīng)常有香客問我甘耿,道長,這世上最難降的妖魔是什么竿滨? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任佳恬,我火速辦了婚禮,結(jié)果婚禮上姐呐,老公的妹妹穿的比我還像新娘殿怜。我一直安慰自己,他們只是感情好曙砂,可當(dāng)我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布头谜。 她就那樣靜靜地躺著,像睡著了一般鸠澈。 火紅的嫁衣襯著肌膚如雪柱告。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天笑陈,我揣著相機與錄音际度,去河邊找鬼。 笑死涵妥,一個胖子當(dāng)著我的面吹牛乖菱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼窒所,長吁一口氣:“原來是場噩夢啊……” “哼鹉勒!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起吵取,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤禽额,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后皮官,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體脯倒,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年捺氢,在試婚紗的時候發(fā)現(xiàn)自己被綠了藻丢。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡讯沈,死狀恐怖郁岩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情缺狠,我是刑警寧澤问慎,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站挤茄,受9級特大地震影響如叼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜穷劈,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一笼恰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧歇终,春花似錦社证、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至奕短,卻和暖如春宜肉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背翎碑。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工谬返, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人日杈。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓遣铝,卻偏偏與公主長得像佑刷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子翰蠢,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,722評論 2 345

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