H.264码流结构及JM8.6中码流的产生步骤

整理自:http://www.cnblogs.com/xkfz007/archive/2012/08/12/2612931.html

H.264中的码流结构

H.264码流结构示意图:





NALU第一字节包括3个语法结构:
forbidden_zero_bit(1) // 1bit禁止位,一般为0;
nal_ref_idc(2) // 2bit 表示该nal单元的重要性,
nal_unit_type(5) // 5bit 表示nalu类型

加起来正好一个字节, 如下图所示:



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中是对其不断进行改变的。

对于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个。

JM8.6中码流的产生步骤:

1. 参数集的写入

在JM8.6的main函数中,先调用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);
(该文最下面有对WriteNALU的介绍)
在函数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. 结束

最后释放空间。

  

关于WriteNALU:

在start_sequence()函数中将WriteNALU函数指针赋值,

int start_sequence()    
{    
    int len=0;    
    NALU_t *nalu;    
    switch(input->of_mode)    
    {    
        case PAR_OF_ANNEXB:    
            OpenAnnexbFile (input->outfile);    
            WriteNALU = WriteAnnexbNALU;    
            //当执行了这步,后面调用WriteNALU就等于调用WriteAnnexbNALU  
            break;    
        case PAR_OF_RTP:    
                OpenRTPFile (input->outfile);    
                WriteNALU = WriteRTPNALU;    
                break;    
        default:    
                snprintf(errortext, ET_SIZE, "Output File Mode %d not supported", input->of_mode);    
                error(errortext,1);    
                return 1;    
    }    
}  


函数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))

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值