Android音視頻開發(fā)——SPS分析與提取

前言

H.264碼流中的NALU進行了一個簡單的劃分础倍,標出了NALU的類型和長度等信息童社。因為我們在解析SPS和PPS中要使用到指數(shù)哥倫布編碼的解析,所以有必要了解一下指數(shù)哥倫布編碼著隆。

哥倫布編碼

指數(shù)哥倫布碼正常來說,可以拓展位k階呀癣,但是在H264中使用的是0階指數(shù)哥倫布編碼美浦,在H.264中使用ue(v)表示0階無符號指數(shù)哥倫布編碼的解碼過程,用se(v)表示0階有符號指數(shù)哥倫布編碼過程

無符號指數(shù)哥倫布編碼

用來表示無符號整數(shù)k階指數(shù)哥倫布編碼的生成步驟如下:

(1)將數(shù)字以二進制形式寫出项栏,去掉最低位的k個比特位浦辨,之后加1

(2)計算留下的比特數(shù)位數(shù),將此數(shù)減1沼沈,即是需要增加的前導0的個數(shù)

(3)將第一步中去掉的最低個比特位補回到比特串尾部

0階無符號指數(shù)哥倫布編碼過程

0階無符號指數(shù)哥倫布編碼最后生成的比特串格式為"前綴1后綴"流酬,前綴和后綴的長度是相同的。

假如我們待編碼的數(shù)字codeNum = 4列另,0階無符號指數(shù)哥倫布編碼的步驟如下:

(1)將數(shù)字以二進制寫出芽腾,4的二進制為100,因為0階指數(shù)哥倫布編碼所有页衙,所以不用去掉低位

(2)將上面的二進制+1摊滔,100加1為101,留下的比特數(shù)為3店乐,3-1=2艰躺,所以需要增加前導0的個數(shù)為2

(3)因為第一步?jīng)]有去掉,所有這一步不進行任何操作眨八,最終生成的比特串為00101

下面對不同codeNum進行編碼結(jié)果

codeNum codeNum+1 codeNum+1的二進制 需補前綴0的個數(shù) 編碼后的比特串(紅色表示補的前綴0)
0 1 1 0 1
1 2 10 1(0) 010
2 3 11 1(0) 011
3 4 100 2(00) 00100
4 5 101 2(00) 00101
5 6 110 2(00) 00110
6 7 111 2(00) 00111

0階無符號指數(shù)哥倫布編碼的解析過程如下

(1)找到第一個不為0的bit腺兴,并記錄總共找到了0的個數(shù)(num),這個時候讀到的這個bit肯定是1

(2)然后讀num個后綴

(3)1后綴轉(zhuǎn)變成十進制就是原來的codeNum廉侧,codeNum = (1 <<i) + 后綴(十進制) - 1;

比如比特串的二進制為:00101页响,首先找到第一個不為0的比特篓足,前面0的個數(shù)為2,然后再讀2個后綴10拘泞,10十進制為2纷纫,這個時候codeNum = (1 << 2) + 2 - 1 = 4 + 3 - 1 = 5

代碼實現(xiàn)

  • 1.將編碼好的數(shù)據(jù)5還原成原來的數(shù)據(jù)4
    (1)5的二進制是101,首先我們需要將5轉(zhuǎn)成字節(jié)碼8位0000 0101
   //5的字節(jié)碼是101,轉(zhuǎn)成8位字節(jié)碼
        byte data = 6 & 0xFF;

(2)我們需要統(tǒng)計0的個數(shù)陪腌,也就是首先要獲取第一個不是0的個數(shù)

               000 00101 
&              000 10000
=              000 00000

當前元素&0x80右移動N位就可以獲取當前的元素辱魁,當獲取到第一個元素是非0跳出

        //0000 0101
        int i = 3;
        //統(tǒng)計0的個數(shù)
        int zeroNum = 0;
        while (i < 8) {
            //找到第一個不為0
            if ((data & (0x80 >> i)) != 0) {
                break;
            }
            zeroNum++;
            i++;
        }

(3)獲取到0的個數(shù)后,跳過獲取到第一個非0的元素诗鸭,也就是i++.找到第一個不為0之后染簇,往后找zeroNum個數(shù)的字節(jié)
比如6的字節(jié)碼是111,找到第一個非0之后往后找兩個字節(jié)强岸,也就是11,字節(jié)碼的值是2*1+1锻弓,也就是說,無論多少字節(jié)蝌箍,最后一個非0的數(shù)據(jù)一定是+1青灼,而最后的前面的數(shù)據(jù)分邊左移1位

        i++;
        //找到第一個不為0之后,往后找zeroNum個數(shù)
        int value = 0;
        for (int j = 0; j < zeroNum; j++) {
            value <<= 1;
            //找到元素不為0 的個數(shù)
            if ((data & (0x80 >> i)) != 0) {
                value += 1;
            }
            i++;
        }
        int result=(1<<zeroNum)+value-1;
        System.out.println(result);

SPS解析分析

我們來看一個h264的sps信息


image.png

NALU

  • NALU:H264編碼數(shù)據(jù)存儲或傳輸?shù)幕締卧嗣ぃ话鉎264碼流最開始的兩個NALU是SPS和PPS杂拨,第三個NALU是IDR。SPS悯衬、PPS弹沽、SEI這三種NALU不屬于幀的范疇。
    NALU語法結(jié)構(gòu).png
  • 原始的NALU單元組成
    [start code] + [NALU header] + [NALU payload]筋粗;
  • H.264碼流在網(wǎng)絡(luò)中傳輸時實際是以NALU的形式進行傳輸?shù)?
    • 每個NALU由一個字節(jié)的HeaderRBSP組成.
    • NAL Header 的組成為:
      forbidden_zero_bit(1bit) + nal_ref_idc(2bit) + nal_unit_type(5bit)

SPS解析

1.1 NAL Header
網(wǎng)上的.png

幀類型.png

我們可以再x264.h源碼中看到這行代碼


image.png
image.png
1.2 profile_idc: 編碼等級,有以下取值
Options:
66 Baseline(直播)
77 Main(一般場景)
88 Extended
100 High (FRExt)
110 High 10 (FRExt)
122 High 4:2:2 (FRExt)
144 High 4:4:4 (FRExt)

我們上面的64是十六進制策橘,轉(zhuǎn)成二進制是100,也就是說我們現(xiàn)在的編碼等級是High娜亿。等級越高丽已,代表視頻越清晰

1.3 level_idc : 標識當前碼流的等級
Options:
10 1 (supports only QCIF format and below with 380160 samples/sec)
11 1.1 (CIF and below. 768000 samples/sec)
12 1.2 (CIF and below. 1536000 samples/sec)
13 1.3 (CIF and below. 3041280 samples/sec)
20 2 (CIF and below. 3041280 samples/sec)
21 2.1 (Supports HHR formats. Enables Interlace support. 5 068 800 samples/sec)
22 2.2 (Supports SD/4CIF formats. Enables Interlace support. 5184000 samples/sec)
30 3 (Supports SD/4CIF formats. Enables Interlace support. 10368000 samples/sec)
31 3.1 (Supports 720p HD format. Enables Interlace support. 27648000 samples/sec)
32 3.2 (Supports SXGA format. Enables Interlace support. 55296000 samples/sec)
40 4 (Supports 2Kx1K format. Enables Interlace support. 62914560 samples/sec)
41 4.1 (Supports 2Kx1K format. Enables Interlace support. 62914560 samples/sec)
42 4.2 (Supports 2Kx1K format. Frame coding only. 125829120 samples/sec)
50 5 (Supports 3672x1536 format. Frame coding only. 150994944 samples/sec)
51 5.1 (Supports 4096x2304 format. Frame coding only. 251658240 samples/sec)
1.4 chroma_format_idc 與亮度取樣對應的色度取樣

chroma_format_idc 的值應該在 0到 3的范圍內(nèi)(包括 0和 3)。當 chroma_format_idc不存在時买决,應推斷其值為 1(4:2:0的色度格式)促脉。

chroma_format_idc 色彩格式
0 單色
1 4:2:0
2 4:2:2
3 4:4:4
1.5 seq_parameter_set_id:哥倫布編碼
image.png
  • seq_parameter_set_id值應該是從0到31,包括0和31
  • 當可用的情況下策州,編碼器應該在sps值不同的情況下使用不同的seq_parameter_set_id值瘸味,而不是變化某一特定值的
1.6 bit_depth_luma_minus8 表示視頻位深
  • 0 High 只支持8bit
  • 1 High10 才支持10bit


    image.png
1.7 log2_max_frame_num_minus4

這個句法元素主要是為讀取另一個句法元素 frame_num 服務(wù)的,frame_num 是最重要的句法元素之一够挂,它標識所屬圖像的解碼順序旁仿。可以在句法表看到, fram-num的解碼函數(shù)是 ue(v)枯冈,函數(shù)中的 v 在這里指定:

v = log2_max_frame_num_minus4 + 4
從另一個角度看毅贮,這個句法元素同時也指明了 frame_num 的所能達到的最大值:
MaxFrameNum = 2( log2_max_frame_num_minus4 + 4 )

變量 MaxFrameNum 表示 frame_num 的最大值,在后文中可以看到尘奏,在解碼過程中它也是一個非常重要的變量滩褥。

值得注意的是 frame_num 是循環(huán)計數(shù)的,即當它到達 MaxFrameNum 后又從 0 重新開始新一輪的計數(shù)炫加。 解碼器必須要有機制檢測這種循環(huán)瑰煎, 不然會引起類似千年蟲的問題,在圖像的順序上造成混亂俗孝。

1.8 pic_order_cnt_type
  • 指明了picture order count的編碼方法酒甸,picture order count標識圖像的播放順序。
1.9 log2_max_pic_order_cnt_lsb_minus4
  • 指明了變量 MaxPicOrderCntLsb 的值:
  • MaxPicOrderCntLsb = 2( log2_max_pic_order_cnt_lsb_minus4 + 4 )
  • 該變量在 pic_order_cnt_type = 0 時使用赋铝。
image.png

SPS分析與提取代碼

   //哥倫布編碼
    public static int columbusCode(byte[] pBuff) {
        //統(tǒng)計0的個數(shù)
        int zeroNum = 0;
        while (i < pBuff.length * 8) {
            //找到第一個不為0
            if ((pBuff[i / 8] & (0x80 >> (i % 8))) != 0) {
                break;
            }
            zeroNum++;
            i++;
        }
        i++;
        //找到第一個不為0之后插勤,往后找zeroNum個數(shù)
        int value = 0;
        for (int j = 0; j < zeroNum; j++) {
            value <<= 1;
            //找到元素不為0 的個數(shù)
            if ((pBuff[i / 8] & (0x80 >> (i % 8))) != 0) {
                value += 1;
            }
            i++;
        }
        int result = (1 << zeroNum) + value - 1;
        return result;
    }

    public static byte[] hexStringToByteArray(String s) {
        //十六進制轉(zhuǎn)byte數(shù)組
        int len = s.length();
        byte[] bs = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            bs[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
        }
        return bs;
    }


    static int i = 0;

    /**
     * @bitIndex 字節(jié)數(shù)位數(shù)
     */
    private static int paraseH264(int bitIndex, byte[] h264) {
        int value = 0;
        for (int j = 0; j < bitIndex; j++) {
            value <<= 1;
            //獲取到每個字節(jié)
            if ((h264[i / 8] & (0x80 >> (i % 8))) != 0) {
                value += 1;
            }
            i++;
        }
        return value;
    }

    public static void main(String[] args) {
        //十六進制轉(zhuǎn)byte數(shù)組
        byte[] h264 = hexStringToByteArray("00 00 00 01 67 64 00 15 AC D9 41 70 C6 84 00 00 03 00 04 00 00 03 00 F0 3C 58 B6 58".replace(" ", ""));
        i = 4 * 8;
        //禁止位
        int forbidden_zero_bit = paraseH264(1, h264);
        System.out.println("forbidden_zero_bit:" + forbidden_zero_bit);
        //重要性
        int nal_ref_idc = paraseH264(2, h264);
        System.out.println("nal_ref_idc:" + nal_ref_idc);
        //幀類型
        int nal_unit_type = paraseH264(5, h264);
        System.out.println("nal_unit_type:" + nal_unit_type);
        if (nal_unit_type == 7) {
            //編碼等級
            int profile_idc = paraseH264(8, h264);
            System.out.println("profile_idc:" + profile_idc);
            //64后面的(00),基本用不到
            //當constrained_set0_flag值為1的時候革骨,就說明碼流應該遵循基線profile(Baseline profile)的所有約束.constrained_set0_flag值為0時农尖,說明碼流不一定要遵循基線profile的所有約束。
            int constraint_set0_flag = paraseH264(1, h264);//(h264[1] & 0x80)>>7;
            // 當constrained_set1_flag值為1的時候良哲,就說明碼流應該遵循主profile(Main profile)的所有約束.constrained_set1_flag值為0時盛卡,說明碼流不一定要遵
            int constraint_set1_flag = paraseH264(1, h264);//(h264[1] & 0x40)>>6;
            //當constrained_set2_flag值為1的時候,就說明碼流應該遵循擴展profile(Extended profile)的所有約束.constrained_set2_flag值為0時臂外,說明碼流不一定要遵循擴展profile的所有約束。
            int constraint_set2_flag = paraseH264(1, h264);//(h264[1] & 0x20)>>5;
            //注意:當constraint_set0_flag,constraint_set1_flag或constraint_set2_flag中不只一個值為1的話喇颁,那么碼流必須滿足所有相應指明的profile約束漏健。
            int constraint_set3_flag = paraseH264(1, h264);//(h264[1] & 0x10)>>4;
            // 4個零位
            int reserved_zero_4bits = paraseH264(4, h264);

            //碼流等級
            int level_idc = paraseH264(8, h264);
            System.out.println("level_idc:" + level_idc);
            //(AC)就是哥倫布編碼
            int seq_parameter_set_id = columbusCode(h264);
            System.out.println("seq_parameter_set_id:" + seq_parameter_set_id);
            if (profile_idc == 100) {
                int chroma_format_idc = columbusCode(h264);
                System.out.println("chroma_format_idc:" + chroma_format_idc);
                int bit_depth_luma_minus8 = columbusCode(h264);
                System.out.println("bit_depth_luma_minus8:" + bit_depth_luma_minus8);
                int bit_depth_chroma_minus8 = columbusCode(h264);
                System.out.println("bit_depth_chroma_minus8:" + bit_depth_chroma_minus8);
                int qpprime_y_zero_transform_bypass_flag = paraseH264(1, h264);
                System.out.println("qpprime_y_zero_transform_bypass_flag:" + qpprime_y_zero_transform_bypass_flag);
                //縮放標志位
                int seq_scaling_matrix_present_flag = paraseH264(1, h264);
                System.out.println("seq_scaling_matrix_present_flag:" + seq_scaling_matrix_present_flag);
            }
            //最大幀率
            int log2_max_frame_num_minus4 = columbusCode(h264);
            System.out.println("log2_max_frame_num_minus4:" + log2_max_frame_num_minus4);
            //確定播放順序和解碼順序的映射
            int pic_order_cnt_type = columbusCode(h264);
            System.out.println("pic_order_cnt_type:" + pic_order_cnt_type);
            int log2_max_pic_order_cnt_lsb_minus4 = columbusCode(h264);
            System.out.println("log2_max_pic_order_cnt_lsb_minus4:" + log2_max_pic_order_cnt_lsb_minus4);
            int num_ref_frames = columbusCode(h264);
            System.out.println("num_ref_frames:" + num_ref_frames);
            int gaps_in_frame_num_value_allowed_flag = paraseH264(1, h264);
            System.out.println("gaps_in_frame_num_value_allowed_flag:" + gaps_in_frame_num_value_allowed_flag);
            System.out.println("------startBit " + i);//83
            int pic_width_in_mbs_minus1 = columbusCode(h264);
            System.out.println("------startBit " + i);//92
            int pic_height_in_map_units_minus1 = columbusCode(h264);
            int width = (pic_width_in_mbs_minus1 + 1) * 16;
            int height = (pic_height_in_map_units_minus1 + 1) * 16;
            System.out.println("width :  " + width + "   height: " + height);
        }
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市橘霎,隨后出現(xiàn)的幾起案子蔫浆,更是在濱河造成了極大的恐慌,老刑警劉巖姐叁,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瓦盛,死亡現(xiàn)場離奇詭異,居然都是意外死亡外潜,警方通過查閱死者的電腦和手機原环,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來处窥,“玉大人嘱吗,你說我怎么就攤上這事√霞荩” “怎么了谒麦?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵俄讹,是天一觀的道長。 經(jīng)常有香客問我绕德,道長患膛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任耻蛇,我火速辦了婚禮踪蹬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘城丧。我一直安慰自己延曙,他們只是感情好,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布亡哄。 她就那樣靜靜地躺著枝缔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蚊惯。 梳的紋絲不亂的頭發(fā)上愿卸,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機與錄音截型,去河邊找鬼趴荸。 笑死,一個胖子當著我的面吹牛宦焦,可吹牛的內(nèi)容都是我干的发钝。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼波闹,長吁一口氣:“原來是場噩夢啊……” “哼酝豪!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起精堕,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤孵淘,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后歹篓,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瘫证,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年庄撮,在試婚紗的時候發(fā)現(xiàn)自己被綠了银酗。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昼接。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡步绸,死狀恐怖享郊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤扭仁,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布垮衷,位于F島的核電站,受9級特大地震影響乖坠,放射性物質(zhì)發(fā)生泄漏搀突。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一熊泵、第九天 我趴在偏房一處隱蔽的房頂上張望仰迁。 院中可真熱鬧,春花似錦顽分、人聲如沸徐许。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽雌隅。三九已至,卻和暖如春缸沃,著一層夾襖步出監(jiān)牢的瞬間恰起,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工趾牧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留检盼,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓翘单,卻偏偏與公主長得像吨枉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子哄芜,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

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