此章節分析承接上一章分析:
【十五】【vlc-android】vlc-sout流媒體輸出端源碼實作分析【Part 2】【01】
9、block_SkipBytes實作分析:
// 【vlc/include/vlc_block_helper.h】
static inline int block_SkipBytes( block_bytestream_t *p_bytestream,
size_t i_data )
{
return block_GetBytes( p_bytestream, NULL, i_data );
}
// 【vlc/include/vlc_block_helper.h】
static inline int block_GetBytes( block_bytestream_t *p_bytestream,
uint8_t *p_data, size_t i_data )
{
// 此處為檢查塊位元組流資料剩餘未讀取資料大小是否小于需要擷取【跳過】的位元組數資料大小
// 若小于則表示需要從位元組流未讀取資料中擷取的資料不足
// 見下面的分析
if( block_BytestreamRemaining( p_bytestream ) < i_data )
return VLC_EGENERIC;
/* Copy the data */
// i_block_offset表示:塊資料内部已讀取資料位置偏移量
// i_data表示:待讀取的資料大小
size_t i_offset = p_bytestream->i_block_offset;
size_t i_size = i_data;
size_t i_copy = 0;
block_t *p_block;
for( p_block = p_bytestream->p_block;
p_block != NULL; p_block = p_block->p_next )
{
// 計算需要讀取資料的大小,并防止目前塊資料讀取越界處理
i_copy = __MIN( i_size, p_block->i_buffer - i_offset );
// 更新待讀取的資料大小
i_size -= i_copy;
if( i_copy && p_data != NULL )
{// 若p_data不為空則将讀取的i_copy大小的資料存入p_data指針中
// 【p_block->p_buffer + i_offset】該計算表示為:得到目前block資料的未讀取資料的開始位置,
// 然後讀取i_copy大小的資料
memcpy( p_data, p_block->p_buffer + i_offset, i_copy );
// 将存放讀取資料的指針往後移動讀取資料大小位置
p_data += i_copy;
}
// 若p_data為空,則表示僅僅跳過待讀取資料的大小位置
// 若所有待讀取資料大小都已讀取完畢則退出讀取
if( i_size == 0 )
break;
// 沒有讀取完畢,則繼續循環讀取下一個block塊資料處理
p_bytestream->i_base_offset += p_block->i_buffer;
// 新block讀取偏移量為0,因為還未讀取呢
i_offset = 0;
}
// 更新塊位元組流中目前位元組流讀取指針塊
p_bytestream->p_block = p_block;
// 更相信目前塊已讀取資料偏移量位置
p_bytestream->i_block_offset = i_offset + i_copy;
return VLC_SUCCESS;
}
static inline size_t block_BytestreamRemaining( const block_bytestream_t *p_bytestream )
{
// 此處處理為:檢查和計算塊位元組流資料剩餘未讀取資料大小
// i_total表示:目前塊位元組流中資料塊鍊的所有資料塊總負載資料大小
// i_base_offset表示:此前已讀取的所有block塊資料已讀取總大小位置即塊基準偏移量
// i_block_offset表示:塊資料内部已讀取資料位置偏移量
return ( p_bytestream->i_total > p_bytestream->i_base_offset + p_bytestream->i_block_offset ) ?
p_bytestream->i_total - p_bytestream->i_base_offset - p_bytestream->i_block_offset : 0;
}
10、PacketizeParse實作分析:打包(分組分包)h264碼流NALU單中繼資料
注意:從分析流程中可知,文中的【p_sys.leading.p_head】資訊為SEI補充增強資訊單元的資訊
//【vlc/modules/packetizer/h264.c】
static block_t *PacketizeParse( void *p_private, bool *pb_ts_used, block_t *p_block )
{
decoder_t *p_dec = p_private;
// 注意:此處的block塊資料中負載資料已經被處理為隻負載一個NALU單中繼資料了即NAL頭 + RBSP【原始位元組序列負載】
// 移除掉目前block資料中一個NALU單元的尾部位元組對齊添加的0位元組資料
// 【條件:負載資料長度需大于5個位元組數,若末尾位元組值為0對齊位元組值,
// 則将i_buffer負載資料大小減1,這樣不會讀到該資料了】
/* Remove trailing 0 bytes */
while( p_block->i_buffer > 5 && p_block->p_buffer[p_block->i_buffer-1] == 0x00 )
p_block->i_buffer--;
return ParseNALBlock( p_dec, pb_ts_used, p_block );
}
// 解析NAL單中繼資料塊
// 解析AnnexB流格式類型的NAL資料
// 傳入的參數【p_frag所有分片】資料塊都必須以0 0 0 1 四個位元組起始碼開始
//【vlc/modules/packetizer/h264.c】
/*****************************************************************************
* ParseNALBlock: parses annexB type NALs
* All p_frag blocks are required to start with 0 0 0 1 4-byte startcode
*****************************************************************************/
static block_t *ParseNALBlock( decoder_t *p_dec, bool *pb_ts_used, block_t *p_frag )
{
decoder_sys_t *p_sys = p_dec->p_sys;
block_t *p_pic = NULL;
// 起始碼四位元組之後第5個位元組就是NAL頭資訊,該位元組後五個比特位表示的值就是NALU單元類型
const int i_nal_type = p_frag->p_buffer[4]&0x1f;
const mtime_t i_frag_dts = p_frag->i_dts;
const mtime_t i_frag_pts = p_frag->i_pts;
if( p_sys->b_slice && (!p_sys->p_active_pps || !p_sys->p_active_sps) )
{// 若需要碼流資料分片并且PPS或SPS類型的NAL單中繼資料不存在時,則進入此處等待SPS或PPS資料
msg_Warn( p_dec, "waiting for SPS/PPS" );
// 重置上下文環境資料
/* Reset context */
// 丢棄已存儲的NAL資料單中繼資料
// 見前面的分析
DropStoredNAL( p_sys );
// 重置輸出端參數值
// 見前面的分析
ResetOutputVariables( p_sys );
// 清空字幕資料
cc_storage_reset( p_sys->p_ccs );
}
switch( i_nal_type )
{// NALU類型,1~23為單個NAL單元包,24~31表示需要分包或組合發送,0表示未定義
/*** Slices ***/
// 值為1:不分區,非IDR圖像的片
case H264_NAL_SLICE:
// 值為2:片分區A ====》此處的DP表示資料分割的英文意思 【Data Partitioning】
case H264_NAL_SLICE_DPA:
// 值為3:片分區B
case H264_NAL_SLICE_DPB:
// 值為4:片分區C
case H264_NAL_SLICE_DPC:
// 值為5:IDR圖像中的片
case H264_NAL_SLICE_IDR:
{// 備注:資料分割DP片層資料,可用于錯誤恢複解碼
// 聲明一個h264碼流片分區資料結構體對象
h264_slice_t newslice;
if( i_nal_type == H264_NAL_SLICE_IDR )
{// 若為IDR圖像中的片類型資料,是以标記可用于錯誤恢複解碼
p_sys->b_recovered = true;
p_sys->i_recovery_frame_cnt = UINT_MAX;
p_sys->i_recoveryfnum = UINT_MAX;
}
// 解析NAL單元中【編碼】片的頭資訊
// 見下面的分析
if( ParseSliceHeader( p_dec, p_frag, &newslice ) )
{// 解析成功
// 注譯:隻有IDR幀才會攜帶傳輸的該圖像ID
// 注意此處p_sys->slice和newslice的差別:
// 前者表示上一個片【幀】資訊,後者表示目前新到來的片資訊。
/* Only IDR carries the id, to be propagated */
if( newslice.i_idr_pic_id == -1 )
newslice.i_idr_pic_id = p_sys->slice.i_idr_pic_id;
// 判斷是否是新圖像即是否是第一個VCL NAL單中繼資料
// 見10.3小節分析
bool b_new_picture = IsFirstVCLNALUnit( &p_sys->slice, &newslice );
if( b_new_picture )
{// 目前NALU單中繼資料為新圖像時解析SEI資訊
// 解析SEI補充增強資訊單元【已比對了SPS/PPS的視訊幀】
/* Parse SEI for that frame now we should have matched SPS/PPS */
for( block_t *p_sei = p_sys->leading.p_head; p_sei; p_sei = p_sei->p_next )
{
if( (p_sei->i_flags & BLOCK_FLAG_PRIVATE_SEI) == 0 )
// 若目前幀資料不為SEI【子產品私有資訊】,則進行查詢下一個資料塊
continue;
// 找到SEI資訊并解析
// HxxxParse_AnnexB_SEI實作見10.4小節分析
// ParseSeiCallback解析SEI資訊的回調方法指針,實作見10.5小節分析
HxxxParse_AnnexB_SEI( p_sei->p_buffer, p_sei->i_buffer,
1 /* nal header */, ParseSeiCallback, p_dec );
}
// 該辨別表示是否目前NALU單中繼資料為上面switch中片類型的資訊
if( p_sys->b_slice )
// 若是片類型NALU資料則輸出目前圖像
// 見10.6小節分析
p_pic = OutputPicture( p_dec );
}
// 緩存目前的新片資訊
/* */
p_sys->slice = newslice;
}
else
{// 片頭部資訊解析失敗,則将pps清空
p_sys->p_active_pps = NULL;
// 注譯:分片資訊稍後将被丢棄
/* Fragment will be discarded later on */
}
// 記錄目前NALU類型為片資訊辨別為true
p_sys->b_slice = true;
// 見上面流程中的相同分析
// // 添加目前片類型圖像資料塊鍊資料到frame幀資料塊鍊尾部
block_ChainLastAppend( &p_sys->frame.pp_append, p_frag );
} break;
// 以下為NALU單元序列的字首資訊
/*** Prefix NALs ***/
// NAL通路單元分隔符:NALU類型為0x09
case H264_NAL_AU_DELIMITER:
if( p_sys->b_slice )
// 目前NALU類型為片資訊辨別時,擷取輸出圖像
p_pic = OutputPicture( p_dec );
// 清空目前打包子產品解碼對象的幀資料塊緩存值等,因為此時的資料塊總是第一個NALU單中繼資料
/* clear junk if no pic, we're always the first nal */
DropStoredNAL( p_sys );
// 辨別記錄為目前子產品私有AUD即通路單元分隔符
p_frag->i_flags |= BLOCK_FLAG_PRIVATE_AUD;
// 添加目前片類型圖像資料塊鍊資料到leading資料塊鍊尾部
// leading結構體從[OutputPicture]方法分析中,
// 大緻可看出其主要儲存的是NALU中附加資訊值即非視訊幀資料塊,
// 而frame結構體儲存的就是視訊幀資料塊。
block_ChainLastAppend( &p_sys->leading.pp_append, p_frag );
break;
// NALU類型為:SPS 0x07 / PPS 0x08
case H264_NAL_SPS:
case H264_NAL_PPS:
if( p_sys->b_slice )
// 目前NALU類型為片資訊辨別時,擷取輸出圖像
p_pic = OutputPicture( p_dec );
// 注譯:存儲用于在關鍵幀上插入【使用這些值】
/* Stored for insert on keyframes */
if( i_nal_type == H264_NAL_SPS )
{// SPS類型的NALU單中繼資料時
// 見10.7小節分析
PutSPS( p_dec, p_frag );
// 并标記目前SPS資料更新了
p_sys->b_new_sps = true;
}
else
{// PPS類型的NALU單中繼資料時
// 見10.8小節分析
PutPPS( p_dec, p_frag );
// 并标記目前PPS資料更新了
p_sys->b_new_pps = true;
}
break;
case H264_NAL_SEI:
if( p_sys->b_slice )
p_pic = OutputPicture( p_dec );
p_frag->i_flags |= BLOCK_FLAG_PRIVATE_SEI;
block_ChainLastAppend( &p_sys->leading.pp_append, p_frag );
break;
case H264_NAL_SPS_EXT:
case H264_NAL_PREFIX: /* first slice/VCL associated data */
case H264_NAL_SUBSET_SPS:
case H264_NAL_DEPTH_PS:
case H264_NAL_RESERVED_17:
case H264_NAL_RESERVED_18:
if( p_sys->b_slice )
p_pic = OutputPicture( p_dec );
block_ChainLastAppend( &p_sys->leading.pp_append, p_frag );
break;
/*** Suffix NALs ***/
case H264_NAL_END_OF_SEQ:
case H264_NAL_END_OF_STREAM:
/* Early end of packetization */
block_ChainLastAppend( &p_sys->frame.pp_append, p_frag );
/* important for still pictures/menus */
p_sys->i_next_block_flags |= BLOCK_FLAG_END_OF_SEQUENCE;
if( p_sys->b_slice )
p_pic = OutputPicture( p_dec );
break;
case H264_NAL_SLICE_WP: // post
case H264_NAL_UNKNOWN:
case H264_NAL_FILLER_DATA:
case H264_NAL_SLICE_EXT:
case H264_NAL_SLICE_3D_EXT:
case H264_NAL_RESERVED_22:
case H264_NAL_RESERVED_23:
default: /* others 24..31, including unknown */
block_ChainLastAppend( &p_sys->frame.pp_append, p_frag );
break;
}
*pb_ts_used = false;
if( p_sys->i_frame_dts <= VLC_TS_INVALID &&
p_sys->i_frame_pts <= VLC_TS_INVALID )
{
p_sys->i_frame_dts = i_frag_dts;
p_sys->i_frame_pts = i_frag_pts;
*pb_ts_used = true;
if( i_frag_dts > VLC_TS_INVALID )
date_Set( &p_sys->dts, i_frag_dts );
}
if( p_pic && (p_pic->i_flags & BLOCK_FLAG_DROP) )
{
block_Release( p_pic );
p_pic = NULL;
}
return p_pic;
}
//【vlc/modules/packetizer/h264.c】
static bool ParseSliceHeader( decoder_t *p_dec, const block_t *p_frag, h264_slice_t *p_slice )
{
decoder_sys_t *p_sys = p_dec->p_sys;
// 【脫掉的】NAL單中繼資料
const uint8_t *p_stripped = p_frag->p_buffer;
size_t i_stripped = p_frag->i_buffer;
// 去掉所有的AnnexB資料流格式資料中的起始碼【三位元組和四位元組的起始碼】
// 【其實也不是釋放其起始碼占用記憶體,而是将已讀取資料指針位置往後移,移除掉起始碼即可】
// 見第十五章節【Part 3】部分1.2.1小節分析 TODO
if( !hxxx_strip_AnnexB_startcode( &p_stripped, &i_stripped ) || i_stripped < 2 )
// 失敗
return false;
// 解碼片【Slice】的頭資訊和資料
// h264_decode_slice:見10.1小節分析
// GetSPSPPS該方法:擷取NAL單元中SPS/PPS資料,見下面的分析
if( !h264_decode_slice( p_stripped, i_stripped, GetSPSPPS, p_sys, p_slice ) )
return false;
// 此處再次擷取SPS、PPS資料,主要用于驗證資料是否正常,因為上面的方法内部已經綁定處理過一次了
const h264_sequence_parameter_set_t *p_sps;
const h264_picture_parameter_set_t *p_pps;
GetSPSPPS( p_slice->i_pic_parameter_set_id, p_sys, &p_sps, &p_pps );
if( unlikely( !p_sps || !p_pps) )
return false;
// 激活可用的【PPS SPS】參數集資料
// 見10.2小節分析
ActivateSets( p_dec, p_sps, p_pps );
return true;
}
//【vlc/modules/packetizer/h264.c】
static void GetSPSPPS( uint8_t i_pps_id, void *priv,
const h264_sequence_parameter_set_t **pp_sps,
const h264_picture_parameter_set_t **pp_pps )
{
decoder_sys_t *p_sys = priv;
// 通過 pps id擷取pps數組中的pps NALU資料,
// 然後通過其資料中的sps id從sps數組中擷取sps NALU資料
*pp_pps = p_sys->pps[i_pps_id].p_pps;
if( *pp_pps == NULL )
*pp_sps = NULL;
else
*pp_sps = p_sys->sps[(*pp_pps)->i_sps_id].p_sps;
}
10.1、h264_decode_slice實作分析:
//【vlc/modules/packetizer/h264_slice.c】
bool h264_decode_slice( const uint8_t *p_buffer, size_t i_buffer,
void (* get_sps_pps)(uint8_t, void *,
const h264_sequence_parameter_set_t **,
const h264_picture_parameter_set_t ** ),
void *priv, h264_slice_t *p_slice )
{
// 由傳入參數可知【priv】為【decoder_sys_t *p_sys】
// 片類型
int i_slice_type;
// h264分片對象資訊初始化
// 見第3小節分析
h264_slice_init( p_slice );
// bit stream 【比特流】碼流資訊結構體對象,注意此處不是x264庫中的結構體,而是vlc中自己定義的
bs_t s;
// 【比特流】碼流大小
unsigned i_bitflow = 0;
// 初始化指派碼流資訊
// 見10.1.1小節分析
bs_init( &s, p_buffer, i_buffer );
// 【比特流】碼流大小
s.p_fwpriv = &i_bitflow;
// 轉換功能【是否将模拟3位元組轉換為RBSP即原始位元組序列負載資料】
// 作用其實就是:解碼時脫殼操作即去掉NALU RBSP中的防僞0x03位元組【避免與起始碼競争】
// 見10.1.2小節分析
s.pf_forward = hxxx_bsfw_ep3b_to_rbsp; /* Does the emulated 3bytes conversion to rbsp */
// NALU header頭部資訊
// 【1個位元組:禁止位(1位)、重要性訓示位(2位)、NALU類型(5位)】
/* nal unit header */
// 此處的處理為碼流跳過頭部資訊第一個位元組的第一位即修改裡面的i_left剩餘可用位數為7
// 見10.1.3小節分析
bs_skip( &s, 1 );
// NAL引用重要訓示位
// 讀取碼流目前位元組資料中剩餘可用位數的2位bits資料值
// 見10.1.4小節分析
const uint8_t i_nal_ref_idc = bs_read( &s, 2 );
// NALU類型
// 讀取碼流目前位元組資料中剩餘可用位數的5位bits資料值
const uint8_t i_nal_type = bs_read( &s, 5 );
// 下面接着根據h264 Slice片頭部資訊進行解碼對應h264文法元素資訊的
// 擷取h264片資料中第一個exp-golomb code 指數哥倫布編碼
// 是一種壓縮編碼算法(視訊編碼中有用到這個了,h264,avs)
// 其實就是擷取h24文法中的first_mb_in_slice即片中的第一個宏塊位址,
// 片通過這個句法元素來标定它自己的位址。在幀場自适應模式下,宏塊都是成對出現,
// 這時本句法元素表示的是第幾個宏塊對,
// 對應的第一個宏塊的真實位址應該是:2*first_mb_in_slice */
// 見10.1.5小節分析
/* first_mb_in_slice */
/* int i_first_mb = */ bs_read_ue( &s );
// 片類型,其實就是h264幀類型【I P B SP SI幀】
/* slice_type */
i_slice_type = bs_read_ue( &s );
// 片類型:取餘即為片類型的小值。
// 關于片類型值和h264幀類型的關聯
// 【0:P 1:B 2:I 3:SP 4:SI 5:P 6:B 7:I 8:SP 9:SI】
p_slice->type = i_slice_type % 5;
/* */
p_slice->i_nal_type = i_nal_type;
p_slice->i_nal_ref_idc = i_nal_ref_idc;
// 讀取到PPS id值 參考的圖像參數集索引
p_slice->i_pic_parameter_set_id = bs_read_ue( &s );
// 最大不能大于255
if( p_slice->i_pic_parameter_set_id > H264_PPS_ID_MAX )
return false;
const h264_sequence_parameter_set_t *p_sps;
const h264_picture_parameter_set_t *p_pps;
// 綁定比對/參考的PPS和SPS
/* Bind matched/referred PPS and SPS */
// 該方法調用見10小節中【GetSPSPPS】方法的分析
get_sps_pps( p_slice->i_pic_parameter_set_id, priv, &p_sps, &p_pps );
if( !p_sps || !p_pps )
return false;
// 此處先講一個log2_XXX字段的值的含義:
// 如log2_xxx = y,其實表示的是log2為底真數V的對數等于y,即求V的值
// 即2的y次方 = V。
// 是以下面涉及到的log2命名就知道為啥如此命名并計算它。
// frame_num【解碼順序計算值】:每個參考幀都有一個連續的frame_num作為它們的辨別,
// 它指明了各圖像的解碼順序。非參考幀也有,但沒有意義。
// log2_max_frame_num_minus4 這個句法元素主要是為讀取另一個句法元素 frame_num 服務的,
// frame_num 是最重要的句法元素之一,它辨別所屬圖像的解碼順序 。
// 這個句法元素同時也指明了 frame_num 的所能達到的最大值: MaxFrameNum = 2*exp( log2_max_frame_num_minus4 + 4 )
p_slice->i_frame_num = bs_read( &s, p_sps->i_log2_max_frame_num + 4 );
// frame_mbs_only_flag 本句法元素等于 0 時表示本序列中所有圖像的編碼模式都是幀,
// 沒有其他編碼模式存在;本句法元素等于 1 時表示本序列中圖像的編碼模式可能是幀,
// 也可能是場或幀場自适應,某個圖像具體是哪一種要由其他句法元素決定。
if( !p_sps->frame_mbs_only_flag )
{// 本序列中所有圖像的編碼模式都是幀時
/* field_pic_flag */
// 讀取目前一個比特位的int值,記錄的是圖像場的類型【初始化為0】
p_slice->i_field_pic_flag = bs_read( &s, 1 );
if( p_slice->i_field_pic_flag )
// 讀取目前一個比特位的int值,記錄的是圖像底場的類型【初始化為-1】
p_slice->i_bottom_field_flag = bs_read( &s, 1 );
}
// 是否是IDR圖像中的片資訊 【值為5】
if( p_slice->i_nal_type == H264_NAL_SLICE_IDR )
// IDR圖像片資料時,讀取并解碼哥倫布編碼的資料,此為IDR圖像ID
p_slice->i_idr_pic_id = bs_read_ue( &s );
// pic_order_cnt_type 指明了 poc (picture order count) 的編碼方法,
// poc 辨別圖像的播放順序即圖像序列号。poc 可以由 frame-num 通過映射關系計算得來,
// 也可以索性由編碼器顯式地傳送。
// 圖像顯示/播放順序類型
p_slice->i_pic_order_cnt_type = p_sps->i_pic_order_cnt_type;
if( p_sps->i_pic_order_cnt_type == 0 )
{// 擷取到POC底有效位
// log2_max_pic_order_cnt_lsb_minus4指明了變量MaxPicOrderCntLsb的值:
// MaxPicOrderCntLsb = pow(2, (log2_max_pic_order_cnt_lsb_minus4 + 4) )
p_slice->i_pic_order_cnt_lsb = bs_read( &s, p_sps->i_log2_max_pic_order_cnt_lsb + 4 );
if( p_pps->i_pic_order_present_flag && !p_slice->i_field_pic_flag )
// 若pps圖像順序展示标志位存在并且片的圖像場标志位不存在時,則擷取POC底場偏移值
// 見10.1.6小節分析
p_slice->i_delta_pic_order_cnt_bottom = bs_read_se( &s );
}
else if( (p_sps->i_pic_order_cnt_type == 1) &&
(!p_sps->i_delta_pic_order_always_zero_flag) )
{// delta_pic_order_always_zero_flag等于1時,句法元素delta_pic_order_cnt[0]和 delta_pic_order_cnt[1]不在片頭出現,
// 并且它們的值預設為0;本句法元素等于0時,上述的兩個句法元素将在片頭出現。
// 是以此處需要擷取這兩個元素值
p_slice->i_delta_pic_order_cnt0 = bs_read_se( &s );
if( p_pps->i_pic_order_present_flag && !p_slice->i_field_pic_flag )
p_slice->i_delta_pic_order_cnt1 = bs_read_se( &s );
}
// 注:pps資料是在【vlc/modules/packetizer/h264_nal.c】的
// 【h264_parse_picture_parameter_set_rbsp】方法中指派的
// 若有多餘的圖像展示标志位值,那麼需要将其去掉【跳過該資料】
if( p_pps->i_redundant_pic_present_flag )
bs_read_ue( &s ); /* redudant_pic_count */
// num_ref_idx_l0_active_minus1 加1後指明目前參考幀隊列的長度,
// 即有多少個參考幀(包括短期和長期)。值得注意的是,當目前解碼圖像是場模式下,
// 參考幀隊列的長度應該是本句法元素再乘以2,因為場模式下各幀必須被分解以場對形式存在。
// (這裡所說的場模式包括圖像的場及幀場自适應下的處于場模式的宏塊對)
// 本句法元素的值有可能在片頭被重載。
// num_ref_idx_l0_active_minus1 , num_ref_idx_l1_active_minus1
// 在序列參數集中有句法元素 num_ref_frames 也是跟參考幀隊列有關,
// 它們的差別是num_ref_frames指明參考幀隊列的最大值,解碼器用它的值來配置設定記憶體空間;num_ref_idx_l0_active_minus1
// 指明在這個隊列中目前實際的、已存在的參考幀數目,這從它的名字“active”中也可以看出來。
// 圖像時,并不是直接傳送該圖像的編号,而是傳送該圖像在參考幀隊列中的序号。
// 這個序号并不是在碼流中傳送的,這個句法元素是 H.264 中最重要的句法元素之一,
// 編碼器要通知解碼器某個運動矢量所指向的是哪個參考,而是編碼器和解碼器同步地、用相同的方法将參考圖像放入隊列,
// 進而獲得一個序号。這個隊列在每解一個圖像,甚至是每個片後都會動态地更新。
// 維護參考幀隊列是編解碼器十分重要的工作,而本句法元素是維護參考幀隊列的重要依據。
// 參考幀隊列的複雜的維護機制是 H.264 重要也是很有特色的組成部分。
unsigned num_ref_idx_l01_active_minus1[2] = {0 , 0};
// 分片【幀】類型【0:P 1:B 2:I 3:SP 4:SI 5:P 6:B 7:I 8:SP 9:SI】
if( i_slice_type == 1 || i_slice_type == 6 ) /* B slices */
// 讀取目前位元組中目前可讀比特數的第一位比特值,此處為跳過該值
// 此值為直接空間運動預測辨別位值
bs_read1( &s ); /* direct_spatial_mv_pred_flag */
if( i_slice_type == 0 || i_slice_type == 5 ||
i_slice_type == 3 || i_slice_type == 8 ||
i_slice_type == 1 || i_slice_type == 6 ) /* P SP B slices */
{
// num_ref_idx_active_override_flag為重載PPS中的參考幀隊列中實際可用的參考幀的數目
// 若該比特位值為1則進行讀取
if( bs_read1( &s ) ) /* num_ref_idx_active_override_flag */
{
// 重載值
num_ref_idx_l01_active_minus1[0] = bs_read_ue( &s );
if( i_slice_type == 1 || i_slice_type == 6 ) /* B slices */
// B幀PPS重載值
num_ref_idx_l01_active_minus1[1] = bs_read_ue( &s );
}
}
// 注譯:下面,進一步處理,以确定MMCO 5存在的POC
/* BELOW, Further processing up to assert MMCO 5 presence for POC */
if( p_slice->i_nal_type == 5 || p_slice->i_nal_ref_idc == 0 )
{// P幀的片,P片沒有mmco5,是以直接傳回,不需要以下mmco5的處理流程
/* Early END, don't waste parsing below */
p_slice->has_mmco5 = false;
return true;
}
// 以下mmco5的處理流程
// 參考【幀】圖像清單修正過程處理即參考幀重排序處理
// 每個參考圖像都有frame_num【圖像解碼序列号】,
// 但編碼器要指定目前圖像的參考圖像時使用的ref_id,frame_num->PicNum->ref_id
/* ref_pic_list_[mvc_]modification() */
// 根據h264的NAL類型定義值可知,20和21為保留值
const bool b_mvc = (p_slice->i_nal_type == 20 || p_slice->i_nal_type == 21 );
unsigned i = 0;
// 分片【幀】類型【0:P 1:B 2:I 3:SP 4:SI 5:P 6:B 7:I 8:SP 9:SI】
if( i_slice_type % 5 != 2 && i_slice_type % 5 != 4 )
// 不是I和SI幀時,将i加1,值為1
i++;
if( i_slice_type % 5 == 1 )
// 若為B幀,再加1,值為2
i++;
for( ; i>0; i-- )
{
// 指明List0是否進行重排序
// 擷取目前1個比特位值類型為1時,表明需要重排序
if( bs_read1( &s ) ) /* ref_pic_list_modification_flag_l{0,1} */
{
uint32_t mod;
do
{
// mod記錄:執行哪種重排序操作
// 0: 短期參考幀重排序,abs_diff_pic_num_minus1會出現在碼流中,
// 從目前圖像的PicNum減去(abs_diff_pic_num_minus1+1)後指明需要重排序的圖像。
// 1: 短期參考幀重排序,abs_diff_pic_num_minus1會出現在碼流中,
// 從目前圖像的PicNum加上(abs_diff_pic_num_minus1+1)後指明需要重排序的圖像
// 2: 長期參考幀重排序,long_term_pic_num會出現在碼流中,指明需要重排序的圖像。
// 3: 結束循環,退出重排序操作。
mod = bs_read_ue( &s );
if( mod < 3 || ( b_mvc && (mod == 4 || mod == 5) ) )
// 是以此處的處理:是将這兩個值讀取,但又沒儲存使用,應該是丢棄了這個資料??TODO 後續分析下原因
bs_read_ue( &s ); /* abs_diff_pic_num_minus1, long_term_pic_num, abs_diff_view_idx_min1 */
}
// bs_remain見本小節下面的分析【處理其實就是計算剩餘可讀位元組中的總可讀剩餘比特數,防止越界】
while( mod != 3 && bs_remain( &s ) );
}
}
// 分片【幀】類型【0:P 1:B 2:I 3:SP 4:SI 5:P 6:B 7:I 8:SP 9:SI】
// 權重預測的處理
/* pred_weight_table() */
if( ( p_pps->weighted_pred_flag && ( i_slice_type == 0 || i_slice_type == 5 || /* P, SP */
i_slice_type == 3 || i_slice_type == 8 ) ) ||
( p_pps->weighted_bipred_idc == 1 && ( i_slice_type == 1 || i_slice_type == 6 ) /* B */ ) )
{// weighted_pred_flag用以指明是否允許P和SP片的權重預測,如果允許,在片頭會出現用以計算權重預測的句法元素。
// weighted_bipred_flag[/idc]用以指明是否允許 B 片的權重預測,本句法元素等于 0 時表示使用預設權重預測模式,
// 等于 1 時表示使用顯式權重預測模式,等于 2 時表示使用隐式權重預測模式。
// luma_log2_weight_denom: 給出參考幀清單中參考圖像所有亮度的權重系數,[0..7]
bs_read_ue( &s ); /* luma_log2_weight_denom */
// 色度不同顔色平面标志位為0
if( !p_sps->b_separate_colour_planes_flag ) /* ChromaArrayType != 0 */
// chroma_log2_weight_denom: 給出參考幀清單中參考圖像所有色度的權重系數,[0..7]
bs_read_ue( &s ); /* chroma_log2_weight_denom */
// 層數值,若B幀則為2,否則為1;即2的意思就是若有B幀則代表B、P幀PPS參考幀都需要重載操作
const unsigned i_num_layers = ( i_slice_type % 5 == 1 ) ? 2 : 1;
for( unsigned j=0; j < i_num_layers; j++ )
{
// num_ref_idx_l01_active_minus1[j]該值為B/P/SP的PPS參考序列重載值
// P SP:j = 0
// B: j = 1
for( unsigned k=0; k<=num_ref_idx_l01_active_minus1[j]; k++ )
{
// 1:在參考幀序列0中的亮度的權重系數存在
if( bs_read1( &s ) ) /* luma_weight_l{0,1}_flag */
{
// 讀取luma_weight_l0[k]:用參考序列0預測亮度值時,所用的權重系數。
// 如果luma_weight_l0_flag=0,luma_weight_l0[k]=2exp(luma_log2_weight_denom)。
bs_read_se( &s );
// luma_offset_l0[k]:用參考序列0預測亮度值時,所用的權重系數的偏移,[-128-..127],
// 如果luma_weight_l0_flag=0,該值0
bs_read_se( &s );
}
// 色度不同顔色平面标志位為0
if( !p_sps->b_separate_colour_planes_flag ) /* ChromaArrayType != 0 */
{// 同Luma亮度處理相似,但用于色度
if( bs_read1( &s ) ) /* chroma_weight_l{0,1}_flag */
{// 此處需要如下讀取兩組【chroma_weight_l0】和【chroma_offset_l0】值
// chroma_weight_l0[k]
bs_read_se( &s );
// chroma_offset_l0[k]
bs_read_se( &s );
// chroma_weight_l0[k]
bs_read_se( &s );
// chroma_offset_l0[k]
bs_read_se( &s );
}
}
}
}
}
// 參考圖像序列标記 處理流程
// 前文介紹的重排序操作是對參考幀隊列重新排序,
// 而标記操作負責将參考圖像移入或移出參考幀隊列。
/* dec_ref_pic_marking() */
if( p_slice->i_nal_type != 5 ) /* IdrFlag */
{// 目前片資訊不為IDR圖像中的片時,則處理
// adaptive_ref_pic_marking_mode_flag值為:
// 0:FIFO,使用滑動窗的機制,先入先出,在這種模式下,無法對長期參考幀進行操作,
// 1:自适應标記,後續碼流中會有一系列句法元素顯式指明操作的步驟。
if( bs_read1( &s ) ) /* adaptive_ref_pic_marking_mode_flag */
{// 自适應标記時
// MMCO表示【memory_management_control_operation】記憶體管理控制操作
// 自适應模式下,指明本次操作的具體内容:
// 0:結束循環
// 1:将一個短期參考圖像标記為非參考圖像,也即将一個短期參考圖像移出參考幀隊列
// 2:将一個長期參考圖像标記為非參考圖像,也即将一個長期參考圖像移出參考幀隊列
// 3:将一個短期參考圖像轉為長期參考圖像
// 4:指明長期參考幀的最大數目
// 5:清空參考幀隊列,将所有參考圖像移出參考幀隊列,并禁用長期參考機制
// 6:将目前圖像存存為一個長期參考幀
uint32_t mmco;
do
{
// 擷取其值【0..6】
mmco = bs_read_ue( &s );
if( mmco == 1 || mmco == 3 )
// difference_of_pic_nums_minus1: 通過該元素可以計算出需要操作的圖像在短期參考隊列中的序号
bs_read_ue( &s ); /* diff_pics_minus1 */
if( mmco == 2 )
// long_term_pic_num: 得到所要操作的長期參考圖像的序号
bs_read_ue( &s ); /* long_term_pic_num */
if( mmco == 3 || mmco == 6 )
// long_term_frame_idx: 配置設定一個長期參考幀的序号給一個圖像
bs_read_ue( &s ); /* long_term_frame_idx */
if( mmco == 4 )
// max_long_tewrm_frame_idx_plus1: 此元素減1,指明長期參考隊列的最大數目,[0..num_ref_frames]
bs_read_ue( &s ); /* max_long_term_frame_idx_plus1 */
if( mmco == 5 )
{// 清空參考幀隊列,将所有參考圖像移參考幀隊列,并禁用長期參考機制
// 即此處就是直接退出mmco的任何處理
p_slice->has_mmco5 = true;
break; /* Early END */
}
}
while( mmco > 0 );
}
}
// 注譯:若需要存儲除上面MMCO存在相關資料之外的更多資料,則需要注意代碼中【"Early END"】情況。
/* If you need to store anything else than MMCO presence above, care of "Early END" cases */
return true;
}
// 梳理一下上面方法中關于bs_read_ue和bs_read_se的處理,
// 很多地方都隻讀但沒儲存讀到的值僅僅讀到後就跳過該資料了,
// 應該是VLC推流打包資料中用不到這些資料,然後進行跳過處理?? TODO 後續分析下原因
// [vlc/include/vlc_bits.h]
static inline int bs_remain( const bs_t *s )
{// 主要的處理就是判斷是否資料已讀取完
if( s->p >= s->p_end )
// 資料已讀取完
return 0;
else
// 此處其實就是計算剩餘可讀位元組中的總可讀剩餘比特數
return( 8 * ( s->p_end - s->p ) - 8 + s->i_left );
}
10.1.1、bs_init實作分析:【此處為vlc的實作】
// 【vlc/include/vlc_bits.h】
static inline void bs_init( bs_t *s, const void *p_data, size_t i_data )
{
bs_write_init( s, (void*) p_data, i_data );
// 設定為隻讀 RD only
s->b_read_only = true;
}
// 【vlc/include/vlc_bits.h】
static inline void bs_write_init( bs_t *s, void *p_data, size_t i_data )
{
// 記錄NALU資料開始結束指針位置
s->p_start = (uint8_t *)p_data;
s->p = s->p_start;
s->p_end = s->p_start + i_data;
// 可用位數:其實應該為目前存放資料的資料位數寬度值即目前[p_data]為8位指針存儲的
s->i_left = 8;
s->b_read_only = false;
s->p_fwpriv = NULL;
s->pf_forward = NULL;
}
// 下面的該分析是x264庫中的實作。。。 但不是此處vlc自己的實作,不過已經分析了就先放着吧。。。 不用看
// 【vlc/contrib/contrib-android-arm-lilnux-androideabi/x264/common/bitstream.h】
static inline void bs_init( bs_t *s, void *p_data, int i_data )
{
// 将指針轉為int值後與3進行與運算取二進制最後兩位值作為偏移量值
// offset值可能為0、1、2、3
int offset = ((intptr_t)p_data & 3);
// 是以此處計算指針為:指針往前移動,指向p_data指針的前0或1或2或3個位元組的資料位置,
// 其實作用一個就是指向此前NALU資料前面的起始碼開始位置 TODO
s->p = s->p_start = (uint8_t*)p_data - offset;
// 指向資料末尾
s->p_end = (uint8_t*)p_data + i_data;
// 計算計數值:可用位數。 乘以8是将其轉換為位機關
// WORD_SIZE宏定義為【sizeof(void*)】即計算的是無類型指針變量本身的大小【如32位系統指針變量永遠為32位4位元組】
s->i_left = (WORD_SIZE - offset)*8;
// 計算目前資料的位數
// 此處通過分析應該其作用是【大端或小端處理】即計算機的位元組存儲次序處理
// M32宏定義表示将傳入參數(8位存儲位元組次序)加載和存儲為對齊的32位類型資料
// 【會自動位元組對齊(備注:memcopy不會自動對齊位元組位數)】
s->cur_bits = endian_fix32( M32(s->p) );
// 将目前資料的總位數右移運算
// 由上面可知,offset值可能為0、1、2、3
// 也就是将目前【cur_bits】位數值除以2的1x8、2x8、3x8、4x8次方之後的商即為其值
// 該參數待後續執行個體分析确認其值含義 TODO
s->cur_bits >>= (4-offset)*8;
}
10.1.2、hxxx_bsfw_ep3b_to_rbsp實作分析:
該方法的處理,簡單的說就是将目前碼流資料脫殼後将真正的碼流真實資料往前移動【i_count】位元組,也就是指向後面的指定位置後的新位元組位置,便于後續讀取碼流資料。
//【vlc/modules/packetizer/hxxx_nal.h】
// 注譯:vlc_bits的bs_t的forward前向回調函數,用于剝離【移除】模拟預防三個位元組
/* vlc_bits's bs_t forward callback for stripping emulation prevention three bytes */
static inline uint8_t *hxxx_bsfw_ep3b_to_rbsp( uint8_t *p, uint8_t *end, void *priv, size_t i_count )
{// 傳入參數p為開始位置指針
// 該參數是用于記錄目前比特流bit flow【碼流】位置,
// 注意此處的【bitflow】作用是:标記循環執行次數有效性【見下分析】
// 由初始化可知該值最開始為0
unsigned *pi_prev = (unsigned *) priv;
for( size_t i=0; i<i_count; i++ )
{
// 注意此處P是先往前移了一個位元組即指向了下一個新位元組
if( ++p >= end )
// 若讀取已到尾部,則傳回退出
return p;
// 右移運算,并和0或1進行或運算,若p指針指向值不為空則和0進行或運算,否則為空時和1進行或運算
*pi_prev = (*pi_prev << 1) | (!*p);
// 重要:從Nal提取RBSP時,過濾掉0x3. 就是說在Nal中遇到 00 00 03 時,需要移除掉03
// 擴充:
// SODB 資料比特串-->最原始的編碼資料
// RBSP 原始位元組序列載荷-->在SODB的後面填加了結尾比特(RBSP trailing bits
// 一個bit“1”)若幹比特“0”,以便位元組對齊。
// EBSP 擴充位元組序列載荷-->在RBSP基礎上填加了仿校驗位元組(0X03)它的原因是:
// 在NALU加到Annexb上時,需要填加每組NALU之前的開始碼StartCodePrefix,
// 如果該NALU對應的slice為一幀的開始則用4位位元組表示,ox00000001,否則用3位位元組表示ox000001.
// 為了使NALU主體中不包括與開始碼相沖突的,在編碼時,每遇到兩個位元組連續為0,
// 就插入一個位元組的0x03。解碼時将0x03去掉。也稱為脫殼操作。
if( *p == 0x03 &&
( p + 1 ) != end ) /* Never escape sequence if no next byte */
{// 注譯:如果沒有下一個位元組,永遠不要轉義序列
// 此處0x06 = 0000 0110 主要是判斷pi_prev是否經過了三個位元組的運算,若是則進入
if( (*pi_prev & 0x06) == 0x06 )
{
// 解碼時脫殼操作即去掉NALU中的防僞0x03位元組
++p;
*pi_prev = !*p;
}
}
}
return p;
}
10.1.3、bs_skip實作分析:
該處理為碼流開始位元組剩餘可用位數位置開始跳過i_count位的計算,即修改裡面的i_left剩餘可用位數
// 【vlc/include/vlc_bits.h】
static inline void bs_skip( bs_t *s, ssize_t i_count )
{
// 由此前分析可知目前位元組剩餘可用位數該值初始化為8,如減去1後為7
s->i_left -= i_count;
// 小于等于0才能進入,否則不處理,因為目前位元組中還有剩餘可通路位數
if( s->i_left <= 0 )
{
// i_left大于等于8并且小于16時,i_bytes該值為0,小于8則該值大于0的整數(通常為1),
// 大于等于16則該值小于0的整數
const size_t i_bytes = 1 + s->i_left / -8;
// 此方法為宏定義
// 見下面的分析
bs_forward( s, i_bytes );
if( i_bytes * 8 < i_bytes /* ofw */ )
s->i_left = i_bytes;
else
s->i_left += 8 * i_bytes;
}
}
// 是以最終調用了pf_forward方法,即上面分析過的流程,解碼時脫殼操作去掉NALU中防僞0x03位元組資料
#define bs_forward( s, i ) \
s->p = s->pf_forward ? s->pf_forward( s->p, s->p_end, s->p_fwpriv, i ) : s->p + i
10.1.4、bs_read實作分析:
// 【vlc/include/vlc_bits.h】
static inline uint32_t bs_read( bs_t *s, int i_count )
{
// 33個32位的十六進制表示的掩碼值
static const uint32_t i_mask[33] =
{ 0x00,
0x01, 0x03, 0x07, 0x0f,
0x1f, 0x3f, 0x7f, 0xff,
0x1ff, 0x3ff, 0x7ff, 0xfff,
0x1fff, 0x3fff, 0x7fff, 0xffff,
0x1ffff, 0x3ffff, 0x7ffff, 0xfffff,
0x1fffff, 0x3fffff, 0x7fffff, 0xffffff,
0x1ffffff, 0x3ffffff, 0x7ffffff, 0xfffffff,
0x1fffffff,0x3fffffff,0x7fffffff,0xffffffff};
int i_shr, i_drop = 0;
uint32_t i_result = 0;
// 若碼流讀取位數大于32位則進行記錄剩餘位數drop值
// 是以該方法處理讀取的值為,一次最多讀取32位即4個位元組的資料,将其轉為int值
if( i_count > 32 )
{
i_drop = i_count - 32;
i_count = 32;
}
while( i_count > 0 )
{
if( s->p >= s->p_end )
{// 開始讀取的記憶體位置和碼流資料結尾位置相同,則退出
break;
}
if( ( i_shr = s->i_left - i_count ) >= 0 )
{// 若目前位元組資料剩餘可用位數足夠讀取,則進入此處直接讀取目前位元組中的指定位數值
/* more in the buffer than requested */
// 如目前讀取NAL header位元組中的第二、三位的值并轉換為int值即重要性訓示位值
// 則可知i_shr為5即7 - 2獲得,然後将目前位元組int值進行5位的右移運算,
// 此時得到的值為高3位,然後在于【i_count】值此時為2,
// 和i_mask[i_count]即值為i_mask[2] = 0x03做與運算,
// 即最後是0000 0111 & 0000 0011 計算即可獲得目前位元組中的第二、三位表示的int值
// 注意:這裡還有一個将此前的result值和目前計算的值進行或運算,
// 其目的是為了當讀取位數值大于目前剩餘的可用位數之後會先進入else中進行分組多次讀取,
// 最後一次進入到if中,進而得到完全的所有位數表示的int值。
i_result |= ( *s->p >> i_shr )&i_mask[i_count];
// 更新剩餘可用位數
s->i_left -= i_count;
if( s->i_left == 0 )
{// 若剛好讀取完目前位元組中剩餘位數,則進行下一個位元組的forward流程處理
// 該方法分析見上面小節相關分析
bs_forward( s, 1 );
// 因為是新位元組資料,是以可用位數變為8
s->i_left = 8;
}
break;
}
else
{// 否則目前位元組剩餘可用位數不足
/* less in the buffer than requested */
// 由上面的分析可知,i_shr此時的值為還需要讀取的位數的負值
if( -i_shr == 32 )
// 為i_shr為-32值,表明此前i_left值為0,那麼重置i_result值
i_result = 0;
else
// 通過上面的類似分析,可知此處處理為先讀取剩餘可用位數,并将其進行左運算符計算,
// 即将目前位元組【8位】中可用位數表示的int值和【-i_shr】值進行左運算符計算,
// 實際就是将其值置為合适的高位位置中,然後可能再次進入到此處
// 即分組多次讀取對應位數位置上表示的int值,将其進行和此前結果或運算,
// 最終得到請求的【i_count】多位數表示的32位int值。
i_result |= (*s->p&i_mask[s->i_left]) << -i_shr;
// 減去本次已讀取的位數值
i_count -= s->i_left;
// 進行下一個位元組的forward流程處理
// 該方法分析見上面小節相關分析
bs_forward( s, 1);
// 因為是新位元組資料,是以可用位數變為8
s->i_left = 8;
}
}
if( i_drop )
// 此處的處理結果:會将目前讀取資料指針位置,向後移動i_drop個位元組資料,
// 即跳過這些位元組資料。
// 目前不知道這麼做的理由是啥,後續見到大于32位讀取時再結合場景分析吧 TODO
bs_forward( s, i_drop );
// 傳回目前請求讀取位數的int轉換值資料
return( i_result );
}
10.1.5、bs_read_ue實作分析:【請檢視下一章分析】
【十五】【vlc-android】vlc-sout流媒體輸出端源碼實作分析【Part 2】【03】