1 .Mpeg-1数据流分析
编码后的视频序列是一个如同计算机网络的OSI模型下的数据序列一样,数据被分成很多层的概念。
视频序列层-画面组层-画面层-片层-宏块层-块层
层次的关系很明显,越往后越是底层,越接近实际的数据。
1.1视频序列层(VideoStream)
视频序列是以一个序列标题开始,之后可以跟着一个或者多个画面组。最后以Sequence_end_code结束。紧挨着每一个画面组之前可以有一个序列标题。也就是说每个画面组,都可以有一个自己的序列标题。
序列标题是一个以sequence_header_code开始,后跟着一系列数据元素的 结构。是视频流中用来解码的重要的参数之一。其中定义了量化矩阵(load_intra_quantizer_matrix和 load_non_intra_quantizer_matrix以及可选的intra_quantizer_matrix和 non_intra_quantizer_ matrix)以及其它的一些重要的数据元素,其中量化矩阵是可以在视频流中重复的量化矩阵中变化的,并且在 每次变化后,量化矩阵重新定义。其它的元素必须与第一个序列标题中的值相同。
整个视频序列的结构可以用下面的代码表示:
Video_Stream{
unsigned int h_size; /* Horiz. size in pixels. */
unsigned int v_size; /* Vert. size in pixels. */
unsigned int mb_height; /* Vert. size in mblocks. */
unsigned int mb_width; /* Horiz. size in mblocks. */
unsigned char aspect_ratio; /* Code for aspect ratio. */
unsigned char picture_rate; /* Code for picture rate. */
unsigned int bit_rate; /* Bit rate. */
unsigned int vbv_buffer_size; /* Minimum buffer size. */
BOOLEAN const_param_flag; /* Contrained parameter flag. */
unsigned char intra_quant_matrix[8][8]; /* Quantization matrix for
intracoded frames. */
unsigned char non_intra_quant_matrix[8][8]; /* Quanitization matrix for
non intracoded frames. */
char *ext_data; /* Extension data. */
char *user_data; /* User data. */
GoP group; /* Current group of pict. */
Pict picture; /* Current picture. */
Slice slice; /* Current slice. */
Macroblock mblock; /* Current macroblock. */
Block block; /* Current block. */
int state; /* State of decoding. */
int bit_offset; /* Bit offset in stream. */
unsigned int *buffer; /* Pointer to next byte in
buffer. */
int buf_length; /* Length of remaining buffer.*/
unsigned int *buf_start; /* Pointer to buffer start. */
int max_buf_length; /* Max lenght of buffer. */
PictImage *past; /* Past predictive frame. */
PictImage *future; /* Future predictive frame. */
PictImage *current; /* Current frame. */
PictImage *ring[RING_BUF_SIZE]; /* Ring buffer of frames. */
} Video_Stream;
具体的序列标题的结构的部分是这样的:
序列
sequence_header{
SEQ_START_CODE 0x000001b3; /* 常量 ,作用使用来定位视频序列的序列头 */
unsigned int h_size; /* Horiz. size in pixels. */
unsigned int v_size; /* Vert. size in pixels. */
unsigned int mb_height; /* Vert. size in mblocks. */
unsigned int mb_width; /* Horiz. size in mblocks. */
unsigned char aspect_ratio; /* Code for aspect ratio. */
unsigned char picture_rate; /* Code for picture rate. */
unsigned int bit_rate; /* Bit rate. */
unsigned int vbv_buffer_size; /* Minimum buffer size. */
BOOLEAN const_param_flag; /* Contrained parameter flag. */
unsigned char load_intra_quantizer_matrix;
unsigned char intra_quant_matrix[8][8]; /* Quantization matrix for intracoded frames. 这个结构是可选的,要看load_intra_quantizer_matrix的值,为真则有这个部分,否则没有,因为
intra_quant_matrix是量化表的值,而Sequence_header结构在视频序列中是可重复的,即在每个画面组之前都有可能再次给出一个sequence_header,并且可以在新的sequence_header 中重新定义量化表*/
unsigned char load_non_intra_quantizer_matrix;
unsigned char non_intra_quant_matrix[8][8]; /* Quanitization matrix for non intracoded frames. 也是可选。愿意于 intra_quant_matrix可选的原因相同。当load_non_intra_quant_matrix的值为真的时候需要定义。 */
char *ext_data; /* Extension data. */
char *user_data; /* User data. */
}
由上面的分析,可以看出来的是:
video_sequence(){
next_start_code()
do{
sequence_header();
do{
group_of_pictures() ;画面组
}while (nextbits()==GROUP_START_CODE)
}while(nextbits()==SEQUENCE_HEADER_CODE)
SEQUENCE_END_CODE
};
正是由于视频序列中存在很多开始码,或者称之为定位码、同步码。用来告诉解码器目前数据的区域信息,所以解码器才可以正确的处理各个数据区的数据,下面就是视频序列中的开始码的罗列:
#define SEQ_END_CODE 0x000001b7
#define SEQ_START_CODE 0x000001b3
#define GOP_START_CODE 0x000001b8
#define PICTURE_START_CODE 0x00000100
#define SLICE_MIN_START_CODE 0x00000101
#define SLICE_MAX_START_CODE 0x000001af
#define EXT_START_CODE 0x000001b5
#define USER_START_CODE 0x000001b2
这些开始码都是一些特殊的32bits的比特序列,在视频码流中不会出现的。他们的起着标志的作用,具体可以从名称上面看出来。
其中EXT_START_CODE和USER_START_CODE在每个层里面都会出现,用来标志扩展数据区和用户数据区,用来添加任意的数据,直到下一个开始码结束。
1.2画面组层(GOP)
在软件xmplay1.1中的定义
typedef struct GoP {
BOOLEAN drop_flag; /* Flag indicating dropped frame. */
unsigned int tc_hours; /* Hour component of time code. */
unsigned int tc_minutes; /* Minute component of time code. */
unsigned int tc_seconds; /* Second component of time code. */
unsigned int tc_pictures; /* Picture counter of time code. */
BOOLEAN closed_gop; /* Indicates no pred. vectors to
previous group of pictures. */
BOOLEAN broken_link; /* B frame unable to be decoded. */
char *ext_data; /* Extension data. */
char *user_data; /* User data. */
} GoP;
当然每个画面组层都是开始与标志码:GOP_START_CODE
该层次语法上的定义是
group_of_pictures{
GOP_START_CODE
Time_code; tc_hours,tc_minutes,tc_seconds,tc_pictures
Closed_gop;
Broken_link;
Next_start_code;
If(nextbits==extension_start_code){
Extension_start_code;
While(nextbits()==”0000 0000 0000 0000 0000 0001”){
Group_extension_data;
}
next_start_code()
}
if(nextbits==user_data_start_code){
user_data_start_code
while(nextbits()!=’0000 0000 0000 0000 0000 0001’){
user_data;
}
next_start_code()
}
do{
picture()
}while(nextbits==picture_start_code)
}
Mpeg流最终显示出来是一系列的画面,而画面组是mpeg流中可以独立编码的最小的单位,每个画面组由一个标题和一系列画面组成。GOP标题包含了时间和编辑的信息。
Mpeg画面组中必须至少有一个I帧画面,可以有数目可变的B帧和P帧画面,也可以没有P和B帧。画面组的第一幅编码画面是I画面,该画面之后跟随着任意数目的I或P画面,每对I、P画面之间可以插入任意数目的B画面。
画面组是画面的集合,每幅画面按照显示的顺序相邻。
画面组中的画面有两种排列顺序:
1.按比特流顺序 必须以I帧开头,后面可按任何的次序,跟上任意数目的I,P或B画面。
2.按显示顺序 必须以I或B画面打头,且以I或P画面结束,最小的画面组由一个I画面组成。
从编码角度,可以精确的陈述的是,画面组以一个画面组标题开始,以最先出现的下一个画面组标题或者下一个序列标题或者序列结束码结束。
Mepg流中的标志码也就是开始码,对正确的分割和识别码流的成分起到了至关重要的作用。
1.3画面层(Pictures)
画面组层中的一幅幅画面就是画面层的数据了。包含了一幅画面的所有编码信息。一幅画面同样始于画面的标题。标题以画面开始码(PICTURE_START_CODE 0x00000100)打头。
解析画面单元的语法结构:
picture(){
picture_start_code
temprol_reference /*时序编号,通常一组画面的编号都在1024以内,如果超过那么在1025幅画面出复位为0,重新计数。*/
picture_coding_type
vbv_delay /*对于固定比特率的视频流,vbv_delay用与解码过程开始和随机存取之后,以保证在第一幅画面被显示之前,解码器已经读到正确数目的比特数。*/
if((picture_coding_type==2) || picture_coding_type== 3){
full_pel_foward_vector /*全象素前向矢量,给定前向矢量的精度,在P和B画面的标题中出现*/
forward_f_code
}
if(picture_coding_type==3){
full_pel_backward_vector
back_f_code
}
while(nextbits()==’1’){
extra_bit_picture
extra_information_picture
}
extra_bit_picture
next_start_code
if(nextbits()==extension_start_code){
extension_start_code
while(nextbits()!=’0000 0000 0000 0000 0000 0001’){
picture_extension_data
}
next_start_code()
}
if(nextbits()==user_data_start_code){
user_data_start_code
while(nextbits()!=’0000 0000 0000 0000 0000 0001’){
user_data
}
next_start_code()
}
do {
slice()
}while(nextbits()==slice_start_code)
}
整个画面单元结构是这样的:
typedef struct pict {
unsigned int temp_ref; /* Temporal reference. */
unsigned int code_type; /* Frame type: P, B, I */
unsigned int vbv_delay; /* Buffer delay. */
BOOLEAN full_pel_forw_vector; /* Forw. vectors specified in full
pixel values flag. */
unsigned int forw_r_size; /* Used for vector decoding. */
unsigned int forw_f; /* Used for vector decoding. */
BOOLEAN full_pel_back_vector; /* Back vectors specified in full
pixel values flag. */
unsigned int back_r_size; /* Used in decoding. */
unsigned int back_f; /* Used in decoding. */
char *extra_info; /* Extra bit picture info. */
char *ext_data; /* Extension data. */
char *user_data; /* User data. */
} Pict;
可以看出整个pictures层的bit流结构中由标题和pictures数据组成。
标题中提供了必要的画面信息数据和运动矢量的信息。
1.4片层(Slice)
片是任意数目宏块组成的序列,其中宏块必须从画面的左上位置开始,按照光栅扫描的方向从左到右,从上到下排列。片中至少包涵一个宏块,片与片之间没有重叠,也没有间隙。
片层的解析语法:
首先给出识别出Slice层数据的头标slice_start_code
『
#define SLICE_MIN_START_CODE 0x00000101
#define SLICE_MAX_START_CODE 0x000001af
』
slice{
slice_start_code /*从中可以计算出slice_vertical_position 片中第一个宏块,以宏块为单位的垂直位置*/
quantizer_scale /*设置量化步长尺寸。1-31*/
while(nextbits()==’1’){
extra_bit_slice ‘1’
extra_information_slice
}
extra_bit_scale ‘0’
do{
macroblock()
}while(nextbits()!=’0000 0000 0000 0000 0000 0000’)
next_start_code()
}
typedef struct slice {
unsigned int vert_pos; /* Vertical position of slice. */
unsigned int quant_scale; /* Quantization scale. */
char *extra_info; /* Extra bit slice info. */
} Slice;
每个片由一个开始码开始,开始后DC系数和矢量解码的预测值都被复位,片开始部位的位置的水平位置由片中第一个宏块的宏块地址决定。这些措施使得在一幅画面内任何一片都可以单独编码而不需要前一片的信息。当解码是出现错误,即可以从后继的片重新开始。
所以,当数据在无错的环境中,可以一幅画面就作为一片,但是如果是有错的环境,则每行宏块作为一片会更加合理。
表2 256×192画面内的片划分(每行宏块作为一个片,每个片的高度都是16pixels)
1开始 1结束
2开始 2结束
3开始 3结束
4开始 4结束
5开始 5结束
6开始 6结束
7开始 7结束
8开始 8结束
9开始 9结束
10开始 10结束
11开始 11结束
12开始 12结束
13开始 13结束
实际情况中片不宜太多,因为片标题,以及新片所需要尽心重新编码花费的开销很大。
片始于片标题,片标题又始于片开始码,片开始码是可以在一个范围中取得得,这个范围就是
#define SLICE_MIN_START_CODE 0x00000101
#define SLICE_MAX_START_CODE 0x000001af
片开始码得最后8为可以给出片得垂直位置,即以宏块为单位从画面顶部位置为1开始算起,片中第一个宏块的垂直位置。宏块有一个行号可以作为它得定位数据,这个行号的计算方法是:片垂直位置-1
宏块的垂直位置最大为175。片中第一个宏块的水平位置,可以由该宏块的地址偏移计算出来,所以不需要依赖画面内的任何其他的宏块的信息。
1.5宏块层(Macroblock)
宏块是包含16pixels*16lines的亮度分量部分,以及在空间位置上对应的两个8pixels*8lines的色度分量部分,一个宏块有4个亮度块和2个色度块。宏块可以指源图像或者重构图像的数据,或者是量化后的DCT系数。
宏块中块的顺序如下:
表1 宏块中块的排列
0 1
2 3
4
5
Y分量 Cb分量 Cr分量
宏块的数据分析语法描述:
macroblock(){
while(nextbits()==’0000 0001 111’){
macroblock_stuffing /*宏块填料,为了防止下溢出,由编码器填入 的数据,有它固定的11位bit格式就是’0000 0001 111’,当然解决下溢出的方法还有很多,编码器可以在标题之前就加入填料位,或者可以减 小quant_scale获得更多的编码系数等等*/
}
while(nextbits()==’0000 0001 000’){
macroblock_escape /*固定模式的bit串,当macroblock_address与previous_macroblock_address的差大于33时将用到该码。使得后继的macroblock_increment所表示的值加33。
}
macroblock_address_increment /*用于表示 macroblock_address和previous_macorblock_ address之间的差值。 最大值为33,当前两者差大于33时用 macroblock_escape补充。 Macroblock_address表示的是宏块在画面中的绝对位置,最左上角的宏块的 macroblock_address为0,previous_macroblock_address指示片中最后一个非跳空宏块的位置。
macroblock_type
if(macroblock_motion_forward){
motion_horizontal_forward_code
if((forward_f!=1) && (motion_horizontal_forward_code!=0))
motion_horizontal_forward_r
motion_vertical_forward_code
if((forward_f!=1) && (motion_vertical_forward_code!=0))
motion_vertical_forward_r
}
if(macroblock_motion_backward){
motion_horizontal_backward_code
if((backward_f!=1) && (motion_horizontal_backward_code!=0))
motion_horizontal_backward_r
motion_vertical_backward_code
if((backward_f!=1) && (motion_vertical_backward_code!=0))
motion_vertical_backward_r
}
if(macroblock_pattern)
coded_block_pattern /*可以得到宏块宏块的pattern_code[i](i=0:5),从而确定该宏块接收到的块的种类有哪些。*/
for(i=0;i<6;i++)
block(i)
if(picture_coding_type==4)
end_of_marcoblock
}
片被分为16pixels*16lines的象素宏块。每个宏块都有它的标题。包含了宏块的地址、类型、量化器标尺信息等等。标题之后是该宏块的6个块的数据。
在Xmplay代码中给出的macrblock的定义:
typedef struct macroblock {
int mb_address; /* Macroblock address. */
int past_mb_addr; /* Previous mblock address. */
int motion_h_forw_code; /* Forw. horiz. motion vector code. */
unsigned int motion_h_forw_r; /* Used in decoding vectors. */
int motion_v_forw_code; /* Forw. vert. motion vector code. */
unsigned int motion_v_forw_r; /* Used in decdoinge vectors. */
int motion_h_back_code; /* Back horiz. motion vector code. */
unsigned int motion_h_back_r; /* Used in decoding vectors. */
int motion_v_back_code; /* Back vert. motion vector code. */
unsigned int motion_v_back_r; /* Used in decoding vectors. */
unsigned int cbp; /* Coded block pattern. */
BOOLEAN mb_intra; /* Intracoded mblock flag. */
BOOLEAN bpict_past_forw; /* Past B frame forw. vector flag. */
BOOLEAN bpict_past_back; /* Past B frame back vector flag. */
int past_intra_addr; /* Addr of last intracoded mblock. */
int recon_right_for_prev; /* Past right forw. vector. */
int recon_down_for_prev; /* Past down forw. vector. */
int recon_right_back_prev; /* Past right back vector. */
int recon_down_back_prev; /* Past down back vector. */
} Macroblock;
1.6块层(Block)
块是一个正交的8pixels*8lines的亮度或者色度分量,块可以指源画面数据或者相应的编码数据元素。
8*8单位象素的源画面数据经过DCT变换后的成为了相应的DCT系数块。
块的具体结构为(xmplay源码中的结构定义):
typedef struct block {
short int dct_recon[8][8]; /* Reconstructed dct coeff matrix. */
short int dct_dc_y_past; /* Past lum. dc dct coefficient. */
short int dct_dc_cr_past; /* Past cr dc dct coefficient. */
short int dct_dc_cb_past; /* Past cb dc dct coefficient. */
} Block;
解析块的语法结构是:
block(i){
if(pattern_code[i]){
if(macroblock_intra){
if(i<4){
dct_dc_size_luminance
if(dc_size_luminance!=0)
dct_dc_differential
}
else{
dct_dc_size_chrominance
if(dc_size_chrominance!=0)
dct_dc_differential
}
}
else{
dct_coeff_first
}
if(picture_coding_type!=4){
while(nextbits()!=’10’)
dct_coeff_next
end_of_block
}
}
}