1. 程式人生 > >h264文件分析(純c解析代碼)

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

return 說明 too long click tms sprintf b- 順序輸出 其他

參考鏈接: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的分析,未進行代碼分析(有些可能不準確)。

技術分享圖片
  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

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