目前,主要是在分析NAL,做抓包实验,所以对NAL的格式要求比较高,这个过程中读了《新一代视频编码》,以前也读过,这是没有遇到实际的问题,读的时候也是似懂非懂的,囫囵吞枣,现在要分析,要用了才知道这些相关文档是要好好读的,ES流也是要好好分析的。在上一篇中关于函数指针和指针函数的理论知识中,我主要是来看看x264中的NAL是怎么在封装的。
在x264中使用的函数指针,关于NAL部分的下面的一些: static int (*p_write_nalu)( hnd_t handle, uint8_t *p_nal, int i_size );,在这里可以看到p_write_nalu)是一个指针函数,p_write_nalu = write_nalu_bsf;即对于p_write_nalu其所指向的函数write_nalu_bsf,类似的还有: p_write_nalu = write_nalu_mp4; p_write_nalu = write_nalu_mkv; //每一个NALU都是由header+payload组成的,在header的结构是可以参考264的相关文档 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, NAL_AUD = 9, /* ref_idc == 0 for 6,9,10,11,12 */ }; enum nal_priority_e { NAL_PRIORITY_DISPOSABLE = 0, NAL_PRIORITY_LOW = 1, NAL_PRIORITY_HIGH = 2, NAL_PRIORITY_HIGHEST = 3, }; //NAL结构 typedef struct { int i_ref_idc; /* nal_priority_e */ int i_type; /* nal_unit_type_e */ /* Size of payload in bytes. */ int i_payload; //负载的大小 /* If param->b_annexb is set, Annex-B bytestream with 4-byte startcode. * Otherwise, startcode is replaced with a 4-byte size. * This size is the size used in mp4/similar muxing; it is equal to i_payload-4 */ uint8_t *p_payload;//如果是字节流格式的NAL时所用到的前缀4bytes } x264_nal_t; 下面主要是跟踪x264,得到NAL的封装流程,如下: 1. //NAL static void x264_nal_start( x264_t *h, int i_type, int i_ref_idc ) { x264_nal_t *nal = &h->out.nal[h->out.i_nal]; nal->i_ref_idc = i_ref_idc; nal->i_type = i_type; nal->i_payload= 0; nal->p_payload= &h->out.p_bitstream[bs_pos( &h->out.bs ) / 8]; } //下面是对bs_pos函数的注解 static inline int bs_pos( bs_t *s ) { return( 8 * (s->p - s->p_start) + (WORD_SIZE*8) - s->i_left ); / /获取当前的NALU的地址?????????- s->i_left } //bs_s的结构 typedef struct bs_s { uint8_t *p_start; uint8_t *p; uint8_t *p_end; intptr_t cur_bits; int i_left; /* i_count number of available bits */ int i_bits_encoded; /* RD only */ } bs_t; 2. static int x264_encoder_encapsulate_nals( x264_t *h ) //NAL封装 { int nal_size = 0, i; uint8_t *nal_buffer; for( i = 0; i < h->out.i_nal; i++ ) nal_size += h->out.nal[i].i_payload; /* Worst-case NAL unit escaping: reallocate the buffer if it's too small. */ if( h->nal_buffer_size < nal_size * 3/2 + h->out.i_nal * 4 ) { uint8_t *buf = x264_malloc( nal_size * 2 + h->out.i_nal * 4 ); if( !buf ) return -1; x264_free( h->nal_buffer ); h->nal_buffer = buf; } nal_buffer = h->nal_buffer; for( i = 0; i < h->out.i_nal; i++ ) { int size = x264_nal_encode( nal_buffer, h->param.b_annexb, &h->out.nal[i] ); h->out.nal[i].i_payload = size; h->out.nal[i].p_payload = nal_buffer; nal_buffer += size; } return nal_buffer - h->nal_buffer; } 3.在2.中有: int x264_nal_encode( uint8_t *dst, int b_annexb, x264_nal_t *nal ) { uint8_t *src = nal->p_payload; //为了同意结构还是将字节流格式的前缀作为指针的初始值 uint8_t *end = nal->p_payload + nal->i_payload; uint8_t *orig_dst = dst; int i_count = 0, size; /* long nal start code (we always use long ones) */ if( b_annexb ) //这里是进行字节流格式的码流编码,有开始前缀码,对于RTP封装则不需要前缀码 { *dst++ = 0x00; *dst++ = 0x00; *dst++ = 0x00; *dst++ = 0x01; } else /* save room for size later */ dst += 4; /* nal header */ *dst++ = ( 0x00 << 7 ) | ( nal->i_ref_idc << 5 ) | nal->i_type;//第一个bit的设置是由编码器自己控制的 while( src < end ) { if( i_count == 2 && *src <= 0x03 ) { *dst++ = 0x03;//伪起始码 i_count = 0; } if( *src == 0 ) i_count++; else i_count = 0; *dst++ = *src++; } size = (dst - orig_dst) - 4; //减4主要是前面的前缀码所占的字节 /* Write the size header for mp4/etc */ if( !b_annexb ) { /* Size doesn't include the size of the header we're writing now. */ orig_dst[0] = size>>24; orig_dst[1] = size>>16; orig_dst[2] = size>> 8; orig_dst[3] = size>> 0; } return size+4; //+4 } 4. static int x264_nal_end( x264_t *h ) { x264_nal_t *nal = &h->out.nal[h->out.i_nal]; nal->i_payload = &h->out.p_bitstream[bs_pos( &h->out.bs )/ 8] - nal ->p_payload; h->out.i_nal++; /* if number of allocated nals is not enough, re-allocate a larger one. */ if( h->out.i_nal >= h->out.i_nals_allocated ) { x264_nal_t *new_out = x264_malloc( sizeof(x264_nal_t) * (h->out.i_nals_allocated*2) ); if( !new_out ) return -1; memcpy( new_out, h->out.nal, sizeof(x264_nal_t) * (h->out.i_nals_allocated) ); x264_free( h->out.nal ); h->out.nal = new_out; h->out.i_nals_allocated *= 2; } return 0; } 5.p_write_nalu int write_nalu_bsf( hnd_t handle, uint8_t *p_nalu, int i_size ) { if( fwrite( p_nalu, i_size, 1, (FILE*)handle ) > 0 ) //就是把p_nalu里面的1*i_size的字节输出到handle里面 return i_size; return -1; } 实验跟踪: 在编码第一个I帧的时候,要编码的NALU的个数为4个,这里主要是指编码的类型为:SEI,SPS,PPS,I帧的NAL的编码,对于一个I帧,也就是对这个GOP中的图像序列参数,图像参数进行编码,即有如下: /* Write SPS and PPS */ if( i_nal_type == NAL_SLICE_IDR && h->param.b_repeat_headers ) { if( h->fenc->i_frame == 0 ) { /* identify ourself */ x264_nal_start( h, NAL_SEI, NAL_PRIORITY_DISPOSABLE ); if( x264_sei_version_write( h, &h->out.bs ) ) return -1; if( x264_nal_end( h ) ) return -1; overhead += h->out.nal[h->out.i_nal-1].i_payload + NALU_OVERHEAD; } /* generate sequence parameters */ x264_nal_start( h, NAL_SPS, NAL_PRIORITY_HIGHEST ); x264_sps_write( &h->out.bs, h->sps ); if( x264_nal_end( h ) ) return -1; overhead += h->out.nal[h->out.i_nal-1].i_payload + NALU_OVERHEAD; /* generate picture parameters */ x264_nal_start( h, NAL_PPS, NAL_PRIORITY_HIGHEST ); x264_pps_write( &h->out.bs, h->pps ); if( x264_nal_end( h ) ) return -1; overhead += h->out.nal[h->out.i_nal-1].i_payload + NALU_OVERHEAD; } int x264_encoder_headers( x264_t *h, x264_nal_t **pp_nal, int *pi_nal ) { int frame_size = 0; /* init bitstream context */ h->out.i_nal = 0; bs_init( &h->out.bs, h->out.p_bitstream, h->out.i_bitstream ); /* Write SEI, SPS and PPS. */ x264_nal_start( h, NAL_SEI, NAL_PRIORITY_DISPOSABLE ); if( x264_sei_version_write( h, &h->out.bs ) ) return -1; if( x264_nal_end( h ) ) return -1; /* generate sequence parameters */ x264_nal_start( h, NAL_SPS, NAL_PRIORITY_HIGHEST ); x264_sps_write( &h->out.bs, h->sps ); if( x264_nal_end( h ) ) return -1; /* generate picture parameters */ x264_nal_start( h, NAL_PPS, NAL_PRIORITY_HIGHEST ); x264_pps_write( &h->out.bs, h->pps ); if( x264_nal_end( h ) ) return -1; bs_flush( &h->out.bs ); frame_size = x264_encoder_encapsulate_nals( h ); /* now set output*/ *pi_nal = h->out.i_nal; *pp_nal = &h->out.nal[0]; h->out.i_nal = 0; return frame_size; } 总结一下:对于NALU,并不是所谓的一帧对应一个NALU,而是对于SLICE而言,一个slice就封装层一个nal,具体一帧中有几个nalu则是可以再pps中参数中进行设定的,每遇到一个IDR,则此时就将对应的SPS,PPS进行一个更新,NAL的生成过程:先是对header里面的8个bits进行设定,然后是Payload,中间的细节,过于复杂,目前还不能够钻的很深,就先理解到这里。只是把大概的流程疏通。对下一步NALU的提取准备。 |