h264文件分析(纯c解析代码)
程序员文章站
2022-05-11 10:35:07
参考链接: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的分析,未进行代码分析(有些可能不准确)。
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 }