天天看點

H264碼流分析導讀

導讀

H.264碼流結構解析

H.264編碼格式

H.264的功能分為兩層:視訊編碼層(VCL, Video Coding Layer)和網絡提取層(NAL, Network Abstraction Layer)

VCL資料即編碼處理的輸出,它表示被壓縮編碼後的視訊資料序列。在VCL資料傳輸或存儲之前,這些編碼的VCL資料,先被映射或封裝進NAL單元中。每個NAL單元包括一個原始位元組序列負荷(RBSP, Raw Byte Sequence Payload)、一組對應于視訊編碼的NAL頭資訊。RBSP的基本結構是:在原始編碼資料的後面填加了結尾比特。一個bit“1”若幹比特“0”,以便位元組對齊。

H264碼流分析導讀

image.png

H.264傳輸

H.264的編碼視訊序列包括一系列的NAL單元,每個NAL單元包含一個RBSP,見表1。編碼片(包括資料分割片IDR片)和序列RBSP結束符被定義為VCL NAL單元,其餘為NAL單元。典型的RBSP單元序列如圖2所示。每個單元都按獨立的NAL單元傳送。單元的資訊頭(一個位元組)定義了RBSP單元的類型,NAL單元的其餘部分為RBSP資料。

H264碼流分析導讀

image.png

H264碼流分析導讀

image.png

  1. H.264碼流結構圖
H264碼流分析導讀

image.png

起始碼:如果NALU對應的Slice為一幀的開始,則用4位元組表示,即0x00000001;否則用3位元組表示,0x000001。

NAL Header:forbidden_bit,nal_reference_bit(優先級),nal_unit_type(類型)。

脫殼操作:為了使NALU主體不包括起始碼,在編碼時每遇到兩個位元組(連續)的0,就插入一位元組0x03,以和起始碼相差別。解碼時,則将相應的0x03删除掉。

H264碼流分析導讀

image.png

  1. H.264解碼

    NAL頭資訊的nal_referrence_idc(NRI)用于在重建過程中标記一個NAL單元的重要性,值為0表示這個NAL單元沒有用預測,是以可以被解碼器抛棄而不會有錯誤擴散;值高于0表示NAL單元要用于無漂移重構,且值越高,對此NAL單元丢失的影響越大。

    NAL頭資訊的隐藏比特位,在H.264編碼器中預設為0,當網絡識别到單元中存在比特錯誤時,可将其置為1。隐藏比特位主要用于适應不同種類的網絡環境(比如有線無線相結合的環境)。

H264碼流分析導讀

image.png

NAL單元解碼的流程為:首先從NAL單元中提取出RBSP文法結構,然後按照如圖4所示的流程處理RBSP文法結構。輸入的是NAL單元,輸出結果是經過解碼的目前圖像的樣值點。

NAL單元中分别包含了序列參數集和圖像參數集。圖像參數集和序列參數集在其他NAL單元傳輸過程中作為參考使用,在這些資料NAL單元的片頭中,通過文法元素pic_parameter_set_id設定它們所使用的圖像參數集編号;而相應的每個圖像參數集中,通過文法元素seq_paramter_set_id設定他們使用的序列參數集編号。

示例分析

結合laifeng_Android中的AnnexbHelper來簡單的看看,如果進行解析。

Android寫死得到的H264是

Annexb

這種格式的。

  • 如何擷取NAUL單元

    其實就是找開頭為 0x0001或者0x000001的位元組段

/**
     * 從硬編出來的byteBuffer中查找nal
     * @param as
     * @param bb
     * @param bi
     */
    private void avcStartWithAnnexb(AnnexbSearch as, ByteBuffer bb, MediaCodec.BufferInfo bi) {
        as.match = false;
        as.startCode = 0;
        int pos = bb.position();
        while (pos < bi.offset + bi.size - 3) {
            // not match.
            if (bb.get(pos) != 0x00 || bb.get(pos + 1) != 0x00) {
                break;
            }

            // match N[00] 00 00 01, where N>=0
            if (bb.get(pos + 2) == 0x01) {
                as.match = true;
                as.startCode = pos + 3 - bb.position();
                break;
            }
            pos++;
        }
    }           

複制

  • 根據

    NAL Header

    内的nal_unit_type判斷目前幀的類型

    根據上面的類型分析,我們知道NAUL裡面前8位中的後5位,表示這個NAUL的類型。

    根據這些可以判斷類型。

    而剩下的就是NAUL内的資料了。

private boolean isSps(byte[] frame) {
        if (frame.length < 1) {
            return false;
        }
        // 5bits, 7.3.1 NAL unit syntax,
        // H.264-AVC-ISO_IEC_14496-10.pdf, page 44.
        //  7: SPS, 8: PPS, 5: I Frame, 1: P Frame
        int nal_unit_type = (frame[0] & 0x1f);
        return nal_unit_type == SPS;
    }

    private boolean isPps(byte[] frame) {
        if (frame.length < 1) {
            return false;
        }
        // 5bits, 7.3.1 NAL unit syntax,
        // H.264-AVC-ISO_IEC_14496-10.pdf, page 44.
        //  7: SPS, 8: PPS, 5: I Frame, 1: P Frame
        int nal_unit_type = (frame[0] & 0x1f);
        return nal_unit_type == PPS;
    }

    private boolean isKeyFrame(byte[] frame) {
        if (frame.length < 1) {
            return false;
        }
        // 5bits, 7.3.1 NAL unit syntax,
        // H.264-AVC-ISO_IEC_14496-10.pdf, page 44.
        //  7: SPS, 8: PPS, 5: I Frame, 1: P Frame
        int nal_unit_type = (frame[0] & 0x1f);
        return nal_unit_type == IDR;
    }

    private static boolean isAccessUnitDelimiter(byte[] frame) {
        if (frame.length < 1) {
            return false;
        }
        // 5bits, 7.3.1 NAL unit syntax,
        // H.264-AVC-ISO_IEC_14496-10.pdf, page 44.
        //  7: SPS, 8: PPS, 5: I Frame, 1: P Frame
        int nal_unit_type = (frame[0] & 0x1f);
        return nal_unit_type == AccessUnitDelimiter;
    }           

複制