zoukankan      html  css  js  c++  java
  • 对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个。

     

     

     

     

  • 相关阅读:
    uniapp中pages文件及iconfont引入
    js判断字符串是否为JSON格式
    分布式事务CAP简介
    C#面试题
    关于跨域问题
    JS显示隐藏节点
    JS时间序列化显示
    前端命令
    Java8
    java14环境变量配置超简单
  • 原文地址:https://www.cnblogs.com/xkfz007/p/2612931.html
Copyright © 2011-2022 走看看