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