此章节分析承接上一章分析:
【十五】【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流媒体处理部分会延缓更新】