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中是对其不断进行改变的。
2011年8月17日
19时14分02秒
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个。