H264视频处理
一、原理
H.264原始碼流(又稱為“裸流”)是由一個一個的NALU組成的。他們的結構如下圖所示
其中每個NALU之間通過startcode(起始碼)進行分隔,起始碼分成兩種:0x000001(3Byte)或者0x00000001(4Byte)。如果NALU對應的Slice為一幀的開始就用0x00000001,否則就用0x000001。
H.264碼流解析的步驟就是首先從碼流中搜索0x000001和0x00000001,分離出NALU;然后再分析NALU的各個字段。
二、協議
2.1.基本概念
- RBSP: 原始字節序列載荷-->在SODB的后面填加了結尾比特(RBSP trailing bits 一個bit“1”)若干比特“0”,以便字節對齊。
- EBSP: 擴展字節序列載荷– >在RBSP基礎上填加了仿校驗字節(0X03)它的原因是: 在NALU加到Annexb上時,需要填加每組
? ? NALU之前的開始碼 StartCodePrefix,如果該NALU對應的slice為一幀的開始則用4位字節表示,ox00000001,否則用3位字節表示 ox000001.為了使NALU主體中不包括與開始碼相沖突的,在編碼時,每遇到兩個字節連續為0,就插入一個字節的0x03。解碼時將0x03去掉。 也稱為脫殼操作
H.264的功能分為兩層,視頻編碼層(VCL)和網絡提取層(NAL)??
? ? VCL數據即被壓縮編碼后的視頻數據序列。把VCL數據要封裝到NAL單元中之后,才可以用來傳輸或存儲。H.264 的編碼視頻序列包括一系列的NAL 單元,每個NAL 單元包含一個RBSP。編碼片(包括數據分割片IDR 片)和序列RBSP 結束符被定義為VCL NAL 單元,其余為NAL 單元。典型的RBSP 單元序列如圖所示。每個單元都按獨立的NAL 單元傳送。單元的信息頭(一個字節)定義了RBSP 單元的類型,NAL 單元的其余部分為RBSP 數據
2.2.NAL單元
? ? 每個NAL單元是一個一定語法元素的可變長字節字符串,包括包含一個字節的頭信息(用來表示數據類型),以及若干整數字節的負荷數據。一個NAL單元可以攜帶一個編碼片、A/B/C型數據分割或一個序列或圖像參數集。
NALU 頭由一個字節組成, 它的語法如下:
NAL單元按RTP序列號按序傳送。其中,T為負荷數據類型,占5bit;R為重要性指示位,占2個bit;最后的F為禁止位,占1bit。具體如下:?
? ? <1>NALU類型位?
? ? ? ? 可以表示NALU的32種不同類型特征,類型1~12是H.264定義的,類型24~31是用于H.264以外的,RTP負荷規范使用這其中的一些值來定義包聚合和分裂,其他值為H.264保留
? ? <2>重要性指示位?
? ? ? ? 用于在重構過程中標記一個NAL單元的重要性,值越大,越重要。值為0表示這個NAL單元沒有用于預測,因此可被解碼器拋棄而不會有錯誤擴散;值高于0表示此NAL單元要用于無漂移重構,且值越高,對此NAL單元丟失的影響越大
? ? <3>禁止位?
? ? ? ? 編碼中默認值為0,當網絡識別此單元中存在比特錯誤時,可將其設為1,以便接收方丟掉該單元,主要 用于適應不同種類的網絡環境(比如有線無線相結合的環境)
264常見的幀頭數據為:
? ? 00 00 00 01?67?(SPS)
? ? 00 00 00 01?68?(PPS)
? ? 00 00 00 01?65?( IDR 幀)
? ? 00 00 00 01?61?(P幀)
F:禁止為,0表示正常,1表示錯誤,一般都是0
NRI:重要級別,11表示非常重要。
TYPE:表示該NALU的類型是什么,
見下表,由此可知7為序列參數集(SPS),8為圖像參數集(PPS),5代表I幀。1代表非I幀。
由此可知,61和41其實都是P幀(type值為1),只是重要級別不一樣(它們的NRI一個是11BIN,一個是10BIN)
NALU類型是我們判斷幀類型的利器,從官方文檔中得出如下圖:
三、H264(NAL簡介與I幀判斷)
我們還是接著看最上面圖的碼流對應的數據來層層分析,以00 00 00 01分割之后的下一個字節就是NALU類型,將其轉為二進制數據后,
解讀順序為從左往右算,如下:
(1)第1位禁止位,值為1表示語法出錯
(2)第2~3位為參考級別
(3)第4~8為是nal單元類型
例如上面00000001后有67,68以及65
其中0x67的二進制碼為:
0110 0111
4-8為00111,轉為十進制7,參考第二幅圖:7對應序列參數集SPS
其中0x68的二進制碼為:
0110 1000?
4-8為01000,轉為十進制8,參考第二幅圖:8對應圖像參數集PPS
其中0x65的二進制碼為:
011 00101
4-8位為00101,轉為十進制5,參考第二幅圖:5對應IDR圖像中的片(I幀)
所以判斷是否為I幀的算法為:
(NALU類型 & 0001 1111) = 5 即 (NALU類型 & 31) = 5?
比如0x65 & 31 = 5 (這種辦法可能不完全正確,具體的做法可參照h264標準文檔判斷)
四、大雷神代碼
#include <stdio.h> #include <stdlib.h> #include <string.h>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 sizeint forbidden_bit; //! should be always FALSEint nal_reference_idc; //! NALU_PRIORITY_xxxxint nal_unit_type; //! NALU_TYPE_xxxx char *buf; //! contains the first byte followed by the EBSP } NALU_t;FILE *h264bitstream = NULL; //!< the bit stream fileint info2=0, info3=0;static int FindStartCode2 (unsigned char *Buf){if(Buf[0]!=0 || Buf[1]!=0 || Buf[2] !=1) return 0; //0x000001?else return 1; }static int FindStartCode3 (unsigned char *Buf){if(Buf[0]!=0 || Buf[1]!=0 || Buf[2] !=0 || Buf[3] !=1) return 0;//0x00000001?else return 1; }int GetAnnexbNALU (NALU_t *nalu){int pos = 0;int StartCodeFound, rewind;unsigned char *Buf;if ((Buf = (unsigned char*)calloc (nalu->max_size , sizeof(char))) == NULL) printf ("GetAnnexbNALU: Could not allocate Buf memory\n");nalu->startcodeprefix_len=3;if (3 != fread (Buf, 1, 3, h264bitstream)){free(Buf);return 0;}info2 = FindStartCode2 (Buf);if(info2 != 1) {if(1 != fread(Buf+3, 1, 1, h264bitstream)){free(Buf);return 0;}info3 = FindStartCode3 (Buf);if (info3 != 1){ free(Buf);return -1;}else {pos = 4;nalu->startcodeprefix_len = 4;}}else{nalu->startcodeprefix_len = 3;pos = 3;}StartCodeFound = 0;info2 = 0;info3 = 0;while (!StartCodeFound){if (feof (h264bitstream)){nalu->len = (pos-1)-nalu->startcodeprefix_len;memcpy (nalu->buf, &Buf[nalu->startcodeprefix_len], nalu->len); nalu->forbidden_bit = nalu->buf[0] & 0x80; //1 bitnalu->nal_reference_idc = nalu->buf[0] & 0x60; // 2 bitnalu->nal_unit_type = (nalu->buf[0]) & 0x1f;// 5 bitfree(Buf);return pos-1;}Buf[pos++] = fgetc (h264bitstream);info3 = FindStartCode3(&Buf[pos-4]);if(info3 != 1)info2 = FindStartCode2(&Buf[pos-3]);StartCodeFound = (info2 == 1 || info3 == 1);}// Here, we have found another start code (and read length of startcode bytes more than we should// have. Hence, go back in the filerewind = (info3 == 1)? -4 : -3;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. // The size of Buf is pos, pos+rewind are the number of bytes excluding the next// start code, and (pos+rewind)-startcodeprefix_len is the size of the NALU excluding the start codenalu->len = (pos+rewind)-nalu->startcodeprefix_len;memcpy (nalu->buf, &Buf[nalu->startcodeprefix_len], nalu->len);//nalu->forbidden_bit = nalu->buf[0] & 0x80; //1 bitnalu->nal_reference_idc = nalu->buf[0] & 0x60; // 2 bitnalu->nal_unit_type = (nalu->buf[0]) & 0x1f;// 5 bitfree(Buf);return (pos+rewind); }/*** Analysis H.264 Bitstream* @param url Location of input H.264 bitstream file.*/ int simplest_h264_parser(char *url){NALU_t *n;int buffersize=100000;//FILE *myout=fopen("output_log.txt","wb+");FILE *myout=stdout;h264bitstream=fopen(url, "rb+");if (h264bitstream==NULL){printf("Open file error\n");return 0;}n = (NALU_t*)calloc (1, sizeof (NALU_t));if (n == NULL){printf("Alloc NALU Error\n");return 0;}n->max_size=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;printf("-----+-------- NALU Table ------+---------+\n");printf(" NUM | POS | IDC | TYPE | LEN |\n");printf("-----+---------+--------+-------+---------+\n");while(!feof(h264bitstream)) {int data_lenth;data_lenth=GetAnnexbNALU(n);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;}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++;}//Freeif (n){if (n->buf){free(n->buf);n->buf=NULL;}free (n);}return 0; }結果:
本程序的輸入為一個H.264原始碼流(裸流)的文件路徑,輸出為該碼流的NALU統計數據,如下圖所示
源碼分析:
- 代碼主要使用0x000001或者0x000000001來確定一個nal數據
- 找到nal數據后解析數據的nal頭(前1個字節),獲得nal頭部信息
總結
- 上一篇: Charles使用详解(For macO
- 下一篇: [css] transition、an