在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中就是这种形式.