天天看點

【十五】【vlc-android】vlc-sout流媒體輸出端源碼實作分析【Part 2】【04】

此章節分析承接上一章分析:

【十五】【vlc-android】vlc-sout流媒體輸出端源碼實作分析【Part 2】【03】

10.6.1、block_ChainGather實作分析:

注意:根據全項目搜尋查找實作,block資料塊結構體中的該字段【p_block->i_length】代表的意思大緻為,目前block資料顯示的時長。如若是視訊block資料塊,則表示目前視訊圖像應該顯示的時長

//【vlc/inlcude/vlc_block.h】
static inline block_t *block_ChainGather( block_t *p_list )
{
    size_t  i_total = 0;
    mtime_t i_length = 0;
    block_t *g;

    if( p_list->p_next == NULL )
        return p_list;  /* Already gathered */

    // 擷取資料塊連結清單中所有block資料塊的總大小和總時長
    // 見下面的分析
    block_ChainProperties( p_list, NULL, &i_total, &i_length );

    // 聚集到一個block資料塊中儲存
    g = block_Alloc( i_total );
    if( !g )
        return NULL;
    // 提取資料塊鍊中所有資料塊資料聚集到g中
    // 見下面的分析
    block_ChainExtract( p_list, g->p_buffer, g->i_buffer );

    // 資料塊類型、PTS、DTS、總時長指派
    g->i_flags = p_list->i_flags;
    g->i_pts   = p_list->i_pts;
    g->i_dts   = p_list->i_dts;
    g->i_length = i_length;

    // 然後就釋放資料塊鍊中所有資料塊資料記憶體
    /* free p_list */
    block_ChainRelease( p_list );
    return g;
}

//【vlc/inlcude/vlc_block.h】
static inline void block_ChainProperties( block_t *p_list, int *pi_count, size_t *pi_size, mtime_t *pi_length )
{
    size_t i_size = 0;
    mtime_t i_length = 0;
    int i_count = 0;

    while( p_list )
    {
        // 計算資料塊連結清單中所有block資料塊的總大小和總時長
        i_size += p_list->i_buffer;
        i_length += p_list->i_length;
        // 計算資料塊鍊中block資料塊總數
        i_count++;

        p_list = p_list->p_next;
    }

    if( pi_size )
        *pi_size = i_size;
    if( pi_length )
        *pi_length = i_length;
    if( pi_count )
        *pi_count = i_count;
}

//【vlc/inlcude/vlc_block.h】
static size_t block_ChainExtract( block_t *p_list, void *p_data, size_t i_max )
{
    size_t  i_total = 0;
    uint8_t *p = (uint8_t*)p_data;

    while( p_list && i_max )
    {
        // 每次從連結清單中讀取資料的大小,取最小值的原因是防止讀取資料越界
        size_t i_copy = __MIN( i_max, p_list->i_buffer );
        // 讀取i_copy大小的資料【位元組數】到p中
        memcpy( p, p_list->p_buffer, i_copy );
        // 減去已讀大小,計算剩餘需要讀取資料大小
        i_max   -= i_copy;
        // 已讀取資料的總大小
        i_total += i_copy;
        // 儲存資料的資料指針移動到資料末尾,以便下次直接在末尾添加新資料
        p       += i_copy;

        // 讀取下一個資料塊資料
        p_list = p_list->p_next;
    }
    return i_total;
}
           

10.6.2、h264_compute_poc實作分析:

//【vlc/modules/packetizer/h264_slice.c】
void h264_compute_poc( const h264_sequence_parameter_set_t *p_sps,
                       const h264_slice_t *p_slice, h264_poc_context_t *p_ctx,
                       int *p_PictureOrderCount, int *p_tFOC, int *p_bFOC )
{// 下面根據POC類型【0、1、2】來區分實作
    // OC: order count序列号, POC: picture order count圖像顯示序列号
    // tFOC: TopFieldOrderCnt表示頂場POC,bFOC:BottomFieldOrderCnt表示底場POC
    *p_tFOC = *p_bFOC = 0;

    // pic_order_cnt_type  指明了 poc  (picture  order  count)  的編碼方法
    if( p_sps->i_pic_order_cnt_type == 0 )
    {// 目前片的POC類型為0時,擷取到POC低有效位
    // pic_order_cnt_type=0:傳低位(提高壓縮效率,隻對POC低位編碼傳輸)
        // log2_max_pic_order_cnt_lsb_minus4[0,12]指明了變量MaxPicOrderCntLsb的值: 
        // MaxPicOrderCntLsb = pow(2, (log2_max_pic_order_cnt_lsb_minus4 + 4) ) 
        // 右移運算相當于上面的公式對2做指數運算,擷取目前片的POC低有效位最大值
        unsigned maxPocLSB = 1U << (p_sps->i_log2_max_pic_order_cnt_lsb  + 4);

        // 1、prevPicOrderCntLsb:目前幀的前一個參考幀(比如低位POC=2的p幀的prevPicOrderCntLsb=60)
        // 2、prevPicOrderCntMsb和prevPicOrderCntLsb在IDR或者mmco=5的時候選擇性複位

        // POC參考
        /* POC reference */
        if( p_slice->i_nal_type == H264_NAL_SLICE_IDR )
        {// IDR關鍵幀時,将前一個參考圖像的POC低有效位和高有效位設定為0
            p_ctx->prevPicOrderCnt.lsb = 0;
            p_ctx->prevPicOrderCnt.msb = 0;
        }
        else if( p_ctx->prevRefPictureHasMMCO5 )
        {// 非I幀并且MMCO【記憶體管理控制操作】标志的值等于5
            // 前一個參考圖像的POC高有效位設為0
            p_ctx->prevPicOrderCnt.msb = 0;
            if( !p_ctx->prevRefPictureIsBottomField )
                // 前一個參考圖像非底場時,設定前一個參考圖像的POC低有效位為前一個參考圖像頂場POC值
                p_ctx->prevPicOrderCnt.lsb = p_ctx->prevRefPictureTFOC;
            else
                // 前一個參考圖像為底場時,設定為0
                p_ctx->prevPicOrderCnt.lsb = 0;
        }
        
        // 分情況讨論:
        // 1、主要場景:Lsb不發生溢出或借位==>前後兩幀的Msb一緻
    	   // 表現為:後解碼Lsb > 先解碼Lsb,而Msb相同

        // 2、臨界情況:Lsb不發生溢出或借位==》前後兩幀的Msb不一緻
    	// 【若發生進位/借位,則他們的播放順序POC之差(的絕對值)必定超過MaxPicOrderCntLsb / 2】
    	// 2.1、借位
    	// 2.2、溢出

        // 計算目前圖像的PicOrderCntMsb【pocMSB】
        /* 8.2.1.1 */
        //【1、Msb不變】
        int pocMSB = p_ctx->prevPicOrderCnt.msb;
        // 目前分片資訊中POC低有效位與前一個參考圖像的POC低有效位的圖像顯示順序序号內插補點
        int64_t orderDiff = p_slice->i_pic_order_cnt_lsb - p_ctx->prevPicOrderCnt.lsb;
        if( orderDiff < 0 && -orderDiff >= maxPocLSB / 2 )
            // 若內插補點小于0并且其絕對值大于等于目前片的POC低有效位最大值的一半,
            // 則加上[maxPocLSB]
            // 【2、Lsb進位,Msb增加MaxPicOrderCntLsb】
            pocMSB += maxPocLSB;
        else if( orderDiff > maxPocLSB / 2 )
            // 若內插補點大于目前片的POC低有效位最大值的一半,則減去[maxPocLSB]
            // 【3、Lsb借位,Msb減少MaxPicOrderCntLsb】
            pocMSB -= maxPocLSB;

        // 頂場/底層FOC值為計算所得POC高有效位加上目前片POC低有效位值
        *p_tFOC = *p_bFOC = pocMSB + p_slice->i_pic_order_cnt_lsb;
        if( p_slice->i_field_pic_flag )
            // 目前片為圖像場時,底場FOC序号值需要加上目前片POC底場偏移量
            *p_bFOC += p_slice->i_delta_pic_order_cnt_bottom;

        // 目前片的NAL重要性訓示位,若不為0,則表示是參考幀圖像
        // nal_ref_idc  ==  0 表示目前圖像是非參考圖像
        /* Save from ref picture */
        if( p_slice->i_nal_ref_idc /* Is reference */ )
        {// 計算前一個參考圖像相關資訊
            // 前一個參考圖像是否為底場,需同時有圖像場标志【i_field_pic_flag】和底場标志【i_bottom_field_flag】
            p_ctx->prevRefPictureIsBottomField = (p_slice->i_field_pic_flag &&
                                                  p_slice->i_bottom_field_flag);
            // 判斷标志位:是否MMCO【記憶體管理控制操作】标志的值類型為5
            p_ctx->prevRefPictureHasMMCO5 = p_slice->has_mmco5;
            // 前一個參考圖像頂場FOC幀解碼序列号
            p_ctx->prevRefPictureTFOC = *p_tFOC;
            // 前一個參考圖像的POC低有效位
            p_ctx->prevPicOrderCnt.lsb = p_slice->i_pic_order_cnt_lsb;
            // 前一個參考圖像的POC高有效位
            p_ctx->prevPicOrderCnt.msb = pocMSB;
        }
    }
    else
    {// 目前片的POC類型不為0時
        // 最大幀【解碼】序列号,同上分析,右移運算相當于計算2的指數計算,指數值為【i_log2_max_frame_num + 4】
        unsigned maxFrameNum = 1 << (p_sps->i_log2_max_frame_num + 4);
        // 幀序号偏移量
        unsigned frameNumOffset;
        // 預期的POC值
        unsigned expectedPicOrderCnt = 0;

/**
0、num_ref_frames_in_pic_order_cnt_cycle:一個POC循環中參考幀數量(IDR幀除外,一般指P幀,而B幀一般不作為參考幀)
	//取值範圍[0,255]
0、frame_num【相對幀号,原因在于其循環計數】
	當一個序列中的參考幀數量超過MaxFramenum時,frame_num在達到MaxFramenum後會重新從0開始循環計數
	故一個序列中可能會存在兩個或多個參考圖像擁有相同的“相對幀序号(即frame_num)”的情況 
	
1、FrameNumOffset【記錄(自IDR到目前幀的)frame_num循環次數,其值=循環次數*MaxFramenum】
// 實際幀号和相對幀号的內插補點 = MaxFrameNum的整數倍
	1、FrameNumOffset = 0			==> 是IDR 【GOP序列開始,當然從零開始計數】
	2、FrameNumOffset = prevFrameNumOffset 	==> 正常繼承
	3、FrameNumOffset = prevFrameNumOffset	+ MaxFrameNum	==> 發生溢出(frame_num溢位)【此時frame_num接近MaxFrameNum又從零開始計數】
   (前一幀的幀号比目前幀大prevFrameNum > frame_num)
**/
        // num_ref_frames_in_pic_order_cnt_cycle: 一個GOP循環中參考幀數量(不包括IDR)
        
        if( p_slice->i_nal_type == H264_NAL_SLICE_IDR )
            // 若目前片為IDR關鍵幀,則設為0,即無偏移量【GOP序列開始,當然從零開始計數】
            frameNumOffset = 0;
        else if( p_ctx->prevFrameNum > p_slice->i_frame_num )
            // 若前一個參考幀序列号大于目前片的幀序列号,則計算幀序列号偏移量
            // 發生溢出即發生了frame_num循環【此時frame_num接近MaxFrameNum又從零開始計數】
            frameNumOffset = p_ctx->prevFrameNumOffset + maxFrameNum;
        else
            // 否則片的幀序号偏移量為前一個參考幀序号偏移量
            // 正常繼承
            frameNumOffset = p_ctx->prevFrameNumOffset;

        // pic_order_cnt_type=1:傳POC偏差(除IDR外後續PB幀循環出現)
        if( p_sps->i_pic_order_cnt_type == 1 )
        {// POC類型為1時
            // 幀【絕對】序列号
            // absFrameNum【絕對(參考)幀号  GOP中每一幀的frame_num】
            unsigned absFrameNum;

            // i_num_ref_frames_in_pic_order_cnt_cycle: 一個POC循環中參考幀數量(IDR幀除外,一般指P幀,而B幀一般不作為參考幀)
            if( p_sps->i_num_ref_frames_in_pic_order_cnt_cycle > 0 )
                // 參考幀數量POC周期值[0,255]大于0則計算幀序列号為目前片序列号加上幀序列号偏移量
                absFrameNum = frameNumOffset + p_slice->i_frame_num;
            else
                // 否則為0
                absFrameNum = 0;

            // 當一個GOP中沒有除IDR外的參考幀,那麼gop中所有的非參考幀前面的參考幀(必定是IDR),其frame必定為0
            if( p_slice->i_nal_ref_idc == 0 && absFrameNum > 0 )
                // nal_ref_idc  ==  0 表示目前圖像是非參考圖像
                // 目前片的NAL重要性訓示位值為0,并且幀【絕對】序列号大于0,則将其序列号減去1。
                // 當為非參考幀時,實際使用前一個參考幀的frame_num
                //(雖然自身frame_num = 前一個參考幀的frame_num+1,但沒有什麼用)
                absFrameNum--;
                
                //【此時得到的absFrameNum 即為參考幀序号】
                // 參考幀的POC計算 = 完整的POC循環數 * 每個循環的累積POC偏移 + 殘缺的POC循環中累積POC偏移
                // 非參考幀的POC基于 已解碼的最後的參考幀的POC偏移 得到

            // 必須要有參考P幀後面才會有absFrameNum>0
            if( absFrameNum > 0 )
            {// 若大于0
                // POC周期的期望增量值【完整POC循環中參考幀的累積POC偏移】
                int32_t expectedDeltaPerPicOrderCntCycle = 0;
                // 計算相鄰POC內插補點和
                // 注:一個參考幀跟下一個參考幀,他們POC的內插補點在傳輸時指定,且是周期變化的,
                // 即每隔num_ref_frames_in_pic_order_cnt_cycle個參考幀,相鄰參考幀之間POC的內插補點循環一次,
                // 而每個周期中,第i個內插補點即為offset_for_ref_frame[i]。
                // offset_for_ref_frame[0]指【IDR幀/上一個循環最後一個參考幀 ==> 新循環的1個參考幀】的POC偏移
                // offset_for_ref_frame指一個GOP循環中每兩個參考幀之間的POC偏移即GOP循環中相鄰參考幀的POC偏移量
                // 注:offset_for_ref_frame[i] 和 offset_for_non_ref_pic 都是在序列參數集中指定,他們的取值範圍都是[-2^31,2^31-1]
                for( int i=0; i<p_sps->i_num_ref_frames_in_pic_order_cnt_cycle; i++ )
                    expectedDeltaPerPicOrderCntCycle += p_sps->offset_for_ref_frame[i];

                // picOrderCntCycleCnt【完整POC周期數】 + frameNumInPicOrderCntCycle【最後不完整的周期中參考幀的數量】
                unsigned picOrderCntCycleCnt = 0;
                unsigned frameNumInPicOrderCntCycle = 0;
                if( p_sps->i_num_ref_frames_in_pic_order_cnt_cycle )
                {
                    // 得到完整周期數
                    picOrderCntCycleCnt = ( absFrameNum - 1 ) / p_sps->i_num_ref_frames_in_pic_order_cnt_cycle;
                    // 得到最後不完整的周期中參考幀的數量
                    frameNumInPicOrderCntCycle = ( absFrameNum - 1 ) % p_sps->i_num_ref_frames_in_pic_order_cnt_cycle;
                }

                // 【期望的POC值】= 循環次數【完整周期數】 * 完整循環内累積POC偏移 + 殘缺【不完整】POC循環中參考幀累積POC偏移
                expectedPicOrderCnt = picOrderCntCycleCnt * expectedDeltaPerPicOrderCntCycle;
                for( unsigned i=0; i <= frameNumInPicOrderCntCycle; i++ )
                    // 循環計算最後一個不完整的POC循環的各參考幀的POC累積偏移
                    expectedPicOrderCnt = expectedPicOrderCnt + p_sps->offset_for_ref_frame[i];
            }

            // 非參考圖像需要基于最新解碼的參考幀修正,修正量 = 單次修正值 * 次數
            // offset_for_non_ref_pic: 非參考幀POC基于非參考幀POC的單次偏移量即非參考圖像的POC偏移補正
            // 注:offset_for_ref_frame[i] 和 offset_for_non_ref_pic 都是在序列參數集SPS中指定,他們的取值範圍都是[-2^31,2^31-1]
            if( p_slice->i_nal_ref_idc == 0 )
                expectedPicOrderCnt = expectedPicOrderCnt + p_sps->offset_for_non_ref_pic;

            // 當圖像序列中出現連續非參考幀時,這些非參考幀的頂場序号和底場序号可以通過delta_pic_order_cnt[0/1]加以差別。
            // 是以POC type = 1的POC計算方法也是支援圖像序列中出現連續非參考幀的。
            
            // 首先計算頂場POC
            *p_tFOC = expectedPicOrderCnt + p_slice->i_delta_pic_order_cnt0;
            // 計算底層POC值
            // offset_for_top_to_bottom_field:頂場POC轉底場POC偏移量
            // SPS中設定對于幀編碼,offset_for_top_to_bottom_field=0;對于場編碼 offset_for_top_to_bottom_field=1;
            // i_delta_pic_order_cnt1為底場POC增量/差量,i_delta_pic_order_cnt0為頂場POC增量/差量
            if( !p_slice->i_field_pic_flag )
                // 幀Slice
                // 底場POC = 頂場POC + 0【頂場POC轉底場POC偏移量】 + 底場POC增量/差量
                *p_bFOC = *p_tFOC + p_sps->offset_for_top_to_bottom_field + p_slice->i_delta_pic_order_cnt1;
            else if( p_slice->i_bottom_field_flag )
                // 場Slice
                // 底場POC = 【期望的POC值】 + 1【頂場POC轉底場POC偏移量】 + 頂場POC增量/差量
                *p_bFOC = expectedPicOrderCnt + p_sps->offset_for_top_to_bottom_field + p_slice->i_delta_pic_order_cnt0;
        }
        else if( p_sps->i_pic_order_cnt_type == 2 )
        {// pic_order_cnt_type=2(不消耗bit):顯示順序與解碼順序一緻
        // 1、求得的POC不區分頂場底場
        // 2、不允許圖像序列中出現連續的非參考幀(因為該方法得到的連續非參考幀的POC相同)
        
            // 1、tempPicOrderCnt = 0					IDR幀
    	 // 2、tempPicOrderCnt = 2*(FrameNumOffset + frame_num)		參考幀 ==> 保證了參考場的POC始終為偶數,并大于同幀的另外一個場
    	 // 3、tempPicOrderCnt = 2*(FrameNumOffset + frame_num)-1	非參考幀
    	 // 連續非參考幀的frame_num相同,導緻多個(非參考)圖像的top/bottom的POC相同,故這裡并不支援連續非參考幀
            unsigned tempPicOrderCnt;

            if( p_slice->i_nal_type == H264_NAL_SLICE_IDR )
                // IDR幀
                tempPicOrderCnt = 0;
            else if( p_slice->i_nal_ref_idc == 0 )
                // nal_ref_idc = 0: 非參考幀
                tempPicOrderCnt = 2 * ( frameNumOffset + p_slice->i_frame_num ) - 1;
            else
                // 參考幀 ==> 保證了參考場的POC始終為偶數,并大于同幀的另外一個場
                tempPicOrderCnt = 2 * ( frameNumOffset + p_slice->i_frame_num );

            // 幀Slice或場Slice的這兩POC都相同
            *p_bFOC = *p_tFOC = tempPicOrderCnt;
        }

        // 将目前圖像幀序号指派給目前POC資訊
        p_ctx->prevFrameNum = p_slice->i_frame_num;
        if( p_slice->has_mmco5 )
            // mmco=5時需要複位重新計數,将前一個參考幀的該值複位
            p_ctx->prevFrameNumOffset = 0;
        else
            // 否則繼承該值
            p_ctx->prevFrameNumOffset = frameNumOffset;
    }

    /* 8.2.1 (8-1) */
    if( !p_slice->i_field_pic_flag ) /* progressive or contains both fields */
        // 幀Slice
        // 逐行【掃描】幀或都包含頂場和底場,則其中取最小值作為POC值
        *p_PictureOrderCount = __MIN( *p_bFOC, *p_tFOC );
    else /* split top or bottom field */ // 場Slice
    if ( p_slice->i_bottom_field_flag )
        // 目前片【幀】資訊為底場時
        *p_PictureOrderCount = *p_bFOC;
    else
        // 目前片【幀】資訊為頂場時
        *p_PictureOrderCount = *p_tFOC;
}
           

三種類型POC計算比較:

bie消耗 序列要求

類型0: 最多(大量的lsb) 無要求

類型1: 在sps和slice_header傳遞bit POC周期變化

類型2: 無需消耗bit 限制最大(直接從frame_num擷取,POC和frmae_num必須一緻,不能有B幀,可以有非參考P幀)

10.6.3、h264_get_num_ts實作:

參數:【i_pic_struct】幀的結構類型,表示是幀還是場,是逐行還是隔行

//【vlc/modules/packetizer/h264_slice.c】
uint8_t h264_get_num_ts( const h264_sequence_parameter_set_t *p_sps,
                         const h264_slice_t *p_slice, uint8_t i_pic_struct,
                         int tFOC, int bFOC )
{
    // pic_struct 表示一幅圖像應顯示為一幀還是一場或更多場。雙倍幀(pic_struct 等于7)表示該幀應連續顯示兩次,
    // 而三倍幀(pic_struct等于8)表示該幀應連續顯示三次。
    // 注:時戳資訊組根據pic_struct的内容用于關聯圖像的對應場或幀,
    // 時戳資訊文法元素的内容說明源時間,拍攝時間或理想的播放時間。
    
    // h264_infer_pic_struct推測圖像結構類型
    i_pic_struct = h264_infer_pic_struct( p_sps, p_slice, i_pic_struct, tFOC, bFOC );
    // 【i_pic_struct】為0,7,8表示倍數場
    /* !WARN modified with nuit field based multiplier for values 0, 7 and 8 */
    const uint8_t rgi_numclock[9] = { 2, 1, 1, 2, 2, 3, 3, 4, 6 };
    // 傳回的是時戳資訊組個數? TODO
    return rgi_numclock[ i_pic_struct ];
}

//【vlc/modules/packetizer/h264_slice.c】
static uint8_t h264_infer_pic_struct( const h264_sequence_parameter_set_t *p_sps,
                                      const h264_slice_t *p_slice,
                                      uint8_t i_pic_struct, int tFOC, int bFOC )
{
    /* See D-1 and note 6 */
    if( !p_sps->vui.b_pic_struct_present_flag || i_pic_struct >= 9 )
    {// 條件【圖像結構類型辨別不存在或圖像結構類型大于等于9】滿足則進入重新判斷
        if( p_slice->i_field_pic_flag )
            // 場Slice
            // 底場辨別加1
            i_pic_struct = 1 + p_slice->i_bottom_field_flag;
        else if( tFOC == bFOC )
            // 逐行
            // 幀Slice 【底場和頂場的POC相同】
            i_pic_struct = 0;
        else if( tFOC < bFOC )
            // 隔行幀的第一個頂場
            i_pic_struct = 3;
        else
            // 隔行幀的第一個底場
            i_pic_struct = 4;
    }

    return i_pic_struct;
}
           

10.6.4、CanSwapPTSwithDTS實作分析:

//【vlc/modules/packetizer/h264.c】
static bool CanSwapPTSwithDTS( const h264_slice_t *p_slice,
                               const h264_sequence_parameter_set_t *p_sps )
{
    if( p_slice->i_nal_ref_idc == 0 && p_slice->type == H264_SLICE_TYPE_B )
        // 非參考幀并且為B幀時
        return true;
    else if( p_sps->vui.b_valid )
         // 視訊可用資訊的重排序幀最大數為0時,即沒有B幀時
        // 【沒有B幀則不需要重排序】
        return p_sps->vui.i_max_num_reorder_frames == 0;
    else
        // 判斷h264檔次設定是否為CAVLC幀内編碼
        // CAVLC:上下文自适應變長編碼,若為true則PTS等于DTS
        return p_sps->i_profile == PROFILE_H264_CAVLC_INTRA;
}
           

10.7、PutSPS實作分析:

//【vlc/modules/packetizer/h264.c】
static void PutSPS( decoder_t *p_dec, block_t *p_frag )
{
    decoder_sys_t *p_sys = p_dec->p_sys;

    // 目前SPS NALU單中繼資料及其大小
    const uint8_t *p_buffer = p_frag->p_buffer;
    size_t i_buffer = p_frag->i_buffer;

    // 該方法實作見前面的相關分析
    // 移除/跳過h264 AnnexB流格式起始碼
    if( !hxxx_strip_AnnexB_startcode( &p_buffer, &i_buffer ) )
    {
        block_Release( p_frag );
        return;
    }

    // sps NALU資料塊解碼
    // 該方法調用為一個方法指針函數定義,其實作見下面的分析
    h264_sequence_parameter_set_t *p_sps = h264_decode_sps( p_buffer, i_buffer, true );
    if( !p_sps )
    {// SPS解碼失敗則表示為無效SPS資料,并釋放目前SPS NALU單元block資料塊
        msg_Warn( p_dec, "invalid SPS" );
        block_Release( p_frag );
        return;
    }

    // 新的SPS NALU單中繼資料,可能目前新解析出來的sps id對應的SPS資料已有緩存的舊值
    // 若找到的SPS資料沒有舊值,則列印出來
    /* We have a new SPS */
    if( !p_sys->sps[p_sps->i_id].p_sps )
        msg_Dbg( p_dec, "found NAL_SPS (sps_id=%d)", p_sps->i_id );

    // 儲存SPS到全局緩存對象p_sys中
    // 見10.7.2小節分析
    StoreSPS( p_sys, p_sps->i_id, p_frag, p_sps );
}

//【vlc/modules/packetizer/h264_nal.c】
// 【1】方法指針函數聲明
h264_sequence_parameter_set_t * h264_decode_sps( const uint8_t *, size_t, bool );

//【vlc/modules/packetizer/h264_nal.c】
// 【2】該方法指針實作時通過宏定義實作:
#define IMPL_h264_generic_decode( name, h264type, decode, release ) \
    // 【name】指針函數名,【h264type】指針函數傳回類型,
    // 【decode】解碼方法名,【release】釋放方法名
    h264type * name( const uint8_t *p_buf, size_t i_buf, bool b_escaped ) \
    { \  
        h264type *p_h264type = calloc(1, sizeof(h264type)); \
        if(likely(p_h264type)) \
        { \ // 記憶體配置設定成功時
            // 碼流對象
            bs_t bs; \
            // 初始化碼流對象中相關字段值,該方法見前面已有分析
            bs_init( &bs, p_buf, i_buf ); \
            // 記錄目前碼流讀取位置
            unsigned i_bitflow = 0; \
            if( b_escaped ) \
            { \ // 根據處理和前面類似分析可知,b_escaped該參數含義為是否需要進行【0x03位元組脫殼操作】
                bs.p_fwpriv = &i_bitflow; \
                // 該方法見前面已有分析【0x03位元組脫殼操作】
                bs.pf_forward = hxxx_bsfw_ep3b_to_rbsp;  /* Does the emulated 3bytes conversion to rbsp */ \
            } \
            else (void) i_bitflow;\
            // 跳過碼流中8個比特數即1個位元組的NAL Header頭部資料
            bs_skip( &bs, 8 ); /* Skip nal_unit_header */ \
            // 進行解碼為對應類型結構體資料:PPS和SPS兩個解碼方法
            // 見下面的分析
            if( !decode( &bs, p_h264type ) ) \
            { \ // 解碼失敗,釋放記憶體【h264_release_sps其實就是直接調用free方法】
                release( p_h264type ); \
                p_h264type = NULL; \
            } \
        } \
        return p_h264type; \
    }
    
//【vlc/modules/packetizer/h264_nal.c】
// 【3】其方法實作定義為:
// [h264_parse_sequence_parameter_set_rbsp]見10.7.1小節分析
IMPL_h264_generic_decode( h264_decode_sps, h264_sequence_parameter_set_t,
                          h264_parse_sequence_parameter_set_rbsp, h264_release_sps )

// 最終:通過上面【1】【2】【3】步驟,即可得到跟正常函數定義實作一緻的函數名為【h264_decode_sps】的函數,即可被調用使用

// 附加一個實作為:
//【vlc/modules/packetizer/h264_nal.c】
h264_picture_parameter_set_t *  h264_decode_pps( const uint8_t *, size_t, bool );
//【vlc/modules/packetizer/h264_nal.c】
IMPL_h264_generic_decode( h264_decode_pps, h264_picture_parameter_set_t,
                          h264_parse_picture_parameter_set_rbsp, h264_release_pps )
// 即和【h264_decode_sps】方法類似的聲明和定義,得到【h264_decode_pps】函數的實作定義

//【vlc/modules/packetizer/h264_nal.c】
void h264_release_sps( h264_sequence_parameter_set_t *p_sps )
{
    free( p_sps );
}

//【vlc/modules/packetizer/h264_nal.c】
void h264_release_pps( h264_picture_parameter_set_t *p_pps )
{
    free( p_pps );
}
           

10.7.1、h264_parse_sequence_parameter_set_rbsp實作分析:

從NALU單中繼資料的RBSP原始位元組序列負載資料中解析出SPS序列參數集資料

備注:方法中的bs_readXX方法實作在前面分析流程中已有分析。

//【vlc/modules/packetizer/h264_nal.c】
static bool h264_parse_sequence_parameter_set_rbsp( bs_t *p_bs,
                                                    h264_sequence_parameter_set_t *p_sps )
{
    int i_tmp;

    // h264碼流對應的檔次【1個位元組表示】 ===》不同畫質
    int i_profile_idc = bs_read( p_bs, 8 );
    p_sps->i_profile = i_profile_idc;
    // 碼流檔次遵從的限制條件類型值【1個位元組表示】
    p_sps->i_constraint_set_flags = bs_read( p_bs, 8 );
    // 碼流等級【1個位元組表示】
    p_sps->i_level = bs_read( p_bs, 8 );
    // 讀取第一個指數哥倫布編碼【bs_read_ue見此前章節中實作分析】,其值表示sps id
    // 指明本序列參數集的 id 号,這個 id 号将被 picture 參數集引用,本句法元素的值應該在[0,31],
    // 編碼需要産生新的序列集時,使用新的id,而不是改變原來參數集的内容。
    /* sps id */
    uint32_t i_sps_id = bs_read_ue( p_bs );
    if( i_sps_id > H264_SPS_ID_MAX )
        // 超出31
        return false;
    p_sps->i_id = i_sps_id;

    if( i_profile_idc == PROFILE_H264_HIGH ||
        i_profile_idc == PROFILE_H264_HIGH_10 ||
        i_profile_idc == PROFILE_H264_HIGH_422 ||
        i_profile_idc == PROFILE_H264_HIGH_444 || /* Old one, no longer on spec */
        i_profile_idc == PROFILE_H264_HIGH_444_PREDICTIVE ||
        i_profile_idc == PROFILE_H264_CAVLC_INTRA ||
        i_profile_idc == PROFILE_H264_SVC_BASELINE ||
        i_profile_idc == PROFILE_H264_SVC_HIGH ||
        i_profile_idc == PROFILE_H264_MVC_MULTIVIEW_HIGH ||
        i_profile_idc == PROFILE_H264_MVC_STEREO_HIGH ||
        i_profile_idc == PROFILE_H264_MVC_MULTIVIEW_DEPTH_HIGH ||
        i_profile_idc == PROFILE_H264_MVC_ENHANCED_MULTIVIEW_DEPTH_HIGH ||
        i_profile_idc == PROFILE_H264_MFC_HIGH )
    {// 碼流檔次為條件中檔次類型時
        // 色度重要性訓示位值【讀取一個指數哥倫布編碼】
        /* chroma_format_idc */
        p_sps->i_chroma_idc = bs_read_ue( p_bs );
        if( p_sps->i_chroma_idc == 3 )
            // 色度不同顔色平面标志位值
            p_sps->b_separate_colour_planes_flag = bs_read1( p_bs );
        else
            // 否則置為0
            p_sps->b_separate_colour_planes_flag = 0;
        // 亮度位深【即一個Y占用的比特數】【讀取一個指數哥倫布編碼再加上8】
        /* bit_depth_luma_minus8 */
        p_sps->i_bit_depth_luma = bs_read_ue( p_bs ) + 8;
        // 色度位深【即一個U或V占用的比特數】【讀取一個指數哥倫布編碼再加上8】
        /* bit_depth_chroma_minus8 */
        p_sps->i_bit_depth_chroma = bs_read_ue( p_bs ) + 8;
        // 跳過該标志位值,即跳過1個比特數
        /* qpprime_y_zero_transform_bypass_flag */
        bs_skip( p_bs, 1 );
        // 序列縮放矩陣存在标志位,1個比特數表示
        /* seq_scaling_matrix_present_flag */
        i_tmp = bs_read( p_bs, 1 );
        if( i_tmp )
        {// 存在時
            for( int i = 0; i < ((3 != p_sps->i_chroma_idc) ? 8 : 12); i++ )
            {
                // 縮放矩陣清單存在标志位值
                // seq/pic_scaling_matrix_present_flag值為0表示用于該圖像中的縮放比例清單應等于那些由序列參數集規定的。
                // 值為1表示存在用來修改在序列參數集中指定的縮放比例清單的參數。
                // seq/pic_scaling_list_present_flag[i]:值為0表示在圖像參數集中不存在縮放比例清單,
                // 需要根據seq_scaling_matrix_present_flag的值擷取級縮放比例清單。
                // 值為1表示存在縮放比例清單的文法結構并用于指定序列号為i的縮放比例清單。
                /* seq_scaling_list_present_flag[i] */
                i_tmp = bs_read( p_bs, 1 );
                if( !i_tmp )
                    // 不存在則繼續下一個處理
                    continue;
                // 縮放清單資料的個數,若小于6則為16,否則為64
                const int i_size_of_scaling_list = (i < 6 ) ? 16 : 64;
                /* scaling_list (...) */
                // 初始化最後一次和下一次的縮放值
                int i_lastscale = 8;
                int i_nextscale = 8;
                for( int j = 0; j < i_size_of_scaling_list; j++ )
                {
                    if( i_nextscale != 0 )
                    {
                        // 縮放差量【讀取第一個哥倫布編碼值,見前面的已有分析】
                        /* delta_scale */
                        i_tmp = bs_read_se( p_bs );
                        // 計算下一次縮放值
                        i_nextscale = ( i_lastscale + i_tmp + 256 ) % 256;
                        /* useDefaultScalingMatrixFlag = ... */
                    }
                    // 更新最後一次縮放值
                    /* scalinglist[j] */
                    i_lastscale = ( i_nextscale == 0 ) ? i_lastscale : i_nextscale;
                }
            }
        }
    }
    else
    {// 未知檔次,設定預設值
        p_sps->i_chroma_idc = 1; /* Not present == inferred to 4:2:0 */
        p_sps->i_bit_depth_luma = 8;
        p_sps->i_bit_depth_chroma = 8;
    }

    // log2_max_frame_num_minus4  這個句法元素主要是為讀取另一個句法元素 frame_num  服務的,
    // frame_num  是最重要的句法元素之一,它辨別所屬圖像的解碼順序 。
    // 這個句法元素同時也指明了 frame_num 的所能達到的最大值: MaxFrameNum = 2*exp( log2_max_frame_num_minus4 + 4 ) 
    // 擷取最大幀數的2的指數值,若大于12則取12
    /* Skip i_log2_max_frame_num */
    p_sps->i_log2_max_frame_num = bs_read_ue( p_bs );
    if( p_sps->i_log2_max_frame_num > 12)
        p_sps->i_log2_max_frame_num = 12;
        
    // 讀取圖像POC類型值    
    /* Read poc_type */
    p_sps->i_pic_order_cnt_type = bs_read_ue( p_bs );
    if( p_sps->i_pic_order_cnt_type == 0 )
    {// 為0類型時
        // 讀取圖像POC低有效的最大值的2的指數值【計算時需要加4】,類似上面
        /* skip i_log2_max_poc_lsb */
        p_sps->i_log2_max_pic_order_cnt_lsb = bs_read_ue( p_bs );
        if( p_sps->i_log2_max_pic_order_cnt_lsb > 12 )
            p_sps->i_log2_max_pic_order_cnt_lsb = 12;
    }
    else if( p_sps->i_pic_order_cnt_type == 1 )
    {// 為1類型時
        // delta_pic_order_always_zero_flag等于1時,句法元素delta_pic_order_cnt[0]和 delta_pic_order_cnt[1]不在片頭出現,
        // 并且它們的值預設為0;本句法元素等于0時,上述的兩個句法元素将在片頭出現。
        p_sps->i_delta_pic_order_always_zero_flag = bs_read( p_bs, 1 );
        // offset_for_non_ref_pic: 非參考幀POC基于非參考幀POC的單次偏移量即非參考圖像的POC偏移補正
        // 注:offset_for_ref_frame[i] 和 offset_for_non_ref_pic 都是在序列參數集SPS中指定,他們的取值範圍都是[-2^31,2^31-1]
        p_sps->offset_for_non_ref_pic = bs_read_se( p_bs );
        // 頂場POC轉底場POC的偏移量
        p_sps->offset_for_top_to_bottom_field = bs_read_se( p_bs );
        // i_num_ref_frames_in_pic_order_cnt_cycle: 一個POC循環中參考幀數量(IDR幀除外,一般指P幀,而B幀一般不作為參考幀)
        p_sps->i_num_ref_frames_in_pic_order_cnt_cycle = bs_read_ue( p_bs );
        if( p_sps->i_num_ref_frames_in_pic_order_cnt_cycle > 255 )
            // 不能超過255
            return false;
        for( int i=0; i<p_sps->i_num_ref_frames_in_pic_order_cnt_cycle; i++ )
            // 讀取每個參考幀偏移量
            // 注:一個參考幀跟下一個參考幀,他們POC的內插補點在傳輸時指定,且是周期變化的,
            // 即每隔num_ref_frames_in_pic_order_cnt_cycle個參考幀,相鄰參考幀之間POC的內插補點循環一次,
            // 而每個周期中,第i個內插補點即為offset_for_ref_frame[i]。
            // offset_for_ref_frame[0]指【IDR幀/上一個循環最後一個參考幀 ==> 新循環的1個參考幀】的POC偏移
            // offset_for_ref_frame指一個GOP循環中每兩個參考幀之間的POC偏移即GOP循環中相鄰參考幀的POC偏移量
            p_sps->offset_for_ref_frame[i] = bs_read_se( p_bs );
    }
    // 讀取/跳過參考幀總數值,沒有儲存使用
    /* i_num_ref_frames */
    bs_read_ue( p_bs );
    // gaps_in_frame_num_value_allowed_flag 這個句法元素等于 1 時,表示允許句法元素 frame_num 可以不連續。
    // 當傳輸信道堵塞嚴重時,編碼器來不及将編碼後的圖像全部發出,這時允許丢棄若幹幀圖像。 
    // 跳過1個比特數的資料
    /* b_gaps_in_frame_num_value_allowed */
    bs_skip( p_bs, 1 );

    // pic_width_in_mbs_minus1 本句法元素加 1 後指明圖像寬度,以宏塊MB為機關:
    // PicWidthInMbs = pic_width_in_mbs_minus1 + 1 通過這個句法元素解碼器可以
    // 計算得到亮度分量以像素為機關的圖像寬度: PicWidthInSamplesL = PicWidthInMbs * 16
    /* Read size */
    p_sps->pic_width_in_mbs_minus1 = bs_read_ue( p_bs );
    // pic_height_in_map_units_minus1 本句法元素加 1 後指明圖像高度:
    // PicHeightInMapUnits = pic_height_in_map_units_minus1 + 1 
    p_sps->pic_height_in_map_units_minus1 = bs_read_ue( p_bs );

    // frame_mbs_only_flag 本句法元素等于 0 時表示本序列中所有圖像的編碼模式都是幀,
    // 沒有其他編碼模式存在;本句法元素等于 1 時表示本序列中圖像的編碼模式可能是幀,
    // 也可能是場或幀場自适應,某個圖像具體是哪一種要由其他句法元素決定。 
    // 讀取一個比特數
    /* b_frame_mbs_only */
    p_sps->frame_mbs_only_flag = bs_read( p_bs, 1 );
    if( !p_sps->frame_mbs_only_flag )
        // 為0即圖像編碼模式均為幀時
        // mb_adaptive_frame_field_flag 指明本序列是否屬于幀場自适應模式。
        // mb_adaptive_frame_field_flag等于1時表明在本序列中的圖像如果不是場模式就是幀場自适應模式,
        // 等于0時表示本序列中的圖像如果不是場模式就是幀模式。
        
        // mb_adaptive_frame_field_flag等于0表明在一個圖像内不能切換使用幀和場宏塊。
        // mb_adaptive_frame_field_flag等于1表示在一幀中有可能使用場和幀的切換,
        // 當mb_adaptive_frame_field_flag沒有設定的時候,應該賦給0.
        p_sps->mb_adaptive_frame_field_flag = bs_read( p_bs, 1 );
    
    // 跳過該标志
    // direct_8x8_inference_flag: 指明了在亮度運動向量生成B_Skip,B_Direct_16x16和B_Direct_8x8的方法。
    // 當frame_mbs_only_flag為0時,direct_8x8_inference_flag應為1
    /* b_direct8x8_inference */
    bs_skip( p_bs, 1 );

    // 幀裁剪資訊
    /* crop */
    if( bs_read1( p_bs ) ) /* frame_cropping_flag */
    {// 讀取1一個比特數值為1即需要裁剪幀時
    
        // frame_cropping_flag用于指明解碼器是否要将圖像裁剪後輸出,如果是的話,
        // 後面緊跟着的四個句法元素分别指出左右、上下裁剪的寬度。
        // 将左右、上下裁剪的長度乘以裁剪機關值得到需要裁剪的真正寬高值,
        // 然後用圖像原始寬高值減去裁剪的對應值,得到需要顯示的寬高值。
        p_sps->frame_crop.left_offset = bs_read_ue( p_bs );
        p_sps->frame_crop.right_offset = bs_read_ue( p_bs );
        p_sps->frame_crop.top_offset = bs_read_ue( p_bs );
        p_sps->frame_crop.bottom_offset = bs_read_ue( p_bs );
    }

    // 視訊可用資訊
    /* vui */
    // vui_parameters_present_flag等于1表示vui_parameters()在碼流中是存在的,
    // vui_parameters_present_flag等于0表明vui_parameters()在碼流中不存在。
    // 讀取視訊可用資訊存在标志位,1存在
    i_tmp = bs_read( p_bs, 1 );
    if( i_tmp )
    {// VUI資訊存在
        // 标記有效
        p_sps->vui.b_valid = true;
        // 若有寬高比部分的資訊,則讀取,通過1個比特數值标志位判斷是否存在
        /* read the aspect ratio part if any */
        i_tmp = bs_read( p_bs, 1 );
        if( i_tmp )
        {// 存在
            // 定義一個SAR視訊采樣寬高比【分辨率】結構體數組【該數組是标準定義的SAR順序】
            static const struct { int w, h; } sar[17] =
            {
                { 0,   0 }, { 1,   1 }, { 12, 11 }, { 10, 11 },
                { 16, 11 }, { 40, 33 }, { 24, 11 }, { 20, 11 },
                { 32, 11 }, { 80, 33 }, { 18, 11 }, { 15, 11 },
                { 64, 33 }, { 160,99 }, {  4,  3 }, {  3,  2 },
                {  2,  1 },
            };
            // 讀取SAR index索引值
            int i_sar = bs_read( p_bs, 8 );
            int w, h;

            if( i_sar < 17 )
            {// 若小于數組個數,則直接取其寬高比的對應值
                w = sar[i_sar].w;
                h = sar[i_sar].h;
            }
            else if( i_sar == 255 )
            {// 255表示自定義寬高比的值傳遞,是以進行讀取其值
                // 均讀取16個比特數來表示的值
                w = bs_read( p_bs, 16 );
                h = bs_read( p_bs, 16 );
            }
            else
            {// 否則就是無效的值
                w = 0;
                h = 0;
            }

            if( w != 0 && h != 0 )
            {// 若都不為0即均為有效值,則儲存起來
                p_sps->vui.i_sar_num = w;
                p_sps->vui.i_sar_den = h;
            }
            else
            {// 無效值時預設設定SAR視訊采樣寬高比為1:1
                p_sps->vui.i_sar_num = 1;
                p_sps->vui.i_sar_den = 1;
            }
        }

        // 過采樣标志? TODO
        // 讀取一個比特數
        /* overscan */
        i_tmp = bs_read( p_bs, 1 );
        if ( i_tmp )
            // 若存在過采樣标志,則讀取/跳過一個比特數的值,vlc此處不處理
            bs_read( p_bs, 1 );

        // 視訊信号類型
        /* video signal type */
        // 是否存在該類型值
        i_tmp = bs_read( p_bs, 1 );
        if( i_tmp )
        {// 存在該類型的值,則處理
            // 跳過3個比特數的值
            bs_read( p_bs, 3 );
            // 讀取目前是否使用的色域值為全色域範圍值
            p_sps->vui.colour.b_full_range = bs_read( p_bs, 1 );
            // 色彩空間描述
            /* colour desc */
            i_tmp = bs_read( p_bs, 1 );
            if ( i_tmp )
            {// 存在描述
                // 視訊格式中的色彩空間YUV基色類型 【讀取8個比特數即1個位元組表示的值】
                p_sps->vui.colour.i_colour_primaries = bs_read( p_bs, 8 );
                // 色彩空間YUV轉RGB的轉換矩陣類型【讀取8個比特數即1個位元組表示的值】
                p_sps->vui.colour.i_transfer_characteristics = bs_read( p_bs, 8 );
                // 色彩空間YUV轉RGB的轉換矩陣系數【讀取8個比特數即1個位元組表示的值】
                p_sps->vui.colour.i_matrix_coefficients = bs_read( p_bs, 8 );
            }
            else
            {// 不存在,則色域描述指派為未定義
                p_sps->vui.colour.i_colour_primaries = HXXX_PRIMARIES_UNSPECIFIED;
                p_sps->vui.colour.i_transfer_characteristics = HXXX_TRANSFER_UNSPECIFIED;
                p_sps->vui.colour.i_matrix_coefficients = HXXX_MATRIX_UNSPECIFIED;
            }
        }

        // 色度取樣位置資訊
        /**
        chromaloc chroma loc info
預設:0,說明:設定色度取樣位置。(H.264标準的附件E中定義)。取值範圍為0-5。
進一步的說明可參見【https://code.videolan.org/videolan/x264/-/blob/master/doc/vui.txt】
建議:
如果你以MPEG1源為輸入做4:2:0采樣的轉碼,而且沒作任何色彩空間轉換,應該設定為1;
如果你以MPEG2源為輸入做4:2:0采樣的轉碼,而且沒作任何色彩空間轉換,應該設定為0;
如果你以MPEG4源為輸入做4:2:0采樣的轉碼,而且沒作任何色彩空間轉換,應該設定為0;
其他情況保持預設。
        **/
        /* chroma loc info */
        i_tmp = bs_read( p_bs, 1 );
        if( i_tmp )
        {// vlc此處将其讀取後丢棄,即跳過該資料
            bs_read_ue( p_bs );
            bs_read_ue( p_bs );
        }

        // 圖像定時資訊
        /* timing info */
        // 1個比特位表示的定時資訊是否存在
        p_sps->vui.b_timing_info_present_flag = bs_read( p_bs, 1 );
        if( p_sps->vui.b_timing_info_present_flag )
        {// 1存在時
            // H.264碼流中一般沒有幀率,比特率資訊到是可以擷取,可參考碼流文法,
            // 碼流有VUI資訊,其有個标志 timing_info_present_flag 若等于1,
            // 則碼流中有num_units_in_tick 和 time_scale。
            // 計算幀率framerate = time_scale/num_units_in_tick。
            
            // 時間縮放機關數值【讀取32個比特數的表示值】
            p_sps->vui.i_num_units_in_tick = bs_read( p_bs, 32 );
            // 時間縮放值【讀取32個比特數的值】
            p_sps->vui.i_time_scale = bs_read( p_bs, 32 );
            // 标記是否為固定的碼流【讀取1個比特數的值】
            p_sps->vui.b_fixed_frame_rate = bs_read( p_bs, 1 );
        }

        // Hypothetical Reference Decoder (HRD) :假定參考解碼器
        // 用來檢查比特流與解碼器一緻性。
        // HRD包含編碼圖像緩存(CPB)、實時解碼過程、解碼圖像緩存(DPB)及輸出裁切
        // 有兩類HRD 參數集可供使用。HRD 參數集通過視訊可用性資訊傳送,
        // 如h264标準附錄E.1 與E.2 節所規定,視訊可用性資訊是序列參數集文法結構的一部分。
        
        // NAL HRD和VC1 HRD參數集資料,預設為false不存在
        /* Nal hrd & VC1 hrd parameters */
        p_sps->vui.b_hrd_parameters_present_flag = false;
        for ( int i=0; i<2; i++ )
        {// 兩類HRD參數集資料,分别讀取
            // 目前HRD參數集資料是否存在
            i_tmp = bs_read( p_bs, 1 );
            if( i_tmp )
            {// 存在時标記為true
                p_sps->vui.b_hrd_parameters_present_flag = true;
                // 讀取哥倫布編碼值加上1表示參數集的個數,但不能超過31
                uint32_t count = bs_read_ue( p_bs ) + 1;
                if( count > 31 )
                    return false;
                // 連續讀取【跳過】兩個4比特數的值 
                bs_read( p_bs, 4 );
                bs_read( p_bs, 4 );
                for( uint32_t j = 0; j < count; j++ )
                {
                    // 判斷目前碼流可用比特數是否足夠,至少23比特數
                    if( bs_remain( p_bs ) < 23 )
                        return false;
                    // 以下讀取對應資料後未儲存使用,即跳過這些資料【這些資料的定義需要參考h264标準文檔】
                    bs_read_ue( p_bs );
                    bs_read_ue( p_bs );
                    bs_read( p_bs, 1 );
                }
                // 再次跳過5個比特數
                bs_read( p_bs, 5 );
                // CPB編碼圖像緩沖區移出AU通路單中繼資料延遲時長 【讀取5個比特數】
                p_sps->vui.i_cpb_removal_delay_length_minus1 = bs_read( p_bs, 5 );
                // 解碼圖像緩沖區DPB輸出AU資料延遲時長 【讀取5個比特數】
                p_sps->vui.i_dpb_output_delay_length_minus1 = bs_read( p_bs, 5 );
                // 再次跳過5個比特數
                bs_read( p_bs, 5 );
            }
        }

        if( p_sps->vui.b_hrd_parameters_present_flag )
            // HRD存在時,讀取/跳過1比特數的值即辨別是否為低延遲HRD? TODO
            bs_read( p_bs, 1 ); /* low delay hrd */

        // 圖像結構類型資訊存在辨別值 【讀取1個比特數】
        /* pic struct info */
        p_sps->vui.b_pic_struct_present_flag = bs_read( p_bs, 1 );

        // 讀取碼流限制标志位
        p_sps->vui.b_bitstream_restriction_flag = bs_read( p_bs, 1 );
        if( p_sps->vui.b_bitstream_restriction_flag )
        {// 為1表示經編碼的視訊序列比特流限制參數存在
            // 以下處理基本是讀取值後未儲存使用,【i_max_num_reorder_frames】除外
            // 運動矢量圖像邊界 【1個比特數】
            bs_read( p_bs, 1 ); /* motion vector pic boundaries */
            // 每張圖像幀最大位元組數
            bs_read_ue( p_bs ); /* max bytes per pic */
            // 每個圖像宏塊最大比特數
            bs_read_ue( p_bs ); /* max bits per mb */
            // 最大的運動矢量H
            bs_read_ue( p_bs ); /* log2 max mv h */
            // 最大的運動矢量V
            bs_read_ue( p_bs ); /* log2 max mv v */
            // 視訊可用資訊的重排序幀最大數,若為0則沒有B幀時
            // 【沒有B幀則不需要重排序】
            p_sps->vui.i_max_num_reorder_frames = bs_read_ue( p_bs );
            // 解碼幀緩沖區最大值即DPB解碼圖像緩沖區大小(幀數)
            bs_read_ue( p_bs ); /* max dec frame buffering */
        }
    }

    return true;
}
           

10.7.2、StoreSPS實作分析:

//【vlc/modules/packetizer/h264.c】
static void StoreSPS( decoder_sys_t *p_sys, uint8_t i_id,
                      block_t *p_block, h264_sequence_parameter_set_t *p_sps )
{
    if( p_sys->sps[i_id].p_block )
        // 若新解析的SPS id有存在block資料塊舊值,則先釋放該資料塊
        block_Release( p_sys->sps[i_id].p_block );
    if( p_sys->sps[i_id].p_sps )
        // 若新解析的SPS id有存在SPS舊值,則先釋放該舊值資料 【直接調用的free方法】
        h264_release_sps( p_sys->sps[i_id].p_sps );
    if( p_sys->sps[i_id].p_sps == p_sys->p_active_sps )
        // 若目前正在使用的SPS資訊和已釋放的SPS舊值相同,則也将正在使用的SPS資訊指派為NULL
        p_sys->p_active_sps = NULL;
    // 更新/儲存新的資料塊資料和SPS資訊結構體對象
    p_sys->sps[i_id].p_block = p_block;
    p_sys->sps[i_id].p_sps = p_sps;
}
           

10.8、PutPPS實作分析:

//【vlc/modules/packetizer/h264.c】
static void PutPPS( decoder_t *p_dec, block_t *p_frag )
{// 整個實作流程來看,其處理流程和10.7小節的SPS解析基本類似

    decoder_sys_t *p_sys = p_dec->p_sys;
    // 資料塊中負載的PPS NALU單中繼資料和大小
    const uint8_t *p_buffer = p_frag->p_buffer;
    size_t i_buffer = p_frag->i_buffer;

    // 該方法實作見前面的相關分析
    // 移除/跳過h264 AnnexB流格式起始碼
    if( !hxxx_strip_AnnexB_startcode( &p_buffer, &i_buffer ) )
    {
        block_Release( p_frag );
        return;
    }

    // PPS NALU資料塊解碼
    // 該方法調用為一個方法指針函數定義,其實作在10.7小節中已經提到,
    // 是以可知其最終調用的方法為【h264_parse_picture_parameter_set_rbsp】
    // 見10.8.1小節分析
    h264_picture_parameter_set_t *p_pps = h264_decode_pps( p_buffer, i_buffer, true );
    if( !p_pps )
    {
        msg_Warn( p_dec, "invalid PPS" );
        block_Release( p_frag );
        return;
    }

    /* We have a new PPS */
    if( !p_sys->pps[p_pps->i_id].p_pps )
        msg_Dbg( p_dec, "found NAL_PPS (pps_id=%d sps_id=%d)", p_pps->i_id, p_pps->i_sps_id );

    StorePPS( p_sys, p_pps->i_id, p_frag, p_pps );
}
           

10.8.1、h264_parse_picture_parameter_set_rbsp實作分析:

從h264碼流的PPS NALU單中繼資料中解析PPS資料

//【vlc/modules/packetizer/h264_nal.c】
static bool h264_parse_picture_parameter_set_rbsp( bs_t *p_bs,
                                                   h264_picture_parameter_set_t *p_pps )
{// bs_read_xxx方法請見此前文章中的已有分析
    // 讀取最前面的一個指數哥倫布編碼,得到pps id值
    uint32_t i_pps_id = bs_read_ue( p_bs ); // pps id
    // 讀取最前面的一個指數哥倫布編碼,得到sps id值
    uint32_t i_sps_id = bs_read_ue( p_bs ); // sps id
    // PPS id不能大于255,SPS id不能大于31
    if( i_pps_id > H264_PPS_ID_MAX || i_sps_id > H264_SPS_ID_MAX )
        return false;
    p_pps->i_id = i_pps_id;
    p_pps->i_sps_id = i_sps_id;

    // 讀取/跳過1個比特數位的值【該值表示的是熵編碼模式類型,vlc此次跳過不處理】
    bs_skip( p_bs, 1 ); // entropy coding mode flag
    // 擷取1個比特數表示的值即POC圖像順序是否存在标志位。
    // 注:POC 的三種計算方法在片層還各需要用一些句法元素作為參數,
    // 本句法元素等于1時表示在片頭會有句法元素指明這些參數;
    // 本句法元素等于0時,表示片頭不會給出這些參數,這些參數使用預設值。 
    p_pps->i_pic_order_present_flag = bs_read( p_bs, 1 );

    // 擷取片【幀】組個數
    // num_slice_groups_minus1 本句法元素加1後指明圖像中片組的個數。
    // H.264 中沒有專門的句法元素用于指明是否使用片組模式,
    // 當本句法元素等于0(即隻有一個片組),表示不使用片組模式,
    // 後面也不會跟有用于計算片組映射的句法元素。
    unsigned num_slice_groups = bs_read_ue( p_bs ) + 1;
    if( num_slice_groups > 8 ) /* never has value > 7. Annex A, G & J */
        return false;
    if( num_slice_groups > 1 )
    {// 片組個數大于1即多個片組時
    
        /** slice_group_map_type  用以指明片組分割類型。
            map_units 的定義:
            1:  當 frame_mbs_only_flag 等于1時,map_units 指的就是宏塊。
            2:  當 frame_mbs_only_flag 等于0時
                1:  幀場自适應模式時,map_units 指的是宏塊對
                2:  場模式時,map_units 指的是宏塊
                3:  幀模式時,map_units 指的是與宏塊對相類似的,上下兩個連續宏塊的組合體。*/ 
    
        // 片組映射類型
        unsigned slice_group_map_type = bs_read_ue( p_bs );
        if( slice_group_map_type == 0 )
        {
            for( unsigned i = 0; i < num_slice_groups; i++ )
                // run_length_minus1[i] 用以指明當片組類型等于0時,每個片組連續的 map_units 個數 
                bs_read_ue( p_bs ); /* run_length_minus1[group] */
        }
        else if( slice_group_map_type == 2 )
        {
            for( unsigned i = 0; i < num_slice_groups; i++ )
            {
                // top_left[i],bottom_right[i] 用以指明當片組類型等于2時,矩形區域的左上及右下位置。 
                bs_read_ue( p_bs ); /* top_left[group] */
                bs_read_ue( p_bs ); /* bottom_right[group] */
            }
        }
        else if( slice_group_map_type > 2 && slice_group_map_type < 6 )
        {
            // slice_group_change_direction_flag 與下一個句法元素一起指明确切的片組分割方法。
            // 片組改變方向
            bs_read1( p_bs );   /* slice_group_change_direction_flag */
            // slice_group_change_rate_minus1 用以指明變量 SliceGroupChangeRate
            // 片組改變速率
            bs_read_ue( p_bs ); /* slice_group_change_rate_minus1 */
        }
        else if( slice_group_map_type == 6 )
        {
            // pic_size_in_map_units_minus1 在片組類型等于6時,
            // 用以指明圖像以 map_units 為機關的大小
            unsigned pic_size_in_maps_units = bs_read_ue( p_bs ) + 1;
            unsigned sliceGroupSize = 1;
            while(num_slice_groups > 1)
            {
                sliceGroupSize++;
                num_slice_groups = ((num_slice_groups - 1) >> 1) + 1;
            }
            for( unsigned i = 0; i < pic_size_in_maps_units; i++ )
            {
                bs_skip( p_bs, sliceGroupSize );
            }
        }
    }

    bs_read_ue( p_bs ); /* num_ref_idx_l0_default_active_minus1 */
    bs_read_ue( p_bs ); /* num_ref_idx_l1_default_active_minus1 */
    p_pps->weighted_pred_flag = bs_read( p_bs, 1 );
    p_pps->weighted_bipred_idc = bs_read( p_bs, 2 );
    bs_read_se( p_bs ); /* pic_init_qp_minus26 */
    bs_read_se( p_bs ); /* pic_init_qs_minus26 */
    bs_read_se( p_bs ); /* chroma_qp_index_offset */
    bs_read( p_bs, 1 ); /* deblocking_filter_control_present_flag */
    bs_read( p_bs, 1 ); /* constrained_intra_pred_flag */
    p_pps->i_redundant_pic_present_flag = bs_read( p_bs, 1 );

    /* TODO */

    return true;
}
           

TODO:此章節系列流媒體處理未分析完整,待後續更新,敬請關注,Thanks♪(・ω・)ノ

【由于本人目前工作内容轉為了android framework多媒體架構層的維護、優化等日常工作,是以後續會先更新android framework多媒體架構層實作分析,而vlc-sout流媒體處理部分會延緩更新】

繼續閱讀