x264中的NAL流程
2009-11-30 12:11
目前,主要是在分析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的提取
准备。