H264视频处理
一、原理
H.264原始碼流(又稱(chēng)為“裸流”)是由一個(gè)一個(gè)的NALU組成的。他們的結(jié)構(gòu)如下圖所示
其中每個(gè)NALU之間通過(guò)startcode(起始碼)進(jìn)行分隔,起始碼分成兩種:0x000001(3Byte)或者0x00000001(4Byte)。如果NALU對(duì)應(yīng)的Slice為一幀的開(kāi)始就用0x00000001,否則就用0x000001。
H.264碼流解析的步驟就是首先從碼流中搜索0x000001和0x00000001,分離出NALU;然后再分析NALU的各個(gè)字段。
二、協(xié)議
2.1.基本概念
- RBSP: 原始字節(jié)序列載荷-->在SODB的后面填加了結(jié)尾比特(RBSP trailing bits 一個(gè)bit“1”)若干比特“0”,以便字節(jié)對(duì)齊。
- EBSP: 擴(kuò)展字節(jié)序列載荷– >在RBSP基礎(chǔ)上填加了仿校驗(yàn)字節(jié)(0X03)它的原因是: 在NALU加到Annexb上時(shí),需要填加每組
? ? NALU之前的開(kāi)始碼 StartCodePrefix,如果該NALU對(duì)應(yīng)的slice為一幀的開(kāi)始則用4位字節(jié)表示,ox00000001,否則用3位字節(jié)表示 ox000001.為了使NALU主體中不包括與開(kāi)始碼相沖突的,在編碼時(shí),每遇到兩個(gè)字節(jié)連續(xù)為0,就插入一個(gè)字節(jié)的0x03。解碼時(shí)將0x03去掉。 也稱(chēng)為脫殼操作
H.264的功能分為兩層,視頻編碼層(VCL)和網(wǎng)絡(luò)提取層(NAL)??
? ? VCL數(shù)據(jù)即被壓縮編碼后的視頻數(shù)據(jù)序列。把VCL數(shù)據(jù)要封裝到NAL單元中之后,才可以用來(lái)傳輸或存儲(chǔ)。H.264 的編碼視頻序列包括一系列的NAL 單元,每個(gè)NAL 單元包含一個(gè)RBSP。編碼片(包括數(shù)據(jù)分割片IDR 片)和序列RBSP 結(jié)束符被定義為VCL NAL 單元,其余為NAL 單元。典型的RBSP 單元序列如圖所示。每個(gè)單元都按獨(dú)立的NAL 單元傳送。單元的信息頭(一個(gè)字節(jié))定義了RBSP 單元的類(lèi)型,NAL 單元的其余部分為RBSP 數(shù)據(jù)
2.2.NAL單元
? ? 每個(gè)NAL單元是一個(gè)一定語(yǔ)法元素的可變長(zhǎng)字節(jié)字符串,包括包含一個(gè)字節(jié)的頭信息(用來(lái)表示數(shù)據(jù)類(lèi)型),以及若干整數(shù)字節(jié)的負(fù)荷數(shù)據(jù)。一個(gè)NAL單元可以攜帶一個(gè)編碼片、A/B/C型數(shù)據(jù)分割或一個(gè)序列或圖像參數(shù)集。
NALU 頭由一個(gè)字節(jié)組成, 它的語(yǔ)法如下:
NAL單元按RTP序列號(hào)按序傳送。其中,T為負(fù)荷數(shù)據(jù)類(lèi)型,占5bit;R為重要性指示位,占2個(gè)bit;最后的F為禁止位,占1bit。具體如下:?
? ? <1>NALU類(lèi)型位?
? ? ? ? 可以表示NALU的32種不同類(lèi)型特征,類(lèi)型1~12是H.264定義的,類(lèi)型24~31是用于H.264以外的,RTP負(fù)荷規(guī)范使用這其中的一些值來(lái)定義包聚合和分裂,其他值為H.264保留
? ? <2>重要性指示位?
? ? ? ? 用于在重構(gòu)過(guò)程中標(biāo)記一個(gè)NAL單元的重要性,值越大,越重要。值為0表示這個(gè)NAL單元沒(méi)有用于預(yù)測(cè),因此可被解碼器拋棄而不會(huì)有錯(cuò)誤擴(kuò)散;值高于0表示此NAL單元要用于無(wú)漂移重構(gòu),且值越高,對(duì)此NAL單元丟失的影響越大
? ? <3>禁止位?
? ? ? ? 編碼中默認(rèn)值為0,當(dāng)網(wǎng)絡(luò)識(shí)別此單元中存在比特錯(cuò)誤時(shí),可將其設(shè)為1,以便接收方丟掉該單元,主要 用于適應(yīng)不同種類(lèi)的網(wǎng)絡(luò)環(huán)境(比如有線無(wú)線相結(jié)合的環(huán)境)
264常見(jiàn)的幀頭數(shù)據(jù)為:
? ? 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表示錯(cuò)誤,一般都是0
NRI:重要級(jí)別,11表示非常重要。
TYPE:表示該NALU的類(lèi)型是什么,
見(jiàn)下表,由此可知7為序列參數(shù)集(SPS),8為圖像參數(shù)集(PPS),5代表I幀。1代表非I幀。
由此可知,61和41其實(shí)都是P幀(type值為1),只是重要級(jí)別不一樣(它們的NRI一個(gè)是11BIN,一個(gè)是10BIN)
NALU類(lèi)型是我們判斷幀類(lèi)型的利器,從官方文檔中得出如下圖:
三、H264(NAL簡(jiǎn)介與I幀判斷)
我們還是接著看最上面圖的碼流對(duì)應(yīng)的數(shù)據(jù)來(lái)層層分析,以00 00 00 01分割之后的下一個(gè)字節(jié)就是NALU類(lèi)型,將其轉(zhuǎn)為二進(jìn)制數(shù)據(jù)后,
解讀順序?yàn)閺淖笸宜?#xff0c;如下:
(1)第1位禁止位,值為1表示語(yǔ)法出錯(cuò)
(2)第2~3位為參考級(jí)別
(3)第4~8為是nal單元類(lèi)型
例如上面00000001后有67,68以及65
其中0x67的二進(jìn)制碼為:
0110 0111
4-8為00111,轉(zhuǎn)為十進(jìn)制7,參考第二幅圖:7對(duì)應(yīng)序列參數(shù)集SPS
其中0x68的二進(jìn)制碼為:
0110 1000?
4-8為01000,轉(zhuǎn)為十進(jìn)制8,參考第二幅圖:8對(duì)應(yīng)圖像參數(shù)集PPS
其中0x65的二進(jìn)制碼為:
011 00101
4-8位為00101,轉(zhuǎn)為十進(jìn)制5,參考第二幅圖:5對(duì)應(yīng)IDR圖像中的片(I幀)
所以判斷是否為I幀的算法為:
(NALU類(lèi)型 & 0001 1111) = 5 即 (NALU類(lèi)型 & 31) = 5?
比如0x65 & 31 = 5 (這種辦法可能不完全正確,具體的做法可參照h264標(biāo)準(zhǔn)文檔判斷)
四、大雷神代碼
#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; }結(jié)果:
本程序的輸入為一個(gè)H.264原始碼流(裸流)的文件路徑,輸出為該碼流的NALU統(tǒng)計(jì)數(shù)據(jù),如下圖所示
源碼分析:
- 代碼主要使用0x000001或者0x000000001來(lái)確定一個(gè)nal數(shù)據(jù)
- 找到nal數(shù)據(jù)后解析數(shù)據(jù)的nal頭(前1個(gè)字節(jié)),獲得nal頭部信息
總結(jié)
- 上一篇: Charles使用详解(For macO
- 下一篇: [css] transition、an