原文地址: http://70565912.blog.51cto.com/1358202/533736/
只大概說(shuō)明要點(diǎn)肛走。更具體的方法恕不祥敘。
我的開(kāi)源工程和很多開(kāi)源項(xiàng)目都有詳細(xì)完整的實(shí)現(xiàn)代碼潮瓶。
這些要點(diǎn)都是我自己學(xué)習(xí)的總結(jié)痕檬,無(wú)責(zé)任保證正確性碴开。僅做參考哼鬓。
如發(fā)現(xiàn)有問(wèn)題請(qǐng)丟磚頭监右,跪求各方高人指正錯(cuò)誤。Orz
內(nèi)容:
H264的ES原始數(shù)據(jù)一般是以NAL(Network Abstract Layer)的格式存在异希〗『校可以直接用于文件存儲(chǔ)和網(wǎng)絡(luò)傳輸。每一個(gè)NALU(Network Abstract Layer Unit)數(shù)據(jù)称簿,是由數(shù)據(jù)頭+RBSP數(shù)據(jù)組成扣癣。
首先需要將數(shù)據(jù)流,分割成一個(gè)一個(gè)獨(dú)立的NALU數(shù)據(jù)憨降。
接著獲取NALU的nal_type父虑,i_nal_type的值等于0x7表示這個(gè)nalu是個(gè)sps數(shù)據(jù)包。找到并解析這個(gè)sps數(shù)據(jù)包授药,里面包含有非常重要的幀率信息
time_scale/num_units_in_tick=fps
然后根據(jù)nal_type判斷slice(H264中的slice類似一個(gè)視頻幀F(xiàn)RAME的概念)士嚎。其中nal_type值小于0x1,或大于0x5悔叽,表示這個(gè)NALU屬于一個(gè)slice莱衩。
// 檢查是否是slice
if ( i_nal_type < 1/NAL_SLICE/ || i_nal_type > 5/NAL_SLICE_IDR/ )
// 找到slice!!!!!
在找到slice的NALU后,可以逐字節(jié)將NALU的數(shù)據(jù)與0x80進(jìn)行與運(yùn)算娇澎,結(jié)果為真表示這個(gè)slice(視頻幀F(xiàn)RAME)的結(jié)束位置膳殷。
// 判斷是否幀結(jié)束
for (uint32_t i = 3; i < nal_length; i++)
{
if (p_nal[i] & 0x80)
{
// 找到frame_begin!!!!上一幀frame的結(jié)束,下一幀frame的開(kāi)始
}
}
上面的這個(gè)代碼是摘抄自FFMPEG。他實(shí)際作用是判斷slice里面的first_mb_in_slice九火,即第1個(gè)宏塊在slice中的位置,如果是一幀開(kāi)始册招,這個(gè)字段的值肯定是標(biāo)識(shí)第1個(gè)宏塊岔激。因此,也可以完整解析slice的頭部信息是掰,解析出first_mb_in_slice虑鼎,如果是0(注意:這是1個(gè)哥倫布數(shù)值),即這個(gè)NALU是一幀的開(kāi)始键痛。
為什么這里的代碼是逐字節(jié)判斷0x80炫彩?我額外寫點(diǎn)某大神的名言:程序猿不是十萬(wàn)個(gè)為什么,不是維基猿絮短,程序猿是需求猿江兢。如果某程序猿已經(jīng)著手開(kāi)始研究如何解析slice頭部格式,他很自然的不會(huì)有這個(gè)疑問(wèn)丁频。
另外通過(guò)nal_type以及silice_type也可以判斷出幀結(jié)束位置杉允,VLC里面的代碼就是這么干邑贴。
解析到位于幀結(jié)束位置的NALU,就可以判斷出每一幀(slice)的開(kāi)始和結(jié)尾叔磷。解析slice的slice_type拢驾,根據(jù)slice_type,可以判斷出這個(gè)slice的IPB類型改基。
// 根據(jù)slice類型判斷幀類型
switch(slice.i_slice_type)
{
case 2: case 7:
case 4: case 9:
p_flags = 0x0002/BLOCK_FLAG_TYPE_I/;
break;
case 0: case 5:
case 3: case 8:
p_flags = 0x0004/BLOCK_FLAG_TYPE_P/;
break;
case 1:
case 6:
p_flags = 0x0008/BLOCK_FLAG_TYPE_B/;
break;
default:
p_flags = 0;
break;
}
從現(xiàn)在開(kāi)始繁疤,就有兩種辦法來(lái)計(jì)算PTS了。
方法一秕狰、根據(jù)前后幀的IPB類型稠腊,可以得知幀的實(shí)際顯示順序,使用前面獲取的sps信息中的幀率封恰,以及幀計(jì)數(shù)frame_count即可計(jì)算出PTS麻养。此方法需要做幾幀緩存(一般緩存一個(gè)group的長(zhǎng)度)。
I P B B I P B B I P B ... 幀類型
1 2 3 4 5 6 7 8 9 10 11 ... 第幾幀
1 4 2 3 5 8 6 7 9 12 10 ... 幀顯示順序
一個(gè)I幀與下一個(gè)I幀之間诺舔,是一個(gè)group鳖昌。
從上圖可見(jiàn),P類型的幀的顯示順序低飒,是排在后面最后一個(gè)B幀之后许昨。
所以要獲取第7幀的pts,起碼要知道他下一幀的類型褥赊,才能得知他的顯示順序糕档。
第8幀的pts=1000(毫秒)7(幀顯示順序)幀率
方法二、每一個(gè)slice的信息里面拌喉,都記錄有pic_order_cnt_lsb速那,當(dāng)前幀在這個(gè)group中的顯示順序。通過(guò)這個(gè)pic_order_cnt_lsb尿背,可以直接計(jì)算出當(dāng)前幀的PTS端仰。此方法不需要做幀緩存。
計(jì)算公式:
pts=1000(i_frame_counter + pic_order_cnt_lsb)(time_scale/num_units_in_tick)
i_frame_counter是最近一次I幀位置的幀序田藐,通過(guò)I幀計(jì)數(shù)+當(dāng)前group中的幀序荔烧,得到幀實(shí)際顯示序列位置,乘上幀率汽久,再乘上1000(毫秒)的base_clock(基本時(shí)鐘頻率)鹤竭,得到PTS。
I P B B I P B B I P B ... 幀類型
1 2 3 4 5 6 7 8 9 10 11 ... 第幾幀
1 4 2 3 5 8 6 7 9 12 10 ... 幀顯示順序
0 6 2 4 0 6 2 4 0 6 2 ... pic_order_cnt_lsb
細(xì)心一點(diǎn)可以注意到景醇,在上圖臀稚,slice里面的pic_order_cnt_lsb是以2進(jìn)行遞增。
通常H264里面的sps中記錄的幀率啡直,也是實(shí)際幀率的2倍time_scale/num_units_in_tick=fps*2
因此烁涌,實(shí)際的計(jì)算公式應(yīng)該是這樣
pts=1000(i_frame_counter2+pic_order_cnt_lsb)* (time_scale/num_units_in_tick)
或者是
pts=1000(i_frame_counter+pic_order_cnt_lsb/2) (time_scale/num_units_in_tick/2)
所以苍碟,第11幀的pts應(yīng)該是這么計(jì)算
1000(92+2)*(time_scale/num_units_in_tick)
結(jié)束語(yǔ):
這里pts的base_clock都是按照1000(毫秒)計(jì)算,如果復(fù)用到ts里撮执,base_clock是90k微峰,所以還應(yīng)該再乘以90。
題外話:關(guān)于H264中sps里面記錄的幀率是實(shí)際幀率的2倍抒钱,包括slice里面的pic_order_cnt_lsb也是2倍遞增蜓肆,我推測(cè)可能是編碼按照分場(chǎng)(頂場(chǎng)、底場(chǎng))編碼所致谋币。另外我注意到sps信息中的offset_for_top_to_bottom_field字段仗扬,從命名上,貌似是可以用來(lái)標(biāo)記是否逐場(chǎng)蕾额,還是分奇偶場(chǎng)編碼早芭。以上都屬猜測(cè),有請(qǐng)高人解惑诅蝶。 Orz