流媒體基本要點簡述:如何在H264數據中獲取PTS?

 流媒體基本要點簡述:如何在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。視頻

  
  
           
  
  
  1. // 檢查是不是slice  
  2. if ( i_nal_type < 1/*NAL_SLICE*/ || i_nal_type > 5/*NAL_SLICE_IDR*/ )  
  3.    // 找到slice!!!!! 

在找到slice的NALU後,能夠逐字節將NALU的數據與0x80進行與運算,結果爲真表示這個slice(視頻幀FRAME)的結束位置。it

  
  
           
  
  
  1. // 判斷是否幀結束  
  2. for (uint32_t i = 3; i < nal_length; i++)  
  3. {  
  4.     if (p_nal[i] & 0x80)  
  5.     {  
  6.         // 找到frame_begin!!!!上一幀frame的結束,下一幀frame的開始 
  7.     }  
  8. }  

上面的這個代碼是摘抄自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

  
  
           
  
  
  1. // 根據slice類型判斷幀類型  
  2. switch(slice.i_slice_type)  
  3. {  
  4. case 2: case 7:   
  5. case 4: case 9:  
  6.     *p_flags = 0x0002/*BLOCK_FLAG_TYPE_I*/;  
  7.     break;  
  8. case 0: case 5:  
  9. case 3: case 8:  
  10.     *p_flags = 0x0004/*BLOCK_FLAG_TYPE_P*/;
  11.      break;  
  12. case 1:  
  13. case 6:  
  14.     *p_flags = 0x0008/*BLOCK_FLAG_TYPE_B*/;  
  15.     break;  
  16. default:  
  17.     *p_flags = 0;  
  18.     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  

相關文章
相關標籤/搜索