encode()函数中循环调用encode_frame()函数进行逐帧编码:
- 调用x264_encoder_encode()函数完成一帧编码;
- 将编码后的码流载入码流文件中。
static int encode_frame( x264_t *h, hnd_t hout, x264_picture_t *pic, int64_t *last_dts )
{
x264_picture_t pic_out; // 编码后输出帧
x264_nal_t *nal; // NAL数据包
int i_nal;
int i_frame_size = 0;
i_frame_size = x264_encoder_encode( h, &nal, &i_nal, pic, &pic_out ); // 编码单帧
FAIL_IF_ERROR( i_frame_size < 0, "x264_encoder_encode failed\n" );
if( i_frame_size )
{
i_frame_size = cli_output.write_frame( hout, nal[0].p_payload, i_frame_size, &pic_out ); // 将编码后的图片装入输出视频流
*last_dts = pic_out.i_dts; // 显示时间戳
}
return i_frame_size;
}
x264_encoder_encode()函数,x264中一个十分重要的函数,理解此函数对后面算法函数的深入十分关键:
- 首先介绍一下poc的概念。视频编码顺序与视频的播放顺序,并不完全相同。视频编码时,如果采用了B帧编码,由于B帧很多时候都是双向预测得来的,这时会先编码B帧的后向预测图像(P帧),然后再进行B帧编码,因此会把视频原来的播放顺序打乱,以新的编码顺序输出码流。而在解码端接收到码流后,需将编码顺序还原为播放顺序,以输出正确的视频播放顺序。在编解码中,视频的播放顺序序号叫做POC(picture order count)
- fenc为x264_frame_t结构体,用于存储待编码帧。初始化fenc变量,为其分配存储空间并初始化数值。分配方式为若有已分配好的存储空间则直接利用,否则新建一个存储空间
- 将原始待编码数据(YUV数据)塞入fenc变量内,以及对fenc结构体内的其他成员变量进行赋值
- 初始化lookahead线程, 将fenc放入lookahead.next.list[](h变量的结构体内部变量)队列,等待确定帧类型
- 在lookhead线程中分析图像帧,通过lookahead分析帧类型。该函数调用x264_slicetype_decide()函数确定slice类型, 调用x264_slicetype_analyse()函数计算并分析slice的统计学信息以及调用x264_slicetype_frame_cost()等函数确定不同帧类型下将产生的编码代价。经过一些列分析之后,最终确定了帧类型信息,并且将帧放入frames.current[]队列
- 根据编码类型进一步给h变量赋值(x264_t 结构体),如NAL结构体初始化,重建图像结构体初始化等等
- 一切参数准备就绪,则通过x264_slices_write()函数编码一帧数据
- 最后,编码结束后做一些后续处理,例如记录一些统计信息。其中调用了x264_encoder_encapsulate_nals()封装NALU(添加起始码),调用x264_frame_push_unused()将fenc重新放回frames.unused[]队列,并且调用x264_ratecontrol_end()结束码率控制,结束时做一些处理,记录一些统计信息 、输出NALU 、输出重建帧等。
/****************************************************************************
* x264_encoder_encode:
* XXX: i_poc : is the poc of the current given picture
* i_frame : is the number of the frame being coded
* ex: type frame poc
* I 0 2*0
* P 1 2*3
* B 2 2*1
* B 3 2*2
* P 4 2*6
* B 5 2*4
* B 6 2*5
****************************************************************************/
int x264_encoder_encode( x264_t *h,
x264_nal_t **pp_nal, int *pi_nal,
x264_picture_t *pic_in,
x264_picture_t *pic_out )
{
x264_t *thread_current, *thread_prev, *thread_oldest; // 当前线程、前一个线程、后一个线程
int i_nal_type, i_nal_ref_idc, i_global_qp; // NAL类型、NAL优先级、全局QP
int overhead = NALU_OVERHEAD; // startcode + NAL type costs 5 bytes per frame,即NAL的头信息字节个数
#if HAVE_OPENCL // 判断是否有OPencl支持
if( h->opencl.b_fatal_error )
return -1;
#endif
if( h->i_thread_frames > 1 )
{
thread_prev = h->thread[ h->i_thread_phase ];
h->i_thread_phase = (h->i_thread_phase + 1) % h->i_thread_frames;
thread_current = h->thread[ h->i_thread_phase ];
thread_oldest = h->thread[ (h->i_thread_phase + 1) % h->i_thread_frames ];
x264_thread_sync_context( thread_current, thread_prev ); // 协调三个线程之间的同步关系
x264_thread_sync_ratecontrol( thread_current, thread_prev, thread_oldest ); // 协调三个线程之间的同步关系
h = thread_current; // 将编码所需相关参数赋值给h
}
else
{
thread_current =
thread_oldest = h;
}
h->i_cpb_delay_pir_offset = h->i_cpb_delay_pir_offset_next;
/* no data out */
*pi_nal = 0;
*pp_nal = NULL;
/* ------------------- Setup new frame from picture -------------------- */
if( pic_in != NULL )
{
if( h->lookahead->b_exit_thread ) // lookahead 线程退出标志位
{
x264_log( h, X264_LOG_ERROR, "lookahead thread is already stopped\n" );
return -1;
}
/* 1: Copy the picture to a frame and move it to a buffer */
// 获取fenc的存储空间,用来存放待编码的帧
x264_frame_t *fenc = x264_frame_pop_unused( h, 0 ); // unused用于回收那些在编码中分配的frame空间,
//当有新的需要时,直接拿过来用,不用重新分配新的空间,
//提高效率,即对frame结构体初始化空间
if( !fenc )
return -1;
// 外部像素数据传递到内部系统
// pic_in(外部结构体x264_picture_t)到fenc(内部结构体x264_frame_t),根据pic_in中图像的颜色空间情况进行了数据的复制
if( x264_frame_copy_picture( h, fenc, pic_in ) < 0 ) // 将pic_in中的帧数据拷贝到fenc中
return -1;
//宽和高都确保是16的整数倍(宏块宽度的整数倍)
if( h->param.i_width != 16 * h->mb.i_mb_width || // 分辨率不是16的倍数,参数校验
h->param.i_height != 16 * h->mb.i_mb_height )
x264_frame_expand_border_mod16( h, fenc ); // 则进行边界处理,扩展至16整数倍
fenc->i_frame = h->frames.i_input++; // 图像的输入序号(原始顺序或播放顺序)
if( fenc->i_frame == 0 ) // 第一帧
h->frames.i_first_pts = fenc->i_pts;
if( h->frames.i_bframe_delay && fenc->i_frame == h->frames.i_bframe_delay )
h->frames.i_bframe_delay_time = fenc->i_pts - h->frames.i_first_pts;
if( h->param.b_vfr_input && fenc->i_pts <= h->frames.i_largest_pts ) // 判断pts是否严格递增
x264_log( h, X264_LOG_WARNING, "non-strictly-monotonic PTS\n" );
h->frames.i_second_largest_pts = h->frames.i_largest_pts; // 更新第二大pts值
h->frames.i_largest_pts = fenc->i_pts; // 更新最大pts值
// 图像类型参数校验
if( (fenc->i_pic_struct < PIC_STRUCT_AUTO) || (fenc->i_pic_struct > PIC_STRUCT_TRIPLE) )
fenc->i_pic_struct = PIC_STRUCT_AUTO;
// 确定帧结构(场、帧、帧场自适应?)
if( fenc->i_pic_struct == PIC_STRUCT_AUTO )
{
#if HAVE_INTERLACED
int b_interlaced = fenc->param ? fenc->param->b_interlaced : h->param.b_interlaced;
#else
int b_interlaced = 0;
#endif
if( b_interlaced )
{
int b_tff = fenc->param ? fenc->param->b_tff : h->param.b_tff;
fenc->i_pic_struct = b_tff ? PIC_STRUCT_TOP_BOTTOM : PIC_STRUCT_BOTTOM_TOP;
}
else
fenc->i_pic_struct = PIC_STRUCT_PROGRESSIVE;
}
// mb_tree(宏块级率控)算法是否开启及编码多遍是否开启
if( h->param.rc.b_mb_tree && h->param.rc.b_stat_read )
{
if( x264_macroblock_tree_read( h, fenc, pic_in->prop.quant_offsets ) )
return -1;
}
else
x264_stack_align( x264_adaptive_quant_frame, h, fenc, pic_in->prop.quant_offsets ); // 自适应确定qp
if( pic_in->prop.quant_offsets_free )
pic_in->prop.quant_offsets_free( pic_in->prop.quant_offsets );
//降低分辨率处理(原来的一半),线性内插
//注意这里并不是6抽头滤波器的半像素内插
if( h->frames.b_have_lowres )
x264_frame_init_lowres( h, fenc );
/* 2: Place the frame into the queue for its slice type decision */
x264_lookahead_put_frame( h, fenc ); // 初始化lookahead线程,将fenc放入lookahead.next.list[]队列,等待确定帧类型
if( h->frames.i_input <= h->frames.i_delay + 1 - h->i_thread_frames )
{
/* Nothing yet to encode, waiting for filling of buffers */
pic_out->i_type = X264_TYPE_AUTO;
return 0;
}
}
else
{
/* signal kills for lookahead thread */
x264_pthread_mutex_lock( &h->lookahead->ifbuf.mutex ); // 加互斥锁
h->lookahead->b_exit_thread = 1;
x264_pthread_cond_broadcast( &h->lookahead->ifbuf.cv_fill );
x264_pthread_mutex_unlock( &h->lookahead->ifbuf.mutex ); // 解互斥锁
}
h->i_frame++; // 编码帧号
/* 3: The picture is analyzed in the lookahead */
if( !h->frames.current[0] )
x264_lookahead_get_frames( h ); // 在lookhead线程中分析图像帧,通过lookahead分析帧类型。该函数调用了x264_slicetype_decide()
// x264_slicetype_analyse()和x264_slicetype_frame_cost()等函数。
// 经过一些列分析之后,最终确定了帧类型信息,并且将帧放入frames.current[]队列
if( !h->frames.current[0] && x264_lookahead_is_empty( h ) )
return x264_encoder_frame_end( thread_oldest, thread_current, pp_nal, pi_nal, pic_out );
/* ------------------- Get frame to be encoded ------------------------- */
/* 4: get picture to encode */
h->fenc = x264_frame_shift( h->frames.current ); // 从frames.current[]队列取出1帧用于编码并更新currect队列
/* If applicable, wait for previous frame reconstruct