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;
}
若文件打开失败或者写入失败,都提示错误。
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)。
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++;
}
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