編解碼的必要性
1.為什么要壓縮
節(jié)省傳輸帶寬;編碼可以將數(shù)據(jù)進(jìn)行壓縮,減少傳輸資源浪費。 節(jié)省存儲空間:當(dāng)顯示器正在播放一個1280*720視頻,幀率是25,那么一秒所產(chǎn)生正常的數(shù)據(jù)大小為:1280*720(位像素)*25(張) / 8(1字節(jié)8位)(結(jié)果:B) / 1024(結(jié)果:KB) / 1024 (結(jié)果:MB) = 2.75MB,一般場景沒必要這么大資源。
2.可以壓縮什么信息
簡單來說就是去除冗余信息
空間冗余:圖像相鄰像素之間有較強(qiáng)的相關(guān)性 時間冗余:視頻序列的相鄰圖像之間內(nèi)容相似 編碼冗余:不同像素值出現(xiàn)的概率不同 視覺冗余:人的視覺系統(tǒng)對某些細(xì)節(jié)不敏感 知識冗余:規(guī)律性的結(jié)構(gòu)可由先驗知識和背景知識得到
3.壓縮類別
無損壓縮(Lossless):Winzip
壓縮前解壓縮后圖像完全一致 壓縮比低(2:1~3:1)
有損壓縮(Lossy):H.264/AVC
壓縮前解壓縮后圖像不一致 壓縮比高(10:1~20:1) 利用人的視覺系統(tǒng)的特性
H.264簡介
上面我們已知h.264是一種有損壓縮,其采?了16*16的分塊??對,對視頻幀圖像進(jìn)?相似?較和壓縮編碼。
1.序列
一段時間內(nèi)圖像變化不大的圖像集我們就可以稱之為一個序列。序列可以理解為有相同特點的一段數(shù)據(jù)。如果某個圖像與之前的圖像變換很大,很難參考之前的幀來生成新的幀,那么就結(jié)束這個序列,開始下一段序列。重復(fù)上一序列的做法,生成新的一段序列。
2. 幀類型
H264結(jié)構(gòu)中,一個視頻圖像編碼后的數(shù)據(jù)叫做一幀,一幀由一個片(slice)或多個片組成,一個片由一個或多個宏塊(MB)組成,一個宏塊由16x16的yuv數(shù)據(jù)組成。宏塊作為H264編碼的基本單位。H264使?幀內(nèi)壓縮和幀間壓縮的?式提?編碼壓縮率,通過I幀、P幀和B幀策略來實現(xiàn),連續(xù)幀之間的壓縮。
?
I幀
幀內(nèi)編碼幀 ,I幀表示關(guān)鍵幀,你可以理解為這一幀畫面的完整保留;解碼時只需要本幀數(shù)據(jù)就可以完成(因為包含完整畫面)。
特點
它是一個全幀壓縮編碼幀。它將全幀圖像信息進(jìn)行JPEG壓縮編碼及傳輸 解碼時僅用I幀的數(shù)據(jù)就可重構(gòu)完整圖像(無需參考其他幀) I幀是P幀和B幀的參考幀(其質(zhì)量直接影響到同組中以后各幀的質(zhì)量) I幀是幀組GOP的基礎(chǔ)幀(第一幀),在一組中只有一個I幀 I幀所占數(shù)據(jù)的信息量比較大 壓縮比為1:7(幀內(nèi)壓縮)
P幀
前向預(yù)測編碼幀。P幀表示的是這一幀跟之前的一個關(guān)鍵幀(或P幀)的差別,解碼時需要用之前緩存的畫面疊加上本幀定義的差別,生成最終畫
特點
P幀采用運動補(bǔ)償?shù)姆椒▊魉退c前面的I或P幀的差值及運動矢量(預(yù)測誤差) 解碼時必須將I幀中的預(yù)測值與預(yù)測誤差求和后才能重構(gòu)完整的P幀圖像 P幀屬于前向預(yù)測的幀間編碼。它只參考前面最靠近它的I幀或P幀 由于P幀是參考幀,它可能造成解碼錯誤的擴(kuò)散 由于是差值傳送,P幀的壓縮比較高,壓縮比為1:20
B幀
雙向預(yù)測內(nèi)插編碼幀。B幀是雙向差別幀,也就是B幀記錄的是本幀與前后幀的差別,換言之,要解碼B幀,不僅要取得之前的緩存畫面,還要解碼之后的畫面,通過前后畫面的與本幀數(shù)據(jù)的疊加取得最終的畫面。
特點
B幀是由前面的I或P幀和后面的P幀來進(jìn)行預(yù)測的 B幀傳送的是它與前面的I或P幀和后面的P幀之間的預(yù)測誤差及運動矢量 B幀是雙向預(yù)測編碼幀 B幀壓縮比最高,因為它只反映丙參考幀間運動主體的變化情況,預(yù)測比較準(zhǔn)確 B幀不是參考幀,不會造成解碼錯誤的擴(kuò)散 B幀壓縮比例高,1:50,但是CPU比較累
3.GOP(畫面組)
GOP即Group of picture(圖像組),指兩個I幀之間的距離,Reference(參考周期)指兩個P幀之間的距離。一個I幀所占用的字節(jié)數(shù)大于一個P幀,一個P幀所占用的字節(jié)數(shù)大于一個B幀。所以在碼率不變的前提下,GOP值越大,P、B幀的數(shù)量會越多,平均每個I、P、B幀所占用的字節(jié)數(shù)就越多,也就更容易獲取較好的圖像質(zhì)量;Reference越大,B幀的數(shù)量越多。
GOP結(jié)構(gòu)一般有兩個數(shù)字,如M=3,N=12。M指定I幀和P幀之間的距離,N指定兩個I幀之間的距離。所以M=3,N=12,GOP結(jié)構(gòu)為:IBBPBBPBBPBBI。在一個GOP內(nèi)I frame解碼不依賴任何的其它幀,p frame解碼則依賴前面的I frame或P frame,B frame解碼依賴前最近的一個I frame或P frame 及其后最近的一個P frame。
值得一提的是業(yè)界的一些并行壓縮方案是可以基于gop來做壓縮的。
4. IDR幀(關(guān)鍵幀)
IDR(Instantaneous Decoding Refresh)即時解碼刷新。 在編碼解碼中為了方便,將GOP中首個I幀要和其他I幀區(qū)別開,把第一個I幀叫IDR,這樣方便控制編碼和解碼流程,所以IDR幀一定是I幀,但I(xiàn)幀不一定是IDR幀;IDR幀的作用是立刻刷新,使錯誤不致傳播,從IDR幀開始算新的序列開始編碼。I幀有被跨幀參考的可能,IDR不會。
IDR的存在是為了解碼的重同步,當(dāng)解碼器解碼到 IDR 圖像時,?即將參考幀隊列清空,將已解碼的數(shù)據(jù)全部輸出或拋棄,重新查找參數(shù)集,開始?個新的序列。這樣,如果前?個序列出現(xiàn)重?錯誤,在這?可以獲得重新同步的機(jī)會。IDR圖像之后的圖像永遠(yuǎn)不會使?IDR之前的圖像的數(shù)據(jù)來解碼。
H264壓縮方式
H264采用的核心算法是幀內(nèi)壓縮和幀間壓縮,幀內(nèi)壓縮是生成I幀的算法,幀間壓縮是生成B幀和P幀的算法。
幀內(nèi)(Intraframe)壓縮也稱為空間壓縮(Spatialcompression)。當(dāng)壓縮一幀圖像時,僅考慮本幀的數(shù)據(jù)而不考慮相鄰幀之間的冗余信息,這實際上與靜態(tài)圖像壓縮類似。幀內(nèi)一般采用有損壓縮算法,由于幀內(nèi)壓縮是編碼一個完整的圖像,所以可以獨立的解碼、顯示。
幀間(Interframe)壓縮的原理是:相鄰幾幀的數(shù)據(jù)有很大的相關(guān)性,或者說前后兩幀信息變化很小的特點。也即連續(xù)的視頻其相鄰幀之間具有冗余信息,根據(jù)這一特性,壓縮相鄰幀之間的冗余量就可以進(jìn)一步提高壓縮量,減小壓縮比。幀間壓縮也稱為時間壓縮(Temporalcompression),它通過比較時間軸上不同幀之間的數(shù)據(jù)進(jìn)行壓縮。幀間壓縮一般是無損的。幀差值(Framedifferencing)算法是一種典型的時間壓縮法,它通過比較本幀與相鄰幀之間的差異,僅記錄本幀與其相鄰幀的差值,這樣可以大大減少數(shù)據(jù)量。
壓縮方式說明
分組,也就是將一系列變換不大的圖像歸為一個組,也就是一個序列,也可以叫GOP(畫面組); 定義幀,將每組的圖像幀歸分為I幀、P幀和B幀三種類型; 預(yù)測幀, 以I幀做為基礎(chǔ)幀,以I幀預(yù)測P幀,再由I幀和P幀預(yù)測B幀; 數(shù)據(jù)傳輸, 最后將I幀數(shù)據(jù)與預(yù)測的差值信息進(jìn)行存儲和傳輸。
H264碼流結(jié)構(gòu)
H264碼流是由一個個的NAL單元組成,其中SPS、PPS、IDR和SLICE是NAL單元某一類型的數(shù)據(jù)。
H.264原始碼流(又稱為“裸流”)是由一個一個的NALU組成的。其中每個NALU之間通過startcode(起始碼)進(jìn)行分隔,起始碼分成兩種:0x000001(3Byte)或者0x00000001(4Byte)。如果NALU對應(yīng)的Slice為一幀的開始就用0x00000001,否則就用0x000001。 H.264碼流解析的步驟就是首先從碼流中搜索0x000001和0x00000001,分離出NALU;然后再分析NALU的各個字段。本文的程序即實現(xiàn)了上述的兩個步驟。參考自視音頻數(shù)據(jù)處理入門:H.264視頻碼流解析
#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é)
以上是生活随笔 為你收集整理的视频编解码之H.264 的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔 推薦給好友。