在我们的视频采集传输设备中,先是通过摄像头采集颜色数据组成一张画面,也就是我们常说的一帧。数据格式可以是YUV数据也可以是RGB数据,他们之间可以通过计算转换。我们看到的视频其实就是由一帧一帧的画面组成,其速度一般是25帧/秒,电影《比利林恩的中场战事》采用的120帧/秒的技术。如果直接将摄像头采集到的颜色编码成视频,那么视频要求的带宽是非常非常高的。以30万像素摄像头YUV420格式来计算一帧数据大小 = 长 * 宽 * 1.5 = 640 * 480 * 1.5 / 1024 = 450 K,视频的码流将会是 450 K * 25 / 1024 = 9.88M/s。为了使视频传输更加的流畅,现在的视频都采用的压缩编码。这里我们将介绍使用X264编码器将YUV420 数据编码成H264格式视频。YUV数据采集在前面已经介绍:
现在直接上本章的代码,也就是编码部分的代码:
/*=============================================================================
# FileName: h264encoder.c
# Desc: this program aim to get image from USB camera,
# used the V4L2 interface.
# Author: Licaibiao
# Version:
# LastChange: 2016-12-11
# History:
=============================================================================*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "./include/h264encoder.h"
void compress_begin(Encoder *en, int width, int height) {
en->param = (x264_param_t *) malloc(sizeof(x264_param_t));
en->picture = (x264_picture_t *) malloc(sizeof(x264_picture_t));
x264_param_default(en->param); //set default param
//en->param->rc.i_rc_method = X264_RC_CQP;
// en->param->i_log_level = X264_LOG_NONE;
en->param->i_threads = X264_SYNC_LOOKAHEAD_AUTO;
en->param->i_width = width; //set frame width
en->param->i_height = height; //set frame height
en->param->i_frame_total = 0;
en->param->i_keyint_max = 10;
en->param->rc.i_lookahead = 0;
en->param->i_bframe = 5;
en->param->b_open_gop = 0;
en->param->i_bframe_pyramid = 0;
en->param->i_bframe_adaptive = X264_B_ADAPT_TRELLIS;
en->param->rc.i_bitrate = 1024 * 10;//rate 10 kbps
en->param->i_fps_num = 25;
en->param->i_fps_den = 1;
x264_param_apply_profile(en->param, x264_profile_names[0]);
if ((en->handle = x264_encoder_open(en->param)) == 0) {
return;
}
/* Create a new pic */
x264_picture_alloc(en->picture, X264_CSP_I420, en->param->i_width,
en->param->i_height);
en->picture->img.i_csp = X264_CSP_I420;
en->picture->img.i_plane = 3;
}
int compress_frame(Encoder *en, int type, uint8_t *in, uint8_t *out) {
x264_picture_t pic_out;
int nNal = -1;
int result = 0;
int i = 0;
uint8_t *p_out = out;
char *y = en->picture->img.plane[0];
char *u = en->picture->img.plane[1];
char *v = en->picture->img.plane[2];
//yuv420_length = 1.5 * en->param->i_width * en->param->i_height
//int length = en->param->i_width * en->param->i_height;
//y = 640*480 ; U = 640*480*0.25, V = 640*480*0.25;
memcpy(y,in,307200);
memcpy(u,in+307200,76800);
memcpy(v,in+384000,76800);
switch (type) {
case 0:
en->picture->i_type = X264_TYPE_P;
break;
case 1:
en->picture->i_type = X264_TYPE_IDR;
break;
case 2:
en->picture->i_type = X264_TYPE_I;
break;
default:
en->picture->i_type = X264_TYPE_AUTO;
break;
}
if (x264_encoder_encode(en->handle, &(en->nal), &nNal, en->picture,
&pic_out) < 0) {
return -1;
}
for (i = 0; i < nNal; i++) {
memcpy(p_out, en->nal[i].p_payload, en->nal[i].i_payload);
p_out += en->nal[i].i_payload;
result += en->nal[i].i_payload;
}
return result;
}
void compress_end(Encoder *en) {
if (en->picture) {
x264_picture_clean(en->picture);
free(en->picture);
en->picture = 0;
}
if (en->param) {
free(en->param);
en->param = 0;
}
if (en->handle) {
x264_encoder_close(en->handle);
}
//free(en);
}
函数compress_begin 初始化的各参数的意思,可以参考 x264重要结构体详细说明 上面的参数我是随意配置的。
compress_frame 里面的
memcpy(y,in,307200);
memcpy(u,in+307200,76800);
memcpy(v,in+384000,76800);
这数值是根据我之前v4l2输出格式计算的,我设置的是YUV420 输出格式,其每个像素是每4个Y分量公用一个UV分量,所以Y = 4/4*width*hight;U = 1/4 *width*hight; V = 1/4 *width*hight。具体的可以参考:图文详解YUV420数据格式
编译方到开发板的执行结果如下:
/tmp #
/tmp # ls
hostapd lib messages utmp x264_test
/tmp # ls lib
libx264.so.148
/tmp # export LD_LIBRARY_PATH=/tmp/lib:$LD_LIBRARY_PATH
/tmp # ./x264_test
camera driver name is : sunxi-vfe
camera device name is : sunxi-vfe
camera bus information: sunxi_vfe vfe.2
n_buffer = 3
x264 [warning]: lookaheadless mb-tree requires intra refresh or infinite keyint
x264 [info]: using cpu capabilities: none!
x264 [info]: profile Constrained Baseline, level 3.0
x264 [warning]: non-strictly-monotonic PTS
encode_frame num = 0
x264 [warning]: non-strictly-monotonic PTS
encode_frame num = 1
....
....
x264 [warning]: non-strictly-monotonic PTS
encode_frame num = 98
x264 [info]: frame I:10 Avg QP:23.29 size: 15057
x264 [info]: frame P:89 Avg QP:26.69 size: 2080
x264 [info]: mb I I16..4: 63.6% 0.0% 36.4%
x264 [info]: mb P I16..4: 10.6% 0.0% 0.2% P16..4: 39.0% 3.5% 1.8% 0.0% 0.0% skip:44.8%
x264 [info]: coded y,uvDC,uvAC intra: 17.2% 83.3% 39.6% inter: 4.0% 30.0% 0.1%
x264 [info]: i16 v,h,dc,p: 49% 24% 13% 14%
x264 [info]: i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 13% 39% 23% 5% 4% 2% 8% 2% 4%
x264 [info]: i8c dc,h,v,p: 63% 18% 16% 3%
x264 [info]: ref P L0: 62.1% 21.6% 16.2%
x264 [info]: kb/s:inf
/tmp #
/tmp # ls -l
total 588
drwxr-x--- 2 root root 60 Jan 1 00:00 hostapd
drwxr-xr-x 2 root root 60 Jan 1 00:00 lib
-rw-r--r-- 1 root root 28372 Jan 1 00:37 messages
-rw-r--r-- 1 root root 335669 Jan 1 00:37 test.h264
-rw-r--r-- 1 root root 1152 Jan 1 00:00 utmp
-rwxrwxrwx 1 root root 20674 Dec 11 2016 x264_test
/tmp #
生成了我们所需要的H264格式视频文件test.h264,使用VLC media player 播放器可以直接播放。本来想做成实时视频流,无奈我开发板太low,编写一帧数据就需要接近两秒的时间,实在是受不了。
下面是使用播放器播放录制的视频截图
这个工程的全部代码和X264编译生成的库文件可以到这里下载 X264编码H264工程
最简单的视频编码到这里告一段落。
2016.12.11