【十五】【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流媒体处理部分会延缓更新】

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值