# H.264 [H.264源代碼解析](https://miaopei.github.io/2019/05/28/FFmpeg/FFmpeg%E7%9A%84H.264%E8%A7%A3%E7%A0%81%E5%99%A8%E6%BA%90%E4%BB%A3%E7%A0%81%E7%AE%80%E5%8D%95%E5%88%86%E6%9E%90/) [H.264 Video Encoder IP Core](https://github.com/openasic-org/xk264/blob/main/README.md) [H264基礎知識](https://juejin.cn/post/7254384256281264188) [H264開源RTL IP](http://www.openasic.org/) ## Compression 壓縮 參考影片 勘誤(DCT為有損壓縮)[參考影片](https://www.youtube.com/watch?v=QUVAmrtlyXM) DCT(離散餘弦轉換)[Wikipedia](https://zh.wikipedia.org/zh-tw/%E7%A6%BB%E6%95%A3%E4%BD%99%E5%BC%A6%E5%8F%98%E6%8D%A2) ## YCbCr,YUV和RGB [YCbCr,YUV和RGB](https://cs.pynote.net/ag/image/202204032/)  4:4:4表示完全取樣。 4:2:2表示2:1的水平取樣,垂直完全採樣。 4:2:0表示2:1的水平取樣,垂直2:1採樣。 4:1:1表示4:1的水平取樣,垂直完全採樣。 ## DCT (Discrete Cosine Transform) 離散餘弦變換 [視頻講解](https://www.youtube.com/watch?v=0_ay7opMTG4) ## CAVLC [CAVLC and CABAC](https://blog.csdn.net/Times_poem/article/details/86680770) [CAVLC手把手教你編碼](https://blog.csdn.net/sunshine1314/article/details/1685948) CAVLC Flow  [CAVLC詳細過程介紹](https://blog.csdn.net/qq_42139383/article/details/118368429?ops_request_misc=%257B%2522request%255Fid%2522%253A%252244C46F7A-BDB5-4EAA-8359-23EEA8410624%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=44C46F7A-BDB5-4EAA-8359-23EEA8410624&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-118368429-null-null.142^v100^pc_search_result_base3&utm_term=cavlc&spm=1018.2226.3001.4187) ## CABAC [CABAC](https://blog.csdn.net/yinyouzhuang/article/details/141093041?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522D707F232-9D5D-4294-B7DD-10A4E1C87D31%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=D707F232-9D5D-4294-B7DD-10A4E1C87D31&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-2-141093041-null-null.142^v100^pc_search_result_base3&utm_term=CABAC&spm=1018.2226.3001.4187) # H264 架構流程圖  ### Encoder(forward Path) 一個輸入的幀或場Fn以宏區塊為單位進行處理。每個宏區塊以內部(Intra)或外部(Inter)模式進行編碼,並基於重建後的圖像樣本為每個宏區塊中的區塊生成預測 PREDPREDPRED(在圖 6.1 中標記為 "P")。在 Intra 模式下,PRED 是由當前片中已經編碼、解碼和重建的樣本生成的(在圖中標記為 uFn;注意,未經濾波的樣本用於生成 PRED)。在 Inter 模式下,PRED 通過從列表 0 和/或列表 1 參考圖像集中選取的參考圖像進行運動補償預測來生成。圖中顯示的參考圖像為先前編碼的圖像 Fn−1,但對於每個宏區塊分區(在 Inter 模式中),可以從已編碼、重建和濾波的先前或未來(顯示順序)圖像中選擇預測參考圖像。 預測 PRED 從當前區塊中減去,以產生殘差(差異)區塊 Dn,該區塊會經過轉換(使用區塊轉換)和量化,生成 X,即一組量化的轉換係數,這些係數會被重新排序並進行熵編碼。經熵編碼的係數與解碼每個宏區塊內區塊所需的輔助資訊(如預測模式、量化參數、運動向量資訊等)一起構成壓縮的位元流,該位元流會傳遞至網路抽象層(NAL)進行傳輸或儲存。 **誤區注意** *Reconstruction path反量化以及反DCT後有路徑回到Choose Intra Prediction是為了提供方Intra預測參考數據,這是因為H264中的Intra預測是基於當前幀內已經編碼且重建的相鄰macroblock來進行的*。 ### Decoder 解碼器從網路抽象層(NAL)接收壓縮的位元流,並對數據元素進行熵解碼,得到一組量化係數 \( X \)。這些係數經過縮放和反變換後產生差異塊 \( D_n \)(與編碼器中的 \( D_n \) 相同)。根據從位元流解碼的標頭資訊,解碼器生成一個預測塊 \( PRED \),該預測塊與編碼器中生成的原始預測 \( PRED \) 相同。接著將 \( PRED \) 加到 \( D_n \) 上得到未濾波的重建塊 \( uF_n \),並進行濾波處理以生成每個解碼後的區塊 \( F_n \)。 ## H.264 Profiles  ## **H.264 Bitstream 的基本結構** ### **每個 H.264 bitstream 由許多個 NALU 組成** ```ccs [ Start Code ] [ NALU ] [ Start Code ] [ NALU ] [ Start Code ] [ NALU ] ... ``` 其中: - **Start Code (起始碼)**:標記 **新 NALU 的開始** - **NALU (Network Abstraction Layer Unit)**:包含一塊 **H.264 編碼數據** - **NAL Header (NAL 頭部)**:NALU 的 metadata,告訴我們這是哪種類型的數據 - **NAL Payload (NAL 負載)**:真正的 H.264 編碼數據(可能是 SPS, PPS, Slice, SEI 等) --- ## **Start Code(起始碼)** 每個 NALU **前面** 會有一個 **起始碼**,告訴解碼器「這裡開始是一個新的 NALU」 **常見起始碼格式**: - **4-byte start code**:`00 00 00 01`(通常用於 bitstream) - **3-byte start code**:`00 00 01`(通常用於 RTP 傳輸) ✅ **範例:** ``` 00 00 00 01 [NALU 1] 00 00 00 01 [NALU 2] 00 00 00 01 [NALU 3] ``` 每個 `00 00 00 01` 都代表一個新的 NALU 開始。 --- ## **NALU 的結構** **每個 NALU = NAL Header + NAL Payload** ```ccs [ NAL Header ] [ NAL Payload ] ``` ### **🔹 NAL Header (1 byte)** | Bit | 名稱 | 作用 | |-----|----------------|----------------| | 0-1 | forbidden_zero_bit | 一定是 `0`(避免錯誤檢測) | | 2-3 | nal_ref_idc | 優先級(0=最低,3=最高) | | 4-7 | nal_unit_type | 這個 NALU 的類型 | ✅ **NAL Header 範例** ```ccs 67 (0110 0111) // SPS (nal_unit_type = 7) 68 (0110 1000) // PPS (nal_unit_type = 8) ``` --- ## **H.264 重要的 NALU 類型** | nal_unit_type | 名稱 | 作用 | |--------------|----------------------|----------------| | 1 | 非 IDR 影像的 Slice | P/B Frame 影像數據 | | 5 | IDR 影像的 Slice | I Frame 影像數據 | | 6 | SEI (補充增強資訊) | 額外資訊(如時間戳) | | 7 | **SPS (序列參數集)** | 設定影像編碼格式(解析度, GOP, profile) | | 8 | **PPS (圖片參數集)** | 設定影像參數(Entropy 編碼模式) | --- ## **H.264 Bitstream 範例解析** 假設你用 **hex dump** 查看 bitstream,得到: ```makefile 00000000: 00 00 00 01 67 42 00 1F AB 40 50 1E D9 08 00 00 00000010: 00 01 68 CE 3C 80 00 00 00 01 65 88 84 00 04 3F ``` ### **解析** 1️⃣ **第一個 NALU** ```r 00 00 00 01 67 42 00 1F AB 40 50 1E D9 08 00 00 ``` - `00 00 00 01` → **Start Code** - `67` → **SPS (nal_unit_type = 7)** - `42 00 1F AB 40 50 1E D9 08` → **SPS Payload** 2️⃣ **第二個 NALU** ```mathematica 00 00 00 01 68 CE 3C 80 ``` - `00 00 00 01` → **Start Code** - `68` → **PPS (nal_unit_type = 8)** - `CE 3C 80` → **PPS Payload** 3️⃣ **第三個 NALU** ```r 00 00 00 01 65 88 84 00 04 3F ``` - `00 00 00 01` → **Start Code** - `65` → **IDR Slice (nal_unit_type = 5)** - `88 84 00 04 3F` → **影像數據** --- ## **如何分析 H.264 Bitstream?** ### **方法 1:手動用 hex dump 分析** 🔹 用 `xxd` 或 `hexdump` 查看 bitstream: ```sh xxd myfile.264 | head -n 30 ``` 🔹 找到 `00 00 00 01`(Start Code),確認 `NALU` 類型 --- ### **方法 2:用 H.264 分析工具** 1️⃣ **使用 `h264nal` 工具** ```sh pip install h264nal h264nal myfile.264 ``` 可以解析 bitstream,顯示 **SPS, PPS, Slice** 內容。 2️⃣ **使用 `ffprobe`** ```sh ffprobe -show_frames -show_packets -i myfile.264 ``` 可以顯示 bitstream 內的 frame 和 NALU。 --- ## **🎯 總結** ✅ **H.264 bitstream** 由多個 **NALU** 組成 ✅ **NALU 結構 = Start Code + NAL Header + NAL Payload** ✅ **SPS (7)** 記錄解析度、影像格式 ✅ **PPS (8)** 記錄 Entropy 編碼模式 ✅ **Slice (1,5)** 是真正的影像數據 ✅ **用 hex dump 找 `00 00 00 01` 確認 NALU** # Inter Prediction NOTES:一个自然的问题是,如果没有找到合适的匹配块怎么办?答案其实很简单,这个宏块采用帧内预测即可。因此,在H.264以及H.265技术中,P帧和B帧都是可以有 Intra-coded 宏块的。所以,在 P 和 B 的 Slice 中,都可以看到存在若干的 I 的帧内预测方式的宏块 。 ## P/B 幀中使用 Intra 編碼:是 Macroblock 還是 Block? 在 H.264/AVC 中,決定「改用 Intra 編碼」是發生在 **整個宏塊(macroblock)** 層級,而不是單純某個子塊(block)預測失敗就局部切換。具體來說: ### 1. 宏塊級別的模式選擇 編碼器對每個 16×16 宏塊,會計算多種候選模式的 Rate–Distortion 成本(Inter 各種分割+MV、Intra_4×4、Intra_8×8、Intra_16×16…),選擇成本最低的那一個來編碼整個宏塊 👉 參考資料:[高玩梁的博客](https://gwliang.com/en/posts/intra-prediction-and-inter-prediction/?utm_source=chatgpt.com) - 如果最便宜的是某種 **Inter** 模式,整個宏塊就以 Inter 預測+殘差編碼。 - 如果 Intra 的成本更低,則整個宏塊就以 **Intra‑coded**(空間內部)方式處理,並不混合部分子塊做 Inter。 --- ### 2. 不能在同一宏塊內混用 Inter 和 Intra 標準只允許在 P/B Slice 內「**混用**」的是不同的宏塊(有些宏塊是 Inter‑coded,有些宏塊是 Intra‑coded),**而非在同一個宏塊裡對部分 4×4 區塊用 Intra,部分用 Inter** 👉 參考資料:[Wikipedia: Video Compression Picture Types](https://en.wikipedia.org/wiki/Video_compression_picture_types?utm_source=chatgpt.com) > 換句話說,Inter 失效不會只影響一個子塊:若整個宏塊改用 Intra,那它的 `mb_type` 就直接變成 `I_4×4`、`I_16×16`(或 `I_PCM`);否則仍舊全塊走 Inter。 --- ### 3. 區塊大小與分割 雖然在 Inter 模式下,一個宏塊可以被切成 16×16、16×8、8×16、8×8…甚至更細的 sub‑block 做 motion estimation,但這些都是 **同屬 Inter**。 只有當「整個宏塊」選擇 Intra 模式,才會完全走 Intra 的預測流程。 # 🔁 H.264 Skip Mode 條件與 MV Predictor 說明 在 H.264 中,Encoder 只有在 **以下兩個條件都滿足** 時,才會將一個 Macroblock 設定為 **Skip Mode**,達到最高的壓縮率。 --- ## ✅ Skip Mode 成立的兩個條件 ### 1️⃣ 實際 MV == MV Predictor - MV Predictor 是用鄰近的 macroblock(通常是左、上、左上)計算出來的。 - 一般採用中位數(median prediction)來預測 MV。 - Encoder 用這個 **預測出來的 MV** 去 reference frame 拿預測區塊,若發現: - 就是最佳的整體 MV(經過 IME/FME 後還是一樣) - **→ delta MV = 0** --- ### 2️⃣ Residual = 0 - 用預測的區塊與 current block 相減,若整塊 residual: - transform & quantize 後得到全 0(所有係數都是 0) - **→ 不需要編碼 residual** --- ## 🧠 簡單流程圖 ```text 取得 MV Predictor (Med(left, top, top‑right)) │ ▼ 用 Predictor MV 去 ref frame 拿 block → 做 IME/FME │ ▼ 1) 算出最佳 MV?──不是 predictor → 正常傳 delta MV + residual │ 是 predictor │ ┌───┴───┐ │ │ 2) 算 residual → transform+quantize → 全 0? │ │ 否 是 │ │ 傳 delta MV (0) + residual (0) ← 還是會傳「零」值 │ ✅ Skip! → bitstream 只飄一個 Skip flag,什麼都不傳 ``` # 📐 Motion Vector Predictor 推導細節(Clause 8.4.1) 在 H.264 中,對於每個宏塊分區(partition),Decoder/Encoder 都會先**推導出一個預測向量(MV Predictor)**,再將從 bitstream 讀到的 **MVD(Motion Vector Difference)** 加上去得到最終 MV: \[ \text{MV} = \text{MV Predictor} + \text{MVD} \] 這個過程既可減少要傳輸的位元,也可保證雙端(Encoder/Decoder)一致性 :contentReference[oaicite:0]{index=0}:contentReference[oaicite:1]{index=1}。 --- ## 1️⃣ 鄰格定義 對於每個子分區,標準定義了三個潛在的鄰近向量來源: - **A (左鄰格)** 同一列、緊鄰在左邊的 macroblock 分區。 - **B (上鄰格)** 同一欄、緊鄰在上方的 macroblock 分區。 - **C (右上鄰格)** 緊鄰在右上方的 macroblock 分區(若超出編碼片段邊界,則改用左上鄰格代替)。 在實作中,通常用下圖表示(□ 為當前分區): ```text ┌───┬───┬───┐ │ │ C │ │ ├───┼───┼───┤ │ A │ □ │ │ └───┴───┴───┘ B
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up