流媒體基本要點簡述:如何在H264資料中擷取PTS?

來源:互聯網
上載者:User

這裡pts的base_clock都是按照1000(毫秒)計算,如果複用到ts裡,base_clock是90k,所以還應該再乘以90。關於H264中sps裡面記錄的幀率是實際幀率的2倍,包括slice裡面的pic_order_cnt_lsb也是2倍遞增,我推測可能是編碼按照分場(頂場、底場)編碼所致。

H264的ES未經處理資料一般是以NAL(Network 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判斷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)的結束位置。

 // 判斷是否幀結束   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類型。

 // 根據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了。

方法一、根據前後幀的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欄位,從命名上,貌似是可以用 來標記是否逐場,還是分奇偶場編碼。以上都屬猜測,有請高人解惑。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.