X264作为一个开源的视频编码函数库,是最好的有损视频编码,小巧而又简单。所以对X264的学习是非常有必要的。

    在下载X264源代码使用时,需要先对其进行编译,产生相应的.dll,.lib,.h文件,然后拷贝到我们的工程下才行。具体的X264的编译过程:点击打开链接。但是X264的编译过程非常麻烦,很费力气,所以可以下载编译好的.dll,.lib,.h文件直接使用,这样比较省力,下载的链接:点击打开链接 。

    

    在进行X264的编码之前,先看一下编码的整体流程图(流程图来自于雷霄骅的博客,链接在下面):

    

    经过这个流程图我们可以很清晰的清楚编码的过程,简单看下它的各个主要函数:

    x264_param_default():用来设置结构体x264_param_t的缺省值(结构体x264_param_t里有着编码的各项参数);

    x264_picture_alloc():为结构体x264_picture_t分配内存;

    x264_encoder_open():打开编码器;

    x264_picture_t:结构体,用来存储压缩编码前的像素数据;

    x264_encoder_encode():编码一帧图像;

    x264_nal_t:结构体,存储压缩编码后的码流数据;

    flush_encoder:这里使用的函数和编码模块一样,输出编码器中剩余的码流数据;

    x264_encoder_close():关闭编码器;

    x264_picture_clean():释放x264_picture_alloc()申请的资源。


    具体代码分析:

    (1)头文件:

#include "stdint.h"  
#include <stdio.h>    
#include <stdlib.h>  
 
extern "C"
{
#include "x264.h" 
}
 
#define width  640  
#define height 360 
    这里x264.h就是我们编译得到的文件,此外还有x264_config.h,要一起保存在项目工程里。


    (2)变量声明:

int i;
int j;
int frame_num = 100;
int csp = X264_CSP_I420;
int y_size = width*height;
int n_nal;
int ret;
x264_param_t* param = (x264_param_t*)malloc(sizeof(x264_param_t));
x264_picture_t* pic_in = (x264_picture_t*)malloc(sizeof(x264_picture_t));
x264_picture_t* pic_out = (x264_picture_t*)malloc(sizeof(x264_picture_t));
x264_nal_t* nal = (x264_nal_t *)malloc(sizeof(x264_nal_t));
x264_t *encoder;

    上面是函数中需要用到的一些参数定义。

    视频尺寸是640x260,YUV420格式,每个像素1.5字节,所以一张YUV图片大小是width*height*3/2,在这里就是y_size*3/2。

    接下来四行定义好结构体x264_param_t、x264_picture_t、x264_nal_t。

    最后一行定义编码器,编码器类型只能声明为x264_t的指针类型。


    (3)读取文件并判断是否读取成功:

FILE* read_filename = fopen("test_yuv420p.yuv", "rb");
FILE* write_filename = fopen("test.h264", "wb");
	
if (read_filename == NULL || write_filename == NULL)
{
	printf("Error open files.\n");
	return -1;
}

    若文件打开失败或者写入失败,都提示错误。


    (4)初始化:
x264_param_default(param);
param->i_width = width;    
param->i_height = height;   
//param->i_fps_num = 25;  
//param->i_fps_den = 1;   
//param->i_frame_total = 0;  
//param->i_threads = X264_SYNC_LOOKAHEAD_AUTO;  
//param->i_log_level = X264_LOG_DEBUG;      
//param->i_bframe = 5;
//param->b_open_gop = 0;
//param->i_bframe_adaptive = X264_B_ADAPT_TRELLIS;
//param->i_bframe_pyramid = 0;        
//param->i_timebase_den = param->i_fps_num;
//param->i_timebase_num = param->i_fps_den;
//param->i_keyint_max = 25;
//param->rc.i_bitrate = 96;   
//param->rc.i_qp_max = 0;
//param->rc.i_qp_min = 0;
//param->b_annexb = true;
//param->b_intra_refresh = 1;
x264_param_apply_profile(param, "baseline");   
param->i_csp = csp;
x264_picture_init(pic_out);         
x264_picture_alloc(pic_in, csp, param->i_width, param->i_height);

encoder = x264_encoder_open(param);

    第一行如流程图开始的部分,定义好x264_param_t结构体:param,并且被函数x264_param_default()调用。

    接下来都是param的参数:

        param->i_width:要编码的图像宽度;

        param->i_height:要编码的图像高度;

        param->i_fps_num:视频数据帧率分子

        param->i_fps_den:视频数据帧率分母;

        param->i_frame_total:编码的总帧数,不知道就设为0;

        param->i_threads:取空缓冲区继续使用不死锁的保证;

        param->i_log_level:LOG参数,不需要打印编码信息时直接注释掉就行;

        param->i_bframe、param->b_open_gop、param->i_bframe_adaptive、param->i_bframe_pyramid:流参数;

        param->rc.i_bitrate:码率,单位kbps;

        x264_param_apply_profile(param, "baseline"):设置Profile属性,这里使用baseline profile;

        其余还有很多属性,我也不太清楚什么意思,慢慢查。

    pic_in被x264_picture_alloc()函数分配内存空间。

    待参数都设置成功后,我们用encoder打开编码器,打开方法:encoder = x264_encoder_open(param)。


    (5)编码过程:
for (i = 0; i < frame_num; i++)
{
	fread(pic_in->img.plane[0], y_size, 1, read_filename);
	fread(pic_in->img.plane[1], y_size / 4, 1, read_filename);
	fread(pic_in->img.plane[2], y_size / 4, 1, read_filename);

	pic_in->i_pts = i;
	ret = x264_encoder_encode(encoder, &nal, &n_nal, pic_in, pic_out);
	if (ret < 0)
	{
		printf("Error.\n");
		return -1;
	}
	printf("Succeed encode frame: %5d\n", i);
	for (j = 0; j < n_nal; ++j)
	{
		fwrite(nal[j].p_payload, 1, nal[j].i_payload, write_filename);
	}
}

    每次编码时,YUV图片信息都保存在pin_in中。

        pic_in->img.plane[0]、pic_in->img.plane[1]、pic_in->img.plane[2]三个通道分别缓存Y、U、V的数据。

    关于pic_in->i_pts的公式有很多,我们只需要让其步长为1递增就行。

    接下来调用ret=x264_encoder_encode(encoder, &nal, &n_nal, pic_in, pic_out)开始编码,encoder就是我们定义的编码器,n_nal是编码后产生的NAL数目,注意:一帧可能是一个NAL,也可能是多个NAL。(nal是一个数组,存着编码后的数据)。

    p_payload就是编码后的H.264的NAL数据,且其长度是i_payload,fwrite写入输出文件。


    (6)输出剩余码流数据

while (1)
{
	ret = x264_encoder_encode(encoder, &nal, &n_nal, NULL, pic_out);
	if (ret == 0)
	{
		break;
	}
	printf("Flush 1 frame.\n");
	for (j = 0; j < n_nal; ++j)
	{
		fwrite(nal[j].p_payload, 1, nal[j].i_payload, write_filename);
	}
	i++;
}


    (7)收尾:
x264_picture_clean(pic_in);
x264_encoder_close(encoder);
encoder = NULL;
free(pic_in);
free(pic_out);
free(param);
fclose(read_filename);
fclose(write_filename);

    编码完成后调用x264_picture_clean释放pic_in的缓冲区,调用x264_encoder_close()关闭编码器。


    编码的各部分代码看完后,再总结两个地方:

    编码的输入数据:

    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_picture_t *pic_in就是编码的输入数据。我们要提前把YUV420数据赋值给它。有时候我们得到的有可能是RGB格式,这时候就需要我们把RGB换回YUV,然后再进行编码,关于它们两者之间的转换,可以看这里:点击打开链接

    编码的输出数据:

    编码后的数据都保存在x264_nal_t *nal里,因为前面设置的参数是baseline profile,输出结果没有B帧。还有x264_nal_encode函数,新版本调用x264_encoder_encode函数得到的数组nal是标准格式,所以这里不需要它。


    最后,我们给出完整的源代码:

#include "stdint.h"  
#include <stdio.h>    
#include <stdlib.h>  
 
extern "C"
{
#include "x264.h" 
}
 
#define width  640  
#define height 360  

int  main(int argc, char **argv)
{
	int i;
	int j;
	int frame_num = 100;
	int csp = X264_CSP_I420;
	int y_size = width*height;
	int n_nal;
	int ret;
	x264_param_t* param = (x264_param_t*)malloc(sizeof(x264_param_t));
	x264_picture_t* pic_in = (x264_picture_t*)malloc(sizeof(x264_picture_t));
	x264_picture_t* pic_out = (x264_picture_t*)malloc(sizeof(x264_picture_t));
	x264_nal_t* nal = (x264_nal_t *)malloc(sizeof(x264_nal_t));
	x264_t *encoder;

    FILE* read_filename = fopen("test_yuv420p.yuv", "rb");
    FILE* write_filename = fopen("test.h264", "wb");
    if (read_filename == NULL || write_filename == NULL)
    {
		printf("Error open files.\n");
	    return -1;
	}
	
	x264_param_default(param);
	param->i_width = width;    
	param->i_height = height;   
	//param->i_fps_num = 25;  
	//param->i_fps_den = 1;   
	//param->i_frame_total = 0;  
	//param->i_threads = X264_SYNC_LOOKAHEAD_AUTO;  
	//param->i_log_level = X264_LOG_DEBUG;      
	//param->i_bframe = 5;
	//param->b_open_gop = 0;
	//param->i_bframe_adaptive = X264_B_ADAPT_TRELLIS;
	//param->i_bframe_pyramid = 0;        
	//param->i_timebase_den = param->i_fps_num;
	//param->i_timebase_num = param->i_fps_den;
	//param->i_keyint_max = 25;
	//param->rc.i_bitrate = 96;   
	//param->rc.i_qp_max = 0;
	//param->rc.i_qp_min = 0;
	//param->b_annexb = true;
	//param->b_intra_refresh = 1;
	x264_param_apply_profile(param, "baseline");   
	param->i_csp = csp;
	x264_picture_init(pic_out);         
	x264_picture_alloc(pic_in, csp, param->i_width, param->i_height);

	encoder = x264_encoder_open(param);

	if (frame_num == 0)
	{
		fseek(read_filename, 0, SEEK_END);
		frame_num = ftell(read_filename) / (width * height * 3 / 2);
		fseek(read_filename, 0, SEEK_SET);
	}
	for (i = 0; i < frame_num; i++)
	{
		fread(pic_in->img.plane[0], y_size, 1, read_filename);
		fread(pic_in->img.plane[1], y_size / 4, 1, read_filename);
		fread(pic_in->img.plane[2], y_size / 4, 1, read_filename);

		pic_in->i_pts = i;
		ret = x264_encoder_encode(encoder, &nal, &n_nal, pic_in, pic_out);
		if (ret < 0)
		{
			printf("Error.\n");
			return -1;
		}
		printf("Succeed encode frame: %5d\n", i);
		for (j = 0; j < n_nal; ++j)
		{
			fwrite(nal[j].p_payload, 1, nal[j].i_payload, write_filename);
		}
	}
	i = 0;
	while (1)
	{
		ret = x264_encoder_encode(encoder, &nal, &n_nal, NULL, pic_out);
		if (ret == 0)
		{
			break;
		}
		printf("Flush 1 frame.\n");
		for (j = 0; j < n_nal; ++j)
		{
			fwrite(nal[j].p_payload, 1, nal[j].i_payload, write_filename);
		}
		i++;
	}

	x264_picture_clean(pic_in);
	x264_encoder_close(encoder);
	encoder = NULL;
	free(pic_in);
	free(pic_out);
	free(param);
	fclose(read_filename);
	fclose(write_filename);
	return 0;
}


    编码后,在项目工程文件里生成test.h264文件:

    播放编码前的yuv文件:

    播放编码后的h264文件:



    项目工程下载:点击打开链接

    参考博客:http://blog.csdn.net/leixiaohua1020/article/details/42078645

                     http://www.cnblogs.com/fojian/archive/2012/09/01/2666627.html