初学音视频(三)-H.264码流分析
来源:互联网 发布:淘宝网ppt 编辑:程序博客网 时间:2024/06/05 22:45
整个代码块如下
- 对代码进行了解释,和步骤的说明
typedef enum { NALU_TYPE_SLICE = 1, NALU_TYPE_DPA = 2, NALU_TYPE_DPB = 3, NALU_TYPE_DPC = 4, NALU_TYPE_IDR = 5, NALU_TYPE_SEI = 6, NALU_TYPE_SPS = 7, NALU_TYPE_PPS = 8, NALU_TYPE_AUD = 9, NALU_TYPE_EOSEQ = 10, NALU_TYPE_EOSTREAM = 11, NALU_TYPE_FILL = 12, } NaluType; typedef enum { NALU_PRIORITY_DISPOSABLE = 0, NALU_PRIRITY_LOW = 1, NALU_PRIORITY_HIGH = 2, NALU_PRIORITY_HIGHEST = 3 } NaluPriority; typedef struct { int startcodeprefix_len; //! 4 for parameter sets and first slice in picture, 3 for everything else (suggested) unsigned len; //! Length of the NAL unit (Excluding the start code, which does not belong to the NALU) unsigned max_size; //! Nal Unit Buffer size int forbidden_bit; //! should be always FALSE int nal_reference_idc; //! NALU_PRIORITY_xxxx int nal_unit_type; //! NALU_TYPE_xxxx char *buf; //! contains the first byte followed by the EBSP } NALU_t; //定义了全局变量FILE *h264bitstream = NULL; //!< the bit stream file int info2=0, info3=0; // 函数: 判断开头的startCode是不是0x0000001开头,如果是 那么就返回1 如果不是 就返回0static int FindStartCode2 (unsigned char *Buf){ if(Buf[0]!=0 || Buf[1]!=0 || Buf[2] !=1) return 0; //0x000001? else return 1; } // 函数: 判断开头的startCode是不是0x000000001开头,如果是 那么就返回1 如果不是 就返回0static int FindStartCode3 (unsigned char *Buf){ if(Buf[0]!=0 || Buf[1]!=0 || Buf[2] !=0 || Buf[3] !=1) return 0;//0x00000001? else return 1; } // 9. 进入到这个方法 传递一个NALU头信息结构体的地址值过来int GetAnnexbNALU (NALU_t *nalu){ // 10. 定义常量,pos:位置 StartCodeFound:开始code占用的字节 int pos = 0; int StartCodeFound, rewind; unsigned char *Buf; // 11. 申请 sizeof(char) 个长度为 nalu->max_size 的连续空间 类型为 unsigned char* if ((Buf = (unsigned char*)calloc (nalu->max_size , sizeof(char))) == NULL) printf ("GetAnnexbNALU: Could not allocate Buf memory\n"); // 12. 先进行一个默认的赋值,默认为3的字节的话 这里就不是一帧画面的数据,而是其他内容 nalu->startcodeprefix_len=3; // 13. 这里表明 至少要有3个字节,不然就是文件有问题,fread函数返回的是成功读取 1 个字节的次数,如果本身h264bitstream只有2byte,那么返回的就是2,那么说明这个结构体是有问题的,eg:fread(buf,3,3,stream),如果正常情况成功的话是返回3 ,失败的话,可能是一个数据块不够3个了,也就是stream读到了尽头,或者是出错 if (3 != fread (Buf, 1, 3, h264bitstream)){ //其实也就是一个判空的处理 , 判断结构体是否正确而已。返回0.就是data_length = 0; free(Buf); return 0; } // 14. 这个Buf是读取了h264bitstream的前三个字节,判断是不是以0x000001开头,返回1 == 是以0x000001x开头,返回0 == 不是以0x000001开头 info2 = FindStartCode2 (Buf); if(info2 != 1) { //14-1-1. 这里是不等于1,说明不是以0x000001开头,再继续读取一个字节 if(1 != fread(Buf+3, 1, 1, h264bitstream)){ //等于判断是否还有下一个字节 free(Buf); return 0; } //14-1-2. 这时候的buf就有4个字节的有效数据了,那么判断是不是以0x00000001开头,如果是 返回1 如果不是 返回0 info3 = FindStartCode3 (Buf); if (info3 != 1){ //14-1-2-1. 这里说明返回的是0,还不是以0x00000001开头,刚才的判断说明也不是以0x000001开头,说明这个文件有问题。不是H.264的裸流 free(Buf); return -1; } else { //14-1-2-2. 这里说明返回的是1,是以0x00000001开头,说明是一帧的画面 为pos赋值,为nalu结构体的startcodeprefix_len赋值 pos = 4; nalu->startcodeprefix_len = 4; } } else{ //14-2 说明返回的是1,是以0x000001开头。为pos和nalu结构体的startcodeprefix_len赋值 nalu->startcodeprefix_len = 3; pos = 3; } //小结:在14这个大步骤中判断前三个或者四个字节是不是0x000001或者0x00000001,为pos和startcodeprefix_len来赋值//15. 重置参数,为下一次做准备 注意info2 info3的赋值是重置,为下一次做准备,但是StartCodeFound的赋值 是说明是进行到了这里 可以进入下一个环节 StartCodeFound = 0; info2 = 0; info3 = 0; //16. 这是15步骤起到的作用 while (!StartCodeFound){ // 16 判断,如果文件有内容 那么 就这读取 if (feof (h264bitstream)){ //16-1. Length of the NAL unit (Excluding the start code, which does not belong to the NALU 这是对于len的解释 //排除这个startcode,他本身不是属于这个NALU单元的 nalu->len = (pos-1)-nalu->startcodeprefix_len; //16-2. 这个Buf等于是h264bitstream刚才读取的起点,将Buf[nalu->startcodeprefix_len]这个值的地址值开始 复制 nalu->len 个 到 nalu->buf这个地址值 //这个nalu->buf中的buf的意识是: contains the first byte followed by the EBSP ,包含首字节 后面的是the EBSP ,之前是申请了空间,现在是赋值 memcpy (nalu->buf, &Buf[nalu->startcodeprefix_len], nalu->len); //16-3. 这个成员变量是should be always FALSE 建议是false nalu->forbidden_bit = nalu->buf[0] & 0x80; //1 bit //16-4. 是NALU_PRIORITY_xxxx 优先级 nalu->nal_reference_idc = nalu->buf[0] & 0x60; // 2 bit //16-5. NALU_TYPE_xxxx nalu的类型 nalu->nal_unit_type = (nalu->buf[0]) & 0x1f;// 5 bit //16-3 ~ 16-5 这三个赋值 依赖的是16-2里面的内存copy free(Buf); return pos-1; } //16 判断,因为如果文件有内容的话 ,在上面的时候就已经return pos-1;了 说明 返回的要么是2 要么是3 // 到了这里的时候就说明文件本身是没有内容的 //17. 这里在0x000001进来后发现 已经读到了最末尾,再去取得4个byte,重新来判断 不过这种几率很小 Buf[pos++] = fgetc (h264bitstream); info3 = FindStartCFindStartCode3ode3(&Buf[pos-4]); if(info3 != 1) info2 = FindStartCode2(&Buf[pos-3]); //这里如果 发现 开头是满足情况的话 再从新判断 StartCodeFound = (info2 == 1 || info3 == 1); } //小结:16步骤主要是为一些结构体内的参数进行赋值 //18. 这里我们发现了一些其他的startcode 也就是非0x000001和非0x00000001的startCode。 并且读取的这个startcode的字节长度超过我们所知道的。我们要将他返回到这个文件中,这个返回的意思就是postion的指针指向 // Here, we have found another start code (and read length of startcode bytes more than we should // have. Hence, go back in the file //19.为重置参数rewind赋值,这个info3 是在 17步骤中进行的赋值,如果info3==1,那么说明是读取了0x00000001四个byte,就要成为-4,否则就是3个byte了 rewind = (info3 == 1)? -4 : -3; //20.重置位置,这里重置位置相当重要 if (0 != fseek (h264bitstream, rewind, SEEK_CUR)){ free(Buf); printf("GetAnnexbNALU: Cannot fseek in the bit stream file"); } // Here the Start code, the complete NALU, and the next start code is in the Buf. // 现在这个startcode,完整的NALU,和下一个startcode就全在这个buf里面了 // The size of Buf is pos, pos+rewind are the number of bytes excluding the next //这个Buf的size是pos,pos+rewind是除了下一个startcode的字节数量,(pos+rewind)-startcodeprefix_len 是这个NALU除了startcode的字节数量 // start code, and (pos+rewind)-startcodeprefix_len is the size of the NALU excluding the start code nalu->len = (pos+rewind)-nalu->startcodeprefix_len; memcpy (nalu->buf, &Buf[nalu->startcodeprefix_len], nalu->len);// nalu->forbidden_bit = nalu->buf[0] & 0x80; //1 bit nalu->nal_reference_idc = nalu->buf[0] & 0x60; // 2 bit nalu->nal_unit_type = (nalu->buf[0]) & 0x1f;// 5 bit free(Buf); return (pos+rewind); } /** * Analysis H.264 Bitstream * @param url Location of input H.264 bitstream file. */ // 2.程序入口走到了这里,不过在此之前 应该定义了一些结构体和全局变量FILE *h264bitstream = NULL;int info2=0, info3=0; int simplest_h264_parser(char *url){ // 3.这个 NALU_t 可以认为是H.264的最小单元NALU的头信息结构体 NALU_t *n; int buffersize=100000; //FILE *myout=fopen("output_log.txt","wb+"); FILE *myout=stdout; // 4. 打开这个文件流的 h264bitstream=fopen(url, "rb+"); if (h264bitstream==NULL){ //判空 printf("Open file error\n"); return 0; } // 5. 分配 sizeof (NALU_t) 个 长度为 1 的连续空间,返回起始地址的指针。n就是NALU_t* n = (NALU_t*)calloc (1, sizeof (NALU_t)); //判空 if (n == NULL){ printf("Alloc NALU Error\n"); return 0; } // 6. 设置 最大的size NAL UNIT BUFFER SIZE 。NAL 这个集合的大小 n->max_sizmax_sizee=buffersize; //contains the first byte followed by the EBSP 设置这个buf,包含第一个字节,随后的是the EBSP 申请sizeof (char)个 大小为buffersize 的连续空间 n->buf = (char*)calloc (buffersize, sizeof (char)); //判空 if (n->buf == NULL){ free (n); printf ("AllocNALU: n->buf"); return 0; } int data_offset=0; int nal_num=0; //打印一些资料。NUM代表打印的次数,POS代表现在stream的位置,IDC代表优先级别,TYPE代表这个NALU的类型,LEN代表这个NALU单元的长度 printf("-----+-------- NALU Table ------+---------+\n"); printf(" NUM | POS | IDC | TYPE | LEN |\n"); printf("-----+---------+--------+-------+---------+\n"); // 7. 开始循环去读取这个stream while(!feof(h264bitstream)) { int data_lenth; // 8. 传递一个NALU的地址值过去 为这个结构体去赋值,得到的是这个NAUL的长度 data_lenth=GetAnnexbNALU(n); //小结:8-18 这几个步骤主要还是赋值,为结构体里面的各个参数赋值。 char type_str[20]={0}; switch(n->nal_unit_type){ case NALU_TYPE_SLICE:sprintf(type_str,"SLICE");break; case NALU_TYPE_DPA:sprintf(type_str,"DPA");break; case NALU_TYPE_DPB:sprintf(type_str,"DPB");break; case NALU_TYPE_DPC:sprintf(type_str,"DPC");break; case NALU_TYPE_IDR:sprintf(type_str,"IDR");break; case NALU_TYPE_SEI:sprintf(type_str,"SEI");break; case NALU_TYPE_SPS:sprintf(type_str,"SPS");break; case NALU_TYPE_PPS:sprintf(type_str,"PPS");break; case NALU_TYPE_AUD:sprintf(type_str,"AUD");break; case NALU_TYPE_EOSEQ:sprintf(type_str,"EOSEQ");break; case NALU_TYPE_EOSTREAM:sprintf(type_str,"EOSTREAM");break; case NALU_TYPE_FILL:sprintf(type_str,"FILL");break; } char idc_str[20]={0}; switch(n->nal_reference_idc>>5){ case NALU_PRIORITY_DISPOSABLE:sprintf(idc_str,"DISPOS");break; case NALU_PRIRITY_LOW:sprintf(idc_str,"LOW");break; case NALU_PRIORITY_HIGH:sprintf(idc_str,"HIGH");break; case NALU_PRIORITY_HIGHEST:sprintf(idc_str,"HIGHEST");break; } //输出方式为“%5d”表示按5位的固定位宽输出整型数值。如果不足5位,则在前面补空格;超过5位,则按实际位数输出。 /* int a=123; int b=123456; printf("%5d\n", a); // 输出 _ _ 123 ( _ 表示空格),不足5位,在前面补空格 printf("%5d\n", b); // 输出123456,超过5位,按实际位数输出*/ fprintf(myout,"%5d| %8d| %7s| %6s| %8d|\n",nal_num,data_offset,idc_str,type_str,n->len); data_offset=data_offset+data_lenth; nal_num++; } //Free if (n){ if (n->buf){ free(n->buf); n->buf=NULL; } free (n); } return 0; }
- 在main函数中调用simplest_h264_parser(“sintel.h264”);
需要弄清楚的知识点
- H264原始码流的基本单元是NALU。
- 对NALU有简单的认识,每个NALU的分隔符都是一个StartCode。0x000001(3Byte)或者0x00000001(4Byte)。如果NALU对应的Slice为一帧的开始就用0x00000001,否则就用0x000001。
- H264码流分析:H.264码流解析的步骤就是首先从码流中搜索0x000001和0x00000001,分离出NALU;然后再分析NALU的各个字段。
0 0
- 初学音视频(三)-H.264码流分析
- 初学音视频(四)-AAC音频码流分析
- H.264视频编码传输的QoS特性分析(三)
- H.264视频编码传输的QoS特性分析(三)
- 音视频数据处理(21)--- H.264视频码流解析代码实现
- 初学音视频(一)-RGB和YUV格式分析
- 初学音视频(五)-FLV格式分析
- H.264-AVC视频编码原理及实现(三)
- H.264-AVC视频编码原理及实现(三)
- H.264-AVC视频编码原理及实现(三)
- 基于RTP的h.264视频传输系统(三)
- H.264视频码流解析
- H.264视频码流解析
- H.264视频码流解析
- H.264视频码流解析2
- H.264视频码流解析
- H.265和H.264对比分析(VR视频传输)
- H.264码流分析
- AVL树的插入、删除、查找操作
- Eclipse安装反编译插件jadclipse
- 网站缓存技术总结( ehcache、memcache、redis对比)
- [leetcode]237. Delete Node in a Linked List
- Hdu2952 Counting Sheep
- 初学音视频(三)-H.264码流分析
- Linux&C语言文件学习笔记(三):文件I/O与系统API
- T * const p与const T * p,const shared_ptr< T> p与const shared_ptr< T> p
- spring中Aware结尾接口(5)
- Unity 灯光Shader
- mysql视图详解
- Oracle中通过substr和instr实现截取指定字符之间的字符串:
- HADOOP ssh设置密码
- 很久很久前初学Java时的笔记--正则表达式+包装类