程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 流媒體基本要點簡述:如何在H264數據中獲取PTS?

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

編輯:關於C語言

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

序:

只大概說明要點。更具體的方法恕不祥敘。
我的開源工程和很多開源項目都有詳細完整的實現代碼。
這些要點都是我自己學習的總結,無責任保證正確性。僅做參考。
如發現有問題請丟磚頭,跪求各方高人指正錯誤。Orz

內容:

H264的ES原始數據一般是以NALNetwork Abstract Layer)的格式存在。可以直接用於文件存儲和網絡傳輸。每一個NALU(Network Abstract Layer Unit)數據,是由數據頭+RBSP數據組成。

首先需要將數據流,分割成一個一個獨立的NALU數據。

接著獲取NALU的nal_type,i_nal_type的值等於0x7表示這個nalu是個sps數據包。找到並解析這個sps數據包,裡面包含有非常重要的幀率信息
time_scale/num_units_in_tick=fps

然後根據nal_type判斷sliceH264中的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)的結束位置。

  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類型。

  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了。

方法一、根據前後幀的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幀顯示順序)*幀率

方法二、每一個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
 

本文出自 “C+偵探de褲子” 博客,請務必保留此出處http://70565912.blog.51cto.com/1358202/533736

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved