初学音视频(三)-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
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 包包用酒精擦了怎么办 灰色泰迪毛发白怎么办 手被野猫抓伤了怎么办 想去香港玩两天怎么办 受凉了一直想吐怎么办 受寒后头晕想吐怎么办 肚子受凉了想吐怎么办 想吐但吐不出来怎么办 抽烟抽的牙黄怎么办 借了大耳窿的钱怎么办 5岁左眼视力不好怎么办 老公才30性不行怎么办 老婆出轨跑了怎么办啊 海螺吃多了头晕怎么办 读书的好与不好怎么办 衣服泡久了发黄怎么办 白衣服洗变色了怎么办 高达贴纸贴错了怎么办 小孩大门牙长歪怎么办 小孩乳牙长歪了怎么办 小孩牙齿张歪了怎么办 8小孩门牙长歪了怎么办 婴儿乳牙长歪了怎么办 模拟人生4胖了怎么办 磁吸静音锁消磁怎么办 学生脸上长痘怎么办呢 脖子两边的筋疼怎么办 脸上长脓包型痘痘怎么办啊 吃了两把消炎药怎么办 对叛离期的孩子怎么办 信耶稣家里有佛怎么办 高达模型装错了怎么办 被别朋友误会了怎么办 被喜欢的人误会怎么办 同事误会我了该怎么办 别人误解你你该怎么办 神武孩子喂错了怎么办 神武孩子吃错了怎么办 肚子饿没东西吃怎么办 晚上饿了没吃的怎么办 上课饿了没吃的怎么办