VideoToolBox 解碼H265

上一篇文章介紹了如何編碼H265欢瞪,這篇主要介紹如果通過VideoToolBox解碼H265愕把,以及如何在只有裸流的情況區(qū)別H265H264次兆;

maxresdefault.jpeg

首先拿到一個(gè)H265的裸流之后器贩,我們已經(jīng)知道保存的這種裸流格式是Annex-b格式的纽帖;

解碼的步驟如下:

1.讀取媒體文件找到第一個(gè)StartCode(0x00 00 00 01 / 0x00 00 01)抡四;
2.找到第一個(gè)StartCode后柜蜈,找到緊挨著的第二個(gè)StartCode,提取NALU Unit
3.根據(jù)H265 Header的結(jié)構(gòu)解析該NALU是什么類型 從而找到 VPS/SPS/PPS 以及P幀/I幀
4.通過VPS/SPS/PPS 創(chuàng)建編碼器指巡,將第一幀為I幀送入編碼器淑履,后續(xù)將B/P幀送入編碼器 得到解碼后的pixelBuffer

以上解析邏輯和關(guān)鍵代碼,如下代碼中均有體現(xiàn)藻雪,覺得不夠直觀的可以直接看demo源碼;

解析裸流文件

判斷是否是 0x00 00 01 / 0x00 00 00 01 起始碼秘噪,及起始碼長度

/// 判斷是否是 00 00 00 01 \ 00 00 01
static BOOL isNaluStartCode(unsigned char *data) {
    BOOL isStartCode = (memcmp(data, startCode4, 4) == 0) || (memcmp(data, startCode3, 3) == 0);
    return isStartCode;
}

/// 獲取startCode 長度
static int getNaluStartCodeLength(unsigned char *data) {
    BOOL isStartCode = (memcmp(data, startCode4, 4) == 0) || (memcmp(data, startCode3, 3) == 0);
    if (!isStartCode) return 0;
    int nalu_startcode_size = 0;
    if (memcmp(data, startCode4, 4) == 0) {
        nalu_startcode_size = sizeof(startCode4);
    } else if (memcmp(data, startCode3, 3) == 0) {
        nalu_startcode_size = sizeof(startCode3);
    } else {
        //do nothing
    }
    return nalu_startcode_size;
}

定時(shí)器循環(huán)解析文件內(nèi)的 NALU Unit

- (void)tick {
    
    dispatch_sync(_decodeQueue, ^{
        packetSize = 0;
        if (packetBuffer) {
            free(packetBuffer);
            packetBuffer = NULL;
        }
    
        BOOL isStartCode = isNaluStartCode(_inputBuffer);
        unsigned int nalu_startcode_size = getNaluStartCodeLength(_inputBuffer);
    
        if (isStartCode && (_inputSize > nalu_startcode_size)) {
            
            uint8_t *pStart = _inputBuffer + nalu_startcode_size;         //pStart 表示 NALU 的起始指針
            uint8_t *pEnd = _inputBuffer + _inputSize;                    //pEnd 表示 NALU 的末尾指針
            while (pStart != pEnd) {
                
                BOOL isNextStartCode  = isNaluStartCode(pStart);
                if (isNextStartCode) {
                    
                    packetSize = (pStart - _inputBuffer);
                    packetBuffer = malloc(packetSize);
                    memcpy(packetBuffer, _inputBuffer, packetSize); //復(fù)制packet內(nèi)容到新的緩沖區(qū)
                    memmove(_inputBuffer, _inputBuffer + packetSize, _inputSize - packetSize); //把緩沖區(qū)前移
                    _inputSize -= packetSize;
                    
                    if (nalu_startcode_size == 3) {
                    /// 額外處理 startCode == 00 00 01 情況
                        long newPacketSize = packetSize + 1;
                        uint8_t *newPacketBuffer = malloc(newPacketSize);
                        memset(newPacketBuffer, 0, sizeof(newPacketSize));
                        memcpy(newPacketBuffer + 1, packetBuffer , packetSize);
                        free(packetBuffer);
                        packetBuffer = newPacketBuffer;
                        packetSize = newPacketSize;
                        
                    }
                    break;
                }
                else {
                    ++pStart;
                }
            }
            
            if ((pStart == pEnd) && (_inputSize > sizeof(startCode4)) && (packetSize == 0)  && (packetBuffer == NULL)) {
                packetSize = _inputSize;//pStart - _inputBuffer - 3;
                packetBuffer = malloc(packetSize);
                memcpy(packetBuffer, _inputBuffer, packetSize);
                memmove(_inputBuffer, _inputBuffer + packetSize, _inputSize - packetSize); //把緩沖區(qū)前移
                _inputSize -= packetSize;
            }
            
        }
        if (packetBuffer == NULL || packetSize == 0) {
            [self endDecode];
            return;
        }
        //2.將packet的前4個(gè)字節(jié)換成大端的長度 (有可能startCode是 00000001 / 000001兩種情況 都需要處理)
        uint32_t nalSize = (uint32_t)(packetSize - 4);
        uint8_t *pNalSize = (uint8_t*)(&nalSize);
        packetBuffer[0] = pNalSize[3];
        packetBuffer[1] = pNalSize[2];
        packetBuffer[2] = pNalSize[1];
        packetBuffer[3] = pNalSize[0];
       
        //3.判斷幀類型(根據(jù)碼流結(jié)構(gòu)可知,startcode后面緊跟著就是碼流的類型)
        int nalType = (packetBuffer[4] & 0x7E) >> 1;
        switch (nalType) {
            case 0x10:
            case 0x11:
            case 0x12:
            case 0x13:
            case 0x14:
            case 0x15:
                {
                    //IDR frame
                    [self _initDecodeSession];
                    [self decodePacket];
                }
                break;
            case 0x27:
                {
                    //SEI
                }
                break;
            case 0x20:
                {
                    //vps
                    if (_vps) { _vps = nil;}
                    size_t vpsSize = (size_t) packetSize - 4;
                    uint8_t *vps = malloc(vpsSize);
                    memcpy(vps, packetBuffer + 4, vpsSize);
                    _vps = [NSData dataWithBytes:vps length:vpsSize];
                    free(vps);
                }
                break;
            case 0x21:
                {
                    //sps
                    if (_sps) { _sps = nil;}
                    size_t spsSize = (size_t) packetSize - 4;
                    uint8_t *sps = malloc(spsSize);
                    memcpy(sps, packetBuffer + 4, spsSize);
                    _sps = [NSData dataWithBytes:sps length:spsSize];
                    free(sps);
                }
                break;
            case 0x22:
                {
                    //pps
                    if (_pps) { _pps = nil; }
                    size_t ppsSize = (size_t) packetSize - 4;
                    uint8_t *pps = malloc(ppsSize);
                    memcpy(pps, packetBuffer + 4, ppsSize);
                    _pps = [NSData dataWithBytes:pps length:ppsSize];
                    free(pps);
                }
                break;
            default:
                {
                    // B/P frame
                    [self decodePacket];
                }
                break;
        }
    });

}

獲取VPS/SPS/PPS 創(chuàng)建編碼器

//2.將packet的前4個(gè)字節(jié)換成大端的長度 (有可能startCode是 00000001 / 000001兩種情況 都需要處理)
        uint32_t nalSize = (uint32_t)(packetSize - 4);
        uint8_t *pNalSize = (uint8_t*)(&nalSize);
        packetBuffer[0] = pNalSize[3];
        packetBuffer[1] = pNalSize[2];
        packetBuffer[2] = pNalSize[1];
        packetBuffer[3] = pNalSize[0];
       
        //3.判斷幀類型(根據(jù)碼流結(jié)構(gòu)可知勉耀,startcode后面緊跟著就是碼流的類型)
        int nalType = (packetBuffer[4] & 0x7E) >> 1;
        switch (nalType) {
            case 0x10:
            case 0x11:
            case 0x12:
            case 0x13:
            case 0x14:
            case 0x15:
                {
                    //IDR frame
                    [self _initDecodeSession];
                    [self decodePacket];
                }
                break;
            case 0x27:
                {
                    //SEI
                }
                break;
            case 0x20:
                {
                    //vps
                    if (_vps) { _vps = nil;}
                    size_t vpsSize = (size_t) packetSize - 4;
                    uint8_t *vps = malloc(vpsSize);
                    memcpy(vps, packetBuffer + 4, vpsSize);
                    _vps = [NSData dataWithBytes:vps length:vpsSize];
                    free(vps);
                }
                break;
            case 0x21:
                {
                    //sps
                    if (_sps) { _sps = nil;}
                    size_t spsSize = (size_t) packetSize - 4;
                    uint8_t *sps = malloc(spsSize);
                    memcpy(sps, packetBuffer + 4, spsSize);
                    _sps = [NSData dataWithBytes:sps length:spsSize];
                    free(sps);
                }
                break;
            case 0x22:
                {
                    //pps
                    if (_pps) { _pps = nil; }
                    size_t ppsSize = (size_t) packetSize - 4;
                    uint8_t *pps = malloc(ppsSize);
                    memcpy(pps, packetBuffer + 4, ppsSize);
                    _pps = [NSData dataWithBytes:pps length:ppsSize];
                    free(pps);
                }
                break;
            default:
                {
                    // B/P frame
                    [self decodePacket];
                }
                break;
        }
    });

通過VPS/SPS/PPS 創(chuàng)建H265類型VideoToolbox

-(void)initVideoToolBox {
    
    if (_decodeSession) {
        return;
    }
    
    CMFormatDescriptionRef formatDescriptionOut;
    const uint8_t * const param[3] = {_vps.bytes,_sps.bytes,_pps.bytes};
    const size_t paramSize[3] = {_vps.length,_sps.length,_pps.length};
    OSStatus formateStatus =
    CMVideoFormatDescriptionCreateFromHEVCParameterSets(kCFAllocatorDefault, 3, param, paramSize, 4, NULL, &formatDescriptionOut);
    _formatDescriptionOut = formatDescriptionOut;
    
    if (formateStatus!=noErr) {
        NSLog(@"FormatDescriptionCreate fail");
        return;
    }
    //2. 創(chuàng)建VTDecompressionSessionRef
    //確定編碼格式
    const void *keys[] = {kCVPixelBufferPixelFormatTypeKey};
    
    uint32_t t = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange;
    const void *values[] = {CFNumberCreate(NULL, kCFNumberSInt32Type, &t)};
    
    CFDictionaryRef att = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
    
    VTDecompressionOutputCallbackRecord VTDecompressionOutputCallbackRecord;
    VTDecompressionOutputCallbackRecord.decompressionOutputCallback = decodeCompressionOutputCallback;
    VTDecompressionOutputCallbackRecord.decompressionOutputRefCon = (__bridge void * _Nullable)(self);
    
    OSStatus sessionStatus = VTDecompressionSessionCreate(NULL,
                                 formatDescriptionOut,
                                 NULL,
                                 att,
                                 &VTDecompressionOutputCallbackRecord,
                                 &_decodeSession);
    CFRelease(att);
    if (sessionStatus != noErr) {
        NSLog(@"SessionCreate fail");
        [self endDecode];
    }
}

解碼H265類型 NALU

如何辨別裸流是 H265 還是H264

探針的作用是試探當(dāng)前的數(shù)據(jù)流是否是某一協(xié)議/編碼格式指煎,一般方法是進(jìn)行部分預(yù)解析/解碼蹋偏,觀察是否滿足指定協(xié)議/編碼的格式要求

以下代碼參考FFMPEG 源碼

/// 判斷是否是 HEVC
static int hevc_probe(unsigned char *buf, unsigned int length)
{
   uint32_t code = -1;
   int vps = 0, sps = 0, pps = 0, irap = 0;
   int i;

   for (i = 0; i < length - 1; i++) {
       code = (code << 8) + buf[i];
       if ((code & 0xffffff00) == 0x100) {
           uint8_t nal2 = buf[i + 1];
           int type = (code & 0x7E) >> 1;

           if (code & 0x81) // forbidden and reserved zero bits
               return 0;

           if (nal2 & 0xf8) // reserved zero
               return 0;

           switch (type) {
           case HEVC_NAL_VPS:        vps++;  break;
           case HEVC_NAL_SPS:        sps++;  break;
           case HEVC_NAL_PPS:        pps++;  break;
           case HEVC_NAL_BLA_N_LP:
           case HEVC_NAL_BLA_W_LP:
           case HEVC_NAL_BLA_W_RADL:
           case HEVC_NAL_CRA_NUT:
           case HEVC_NAL_IDR_N_LP:
           case HEVC_NAL_IDR_W_RADL: irap++; break;
           }
       }
   }

   if (vps && sps && pps && irap)
       return  1; // 1 more than .mpg
   return 0;
}

源碼地址 源碼地址: https://github.com/hunter858/OpenGL_Study/AVFoundation/VideoToolBox-decoderH265

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市至壤,隨后出現(xiàn)的幾起案子威始,更是在濱河造成了極大的恐慌,老刑警劉巖像街,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件黎棠,死亡現(xiàn)場離奇詭異,居然都是意外死亡镰绎,警方通過查閱死者的電腦和手機(jī)脓斩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來畴栖,“玉大人随静,你說我怎么就攤上這事÷鹧龋” “怎么了燎猛?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長关翎。 經(jīng)常有香客問我扛门,道長,這世上最難降的妖魔是什么纵寝? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任论寨,我火速辦了婚禮,結(jié)果婚禮上爽茴,老公的妹妹穿的比我還像新娘葬凳。我一直安慰自己,他們只是感情好室奏,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布火焰。 她就那樣靜靜地躺著,像睡著了一般胧沫。 火紅的嫁衣襯著肌膚如雪昌简。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天绒怨,我揣著相機(jī)與錄音纯赎,去河邊找鬼。 笑死南蹂,一個(gè)胖子當(dāng)著我的面吹牛犬金,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼晚顷,長吁一口氣:“原來是場噩夢啊……” “哼峰伙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起该默,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤瞳氓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后栓袖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體顿膨,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年叽赊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片必搞。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡必指,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出恕洲,到底是詐尸還是另有隱情塔橡,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布霜第,位于F島的核電站葛家,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏泌类。R本人自食惡果不足惜癞谒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望刃榨。 院中可真熱鬧弹砚,春花似錦、人聲如沸枢希。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽苞轿。三九已至茅诱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間搬卒,已是汗流浹背瑟俭。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留秀睛,地道東北人尔当。 一個(gè)月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親椭迎。 傳聞我的和親對象是個(gè)殘疾皇子锐帜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

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