摘要:
本文作为 “x264视频编码器应用与实现” 系列博文的第八篇,主要讨论x264编码器实例的图像编码API的实现。
一、整体结构
在example.c中,首先执行的是打开编码器操作,使用的API为x264_encoder_open,随后进行编码的主体循环结构,从输入的yuv文件中循环读取像素数据进行编码,并写出到输出的码流文件中:
1 | for( ;; i_frame++ ) |
从该段代码中可知,输入yuv数据的亮度分量被保存在pic.img.plane[0]中,色度分量分别被保存在pic.img.plane[1]和pic.img.plane[2]中。然后pic将作为一个参数传入x264_encoder_encode中进行编码。
下图给出x264_encoder_encode函数前半部分的内部函数调用路径图:
二、x264_encoder_encode实现
x264_encoder_encode的作用是将一帧像素格式的输入图像编码为一段码流格式的NALU码流,其实现在encoder.c的3224~3797行,其声明形式如下所述:
1 | /* x264_encoder_encode: |
由于该函数是实现对一帧图像进行编码的核心过程,x264_encoder_encode的实现较为复杂。本章节逐步讨论其功能。
2.1 设置线程相关参数
x264_encoder_encode的最开始为设置线程相关的操作,其实现为:
1 | /**************************************************************************** |
回顾:设置编码线程数
其中的关键参数i_thread_frames由 validate_parameters()设置:
1 | h->i_thread_frames = h->param.b_sliced_threads ? 1 : h->param.i_threads; |
换言之,i_thread_frames 受 b_sliced_threads 控制:b_sliced_threads 为1,则为1;为0,则为i_threads的值(即实际线程数)。
i_threads 在x264_param_default中初始化为X264_THREADS_AUTO即0,在validate_parameters中,根据情况进行修正。以下是在validate_parameters中的实现:
1 | if( h->param.i_threads == X264_THREADS_AUTO ) |
对i_threads的修正:
- 初始修正值:cpu核数的1倍(条带多线程b_sliced_threads关闭)或1.5倍(b_sliced_threads开启);
- 如果最终只有一个线程(即i_threads为1),则关闭条带多线程并将前瞻线程设为数设为1;
- 理论上限:不超过128;
如果最终只有一个线程(即i_threads为1),则关闭条带多线程并将前瞻线程设为数设为1;
线程上下文同步
在x264_encoder_open中分配thread句柄:
1 | h->thread[0] = h; |
在x264_encoder_open中x264_t结构h的内存空间将被置为0,包括i_thread_phase。在x264_encoder_encode的以下代码中实现线程的上下文同步:
1 | if( h->i_thread_frames > 1 ) |
thread_sync_context的实现如下:
1 | static void thread_sync_context( x264_t *dst, x264_t *src ) |
thread_sync_context将前一个句柄对应的上下文实例拷贝到当前句柄。
x264_thread_sync_ratecontrol的功能类似,区别在于针对的是码率控制相关的参数。
2.2 保存传入的图像数据
在x264_encoder_encode中需要将外部传入的图像保存至编码器内部,其实现如下:
1 | int x264_encoder_encode( x264_t *h, |
从上述实现可知,保存外部图像数据需要以下步骤:
- x264_frame_pop_unused,从内存缓存区中取出一帧图像存储结构对象;
- x264_frame_copy_picture,将外部图像数据复制到内部图像缓存结构中;
- x264_frame_expand_border_mod16,确保图像宽高都为16像素的倍数;
x264_frame_pop_unused
x264_frame_pop_unused函数的实现如下:
1 | x264_frame_t *x264_frame_pop_unused( x264_t *h, int b_fdec ) |
该函数的主要作用是获取一个x264_frame_t类型的frame,并返回给调用者。获取一个frame根据当前空余frame缓存的情况,用以下两种方法之一获取frame:
- x264_frame_pop:从frame缓存中获取一个frame结构;
- frame_new:新建一个frame结构;
x264_frame_pop的实现如下:
1 | x264_frame_t *x264_frame_pop( x264_frame_t **list ) |
frame_new的实现相当的长,具体如下:
1 | static x264_frame_t *frame_new( x264_t *h, int b_fdec ) |
x264_frame_copy_picture
该函数的声明如下:
1 | int x264_frame_copy_picture( x264_t *h, x264_frame_t *dst, x264_picture_t *src ); |
所实现的功能很容易理解,即将 x264_picture_t 类型的输入参数 src 中的数据复制到 x264_frame_t 类型的参数 dst 中。其实现如下:
1 | int x264_frame_copy_picture( x264_t *h, x264_frame_t *dst, x264_picture_t *src ) |
x264_frame_expand_border_mod16
该函数的作用是对 x264_frame_t 结构实例 frame 的 plane 存储空间进行扩展,使其宽高都可以被16整除。具体实现如下:
1 | void x264_frame_expand_border_mod16( x264_t *h, x264_frame_t *frame ) |
2.3 将图像保存到队列
当从空闲图像结构队列中获取到一个空的 frame 结构,并且将输入图像的数据复制到其中后,下一步需要将这个 frame 结构保存到图像队列中,并进行 slice 类型的判断。
1 | int x264_encoder_encode( x264_t *h, |
其中 x264_lookahead_put_frame的实现非常简单:
1 | void x264_lookahead_put_frame( x264_t *h, x264_frame_t *frame ) |
在 tune 取值为zerolatency时,h->param.i_sync_lookahead的默认取值为0,因此,在 x264_lookahead_put_frame 中会执行
- x264_sync_frame_list_push( &h->lookahead->next, frame )
x264_sync_frame_list_push的实现实际上非常简单:
1 | void x264_sync_frame_list_push( x264_sync_frame_list_t *slist, x264_frame_t *frame ) |
从上述代码的调用中可知,x264_lookahead_put_frame的设计功能就是讲 fenc 添加到 h->lookahead->next->list[ x ] 中。
随后的判断中,h->frames.i_input表示了当前已输入的视频图像帧数,i_input 在 x264_encoder_open 中初始化为0,并在每次调用 x264_encoder_encode 时自增1。当读入的图像数量未达到下限时,x264_encoder_encode 将直接返回。
2.4 分析帧信息
x264_encoder_encode 下一步要执行的操作是对当前帧进行分析,其主要任务包括:
- 判断当前帧的编码类型;
- 计算当前帧的duration;
在该函数内的实现为:
1 | h->i_frame++; |
其中,x264_lookahead_get_frames的实现为:
1 | void x264_lookahead_get_frames( x264_t *h ) |
该函数中最核心的部分为 x264_slicetype_decide,其实现为:
1 | void x264_slicetype_decide( x264_t *h ) |
在该函数的最后一个for循环中调用了calculate_durations方法计算当前cpb中总的的duration,实现如下:
1 | static void calculate_durations( x264_t *h, x264_frame_t *cur_frame, x264_frame_t *prev_frame, int64_t *i_cpb_delay, int64_t *i_coded_fields ) |
PS: x264_frame_t 结构中有两个duration,二者代表的单位不同,需注意区分:
- i_duration:以sps中的time_base为单位;
- f_duration:以自然时间的秒为单位;