轉自:http://blog.csdn.net/zhuweigangzwg/article/details/44152239
這裡首先說明下H264的結構:
00 00 00 01/00 00 01->nal(1bytes)->slice->宏塊->運動估計向量。
如果h264的body中出現了首碼則由00 00 00 01/00 00 01變為00 03 00 00 01/00 03 00 01.
我們看到常用naltype 像sps= 0x07 pps= 0x08 sei = 0x06 I/P/B= 0x01/0x05 也就是說只判斷naltype = 0x01/0x05是判斷不出來I/P/B框架類型的,需要到slice層去判斷用到“熵編碼”具體的“熵編碼”內容請看:“H.264官方中文版.pdf”.
下面是扣的ffmpeg的源碼判斷I/P/B框架類型的實現:
int GetFrameType(NALU_t * nal){bs_t s;int frame_type = 0; unsigned char * OneFrameBuf_H264 = NULL ;if ((OneFrameBuf_H264 = (unsigned char *)calloc(nal->len + 4,sizeof(unsigned char))) == NULL){printf("Error malloc OneFrameBuf_H264\n");return getchar();}if (nal->startcodeprefix_len == 3){OneFrameBuf_H264[0] = 0x00;OneFrameBuf_H264[1] = 0x00;OneFrameBuf_H264[2] = 0x01;memcpy(OneFrameBuf_H264 + 3,nal->buf,nal->len);}else if (nal->startcodeprefix_len == 4){OneFrameBuf_H264[0] = 0x00;OneFrameBuf_H264[1] = 0x00;OneFrameBuf_H264[2] = 0x00;OneFrameBuf_H264[3] = 0x01;memcpy(OneFrameBuf_H264 + 4,nal->buf,nal->len);}else{printf("H264讀取錯誤。\n");}bs_init( &s,OneFrameBuf_H264 + nal->startcodeprefix_len + 1 ,nal->len - 1 );if (nal->nal_unit_type == NAL_SLICE || nal->nal_unit_type == NAL_SLICE_IDR ){/* i_first_mb */bs_read_ue( &s );/* picture type */frame_type = bs_read_ue( &s );switch(frame_type){case 0: case 5: /* P */nal->Frametype = FRAME_P;break;case 1: case 6: /* B */nal->Frametype = FRAME_B;break;case 3: case 8: /* SP */nal->Frametype = FRAME_P;break;case 2: case 7: /* I */nal->Frametype = FRAME_I;I_Frame_Num ++;break;case 4: case 9: /* SI */nal->Frametype = FRAME_I;break;}}else if (nal->nal_unit_type == NAL_SEI){nal->Frametype = NAL_SEI;}else if(nal->nal_unit_type == NAL_SPS){nal->Frametype = NAL_SPS;}else if(nal->nal_unit_type == NAL_PPS){nal->Frametype = NAL_PPS;}if (OneFrameBuf_H264){free(OneFrameBuf_H264);OneFrameBuf_H264 = NULL;}return 1;}
//H264一幀資料的結構體typedef struct Tag_NALU_t{unsigned char forbidden_bit; //! Should always be FALSEunsigned char nal_reference_idc; //! NALU_PRIORITY_xxxxunsigned char nal_unit_type; //! NALU_TYPE_xxxx unsigned int startcodeprefix_len; //! 首碼位元組數unsigned int len; //! 包含nal 頭的nal 長度,從第一個00000001到下一個000000001的長度unsigned int max_size; //! 最多一個nal 的長度unsigned char * buf; //! 包含nal 頭的nal 資料unsigned char Frametype; //! 框架類型unsigned int lost_packets; //! 預留} NALU_t;//nal類型enum nal_unit_type_e{NAL_UNKNOWN = 0,NAL_SLICE = 1,NAL_SLICE_DPA = 2,NAL_SLICE_DPB = 3,NAL_SLICE_DPC = 4,NAL_SLICE_IDR = 5, /* ref_idc != 0 */NAL_SEI = 6, /* ref_idc == 0 */NAL_SPS = 7,NAL_PPS = 8/* ref_idc == 0 for 6,9,10,11,12 */};//框架類型enum Frametype_e{FRAME_I = 15,FRAME_P = 16,FRAME_B = 17};
//Mybs.h#pragma once#include "Information.h"//讀取位元組結構體typedef struct Tag_bs_t{unsigned char *p_start; //緩衝區首地址(這個開始是最低地址)unsigned char *p; //緩衝區當前的讀寫指標 當前位元組的地址,這個會不斷的++,每次++,進入一個新的位元組unsigned char *p_end; //緩衝區尾地址//typedef unsigned char uint8_t;int i_left; // p所指位元組當前還有多少 “位” 可讀寫 count number of available(可用的)位 }bs_t;/*函數名稱:函數功能:初始化結構體參 數:返 回 值:無傳回值,void類型思 路:資 料: */void bs_init( bs_t *s, void *p_data, int i_data );/*該函數的作用是:從s中讀出i_count位,並將其做為uint32_t類型返回思路:若i_count>0且s流並未結束,則開始或繼續讀取碼流;若s當前位元組中剩餘位元大於等於要讀取的位元i_count,則直接讀取;若s當前位元組中剩餘位元小於要讀取的位元i_count,則讀取剩餘位,進入s下一位元組繼續讀取。補充:寫入s時,i_left表示s當前位元組還沒被寫入的位,若一個新的位元組,則i_left=8;讀取s時,i_left表示s當前位元組還沒被讀取的位,若一個新的位元組,則i_left=8。注意兩者的區別和聯絡。00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 0000000-------- -----000 00000000 ...寫入s時:i_left = 3讀取s時:i_left = 5我思:位元組流提前放在了結構體bs_s的對象bs_t裡了,可能位元組流不會一次性讀取/分析完,而是根據需要,每次都讀取幾位元bs_s裡,有專門的欄位用來記錄曆史讀取的結果,每次讀取,都會在上次的讀取位置上進行比如,100位元組的流,經過若干次讀取,當前位置處於中間一個位元組處,前3個位元已經讀取過了,此次要讀取2位元00001001000 01 001 (已讀過的 本次要讀的 以後要讀的 )i_count = 2(計划去讀2位元)i_left = 5(還有5位元未讀,在本位元組中)i_shr = s->i_left - i_count = 5 - 2 = 3*s->p >> i_shr,就把本次要讀的位元移到了位元組最右邊(未讀,但本次不需要的給移到了位元組外,拋掉了)00000001i_mask[i_count] 即i_mask[2] 即0x03:00000011( *s->p >> i_shr )&i_mask[i_count]; 即00000001 & 00000011 也就是00000001 按位與 00000011結果是:00000001i_result |= ( *s->p >> i_shr )&i_mask[i_count];即i_result |=00000001 也就是 i_result =i_result | 00000001 = 00000000 00000000 00000000 00000000 | 00000001 =00000000 00000000 00000000 00000001i_result =return( i_result ); 返回的i_result是4位元組長度的,是unsigned類型 sizeof(unsigned)=4*/int bs_read( bs_t *s, int i_count );/*函數名稱:函數功能:從s中讀出1位,並將其做為uint32_t類型返回。函數參數:返 回 值:思 路:若s流並未結束,則讀取一位資 料:畢厚傑:第145頁,u(n)/u(v),讀進連續的若干位元,並將它們解釋為“不帶正負號的整數”return i_result;//unsigned int*/int bs_read1( bs_t *s );/*函數名稱:函數功能:從s中解碼並讀出一個文法元素值參 數:返 回 值:思 路:從s的當前位讀取並計數,直至讀取到1為止;while( bs_read1( s ) == 0 && s->p < s->p_end && i < 32 )這個迴圈用i記錄了s當前位置到1為止的0的個數,並丟棄讀到的第一個1;返回2^i-1+bs_read(s,i)。例:當s位元組中存放的是0001010時,1前有3個0,所以i=3;返回的是:2^i-1+bs_read(s,i)即:8-1+010=9資 料:畢厚傑:第145頁,ue(v);無符號指數Golomb熵編碼x264中bs.h檔案部分函數解讀 http://wmnmtm.blog.163.com/blog/static/382457142011724101824726/不帶正負號的整數指數哥倫布碼編碼 http://wmnmtm.blog.163.com/blog/static/38245714201172623027946/*/int bs_read_ue( bs_t *s );
//Mybs.cpp#include "Mybs.h"void bs_init( bs_t *s, void *p_data, int i_data ){s->p_start = (unsigned char *)p_data;//用傳入的p_data首地址初始化p_start,只記下有效資料的首地址s->p = (unsigned char *)p_data;//位元組首地址,一開始用p_data初始化,每讀完一個整位元組,就移動到下一位元組首地址s->p_end = s->p + i_data; //尾地址,最後一個位元組的首地址?s->i_left = 8; //還沒有開始讀寫,當前位元組剩餘未讀取的位是8}int bs_read( bs_t *s, int i_count ){ static int i_mask[33] ={0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff, 0x1ff, 0x3ff, 0x7ff, 0xfff, 0x1fff, 0x3fff, 0x7fff, 0xffff, 0x1ffff, 0x3ffff, 0x7ffff, 0xfffff, 0x1fffff, 0x3fffff, 0x7fffff, 0xffffff, 0x1ffffff, 0x3ffffff, 0x7ffffff, 0xfffffff, 0x1fffffff,0x3fffffff,0x7fffffff,0xffffffff};/* 數組中的元素用二進位表示如下: 假設:初始為0,已寫入為+,已讀取為- 位元組:1234 00000000 00000000 00000000 00000000下標 0x00: 00000000x[0] 0x01: 00000001x[1] 0x03: 00000011x[2] 0x07: 00000111x[3] 0x0f: 00001111x[4] 0x1f: 00011111x[5] 0x3f: 00111111x[6] 0x7f: 01111111x[7] 0xff: 11111111x[8]1位元組 0x1ff: 0001 11111111x[9] 0x3ff: 0011 11111111x[10]i_mask[s->i_left] 0x7ff: 0111 11111111x[11] 0xfff: 1111 11111111x[12]1.5位元組0x1fff: 00011111 11111111x[13]0x3fff: 00111111 11111111x[14]0x7fff: 01111111 11111111x[15]0xffff: 11111111 11111111x[16]2位元組 0x1ffff:0001 11111111 11111111x[17] 0x3ffff:0011 11111111 11111111x[18] 0x7ffff:0111 11111111 11111111x[19] 0xfffff:1111 11111111 11111111x[20]2.5位元組 0x1fffff:00011111 11111111 11111111x[21] 0x3fffff:00111111 11111111 11111111x[22] 0x7fffff:01111111 11111111 11111111x[23] 0xffffff:11111111 11111111 11111111x[24]3位元組 0x1ffffff: 0001 11111111 11111111 11111111x[25] 0x3ffffff: 0011 11111111 11111111 11111111x[26] 0x7ffffff: 0111 11111111 11111111 11111111x[27] 0xfffffff: 1111 11111111 11111111 11111111x[28]3.5位元組0x1fffffff:00011111 11111111 11111111 11111111x[29]0x3fffffff:00111111 11111111 11111111 11111111x[30]0x7fffffff:01111111 11111111 11111111 11111111x[31]0xffffffff:11111111 11111111 11111111 11111111x[32]4位元組 */ int i_shr; // int i_result = 0; //用來存放讀取到的的結果 typedef unsigned uint32_t; while( i_count > 0 ) //要讀取的位元數 { if( s->p >= s->p_end )//位元組流的當前位置>=流結尾,即代表此位元流s已經讀完了。 {// break; } if( ( i_shr = s->i_left - i_count ) >= 0 )//當前位元組剩餘的未讀位元,比要讀取的位元多,或者相等 {//i_left當前位元組剩餘的未讀位元,本次要讀i_count位元,i_shr=i_left-i_count的結果如果>=0,說明要讀取的都在當前位元組內//i_shr>=0,說明要讀取的位元都處於當前位元組內//這個階段,一次性就讀完了,然後返回i_result(退出了函數) /* more in the buffer than requested */ i_result |= ( *s->p >> i_shr )&i_mask[i_count];//“|=”:按位或賦值,A |= B 即 A = A|B//|=應該在最後執行,把結果放在i_result(按位與優先順序高於複合操作符|=)//i_mask[i_count]最右側各位都是1,與括弧中的按位與,可以把括弧中的結果複製過來//!=,左邊的i_result在這兒全是0,右側與它按位或,還是複製結果過來了,好象好幾步都多餘/*讀取後,更新結構體裡的欄位值*/ s->i_left -= i_count; //即i_left = i_left - i_count,當前位元組剩餘的未讀位元,原來的減去這次讀取的 if( s->i_left == 0 )//如果當前位元組剩餘的未讀位元正好是0,說明當前位元組讀完了,就要開始下一個位元組 { s->p++;//移動指標,所以p好象是以位元組為步長移動指標的 s->i_left = 8;//新開始的這個位元組來說,當前位元組剩餘的未讀位元,就是8位元了 } return( i_result );//可能的傳回值之一為:00000000 00000000 00000000 00000001 (4位元組長) } else/* i_shr < 0 ,跨位元組的情況*/ {//這個階段,是while的一次迴圈,可能還會進入下一次迴圈,第一次和最後一次都可能讀取的非整位元組,比如第一次讀了3位元,中間讀取了2位元組(即2x8位元),最後一次讀取了1位元,然後退出while迴圈//當前位元組剩餘的未讀位元,比要讀取的位元少,比如當前位元組有3位未讀過,而本次要讀7位//???對當前位元組來說,要讀的位元,都在最右邊,所以不再移位了(移位的目的是把要讀的位元放在當前位元組最右) /* less(較少的) in the buffer than requested */i_result |= (*s->p&i_mask[s->i_left]) << -i_shr;//"-i_shr"相當於取了絕對值//|= 和 << 都是位操作符,優先順序相同,所以從左往右順序執行//舉例:int|char ,其中int是4位元組,char是1位元組,sizeof(int|char)是4位元組//i_left最大是8,最小是0,取值範圍是[0,8]i_count -= s->i_left;//待讀取的位元數,等於原i_count減去i_left,i_left是當前位元組未讀過的位元數,而此else階段,i_left代表的當前位元組未讀的位元全被讀過了,所以減它s->p++;//定位到下一個新的位元組s->i_left = 8;//對一個新位元組來說,未讀過的位元當然是8,即本位元組所有位都沒讀取過 } } return( i_result );//可能的傳回值之一為:00000000 00000000 00000000 00000001 (4位元組長)}int bs_read1( bs_t *s ){if( s->p < s->p_end ){unsigned int i_result;s->i_left--; //當前位元組未讀取的位元少了1位i_result = ( *s->p >> s->i_left )&0x01;//把要讀的位元移到當前位元組最右,然後與0x01:00000001進行邏輯與操作,因為要讀的只是一個位元,這個位元不是0就是1,與0000 0001按位與就可以得知此情況if( s->i_left == 0 ) //如果當前位元組剩餘未讀位元是0,即是說當前位元組全讀過了{s->p++; //指標s->p 移到下一位元組s->i_left = 8; //新位元組中,未讀位元當然是8位}return i_result; //unsigned int}return 0; //返回0應該是沒有讀到東西}int bs_read_ue( bs_t *s ){int i = 0;while( bs_read1( s ) == 0 && s->p < s->p_end && i < 32 )//條件為:讀到的當前位元=0,指標未越界,最多隻能讀32位元{i++;}return( ( 1 << i) - 1 + bs_read( s, i ) );}