首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

H.264 碼流中 PTS 解析全攻略:從 NALU 分割到時間戳計算

     編輯:關於C語言

前言

在流媒體開發中,從 H.264 原始碼流(ES)提取呈現時間戳(PTS)是關鍵技術。本文系統整理從 NALU 分割、SPS 解析幀率、Slice 類型判斷、幀邊界偵測,到兩種 PTS 計算方法的完整流程,並提供核心程式碼片段與公式推導。

一、基礎架構:NALU 與 RBSP

H.264 ES 資料以 NALU(Network Abstraction Layer Unit)為單位傳輸/儲存。每個 NALU = NALU Header + RBSP(Raw Byte Sequence Payload)。

第一步:將位元流分割為獨立 NALU(依起始碼 0x000001 或 0x00000001 切分)。

二、關鍵參數獲取:SPS 中的幀率資訊

  • NALU 類型 nal_type == 0x07 為 SPS(Sequence Parameter Set)。
  • 解析 SPS 取得 time_scale 與 num_units_in_tick,幀率公式:
fps = time_scale / num_units_in_tick

注意:標準中此值通常為實際幀率的 2 倍(因場編碼),後續計算需除以 2 或對應調整。

三、Slice 識別與幀邊界偵測

1. 判斷是否為 Slice

// nal_type 範圍 1~5 為 Slice 類型
if (i_nal_type < 1 || i_nal_type > 5) {
    // 找到 Slice NALU
}

2. 簡易幀結束判斷(FFmpeg 風格)

掃描 NALU 資料,若某位元組與 0x80 邏輯與為真,視為幀邊界(實為 first_mb_in_slice == 0 的副作用)。

for (uint32_t i = 3; i < nal_length; i++) {
    if (p_nal[i] & 0x80) {
        // 找到 frame_begin:上一幀結束、下一幀開始
    }
}

3. 正規做法:解析 Slice Header 取得 first_mb_in_slice

完整解析 Slice Header,若 first_mb_in_slice == 0(UE 解碼後),即為新幀開始。VLC 採用 nal_type + slice_type 組合判斷。

四、幀類型(I/P/B)判斷

根據 slice_type 對映:

switch (slice_type) {
    case 2: case 7: case 4: case 9:  flags = I_FRAME; break; // I
    case 0: case 5: case 3: case 8:  flags = P_FRAME; break; // P
    case 1: case 6:                  flags = B_FRAME; break; // B
    default:                         flags = 0;       break;
}

五、PTS 計算兩大方法

方法一:幀緩存 + 顯示順序推導(需緩存一個 GOP)

H.264 編碼順序 ≠ 顯示順序。例:

編碼順序:  I  P  B  B  I  P  B  B ...
顯示順序:  1  4  2  3  5  8  6  7 ...

公式:

PTS = 1000 * (display_order) * (time_scale / num_units_in_tick)

需緩存至少一個 GOP 才能確知 P 幀的顯示位置(因其在後續 B 幀之後)。

方法二:直接使用 pic_order_cnt_lsb(無需幀緩存)

每個 Slice Header 含 pic_order_cnt_lsb,表示該幀在當前 GOP 內的顯示順序(以 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)
  • i_frame_counter:最近一個 IDR 幀(I 幀)的幀序號(以 2 為單位累加)。
  • pic_order_cnt_lsb:當前幀在 GOP 內的相對顯示順序(0, 2, 4, 6...)。

實例驗證

幀類型:     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 ...
POC lsb:    0  6  2  4  0  6  2  4  0  6  2  ...

第 11 幀(B 幀)PTS 計算:1000 * (9*2 + 2) * (time_scale/num_units_in_tick)。

六、封裝到 TS 時的時鐘基準轉換

上述 PTS 基準為 1ms(1000 Hz)。MPEG-TS 要求 90 kHz 基準,故最終須乘以 90:

PTS_90k = PTS_1ms * 90

七、關於 SPS 幀率與 POC 為何為 2 倍關係的推測

推測與場編碼有關:SPS 中 time_scale/num_units_in_tick = 2 * fps,POC 以 2 遞增,對應頂場/底場。SPS 中的 offset_for_top_to_bottom_field 或可指示場結構,但屬推測,待專家確認。

結語

掌握 NALU 分割、SPS/Slice 解析、POC 機制,即可在無容器封裝的裸流中精準重建 PTS。方法二(POC 法)因無需緩存、延遲低,更適合即時串流解析場景。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved