(一)common/set.h的注释
#ifndef _SET_H
#define _SET_H 1
enum profile_e
{
PROFILE_BASELINE = 66,
PROFILE_MAIN = 77,
PROFILE_EXTENTED = 88,
PROFILE_HIGH = 100,
PROFILE_HIGH10 = 110,
PROFILE_HIGH422 = 122,
PROFILE_HIGH444 = 144
};
enum cqm4_e
{
CQM_4IY = 0,
CQM_4PY = 1,
CQM_4IC = 2,
CQM_4PC = 3
};
enum cqm8_e
{
CQM_8IY = 0,
CQM_8PY = 1
};
typedef struct//profile:外形,评测,概况,剖面
{
int i_id; //?????
int i_profile_idc; //指明所用profile
int i_level_idc; //指明所用level
int b_constraint_set0;//[毕厚杰书 160 页,constraint_set0_flag],等于1时,表时必须遵从A.2.1 ...所指明的所有制约条件,等于0时表示不必遵从所有条件
int b_constraint_set1;//[毕厚杰书 160 页,constraint_set0_flag],等于1时,表时必须遵从A.2.2 ...所指明的所有制约条件,等于0时表示不必遵从所有条件
int b_constraint_set2;//[毕厚杰书 160 页,constraint_set0_flag],等于1时,表时必须遵从A.2.3 ...所指明的所有制约条件,等于0时表示不必遵从所有条件//constraint:约束;限制;强迫;强制
//注意:当constraint_set0_flag、中有两个以上等于1时,A.2中的所有制约条件都要被遵从
int i_log2_max_frame_num;//表示图像解码顺序的最大取值
//[毕 厚杰书 160 页,log2_max_frame_num_minus4],这个句法元素主要是为读取另一个句法元素frame_num服务的,frame_num是最 重要的句法元素之一,它标识所属图像的解码顺序。可以在句法表中看到,frame_num的解码函数是ue(v),函数中的v在这里指 定,v=log2_max_frame_num_minus + 4
int i_poc_type; //[毕厚杰书 160 页,pic_order_cnt_type],指明了POC(Picture Order Count)的编码方法、POC标识图像的播放顺序。由于H.264使用了B帧预测,使得图像的解码顺序不一定等于播放顺序,但它们之间存在一定的映射关 系。POC可以由frame_num通过映射关系计算得来,也可以索性由编码器显式地传送。H.264一共定义了3种POC的编码方法,这个句法元素就是 用来通知解码器该用哪种方法来计算POC。pic_order_cnt_tye的取值范围是[0,2]...
int i_log2_max_poc_lsb; //[毕厚杰书 160 页,log2_max_pic_order_cnt_lsb_minus4],指明了变量MaxPicOrderCntLsb的值。
int b_delta_pic_order_always_zero;//[毕厚杰书 161 页,delta_pic_order_always_zero_flag],其值等于1时句法元素delta_pic_order_cnt[0]和 delta_pic_order_cnt[1]不再片头出现,且他们的默认值都为0。为0时上述则出现。
int i_offset_for_non_ref_pic;//[毕厚杰书 161 页,offset_for_non_ref_pic],用来计算非参考帧或场的picture order count ,其值应在[-2e31,2e31-1]
int i_offset_for_top_to_bottom_field;//[毕厚杰书 161 页,offset_for_top_to_bottom_field],用来计算帧的底场的picture order count 其值应在[-2e31,2e31-1]
int i_num_ref_frames_in_poc_cycle;//[毕厚杰书 161 页,num_ref_frames_in_pic_order_cnt_cycle],用来解码picture order count 取值应在[0,255]之间
int i_offset_for_ref_frame[256];//[毕厚杰书 161 页,offset_for_ref_frame[i]],当picture order count type=1时用来解码poc,这句语法对循环num_ref_frames_in_poc_cycle中的每一个元素指定了一个偏移
int i_num_ref_frames;//[毕厚杰书 161 页,num_ref_frames],指定参考帧队列的最大长度,h264规定最多可为16个参考帧,本句法元素的值最大为16。值得注意的是这个长度以 帧为单位,如果在场模式下,应该相应地扩展一倍。
int b_gaps_in_frame_num_value_allowed;//[毕厚杰书 161 页,gaps_in_frame_num_value_allowed_flag],为1时表示允许句法frame_num可以不连续。当传输信道堵塞严 重时,编码器来不及将编码后的图像全部发出,这时允许丢弃若干帧图像。在正常情况下每一帧图像都有依次连续的frame_num值,解码器检查到如果 frame_num不连续,便能确定有图像被编码器丢弃。这时,解码器必须启动错误掩藏机制来近似地恢复这些图像,因为这些图像有可能被后续图像用作参考
帧。
//当这个句法元素=0时,表示不允许frame_num不连续,即编码器在任何情况下都不能丢弃图像。这时,H.264 允许解码器可以不去检查frame_num的连续性以减少计算量。这种情况下如果依然发生frame_num不连续,表示在传输中发生丢包,解码器会通过 其他机制检测到丢包的发生,然后启动错误掩藏的恢复图像。
int i_mb_width;//[毕厚杰书 161 页,pic_width_in_mbs_minus1],本句法元素加1后指明图像宽度,以宏块为单位。
int i_mb_height;//[毕厚杰书 161 页,pic_height_in_map_units_minus1],本句法元素加1后指明图像的高度。
int b_frame_mbs_only;//[毕厚杰书 161 页,frame_mbs_only_flag],本句法元素=1时,表示本序列中所有图像的编码模式都是帧,没有其他编码模式存在;本句法元素=0时,表 示本序列中图像的编码模式可能是帧,也可能是场或帧场自适应,某个图像具体是哪一种要由其他句法元素决定。
int b_mb_adaptive_frame_field;//[毕厚杰书 161 页,mb_adaptive_frame_field_flag],指明本序列是否属于帧场自适应模式。=1时,表明在本序列
int b_direct8x8_inference;//指明b片的直接和skip模式下运动矢量的预测方法
int b_crop;//crop:剪裁 [毕厚杰书 162 页,frame_cropping_flag],用于指明解码器是否要将图像裁剪后输出,如果是的话,后面紧跟着的四个句法元素分别指出左、右、上、下裁剪的宽度。
struct
{
int i_left; //[毕厚杰书 162 页,frame_crop_left_offset],左
int i_right; //[毕厚杰书 162 页,frame_crop_left_offset],右
int i_top; //[毕厚杰书 162 页,frame_crop_left_offset],上
int i_bottom; //[毕厚杰书 162 页,frame_crop_left_offset],下
} crop;//图像剪裁后输出的参数;crop:剪裁,剪切,剪辑
int b_vui; ////[毕厚杰书 162 页,vui_parameters_present_flag],指明vui子结构是否出现在码流中,vui的码流结构在...附录指明,用以表征视频格式等额外信息
struct
{
int b_aspect_ratio_info_present;
int i_sar_width;
int i_sar_height;
int b_overscan_info_present;
int b_overscan_info;
int b_signal_type_present;
int i_vidformat;
int b_fullrange;
int b_color_description_present;
int i_colorprim;
int i_transfer;
int i_colmatrix;
int b_chroma_loc_info_present;
int i_chroma_loc_top;
int i_chroma_loc_bottom;
int b_timing_info_present;
int i_num_units_in_tick;
int i_time_scale;
int b_fixed_frame_rate;
int b_bitstream_restriction;
int b_motion_vectors_over_pic_boundaries;
int i_max_bytes_per_pic_denom;
int i_max_bits_per_mb_denom;
int i_log2_max_mv_length_horizontal;
int i_log2_max_mv_length_vertical;
int i_num_reorder_frames;
int i_max_dec_frame_buffering;
} vui;
int b_qpprime_y_zero_transform_bypass;
} x264_sps_t;//x264_sps_t定义序列参考队列的参数以及初始化
typedef struct
{
int i_id;//[毕厚杰:P146 pic_parameter_set_id] 本参数集的序号,在各片的片头被引用
int i_sps_id;//[毕厚杰:P146 seq_parameter_set_id] 指明本图像参数集所引用的序列参数集的序号
int b_cabac;//[毕厚杰:P146 entropy_coding_mode_flag] 指明熵编码的选择:0时使用cavlc,1时使用cabac
int b_pic_order;//[毕厚杰:P147 pic_order_present_flag] poc的三种计算方法在片层还各需要用一些句法元素作为参数;当等于时,表示在片头会有句句法元素指明这些参数;当不为时,表示片头不会给出这些参数
int i_num_slice_groups;//[毕厚杰:P147 num_slice_groups_minus1] 加一表示图像中片组的个数
int i_num_ref_idx_l0_active;//[毕厚杰:P147 num_ref_idx_10_active_minus1] 指明目前参考帧队列的长度,即有多少个参考帧(短期和长期),用于list0
int i_num_ref_idx_l1_active;//[毕厚杰:P147 num_ref_idx_11_active_minus1] 指明目前参考帧队列的长度,即有多少个参考帧(短期和长期),用于list1
int b_weighted_pred;//[毕厚杰:P147 weighted_pred_flag] 指明是否允许p和sp片的加权预测,如果允许,在片头会出现用以计算加权预测的句法元素
int b_weighted_bipred;//[毕厚杰:P147 weighted_bipred_idc] 指明是否允许b片的加权预测
int i_pic_init_qp;//[毕厚杰:P147 pic_init_qp_minus26] 亮度分量的量化参数的初始值
int i_pic_init_qs;//[毕厚杰:P147 pic_init_qs_minus26] 亮度分量的量化参数的初始值,用于SP和SI
int i_chroma_qp_index_offset;//[毕厚杰:P147 chroma_qp_index_offset] 色度分量的量化参数是根据亮度分量的量化参数计算出来的,本句法元素用以指明计算时用到的参数表示为在 QPC 值的表格中寻找 Cb色度分量而应加到参数 QPY 和 QSY 上的偏移,chroma_qp_index_offset 的值应在-12 到 +12范围内(包括边界值)
int b_deblocking_filter_control;//[毕厚杰:P147 deblocking_filter_control_present_flag] 编码器可以通过句法元素显式地控制去块滤波的强度
int b_constrained_intra_pred;//[毕厚杰:P147 constrained_intra_pred_flag] 在p和b片中,帧内编码的宏块的邻近宏块可能是采用的帧间编码
int b_redundant_pic_cnt;//[毕厚杰:P147 redundant_pic_cnt_present_flag] redundant_pic_cnt 对于那些属于基本编码图像的条带和条带数据分割块应等于0。在冗余编码图像中的编码条带和编码条带数据分割块的 redundant_pic_cnt 的值应大于 0。当redundant_pic_cnt 不存在时,默认其值为 0。redundant_pic_cnt的值应该在 0到 127范围内(包括 0和127)
int b_transform_8x8_mode;//?????????????
int i_cqm_preset;//cqm:外部量化矩阵的设置
const uint8_t *scaling_list[6];
} x264_pps_t;//图像参数集[毕厚杰,P146 ]
static const uint8_t x264_cqm_jvt4i[16] =
{
6,13,20,28,
13,20,28,32,
20,28,32,37,
28,32,37,42
};
static const uint8_t x264_cqm_jvt4p[16] =
{
10,14,20,24,
14,20,24,27,
20,24,27,30,
24,27,30,34
};
static const uint8_t x264_cqm_jvt8i[64] =
{
6,10,13,16,18,23,25,27,
10,11,16,18,23,25,27,29,
13,16,18,23,25,27,29,31,
16,18,23,25,27,29,31,33,
18,23,25,27,29,31,33,36,
23,25,27,29,31,33,36,38,
25,27,29,31,33,36,38,40,
27,29,31,33,36,38,40,42
};
static const uint8_t x264_cqm_jvt8p[64] =
{
9,13,15,17,19,21,22,24,
13,13,17,19,21,22,24,25,
15,17,19,21,22,24,25,27,
17,19,21,22,24,25,27,28,
19,21,22,24,25,27,28,30,
21,22,24,25,27,28,30,32,
22,24,25,27,28,30,32,33,
24,25,27,28,30,32,33,35
};
static const uint8_t x264_cqm_flat16[64] =
{
16,16,16,16,16,16,16,16,
16,16,16,16,16,16,16,16,
16,16,16,16,16,16,16,16,
16,16,16,16,16,16,16,16,
16,16,16,16,16,16,16,16,
16,16,16,16,16,16,16,16,
16,16,16,16,16,16,16,16,
16,16,16,16,16,16,16,16
};
static const uint8_t * const x264_cqm_jvt[6] =
{
x264_cqm_jvt4i, x264_cqm_jvt4p,
x264_cqm_jvt4i, x264_cqm_jvt4p,
x264_cqm_jvt8i, x264_cqm_jvt8p
};
void x264_cqm_init( x264_t *h );
void x264_cqm_delete( x264_t *h );
int x264_cqm_parse_file( x264_t *h, const char *filename );
#endif
(二)
1. if( p_set_outfile_param( opt->hout, param ) ) // p_set_outfile_param = set_param_bsf 判断输出文件
{
fprintf( stderr, "can't set outfile param\n" );
p_close_infile( opt->hin );
p_close_outfile( opt->hout );
return -1;
}
p_set_outfile_param = set_param_bsf , 在muxers.c中,函数原型为:
int set_param_bsf( hnd_t handle, x264_param_t *p_param )
{
return 0;
}
2. x264_picture_alloc( &pic, X264_CSP_I420, param->i_width, param->i_height );
//构造一个图像帧的初始化空间,在common.c中,函数原型为:
void x264_picture_alloc( x264_picture_t *pic, int i_csp, int i_width, int i_height )
{
pic->i_type = X264_TYPE_AUTO;
pic->i_qpplus1 = 0;
pic->img.i_csp = i_csp;
switch( i_csp & X264_CSP_MASK )
{
case X264_CSP_I420:
case X264_CSP_YV12:
pic->img.i_plane = 3;
pic->img.plane[0] = x264_malloc( 3 * i_width * i_height / 2 );
pic->img.plane[1] = pic->img.plane[0] + i_width * i_height;
pic->img.plane[2] = pic->img.plane[1] + i_width * i_height / 4;
pic->img.i_stride[0] = i_width;
pic->img.i_stride[1] = i_width / 2;
pic->img.i_stride[2] = i_width / 2;
break;
case X264_CSP_I422:
...
}
}
3. i_start = x264_mdate(); //用于编码用时的计算,设定起始时间,在 mdate.c中,函数原型为:
int64_t x264_mdate( void )
{
#if !(defined(_MSC_VER) || defined(__MINGW32__))
struct timeval tv_date;
gettimeofday( &tv_date, NULL );
return( (int64_t) tv_date.tv_sec * 1000000 + (int64_t) tv_date.tv_usec );
#else
struct _timeb tb;
_ftime(&tb);
return ((int64_t)tb.time * (1000) + (int64_t)tb.millitm) * (1000);
#endif
}
4. 进入编码帧
for( i_frame = 0, i_file = 0, i_progress = 0;
b_ctrl_c == 0 && (i_frame < i_frame_total || i_frame_total == 0); )
{
if( p_read_frame( &pic, opt->hin, i_frame + opt->i_seek ) )//读取
break;
//p_read_frame() 按照h->hin提供的输入文件的地址,读入图像的内容到&pic提供的存储区的首地址
pic.i_pts = (int64_t)i_frame * param->i_fps_den;
i_file += Encode_frame( h, opt->hout, &pic );//编码并保存,
//Encode_frame(
h, opt->hout, &pic )实现编码,是x264的核心部分
i_frame++;
if( opt->b_progress && param->i_log_level < X264_LOG_DEBUG &&
( i_frame_total ? i_frame * 1000 / i_frame_total > i_progress
: i_frame % 10 == 0 ) )
{
int64_t i_elapsed = x264_mdate() - i_start;
double fps = i_elapsed > 0 ? i_frame * 1000000. / i_elapsed : 0;
if( i_frame_total )
{
int eta = i_elapsed * (i_frame_total - i_frame) / ((int64_t)i_frame * 1000000);
i_progress = i_frame * 1000 / i_frame_total;
fprintf( stderr, "encoded frames: %d/%d (%.1f%%), %.2f fps, eta %d:d:d \r",
i_frame, i_frame_total, (float)i_progress / 10, fps,
eta/3600, (eta/60)`, eta` );
}
else
fprintf( stderr, "encoded frames: %d, %.2f fps \r", i_frame, fps );
fflush( stderr ); // needed in windows
}
}
注1: //在本文环境下,p_read_frame=read_frame_yuv,read_frame_yuv()定义在muxers.c中,原型为:
int read_frame_yuv( x264_picture_t *p_pic, hnd_t handle, int i_frame )
{
yuv_input_t *h = handle;
if( i_frame != h->next_frame )
if( fseek( h->fh, (uint64_t)i_frame * h->width * h->height * 3 / 2, SEEK_SET ) )
return -1;
if( fread( p_pic->img.plane[0], 1, h->width * h->height, h->fh ) <= 0
|| fread( p_pic->img.plane[1], 1, h->width * h->height / 4, h->fh ) <= 0
|| fread( p_pic->img.plane[2], 1, h->width * h->height / 4, h->fh ) <= 0 )
return -1;
h->next_frame = i_frame+1;
return 0;
}
从 文件中分别读取288*352(亮度信息),144*176(Cr),144*176(Cb)的数据放入 p_pic->img.plane[0],p_pic->img.plane[1],p_pic->img.plane[2],如果成 功则继续执行一下程序;如果不成功则打断程序,返回-1.
注2: i_file += Encode_frame( h, opt->hout, &pic );//编码并保存,
//Encode_frame( h, opt->hout, &pic )实现编码,是x264的核心部分,在X264.c中,
//这个函数主要是调用了 x264_encoder_encode( h, &nal, &i_nal, pic, &pic_out ) 来实现编码。
// 原型为:
static int Encode_frame( x264_t *h, hnd_t hout, x264_picture_t *pic )
{
x264_picture_t pic_out;
x264_nal_t *nal;
int i_nal, i;
int i_file = 0;
if( pic )
{
pic->i_type = X264_TYPE_AUTO;
pic->i_qpplus1 = 0;
}
if( x264_encoder_encode( h, &nal, &i_nal, pic, &pic_out ) < 0 )
{
fprintf( stderr, "x264_encoder_encode failed\n" );
}
for( i = 0; i < i_nal; i++ )
{
int i_size;
int i_data;
i_data = DATA_MAX;
if( ( i_size = x264_nal_encode( data, &i_data, 1, &nal[i] ) ) > 0 )
{
i_file += p_write_nalu( hout, data, i_size );
}
else if( i_size < 0 )
{
fprintf( stderr, "need to increase buffer size (size=%d)\n", -i_size );
}
}
if (i_nal)
p_set_eop( hout, &pic_out );
return
i_file;
}
(三)
1. x264_encoder_encode( x264_t *h, x264_nal_t **pp_nal, int *pi_nal,
x264_picture_t
*pic_in, x264_picture_t *pic_out ) //encoder.c中
(1)x264_frame_t *frame_psnr = h->fdec; // just to keep the current decoded frame for psnr calculation
(2) x264_frame_t *fenc = x264_frame_get( h->frames.unused );//返回h->frames.unused[0]的值,即得到了一幀图像给编码器fenc, x264_frame_get函数在encoder.c中,原型为:
static x264_frame_t *x264_frame_get( x264_frame_t *list[X264_BFRAME_MAX+1] )
{
x264_frame_t *frame = list[0];
int i;
for( i = 0; list[i]; i++ )
list[i] = list[i+1];
return frame;
}
(3) x264_frame_copy_picture( h, fenc, pic_in ); // pic_in复制到 fenc ,把要编码的帧存放到 fenc
该函数在frame.c中,原型为:
void x264_frame_copy_picture( x264_t *h, x264_frame_t *dst, x264_picture_t *src )
{
dst->i_type = src->i_type;
dst->i_qpplus1 = src->i_qpplus1;
dst->i_pts = src->i_pts;
switch( src->img.i_csp & X264_CSP_MASK )
{
case X264_CSP_I420:
h->csp.i420( dst, &src->img, h->param.i_width, h->param.i_height );
break;
....
default:
x264_log( h, X264_LOG_ERROR, "Arg invalid CSP\n" );
break;
}
}
(4) if( h->param.i_width % 16 || h->param.i_height % 16 ) x264_frame_expand_border_mod16( h, fenc );
fenc->i_frame = h->frames.i_input++;
//若图像宽或高不是16的整数倍,调用 x264_frame_expand_border_mod16( h, fenc );
x264_frame_put( h->frames.next, fenc );//将fenc拷贝给h->frames.next数组中第一个不为0的位置,该函数在encoder.c中,原型为:
static void x264_frame_put( x264_frame_t *list[X264_BFRAME_MAX], x264_frame_t *frame )
{
int i = 0;
while( list[i] ) i++;
list[i] = frame;
}
(5)x264_slicetype_decide( h ); //判断slice的类型,在slicetype_decision.c中,如下:
void x264_slicetype_decide( x264_t *h )
{
...
if( h->param.rc.b_stat_read )
{
for( i = 0; h->frames.next[i] != NULL; i++ )
h->frames.next[i]->i_type =
x264_ratecontrol_slice_type( h, h->frames.next[i]->i_frame );
}
else if( h->param.i_bframe && h->param.b_bframe_adaptive )
x264_slicetype_analyse( h );//x264_slicetype_analyse函数在slicetype_decision.c中。
//这里要注意默认的x264里面是没有B帧的,如果需要用到B帧可以在最初的参数设置的时候,
//用语句“--frames 数字”来定义,x264_slicetype_analyse 也只有在定义了“--frames 数字”时候,
//才会执行。
...
}
(6)
while( IS_X264_TYPE_B( h->frames.next[bframes]->i_type ) )
bframes++;
x264_frame_put( h->frames.current, x264_frame_get( &h->frames.next[bframes] ) );
//将当前要编码的帧h->frames.current送给编码器,然后开始编码
(7) h->fenc = x264_frame_get( h->frames.current );//将当前要编码的帧给编码器
(8) if( h->fenc->i_type == X264_TYPE_IDR )
{
h->frames.i_last_idr = h->fenc->i_frame;
}
TIMER_START( i_mtime_encode_frame );
// 如果是解码即时刷新片,则h->frames.i_last_idr = h->fenc->i_frame;然后计时开
(9)if( h->fenc->i_type == X264_TYPE_IDR )
{
x264_reference_reset( h );
i_nal_type = NAL_SLICE_IDR;
i_nal_ref_idc = NAL_PRIORITY_HIGHEST;
i_slice_type = SLICE_TYPE_I;
}
else if( h->fenc->i_type == X264_TYPE_I )
{
i_nal_type = NAL_SLICE;
i_nal_ref_idc = NAL_PRIORITY_HIGH;
i_slice_type = SLICE_TYPE_I;
}
else if( h->fenc->i_type == X264_TYPE_P )
{
i_nal_type = NAL_SLICE;
i_nal_ref_idc = NAL_PRIORITY_HIGH;
i_slice_type = SLICE_TYPE_P;
}
else if( h->fenc->i_type == X264_TYPE_BREF )
{
i_nal_type = NAL_SLICE;
i_nal_ref_idc = NAL_PRIORITY_HIGH;
i_slice_type = SLICE_TYPE_B;
}
else
{
i_nal_type = NAL_SLICE;
i_nal_ref_idc = NAL_PRIORITY_DISPOSABLE;
i_slice_type = SLICE_TYPE_B;
}
//根据帧的类型初始化数据。总共有五种片的类型IDR,I,P,BREF,B 。如果是解码即时刷新片(idr),则要对参考帧重新设置
(四)
(1)
x264_reference_build_list( h, h->fdec->i_poc, i_slice_type );
//创建list0和list1(ref0和ref1),ref0从大到小,ref1从小到大
//x264_reference_build_list( h, h->fdec->i_poc, i_slice_type )定义在encoder.c中,如下:
static inline void x264_reference_build_list( x264_t *h, int i_poc, int i_slice_type )
{
int i;
int b_ok;
...
}
(2)
x264_ratecontrol_start( h, i_slice_type, h->fenc->i_qpplus1 );
i_global_qp = x264_ratecontrol_qp( h );
pic_out->i_qpplus1 = h->fdec->i_qpplus1 = i_global_qp + 1;
// 初始化速率和量化步长
(3) x264_slice_init( h, i_nal_type, i_slice_type, i_global_qp );
// 创建片头,片头初始化;
(4)
if( i_nal_type == NAL_SLICE_IDR && h->param.b_repeat_headers )
{
if( h->fenc->i_frame == 0 )
{
x264_nal_start( h, NAL_SEI, NAL_PRIORITY_DISPOSABLE );
x264_sei_version_write( h, &h->out.bs );
x264_nal_end( h );
}
x264_nal_start( h, NAL_SPS, NAL_PRIORITY_HIGHEST );
x264_sps_write( &h->out.bs, h->sps );
x264_nal_end( h );
x264_nal_start( h, NAL_PPS, NAL_PRIORITY_HIGHEST );
x264_pps_write( &h->out.bs, h->pps );
x264_nal_end( h );
}
(5)
i_frame_size = x264_slices_write( h );
// encoder.c中,原型:
static inline int x264_slices_write( x264_t *h )
{
...
}
关键部分为x264_slice_write( h );该函数定义在encoder.c中,实现一帧的编码。
注1:for( mb_xy = h->sh.i_first_mb, i_skip = 0; mb_xy < h->sh.i_last_mb; mb_xy++ )
//循环22*18=396次对396个宏块进行编码,mb_xy是16*16宏块序号0~395,
//其赋值过程在x264_encoder_encode 中的x264_slice_init里。
const int i_mb_y = mb_xy / h->sps->i_mb_width;
//计算当前宏块垂直坐标(行坐标,以宏块为单位) i_mb_width=22
const int i_mb_x = mb_xy % h->sps->i_mb_width;
//计算当前宏块水平坐标(列坐标,以宏块为单位)
int mb_spos = bs_pos(&h->out.bs);
//在bs.h中,静态内联函数,如下:
static inline int bs_pos( bs_t *s )
{
return( 8 * ( s->p - s->p_start ) + 8 - s->i_left );
}
x264_macroblock_cache_load( h, i_mb_x, i_mb_y );
//它是将要编码的宏块的周围宏块的值读进来。要想得到当前块的预测值,要先知道上面,左面的预测值。
// 分析参数选择合适的块编码模式,加载相邻块的信息。
x264_macroblock_analyse( h );
//对一个16*16块进行预测模式选择,通过比较得出最佳预测模式。
// 定义在analyse.c 中,计算sad值分析是否要对16*16的宏块进行分割和采用哪种分割方式合适。
x264_macroblock_encode( h );
// 根据前面分析得到的h->mb.i_type的类型,对一个16*16块进行编码。
(五)
X264代码主要的部分分为三个步骤,即数据的读入与存放,视频编码层(VCL)的视频编码和网络提取层(Network
Abstraction Layer,NAL)单元输出。
数 据的读入与存放:X264开辟了unused、next、current、refrence等区域分别保存未处理原始图片序列、即将编码帧序列、当前编码 帧和参考帧序列,同时还开辟了fenc和fdec区域用于存放已编码帧和重构帧 。程序按以下顺序读入数据:首先,从YUV数据文件中读取数据存到临时变量pic_in,同时为unused开辟存储空间,并用fenc指针指向这个空 间。接着,将pic_in中的图片数据拷贝到fenc所指向的区域,并在拷贝完成后对图片大小进行判断,如果长宽不为16的整数倍则进行像素扩展;将处理
后的fenc区域数据放入next区域。之后,如果存在B帧,则从next区域取出B帧以后的P帧放到current区域中,也就是说先编码I、P帧再编 码之间的B帧;否则,直接从next区域取出一帧存入current区域。此时current区域中存放的就是已经过预处理的即将要编码的帧数据了。最 后,由于fenc区域是编码的直接对象,再将current区域中的内容拷贝到fenc中正式开始编码 。
视频编码层(VCL)的视频编码:输入文件foreman.yuv格式是CIF,即352*288。在对一帧图像进行处理的过程中以宏块(16*16)为单位,一帧图像处理步骤如下:
定位当前处理的16*16宏块的位置;
X264_macroblock_chche_load(h,i_mb_x,I,mb_y) ;
X264_macroblock_analyse(h);X264_macroblock_encode(h) ;
根据h->mb.i_type的类型进行操作, 如果是I帧,则x264_macroblock_write_caclc(h,&h->out.bs);
X264_macroblock_chche_save(h) ;
计算mb stats.