从JM8.6代码看Bitstream、DataPartition、Slice、Picture的关系及码流结构本质

        在global.h中有:

typedef struct
{
  int             byte_pos;           //!< current position in bitstream;
  int             bits_to_go;         //!< current bitcounter
  byte            byte_buf;           //!< current buffer for last written byte
  int             stored_byte_pos;    //!< storage for position in bitstream;
  int             stored_bits_to_go;  //!< storage for bitcounter
  byte            stored_byte_buf;    //!< storage for buffer of last written byte

  byte            byte_buf_skip;      //!< current buffer for last written byte
  int             byte_pos_skip;      //!< storage for position in bitstream;
  int             bits_to_go_skip;    //!< storage for bitcounter

  byte            *streamBuffer;      //!< actual buffer for written bytes
  int             write_flag;         //!< Bitstream contains data and needs to be written

} Bitstream;

      在Bitstream中最重要的便是streamBuffer,这个里面装码流
   

typedef struct datapartition
{

  Bitstream           *bitstream;
  EncodingEnvironment ee_cabac;

  int                 (*writeSyntaxElement)(SyntaxElement *, struct datapartition *);
                      /*!< virtual function;
                           actual method depends on chosen data partition and
                           entropy coding method  */
} DataPartition;

     可见,一个数据分块对应一个Bitstream

typedef struct
{
  int                 picture_id;
  int                 qp;
  int                 picture_type; //!< picture type
  int                 start_mb_nr;
  int                 max_part_nr;  //!< number of different partitions
  int                 num_mb;       //!< number of MBs in the slice
  DataPartition       *partArr;     //!< array of partitions
  MotionInfoContexts  *mot_ctx;     //!< pointer to struct of context models for use in CABAC
  TextureInfoContexts *tex_ctx;     //!< pointer to struct of context models for use in CABAC

  // !KS: RMPNI buffer should be retired. just do some sore simple stuff
  RMPNIbuffer_t        *rmpni_buffer; //!< stores the slice temporary buffer remapping commands

  int                 ref_pic_list_reordering_flag_l0;
  int                 *remapping_of_pic_nums_idc_l0;
  int                 *abs_diff_pic_num_minus1_l0;
  int                 *long_term_pic_idx_l0;
  int                 ref_pic_list_reordering_flag_l1;
  int                 *remapping_of_pic_nums_idc_l1;
  int                 *abs_diff_pic_num_minus1_l1;
  int                 *long_term_pic_idx_l1;

  Boolean             (*slice_too_big)(int bits_slice); //!< for use of callback functions

  int                 field_ctx[3][2]; //GB

} Slice;

       可见,x(x >= 1,且x通常为1,为了简便起见,此处只讨论x为1)个数据分块对应一个片(也叫条带)


 

typedef struct 
{
  int   no_slices;
  int   idr_flag;
  Slice *slices[MAXSLICEPERPICTURE];
  int bits_per_picture;
  float distortion_y;
  float distortion_u;
  float distortion_v;
} Picture;

       可见,x(x >= 1,且x通常为1)片构成一个图像(可以是帧,可以是顶场和底场)

 

      从宏观上来看,码流的结构大致是这样的: (其中      表示分隔符,只考虑一个数据分块对应一个片)

                SPS             PPS          IDR Slice           Slice            Slice             PPS              Slice  ............

            NALU1       NALU2        NALU3        NALU4        NALU5       NALU6       NALU7

    (注意:PPS理论上可以很多,实际上可以只有第一个,  具体情况由编码器决定.)

 

      我们知道,写码流操作是这样的:每写一次码流,实际上要写“分隔符” + “NALU”. 程序在调用start_sequence函数的时候,写入的是“分隔符” + “SPS” 和 “分隔符” + “PPS”. 跟踪程序发现,除了这两次之外,每编码一个片都要进行一次码流的写入,一下程序就证明了这一点:

 

static int writeout_picture(Picture *pic)
{
  Bitstream *currStream;
  int partition, slice;
  Slice *currSlice;

  img->currentPicture=pic;


  // 每个片,都会调用一次writeUnit,每个writeUnit都会调用一次WriteAnnexNALU
 for (slice=0; slice<pic->no_slices; slice++)
 {
    currSlice = pic->slices[slice];
    for (partition=0; partition<currSlice->max_part_nr; partition++) // 调试发现,这层循环只有一次,故为了简便起见,仅仅讨论一个数据分片对应一个片的情形
    {
      currStream = (currSlice->partArr[partition]).bitstream;
      assert (currStream->bits_to_go == 8);    //! should always be the case, the 
                                               //! byte alignment is done in terminate_slice
	  writeUnit (currSlice->partArr[partition].bitstream,partition);

    }           // partition loop
  }           // slice loop
  return 0;   
}

 

      综上所述:(为了简便起见,仅考虑一个数据分块对应一个片

     

      1.  一个Bitstream对应一个DataPartition对应一个Slice, 而x(x >= 1)个Slice对应一个Picture.
           (一个Slice最终可以组装成一个NALU)

 

      2. 组成H.264码流的

          从理论上来讲可以是:一个SPS 和m个PPS和n个Slice,并在其前插入(1 + m + n)个分隔符.

          但从实际编码器来讲:视频序列的PPS可以共用,故只需插入 (1 + 1 + n)个分隔符,JM8.6中就是这种形式.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值