iOS 視頻硬編碼H264/H265

視頻采集

視頻采集部分,上一篇文章已經(jīng)提及
iOS視頻采集

視頻編碼

首先初始化編碼器

- (void)initEncoder{
    //視頻寬、高、幀率、碼率
    int width = 1280, height = 720, FPS = 25, bitrate = 2048;
    
    //默認(rèn)H264編碼,H265編碼需要iOS 11
    encoderType = kCMVideoCodecType_H264;
    
    //    if (@available(iOS 11.0, *)) {
    //        if ([[AVAssetExportSession allExportPresets] containsObject:AVAssetExportPresetHEVCHighestQuality]){
    //            ///是否支持H265
    //            encoderType = kCMVideoCodecType_HEVC;
    //        }
    //    }
    
    lock = [[NSLock alloc] init];
    
    [lock lock];
    
    //創(chuàng)建session
    VTCompressionSessionRef session;
    OSStatus status = VTCompressionSessionCreate(NULL,
                                                 width,
                                                 height,
                                                 encoderType,
                                                 NULL,
                                                 NULL,
                                                 NULL,
                                                 videoEncoderCallBack,
                                                 (__bridge void*)self,
                                                 &session);
    if (status == noErr) {
        
    }else{
        NSLog(@"創(chuàng)建session失敗");
    }
    
    ///設(shè)置session屬性
    // 設(shè)置實(shí)時(shí)編碼輸出(避免延遲)
    status = VTSessionSetProperty(session, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue);
    
    if (encoderType == kCMVideoCodecType_H264) {
        if ((YES)/*支持實(shí)時(shí)編碼*/) {
            status = VTSessionSetProperty(session, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_Main_AutoLevel);
        }else{
            //表示使用H264的Profile規(guī)格,可以設(shè)置Hight的AutoLevel規(guī)格
            status = VTSessionSetProperty(session, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_Baseline_AutoLevel);
            //若是支持h264該屬性設(shè)置編碼器是否應(yīng)該使用基于CAVLC 或是 CABAC
            status = VTSessionSetProperty(session, kVTCompressionPropertyKey_H264EntropyMode, kVTH264EntropyMode_CAVLC);
        }
        
    }else if (encoderType == kCMVideoCodecType_HEVC){
        ///H265
        status = VTSessionSetProperty(session, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_HEVC_Main_AutoLevel);
    }
    
    // 設(shè)置關(guān)鍵幀(GOPsize)間隔
    int frameInterval = 10;
    CFNumberRef  frameIntervalRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &frameInterval);
    status = VTSessionSetProperty(session, kVTCompressionPropertyKey_MaxKeyFrameInterval, frameIntervalRef);
    
    // 設(shè)置期望幀率
    CFNumberRef  fpsRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &FPS);
    status = VTSessionSetProperty(session, kVTCompressionPropertyKey_ExpectedFrameRate, fpsRef);
    
    //設(shè)置碼率,上限,單位是bps
    int bitRate = width * height * 3 * 4 * 8;
    CFNumberRef bitRateRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &bitRate);
    status =  VTSessionSetProperty(session, kVTCompressionPropertyKey_AverageBitRate, bitRateRef);
    
    //設(shè)置碼率,均值,單位是byte
    int bitRateLimit = width * height * 3 * 4;
    CFNumberRef bitRateLimitRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &bitRateLimit);
    status = VTSessionSetProperty(session, kVTCompressionPropertyKey_DataRateLimits, bitRateLimitRef);
    
    //告訴編碼器開始編碼
    status = VTCompressionSessionPrepareToEncodeFrames(session);
    
    [lock unlock];
    
    if (status != noErr) {
        NSLog(@"失敗,需要排查問題和參數(shù)");
    }else{
        videoSession = session;
    }
    
}

傳入需要編碼的數(shù)據(jù)

/// 傳入需要編碼的數(shù)據(jù)進(jìn)行編碼
/// @param sampleBuffer 需要編碼的原始數(shù)據(jù)
/// @param isNeedFreeBuffer 是否需要釋放,如果自己組裝的,需要手動(dòng)釋放,系統(tǒng)返回的不需要
- (void)startEncodeWithSampleBuffer:(CMSampleBufferRef)sampleBuffer isNeedFreeBuffer:(BOOL)isNeedFreeBuffer{
    [lock lock];
    //第一幀必須是iframe,然后創(chuàng)建引用的時(shí)間戳
    static BOOL isFirstFrame = YES;
    if (isFirstFrame && encode_capture_base_time == 0) {
        CMTime pts = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
        encode_capture_base_time = CMTimeGetSeconds(pts);
        isFirstFrame = NO;
    }
    
    CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    CMTime presentationTimeStamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
    // 切換不同源數(shù)據(jù)會(huì)顯示馬賽克,因?yàn)闀r(shí)間戳不同步
    static int64_t lastPts = 0;
    int64_t currentPts = (int64_t)(CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(sampleBuffer)) * 1000);
    lastPts = currentPts;
    
    OSStatus status = noErr;
    
    //如果編碼中途進(jìn)行其他操作,這時(shí)候強(qiáng)制添加關(guān)鍵幀
    BOOL needForceInsertKeyFrame = NO;//是否需要強(qiáng)制插入關(guān)鍵幀
    NSDictionary *properties = @{(__bridge NSString *)kVTEncodeFrameOptionKey_ForceKeyFrame:@(needForceInsertKeyFrame)};
    //編碼
    status = VTCompressionSessionEncodeFrame(videoSession,
                                             imageBuffer,
                                             presentationTimeStamp,
                                             kCMTimeInvalid,
                                             (__bridge CFDictionaryRef)properties,
                                             NULL,
                                             NULL);
    if (status != noErr) {
        [self handleEncodeFailedWithIsNeedFreeBuffer:isNeedFreeBuffer sampleBuffer:sampleBuffer];
    }
    [lock unlock];
    
    if (isNeedFreeBuffer) {
        if (sampleBuffer != NULL) {
            CFRelease(sampleBuffer);
        }
    }
}

編碼回調(diào)

#pragma mark - callback -
static void videoEncoderCallBack(void *outputCallbackRefCon,
                                 void *souceFrameRefCon,
                                 OSStatus status,
                                 VTEncodeInfoFlags infoFlags,
                                 CMSampleBufferRef sampleBuffer) {
    (當(dāng)前類)*encoder = (__bridge (當(dāng)前類)*)outputCallbackRefCon;
    if (status != noErr) {
        NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
        NSLog(@"編碼返回失敗 %@", error);
        return;
    }
    CMBlockBufferRef block = CMSampleBufferGetDataBuffer(sampleBuffer);
    CMTime pts = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
    CMTime dts = CMSampleBufferGetDecodeTimeStamp(sampleBuffer);
    
    // 利用我們定義的時(shí)間。(此時(shí)間用于同步音頻和視頻)
    int64_t ptsAfter = (int64_t)((CMTimeGetSeconds(pts) - encode_capture_base_time) * 1000);
    int64_t dtsAfter = (int64_t)((CMTimeGetSeconds(dts) - encode_capture_base_time) * 1000);
    dtsAfter = ptsAfter;
    
    /*有時(shí)相對(duì)DTS為零,提供一個(gè)恢復(fù)DTS的工作區(qū)*/
    static int64_t last_dts = 0;
    if(dtsAfter == 0){
        dtsAfter = last_dts +33;
    }else if (dtsAfter == last_dts){
        dtsAfter = dtsAfter + 1;
    }
    
    
    BOOL isKeyFrame = NO;
    CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, false);
    if(attachments != NULL) {
        CFDictionaryRef attachment =(CFDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);
        CFBooleanRef dependsOnOthers = (CFBooleanRef)CFDictionaryGetValue(attachment, kCMSampleAttachmentKey_DependsOnOthers);
        isKeyFrame = (dependsOnOthers == kCFBooleanFalse);
    }
    
    if(isKeyFrame) {
        static uint8_t *keyParameterSetBuffer    = NULL;
        static size_t  keyParameterSetBufferSize = 0;
        
        // 注意:如果視頻分辨率不改變,NALU頭不會(huì)改變。
        if (keyParameterSetBufferSize == 0 || YES == encoder->needResetKeyParamSetBuffer) {
            const uint8_t  *vps, *sps, *pps;
            size_t         vpsSize, spsSize, ppsSize;
            int            NALUnitHeaderLengthOut;
            size_t         parmCount;
            
            if (keyParameterSetBuffer != NULL) {
                free(keyParameterSetBuffer);
            }
            
            CMFormatDescriptionRef format = CMSampleBufferGetFormatDescription(sampleBuffer);
            if (encoder->encoderType == kCMVideoCodecType_H264) {
                CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 0, &sps, &spsSize, &parmCount, &NALUnitHeaderLengthOut);
                CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 1, &pps, &ppsSize, &parmCount, &NALUnitHeaderLengthOut);
                
                keyParameterSetBufferSize = spsSize+4+ppsSize+4;
                keyParameterSetBuffer = (uint8_t*)malloc(keyParameterSetBufferSize);
                memcpy(keyParameterSetBuffer, "\x00\x00\x00\x01", 4);
                memcpy(&keyParameterSetBuffer[4], sps, spsSize);
                memcpy(&keyParameterSetBuffer[4+spsSize], "\x00\x00\x00\x01", 4);
                memcpy(&keyParameterSetBuffer[4+spsSize+4], pps, ppsSize);
                
                NSLog(@ "H264 find IDR frame, spsSize : %zu, ppsSize : %zu",spsSize, ppsSize);
            }else if (encoder->encoderType == kCMVideoCodecType_HEVC) {
                CMVideoFormatDescriptionGetHEVCParameterSetAtIndex(format, 0, &vps, &vpsSize, &parmCount, &NALUnitHeaderLengthOut);
                CMVideoFormatDescriptionGetHEVCParameterSetAtIndex(format, 1, &sps, &spsSize, &parmCount, &NALUnitHeaderLengthOut);
                CMVideoFormatDescriptionGetHEVCParameterSetAtIndex(format, 2, &pps, &ppsSize, &parmCount, &NALUnitHeaderLengthOut);
                
                keyParameterSetBufferSize = vpsSize+4+spsSize+4+ppsSize+4;
                keyParameterSetBuffer = (uint8_t*)malloc(keyParameterSetBufferSize);
                memcpy(keyParameterSetBuffer, "\x00\x00\x00\x01", 4);
                memcpy(&keyParameterSetBuffer[4], vps, vpsSize);
                memcpy(&keyParameterSetBuffer[4+vpsSize], "\x00\x00\x00\x01", 4);
                memcpy(&keyParameterSetBuffer[4+vpsSize+4], sps, spsSize);
                memcpy(&keyParameterSetBuffer[4+vpsSize+4+spsSize], "\x00\x00\x00\x01", 4);
                memcpy(&keyParameterSetBuffer[4+vpsSize+4+spsSize+4], pps, ppsSize);
                NSLog(@ "H265 find IDR frame, vpsSize : %zu, spsSize : %zu, ppsSize : %zu",vpsSize,spsSize, ppsSize);
            }
            
            encoder->needResetKeyParamSetBuffer = NO;
        }
        //        是否是關(guān)鍵幀 NO
        //        是否是額外數(shù)據(jù) YES
        //        keyParameterSetBuffer 就是編碼后的數(shù)據(jù)
        //        keyParameterSetBufferSize 就是編碼后數(shù)據(jù)的size
        //        dtsAfter timeStamp
        
        //        回調(diào)數(shù)據(jù)的地方 (2-1)
        NSLog(@"視頻編碼,加載 I 幀");
    }
    
    size_t   blockBufferLength;
    uint8_t  *bufferDataPointer = NULL;
    CMBlockBufferGetDataPointer(block, 0, NULL, &blockBufferLength, (char **)&bufferDataPointer);
    
    size_t bufferOffset = 0;
    while (bufferOffset < blockBufferLength - kStartCodeLength)
    {
        uint32_t NALUnitLength = 0;
        memcpy(&NALUnitLength, bufferDataPointer+bufferOffset, kStartCodeLength);
        NALUnitLength = CFSwapInt32BigToHost(NALUnitLength);
        memcpy(bufferDataPointer+bufferOffset, kStartCode, kStartCodeLength);
        bufferOffset += kStartCodeLength + NALUnitLength;
    }
    //    是否是關(guān)鍵幀 isKeyFrame
    //    是否是額外數(shù)據(jù) NO
    //    bufferDataPointer 就是編碼后的數(shù)據(jù)
    //    blockBufferLength 就是編碼后數(shù)據(jù)的size
    //    dtsAfter timeStamp
    
    //    回調(diào)數(shù)據(jù)的地方 (2-2)
    
    last_dts = dtsAfter;
}

其他參數(shù):

{
    VTCompressionSessionRef videoSession;
    NSLock *lock;
    ///是否需要重置
    BOOL needResetKeyParamSetBuffer;
    ///編碼方式
    CMVideoCodecType encoderType;
}
static const size_t  kStartCodeLength           = 4;
static const uint8_t kStartCode[]               = {0x00, 0x00, 0x00, 0x01};
uint32_t             encode_capture_base_time   = 0;

Demo地址整理后奉上。
有其他不明白的,可以留言,看到就會(huì)回復(fù)。
如果喜歡,請(qǐng)幫忙點(diǎn)贊。支持轉(zhuǎn)載,轉(zhuǎn)載請(qǐng)附原文鏈接。

?著作權(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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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