流媒體基本要點簡述:如何在H264數據中獲取PTS?緩存
序:網絡
只大概說明要點。更具體的方法恕不祥敘。
個人開源工程和不少開源項目都有詳細完整的實現代碼。
這些要點都是我本身學習的總結,無責任保證正確性。僅作參考。
如發現有問題請丟磚頭,跪求各方高人指正錯誤。Orzide
內容:學習
H264的ES原始數據通常是以NAL(Network Abstract Layer)的格式存在。能夠直接用於文件存儲和網絡傳輸。每個NALU(Network Abstract Layer Unit)數據,是由數據頭+RBSP數據組成。ui
首先須要將數據流,分割成一個一個獨立的NALU數據。編碼
接着獲取NALU的nal_type,i_nal_type的值等於0x7表示這個nalu是個sps數據包。找到並解析這個sps數據包,裏面包含有很是重要的幀率信息
time_scale/num_units_in_tick=fpsspa
而後根據nal_type判斷slice(H264中的slice相似一個視頻幀FRAME的概念)。其中nal_type值小於0x1,或大於0x5,表示這個NALU屬於一個slice。視頻
- // 檢查是不是slice
- if ( i_nal_type < 1/*NAL_SLICE*/ || i_nal_type > 5/*NAL_SLICE_IDR*/ )
- // 找到slice!!!!!
在找到slice的NALU後,能夠逐字節將NALU的數據與0x80進行與運算,結果爲真表示這個slice(視頻幀FRAME)的結束位置。it
- // 判斷是否幀結束
- for (uint32_t i = 3; i < nal_length; i++)
- {
- if (p_nal[i] & 0x80)
- {
- // 找到frame_begin!!!!上一幀frame的結束,下一幀frame的開始
- }
- }
上面的這個代碼是摘抄自FFMPEG。他實際做用是判斷slice裏面的first_mb_in_slice,即第1個宏塊在slice中的位置,若是是一幀開始,這個字段的值確定是標識第1個宏塊。所以,也能夠完整解析slice的頭部信息,解析出first_mb_in_slice,若是是0(注意:這是1個哥倫布數值),即這個NALU是一幀的開始。
爲何這裏的代碼是逐字節判斷0x80?我額外寫點某大神的名言:程序猿不是十萬個爲何,不是維基猿,程序猿是需求猿。若是某程序猿已經着手開始研究如何解析slice頭部格式,他很天然的不會有這個疑問。
另外經過nal_type以及silice_type也能夠判斷出幀結束位置,VLC裏面的代碼就是這麼幹。
解析到位於幀結束位置的NALU,就能夠判斷出每一幀(slice)的開始和結尾。解析slice的slice_type,根據slice_type,能夠判斷出這個slice的IPB類型。class
- // 根據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;
- }
從如今開始,就有兩種辦法來計算PTS了。
方法1、根據先後幀的IPB類型,能夠得知幀的實際顯示順序,使用前面獲取的sps信息中的幀率,以及幀計數frame_count便可計算出PTS。此方法須要作幾幀緩存(通常緩存一個group的長度)。
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 ... 幀顯示順序
一個I幀與下一個I幀之間,是一個group。
從上圖可見,P類型的幀的顯示順序,是排在後面最後一個B幀以後。
因此要獲取第7幀的pts,起碼要知道他下一幀的類型,才能得知他的顯示順序。
第8幀的pts=1000(毫秒)*7(幀顯示順序)*幀率
方法2、每個slice的信息裏面,都記錄有pic_order_cnt_lsb,當前幀在這個group中的顯示順序。經過這個pic_order_cnt_lsb,能夠直接計算出當前幀的PTS。此方法不須要作幀緩存。
計算公式:
pts=1000*(i_frame_counter + pic_order_cnt_lsb)*(time_scale/num_units_in_tick)
i_frame_counter是最近一次I幀位置的幀序,經過I幀計數+當前group中的幀序,獲得幀實際顯示序列位置,乘上幀率,再乘上1000(毫秒)的base_clock(基本時鐘頻率),獲得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
細心一點能夠注意到,在上圖,slice裏面的pic_order_cnt_lsb是以2進行遞增。
一般H264裏面的sps中記錄的幀率,也是實際幀率的2倍time_scale/num_units_in_tick=fps*2
所以,實際的計算公式應該是這樣
pts=1000*(i_frame_counter*2+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應該是這麼計算
1000*(9*2+2)*(time_scale/num_units_in_tick)
結束語: 這裏pts的base_clock都是按照1000(毫秒)計算,若是複用到ts裏,base_clock是90k,因此還應該再乘以90。 題外話:關於H264中sps裏面記錄的幀率是實際幀率的2倍,包括slice裏面的pic_order_cnt_lsb也是2倍遞增,我推測多是編碼按照分場(頂場、底場)編碼所致。另外我注意到sps信息中的offset_for_top_to_bottom_field字段,從命名上,貌似是能夠用來標記是否逐場,仍是分奇偶場編碼。以上都屬猜想,有請高人解惑。 Orz