此章節分析承接上一章分析:
【十五】【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流媒體處理部分會延緩更新】