h264裸码流,根据nalu_header可以知道类型,例如该帧是I帧,P帧/B帧。
例如,常见的0x65代表I帧,0x41代表非关键帧,即P帧或B帧,但是只根据nalu_header是无法区分P帧和B帧的,还需进入到RBSP内部根据语法含义来做判断。
1. JM实现的判别
1 int FirstPartOfSliceHeader() 2 { 3 Slice *currSlice = img->currentSlice; 4 int dP_nr = assignSE2partition[currSlice->dp_mode][SE_HEADER]; 5 DataPartition *partition = &(currSlice->partArr[dP_nr]); 6 Bitstream *currStream = partition->bitstream; 7 int tmp; 8 9 UsedBits= partition->bitstream->frame_bitoffset; // was hardcoded to 31 for previous start-code. This is better. 10 11 // Get first_mb_in_slice 12 currSlice->start_mb_nr = ue_v ("SH: first_mb_in_slice", currStream); 13 14 tmp = ue_v ("SH: slice_type", currStream); 15 16 if (tmp>4) tmp -=5; 17 18 img->type = currSlice->picture_type = (SliceType) tmp; 19 20 currSlice->pic_parameter_set_id = ue_v ("SH: pic_parameter_set_id", currStream); 21 22 return UsedBits; 23 }
如上代码,第二次的ue(无符号哥伦布编码)得出了帧类型,但是还需注意到做差:"if (tmp>4) tmp -=5;",才能得到真正的SliceType:
1 typedef enum { 2 P_SLICE = 0, 3 B_SLICE, 4 I_SLICE, 5 SP_SLICE, 6 SI_SLICE 7 } SliceType;
例如,一段nalu数据为:0x00 00 01 65 88 80 06 64
那么,从0x65后的字节0x88开始算起,
第一个ue得到start_mb_nr,即其值为(2^0 - 1 + 0)= 0,0x88使用了最高位,当前bit_offset=1
第二个ue得到tmp值,即其值为(2^3 - 1 + 000b)= 7,使用了7bit来计算,当前bit_offset=8,再将tmp-5得到2,那么picture_type为I帧。
第三个ue得到pps_id值,从0x80的最高位算起,值为(2^0 - 1 + 0)= 0
2. ffmpeg的实现
实现类似,参考文件h264_slice.c,关键代码如下:
1 static int h264_slice_header_parse(const H264Context *h, H264SliceContext *sl, 2 const H2645NAL *nal) 3 { 4 const SPS *sps; 5 const PPS *pps; 6 int ret; 7 unsigned int slice_type, tmp, i; 8 int field_pic_flag, bottom_field_flag; 9 int first_slice = sl == h->slice_ctx && !h->current_slice; 10 int picture_structure; 11 12 if (first_slice) 13 av_assert0(!h->setup_finished); 14 15 sl->first_mb_addr = get_ue_golomb_long(&sl->gb); 16 17 slice_type = get_ue_golomb_31(&sl->gb); 18 if (slice_type > 9) { 19 av_log(h->avctx, AV_LOG_ERROR, 20 "slice type %d too large at %d\n", 21 slice_type, sl->first_mb_addr); 22 return AVERROR_INVALIDDATA; 23 } 24 if (slice_type > 4) { 25 slice_type -= 5; 26 sl->slice_type_fixed = 1; 27 } else 28 sl->slice_type_fixed = 0; 29 30 slice_type = ff_h264_golomb_to_pict_type[slice_type]; 31 sl->slice_type = slice_type; 32 sl->slice_type_nos = slice_type & 3; 33 34 if (nal->type == H264_NAL_IDR_SLICE && 35 sl->slice_type_nos != AV_PICTURE_TYPE_I) { 36 av_log(h->avctx, AV_LOG_ERROR, "A non-intra slice in an IDR NAL unit.\n"); 37 return AVERROR_INVALIDDATA; 38 } 39 40 sl->pps_id = get_ue_golomb(&sl->gb); 41 ... 42 }
同样也是超过4时,再减去5即得到了帧类型。
3. 小工具实现
本人曾做了一个小工具,参考核心代码如下:
1 static int GetFrameType(NALU_t * nal) 2 { 3 bs_t s; 4 int frame_type = 0; 5 6 bs_init(&s, nal->buf+1, nal->len - 1); 7 8 if (nal->nal_unit_type == NALU_TYPE_SLICE || nal->nal_unit_type == NALU_TYPE_IDR) 9 { 10 /* i_first_mb */ 11 bs_read_ue(&s); 12 /* picture type */ 13 frame_type = bs_read_ue(&s); 14 switch(frame_type) 15 { 16 case 0: case 5: /* P */ 17 nal->frame_type = FRAME_TYPE_P; 18 break; 19 case 1: case 6: /* B */ 20 nal->frame_type = FRAME_TYPE_B; 21 break; 22 case 3: case 8: /* SP */ 23 nal->frame_type = FRAME_TYPE_P; 24 break; 25 case 2: case 7: /* I */ 26 nal->frame_type = FRAME_TYPE_I; 27 break; 28 case 4: case 9: /* SI */ 29 nal->frame_type = FRAME_TYPE_I; 30 break; 31 default: 32 printf("unknown frame type! nalu_data[%#x,%#x,%#x,%#x]\n", nal->buf[0], nal->buf[1], nal->buf[2], nal->buf[3]); 33 break; 34 } 35 } 36 else 37 { 38 nal->frame_type = nal->nal_unit_type; 39 } 40 41 return 0; 42 }