对H.264码流结构的理解

2011年8月18日 09时31分13秒

SODB到RBSP的转换:

对SODB的最后填充rbsp_trailing_bits就得到RBSP,而这个rbsp_trailing_bits是第一个比特为1,接下来是0,直到字节对齐。比如SODB的最后几个比特是1001,这时rbsp_trailing_bits即为:1000

SODB 到RBSP到转换代码如下:

void SODBtoRBSP(Bitstream*currStream)

{

currStream->byte_buf <<= 1;  //左移1bit

currStream->byte_buf |= 1;   //在尾部填一个"1"占1bit

currStream->bits_to_go--;

currStream->byte_buf <<= currStream->bits_to_go;

currStream->streamBuffer[currStream->byte_pos++] =currStream->byte_buf;

currStream->bits_to_go = 8;

currStream->byte_buf = 0;

}

前4句就是要得到rbsp_trailing_bits,bits_to_go用来记录要添0的个数。bits_to_go这个量在进行u_v等函数调用的函数writeUVLC2buffer中是对其不断进行改变的。

 

2011817

191402

NALU第一字节就是包括3个语法结构: forbidden_zero_bit(1),nal_ref_idc(2),nal_unit_type(5),加起来正好一个字节, 如下图所示:

下图展示的是H.264中的码流结构:

 

JM8.6代码中对H.264的描述:

在编码函数中,先将NALU填充完,然后再写nalu的函数WriteAnnexbNALU中,先写3个字节的起始码0x000001

即:

putc (0, f);

putc (0, f);

putc (1, f);

BitsWritten += 24;

然后再将NALU写入到文件中,即:

if (n->len != fwrite (n->buf, 1, n->len, f))

 

在JM8.6的main函数中,

1. 先调用start_sequence();函数来开始序列:start_sequence()函数中进行了写序列参数集和图像参数集的操作。

(1) GenerateSeq_parameter_set_NALU产生序列参数集,WriteNALU函数写NALU,即:

nalu = GenerateSeq_parameter_set_NALU ();

len += WriteNALU (nalu);

(2) GeneratePic_parameter_set_NALU产生图像参数集,WriteNALU函数写NALU,即:

nalu = GeneratePic_parameter_set_NALU ();

len += WriteNALU (nalu);

在函数GenerateSeq_parameter_set_NALU和函数GeneratePic_parameter_set_NALU中都是先将参数集数据写入到数据结构bitstream->streamBuffer中,然后利用SODBtoRBSP函数对原始数据进行处理得到RBSP,然后再函数RBSPtoNALU函数中将RBSP转为EBSP 最后调用WriteNALU (nalu)函数将所写好的nalu写入到文件中去。

2. 在for循环中调用encode_one_frame函数编码每一帧,并且包括将编码完每一帧得到熵编码得到的码流 写入到一个NALU中去,所以说一个NALU就是一帧图像编码过程中的编码结果是保存在currStream->streamBuffer数据结构中的,然后在函数writeUnit中将currStream->streamBuffer中的数据复制到了nalu结构中。其实是在writeUnit函数中先生成了一个nalu数据结构,然后填充nalu的相关数据,最后调用WriteNALU函数将nalu写入到文件中。

帧图像:encode_one_frame()

->(1) frame_picture()->code_a_picture()->(while循环)encode_one_slice->encode_one_MB

(2) writeout_picture()->(for循环)writeUnit()->WriteNALU()[准确来说是写一个slice]

场图像:encode_one_frame()

->(1)field_picture (top_pic, bottom_pic);->code_a_picture(分别对顶场和底场编码)

(2)writeout_picture (top_pic);

writeout_picture (bottom_pic);分别写顶场和底场

 

通过分析可以知道编码时是将一个slice作为一个小组(区域单位)进行编码的,一帧图像可以包括多个slice。在JM8.6中数据结构的层次是:

Picture{

Slice

{DataPartition

{Bitstream

}

}

}

从这个我们可以看到一幅图像可以包括多个slice,一个slice可以对应1个Bitstream,而在函数writeUnit中是以slice为单位进行写的,所以我们可以得到一个NALU中包含一个slice,一般情况下一个slice对应一幅图像,所以,此时一个NALU也就对应一个slice

 

3. 最后释放空间

对于RBSPtoEBSP函数的理解:

这个函数是将RBSP转为EBSP,需要进行填充防止竞争的0x03,需要对以下几种进行变化:

0x000000----->0x00000300

0x000001----->0x00000301

0x000002----->0x00000302

0x000003------>0x00000303

在具体的代码中, 是利用下面的代码进行实现的:

if(count == ZEROBYTES_SHORTSTARTCODE && !(NAL_Payload_buffer[i] & 0xFC))

{

streamBuffer[j] = 0x03;

j++;

count = 0;

}

注意:在上面的if语句中,count用于统计0x00字节的个数,当count==2时,我们需要检测接下来的一个字节是否为(00,01,02或03),此处用的方法比较巧妙:0xFC=11111100b,也就是说屏蔽了最后两位比特,这样要求NAL_Payload_buffer[i] 必须为0。如果满足这个说明就是我们要找的那4个。

 

 

 

 

posted @ 2012-07-28 11:16  Mr.Rico  阅读(...)  评论(...编辑  收藏