h264文件分析(純c解析代碼)

来源:https://www.cnblogs.com/leaffei/archive/2019/03/10/10507295.html
-Advertisement-
Play Games

參考鏈接:1. 解析H264的SPS信息 https://blog.csdn.net/lizhijian21/article/details/80982403 2. h.264的POC計算 https://www.cnblogs.com/TaigaCon/p/3551001.html 3. 視音頻數 ...


參考鏈接:1. 解析H264的SPS信息 https://blog.csdn.net/lizhijian21/article/details/80982403
               2. h.264的POC計算 https://www.cnblogs.com/TaigaCon/p/3551001.html
               3. 視音頻數據處理入門:H.264視頻碼流解析 https://blog.csdn.net/leixiaohua1020/article/details/50534369

代碼中的註釋, 有對SPS,PPS,SLICE的分析,未進行代碼分析(有些可能不准確)。

SPS語法

PPS語法

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <string.h>
  4 #include <arpa/inet.h>
  5 
  6 #define TAB44 "    "
  7 #define PRINTF_DEBUG
  8 
  9 #define PRTNTF_STR_LEN 10
 10 
 11 /************************************************************************************************************
 12 **                                        nalu header: 負責將VCL產生的比特字元串適配到各種各樣的網路和多元環境中, 
 13                                                        覆蓋了所有片級以上的語法級別(NALU的作用, 方便網路傳輸)
 14 **
 15 -------------------------------------------------------------------------------------------------------------
 16 **        欄位名稱               |    長度(bits)    |        有關描述
 17 -------------------------------------------------------------------------------------------------------------
 18 **        forbidden_bit          |    1             |        編碼中預設值為0, 當網路識別此單元中存在比特錯誤時, 可將其設為1, 以便接收方丟掉該單元
 19 **        nal_reference_idc      |    2             |        0~3標識這個NALU的重要級別
 20 **        nal_unit_type          |    5             |          NALU的類型(類型1~12是H.264定義的, 類型24~31是用於H.264以外的, 
 21                                                              RTP負荷規範使用這其中的一些值來定義包聚合和分裂, 其他值為H.264保留)
 22 
 23 ** nal_unit_type:
 24     0                未使用
 25     1                未使用Data Partitioning, 非IDR圖像的Slice
 26     2                使用Data Partitioning且為Slice A
 27     3                使用Data Partitioning且為Slice B
 28     4                使用Data Partitioning且為Slice C
 29     5                IDR圖像的Slice(立即刷新)
 30     6                補充增強信息(SEI)
 31     7                序列參數集(sequence parameter set, SPS)
 32     8                圖像參數集(picture parameter set, PPS)
 33     9                分界符
 34     10                序列結束
 35     11                碼流結束
 36     12                填充
 37     13...23            保留
 38     24...31            未使用
 39     
 40 ** SPS, PPS. SLICE等信息就不解析了. 為了減少bits, 用了哥倫布編碼(自己解析比較麻煩, 但是網上有很多).
 41 
 42 ** SPS信息說明:
 43         1. 視頻寬高, 幀率等信息;
 44         2. seq_parameter_set_id, 指明本序列參數集的id號, 這個id號將被picture參數集引用;
 45         3. pic_width_in_mbs_minus1, 加1指定以巨集塊(16*16)為單位的每個解碼圖像的寬度, 即width = (pic_width_in_mbs_minus1 + 1) * 16
 46         4. pic_height_in_map_units_minus1;
 47         5. pic_order_cnt_type, 視頻的播放順序序號叫做POC(picture order count), 取值0,1,2;
 48         6. time_scale, fixed_frame_rate_flag, 計算幀率(fps).
 49            視頻幀率信息在SPS的VUI parameters syntax中, 需要根據time_scale, fixed_frame_rate_flag計算得到: fps = time_scale / num_units_in_tick.
 50            但是需要判斷參數timing_info_present_flag是否存在, 若不存在表示FPS在信息流中無法獲取.
 51            同時還存在另外一種情況: fixed_frame_rate_flag為1時, 兩個連續圖像的HDR輸出時間頻率為單位, 獲取的fps是實際的2倍.
 52 
 53 ** PPS信息說明:    
 54         1. pic_parameter_set_id, 用以指定本參數集的序號, 該序號在各片的片頭被引用;
 55         2. seq_parameter_set_id, 指明本圖像參數集所引用的序列參數集的序號;
 56         3. 其他高深的暫時還不理解, 指明參考幀隊列等.
 57         
 58 ** SLICE信息說明:
 59         1. slice_type, 片的類型;
 60         2. pic_parameter_set_id, 引用的圖像索引;
 61         3. frame_num, 每個參考幀都有一個連續的frame_num作為它們的標識, 它指明瞭各圖像的解碼順序. 非參考幀也有,但沒有意義;
 62         4. least significant bits;
 63         5. 綜合三種poc(pic_order_cnt_type), 類型2應該是最省bit的, 因為直接從frame_num獲得, 但是序列方式限制最大;
 64            類型1, 只需要一定的bit量在sps標誌出一些信息還在slice header中表示poc的變化, 但是比類型0要節省bit, 但是其序列並不是隨意的, 要周期變化;
 65            對於類型0因為要對poc的lsb(pic_order_cnt_lsb, last bit)進行編碼所以用到的bit最多, 優點是序列可以隨意.
 66            ** 自我理解, 不一定准確(這邊算顯示順序, 要根據SPS中的pic_order_cnt_type, 為2, 意味著碼流中沒有B幀, frame_num即為顯示順序;
 67               為1, 依賴frame_num求解POC; 為0, 把POC的低位編進碼流內, 但這隻是低位, 而POC的高位PicOrderCntMsb則要求解碼器自行計數,
 68               計數方式依賴於前一編碼幀(PrevPicOrderCntMsb與PrevPicOrderCntLsb.
 69               
 70            ** 一般的碼流分析所見(未仔細證實): pic_order_cnt_type=2, 只有frame_num(無B幀);
 71               pic_order_cnt_type=1, 暫未分析到;
 72               pic_order_cnt_type=0, pic_order_cnt_lsb指示顯示順序, 一般為偶數增長(0, 2, 4, 6, 據說是什麼場方式和幀方式, 場時其實是0 0 2 2 4 4).
 73               
 74            ** 編碼與顯示的原因: 視頻編碼順序與視頻的播放順序, 並不完全相同, 視頻編碼時, 如果採用了B幀編碼, 由於B幀很多時候都是雙向預測得來的,
 75               這時會先編碼B幀的後向預測圖像(P幀), 然後再進行B幀編碼, 因此會把視頻原來的播放順序打亂, 以新的編碼順序輸出碼流,
 76               而在解碼斷接收到碼流後, 需要把順序還原成原本的播放順序, 以輸出正確的視頻. 在編解碼中, 視頻的播放順序序號叫做POC(picture order count).
 77               
 78 ** 總結: 1. 碼流中有很多SPS(序列), 一個序列中有多個圖像, 一個圖像中有多個片, 一個片中有多個塊;
 79          2. SPS中有seq_parameter_set_id. PPS中有pic_parameter_set_id, 並通過seq_parameter_set_id指明關聯的序列.
 80             SLICE中有pic_parameter_set_id, 指明關聯的圖像;
 81          3. SPS中可計算寬高以及幀率, pic_order_cnt_type(顯示順序的類型);
 82             SLICE HEADER中可算出解碼的順序, 以及根據pic_order_cnt_type算出顯示順序.            
 83 ************************************************************************************************************/
 84 typedef enum e_h264_nalu_priority
 85 {
 86     NALU_PRIORITY_DISPOSABLE = 0,
 87     NALU_PRIORITY_LOW         = 1,
 88     NALU_PRIORITY_HIGH       = 2,
 89     NALU_PRIORITY_HIGHEST    = 3,
 90 } E_H264_NALU_PRIORITY;
 91 
 92 typedef enum e_h264_nalu_type
 93 {
 94     NALU_TYPE_SLICE    = 1,
 95     NALU_TYPE_DPA      = 2,
 96     NALU_TYPE_DPB      = 3,
 97     NALU_TYPE_DPC      = 4,
 98     NALU_TYPE_IDR      = 5,
 99     NALU_TYPE_SEI      = 6,
100     NALU_TYPE_SPS      = 7,
101     NALU_TYPE_PPS      = 8,
102     NALU_TYPE_AUD      = 9,
103     NALU_TYPE_EOSEQ    = 10,
104     NALU_TYPE_EOSTREAM = 11,
105     NALU_TYPE_FILL     = 12,
106 } E_H264_NALU_TYPE;
107 
108 typedef struct t_h264_nalu_header
109 {
110     unsigned char forbidden_bit:1, nal_reference_idc:2, nal_unit_type:5;
111 } T_H264_NALU_HEADER;
112 
113 typedef struct t_h264_nalu
114 {
115     int startCodeLen;
116     
117     T_H264_NALU_HEADER h264NaluHeader;
118     
119     unsigned int bodyLen;
120     
121     unsigned char *bodyData;
122 } T_H264_NALU;
123 
124 /**********************************************************************************
125  1. h264的起始碼: 0x000001(3 Bytes)或0x00000001(4 Bytes);
126  2. 文件流中用起始碼來區分NALU.
127 ***********************************************************************************/
128 static int FindStartCode3Bytes(unsigned char *scData)
129 {
130     int isFind = 0;
131 
132     if ((0==scData[0]) && (0==scData[1]) && (1==scData[2]))
133     {
134         isFind = 1;
135     }
136     
137     return isFind;
138 }
139 
140 static int FindStartCode4Bytes(unsigned char *scData)
141 {
142     int isFind = 0;
143 
144     if ((0==scData[0]) && (0==scData[1]) && (0==scData[2]) && (1 == scData[3]))
145     {
146         isFind = 1;
147     }
148     
149     return isFind;
150 }
151 
152 static int GetNaluDataLen(int startPos, int h264BitsSize, unsigned char *h264Bits)
153 {
154     int parsePos = 0;
155     
156     parsePos = startPos;
157     
158     while (parsePos < h264BitsSize)
159     {
160         if (FindStartCode3Bytes(&h264Bits[parsePos]))
161         {
162             return parsePos - startPos;
163         }
164         else if (FindStartCode4Bytes(&h264Bits[parsePos]))
165         {
166             return parsePos - startPos;
167         }
168         else
169         {
170             parsePos++;
171         }
172     }
173     
174     return parsePos - startPos; // if file is end
175 }
176 
177 static void ParseNaluData(const unsigned int naluLen, unsigned char* const nuluData)
178 {
179     static int naluNum = 0;
180     
181     unsigned char *data = NULL;
182     unsigned char priorityStr[PRTNTF_STR_LEN+1] = {0};
183     unsigned char typeStr[PRTNTF_STR_LEN+1] = {0};
184     
185     T_H264_NALU_HEADER h264NaluHeader = {0};
186     
187     data = nuluData;
188     
189     memset(&h264NaluHeader, 0x0, sizeof(T_H264_NALU_HEADER));
190     
191     h264NaluHeader.nal_reference_idc = data[0]>>5 & 0x3;
192     h264NaluHeader.nal_unit_type = data[0] & 0x1f;
193     
194     naluNum++;
195     
196 #ifdef PRINTF_DEBUG
197     switch (h264NaluHeader.nal_reference_idc)
198     {
199         case NALU_PRIORITY_DISPOSABLE:
200             sprintf(priorityStr, "DISPOS");
201             break;
202             
203         case NALU_PRIORITY_LOW:
204             sprintf(priorityStr, "LOW");
205             break;
206 
207         case NALU_PRIORITY_HIGH:
208             sprintf(priorityStr, "HIGH");
209             break;
210 
211         case NALU_PRIORITY_HIGHEST:
212             sprintf(priorityStr, "HIGHEST");
213             break;
214 
215         default:
216             break;
217     }
218     
219     switch (h264NaluHeader.nal_unit_type)
220     {
221         case NALU_TYPE_SLICE:
222             sprintf(typeStr,"SLICE");
223             break;
224             
225         case NALU_TYPE_DPA:
226             sprintf(typeStr,"DPA");
227             break;
228             
229         case NALU_TYPE_DPB:
230             sprintf(typeStr,"DPB");
231             break;
232             
233         case NALU_TYPE_DPC:
234             sprintf(typeStr,"DPC");
235             break;
236             
237         case NALU_TYPE_IDR:
238             sprintf(typeStr,"IDR");
239             break;
240             
241         case NALU_TYPE_SEI:
242             sprintf(typeStr,"SEI");
243             break;
244             
245         case NALU_TYPE_SPS:
246             sprintf(typeStr,"SPS");
247             break;
248             
249         case NALU_TYPE_PPS:
250             sprintf(typeStr,"PPS");
251             break;
252             
253         case NALU_TYPE_AUD:
254             sprintf(typeStr,"AUD");
255             break;
256             
257         case NALU_TYPE_EOSEQ:
258             sprintf(typeStr,"EOSEQ");
259             break;
260             
261         case NALU_TYPE_EOSTREAM:
262             sprintf(typeStr, "EOSTREAM");
263             break;
264             
265         case NALU_TYPE_FILL:
266             sprintf(typeStr, "FILL");
267             break;
268         
269         default:
270             break;
271     }
272     
273     printf("%5d| %7s| %6s| %8d|\n",naluNum,priorityStr,typeStr,naluLen);
274 #endif
275     
276 }
277 
278 int main(int argc, char *argv[])
279 {
280     int fileLen = 0;
281     int naluLen = 0;
282     int h264BitsPos = 0;
283 
284     unsigned char *h264Bits = NULL;
285     unsigned char *naluData = NULL;
286     
287     FILE *fp = NULL;
288     
289     if (2 != argc)
290     {
291         printf("Usage: flvparse **.flv\n");
292 
293         return -1;
294     }
295 
296     fp = fopen(argv[1], "rb");
297     if (!fp)
298     {
299         printf("open file[%s] error!\n", argv[1]);
300 
301         return -1;
302     }
303     
304     fseek(fp, 0, SEEK_END);
305     
306     fileLen = ftell(fp);
307     
308     fseek(fp, 0, SEEK_SET);
309     
310     h264Bits = (unsigned char*)malloc(fileLen);
311     if (!h264Bits)
312     {
313         printf("maybe file is too long, or memery is not enough!\n");
314         
315         fclose(fp);
316     
317         return -1;
318     }
319     
320     memset(h264Bits, 0x0, fileLen);
321     
322     if (fread(h264Bits, 1, fileLen, fp) < 0)
323     {
324         printf("read file data to h264Bits error!\n");
325         
326         fclose(fp);
327         free(h264Bits);
328         
329         h264Bits = NULL;
330         
331         return -1;
332     }
333     
334     fclose(fp);
335     
336     printf("-----+-------- NALU Table ------+\n");
337     printf(" NUM |    IDC |  TYPE |   LEN   |\n");
338     printf("-----+--------+-------+---------+\n");
339 
340     while (h264BitsPos < (fileLen-4))
341     {
342         if (FindStartCode3Bytes(&h264Bits[h264BitsPos]))
343         {
344             naluLen = GetNaluDataLen(h264BitsPos+3, fileLen, h264Bits);
345 
346             naluData = (unsigned char*)malloc(naluLen);
347             if (naluData)
348             {
349                 memset(naluData, 0x0, naluLen);
350                 
351                 memcpy(naluData, h264Bits+h264BitsPos+3, naluLen);
352                 
353                 ParseNaluData(naluLen, naluData);
354                 
355                 free(naluData);
356                 naluData = NULL;
357             }
358             
359             h264BitsPos += (naluLen+3);
360         }
361         else if (FindStartCode4Bytes(&h264Bits[h264BitsPos]))
362         {
363             naluLen = GetNaluDataLen(h264BitsPos+4, fileLen, h264Bits);
364 
365             naluData = (unsigned char*)malloc(naluLen);
366             if (naluData)
367             {
368                 memset(naluData, 0x0, naluLen);
369 
370                 memcpy(naluData, h264Bits+h264BitsPos+4, naluLen);
371 
372                 ParseNaluData(naluLen, naluData);
373                 
374                 free(naluData);
375                 naluData = NULL;
376             }
377             
378             h264BitsPos += (naluLen+4);
379         }
380         else
381         {
382             h264BitsPos++;
383         }
384     }
385 
386     return 0;
387 }
View Code

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 報這個錯誤是因為我的 application_context.service.xml 文件里的的dubbo聲明暴露口時的ref屬性寫錯了。 ...
  • 這段時間在學習Spring,依賴註入DI和麵向切麵編程AOP是Spring框架最核心的部分。這次主要是總結依賴註入的bean的裝配方式。 什麼是依賴註入呢?也可以稱為控制反轉,簡單的來說,一般完成稍微複雜的業務邏輯,可能需要多個類,會出現有些類要引用其他類的實例,也可以稱為依賴其他類。傳統的方法就是 ...
  • 晚上在閱讀go lang的資料時突然想到一個問題,go是如何分配變數的記憶體結構的呢?好在網上的一篇文章做了透徹的分析見【go語言局部變數分配在棧還是堆】。 其結論是go語言局部變數的分配是由編譯器決定的。go語言編譯器會自動決定把一個變數放在棧還是放在堆,編譯器會做逃逸分析(escape analy ...
  • 忙瘋警告,這兩天可能進度很慢,下午打了一下午訓練賽,訓練賽的題我就不拿過來的,pta就做了一點點,明天又是滿課的一天,所以進度很慢啦~ L1-021 重要的話說三遍 這道超級簡單的題目沒有任何輸入。 你只需要把這句很重要的話 —— “I'm gonna WIN!”——連續輸出三遍就可以了。 註意每遍 ...
  • 因為極驗官網給的是用session作為驗證的,而我們做前後端分離的用的是token,而不是session,所以對於目前來說就不適用了,所以需要根據具體業務邏輯來改動。當然,大佬可以直接忽略 好的,直接上例子: 還是用的 Python高級應用(3)—— 為你的項目添加驗證碼 這文章最後的Lo... ...
  • 新聞 ".NET Core 3預覽版3之宣告" .NET Core 3.0將在2019年下半年發佈 .NET Standard 2.1的首個預覽版 Docker與cgroup的記憶體限制 "LambdAle 2019徵文" "使用TypeShape生成透鏡" "為什麼使用Ply(F的高性能TPL類庫) ...
  • Problem Description In this problem, your task is to calculate SUM(n) = 1 + 2 + 3 + ... + n. Input The input will consist of a series of integers n, o ...
  • 一、什麼是AOP? Aspect oritention programming(面向切麵編程),AOP是一種思想,高度概括的話是“橫向重覆,縱向抽取”,如何理解呢?舉個例子:訪問頁面時需要許可權認證,如果每個頁面都去實現方法顯然是不合適的,這個時候我們就可以利用切麵編程。 每個頁面都去實現這個方法就是 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...