一、项目思路
实时监控系统/直播系统,通过V4L2接口从摄像头采集YUYV格式的视频;用H264对视频数据进行编码;然后通过RTMP协议发送至支持了rtmp的nginx流媒体服务器;用客户端使用VLC从服务器拉流显示。
V4L2----->H264----->RTMP------>nginx------>VLC
二、配置环境
2.1、获取X264库源码
git config --global http.sslVerify false //解决ssl证书验证失败的问题
git clone VideoLAN / x264 · GitLab //下载代码
2.2、编译安装X264库
2.2.1、编译安装nasm
1)下载地址:Index of /pub/nasm/releasebuilds 需要下载2.13版本的
2)下载后复制到ubuntu
tar -xvf nasm-2.13.tar.gz
cd nasm-2.13/
3)配置编译nasm
sudo ./configure
sudo make
sudo make install
2.2.2、编译安装x264库
cd x264
sudo ./configure --enable-shared --enable-static --prefix=/usr
sudo make
sudo make install
三、H264
H264压缩图像是一个序列一个序列压缩的,一个序列中的多个帧基本是相似度非常高的图像,在一个序列中的第一个图像叫做IDR图像(立即刷新图像),IDR图像都是I帧图像。H264引入IDR图像是为了解码的重同步,当解码器解码到IDR图像时,立即将参考帧队列清空,将已解码的数据全部输出或抛弃,重新查找参数集,开始一个新的序列。本设计只是用了H.264的编码部分,使用目前最为流行,性能最好的X264作为编码器
四、源代码
1.cam.c
2.X264_Code.h
#ifndef __X264_Code__
#define __X264_Code__
#include <stdint.h>
#include <x264.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//编码器信息相关结构体
typedef struct encode{
x264_param_t param; //相关配置信息
x264_nal_t *nal; //NAL单元 编码之后存储的NAL单元
x264_picture_t picture; //H264图像,主要是用来存YUV分量的
x264_t *handle; //编码器指针
}Encode;
//SPS和PPS相关结构体 ----单独的存放SPS和PPS
typedef struct sps_pps{
uint8_t * sps; //sps内容
uint8_t * pps; //pps内容
uint32_t sps_len; //sps长度
uint32_t pps_len; //pps长度
}sps_pps;
typedef struct sps_pps_buf{
char *buf; //sps和pps的NAL单元 0x00 00 00 01+SPS+0x00 00 00 01+PPS
unsigned int length; //sps+pps的大小
}sps_pps_buf;
//编码器初始化
int X264_Iinit(Encode * en,sps_pps * sp);
//转码
int X264_Encode(Encode * en,unsigned char * cam_buf,int * nal_num);
//资源释放
int X264_Release(Encode * en,sps_pps * sp,sps_pps_buf * buff);
//pps 和SPS打包
void X264_GetSPS_PPS_Buff(sps_pps * sp, sps_pps_buf * buff);
#endif
3.X264_Code.c
#include "X264_Code.h"
#define WIDTH 640
#define HEIGHT 480
/**********************************************
函数功能:初始化X264编码器库
函数参数:en Encode结构体指针 sp sps_pps结构体指针(存放sps pps内容的)
函数返回值:成功返回0 失败返回-1
**********************************************/
int X264_Iinit(Encode * en,sps_pps * sp)
{
//初始化编码器默认属性
x264_param_default(&en->param);
//预设编码库
x264_param_default_preset(&en->param,"veryfast","zerolatency");
//设置编码器相关属性
en->param.i_threads = X264_SYNC_LOOKAHEAD_AUTO; /* 取空缓冲区继续使用不死锁的保证 */
en->param.i_sync_lookahead = X264_SYNC_LOOKAHEAD_AUTO;//自动获取线程超前缓冲区的大小
//设置视频属性
en->param.i_width = WIDTH; //图像的宽度
en->param.i_height = HEIGHT; //图像的高度
en->param.i_frame_total = 0; //*编码总帧数.不知道用0.
en->param.i_keyint_min = 0; //关键帧最小间隔
en->param.i_keyint_max = 60; //关键帧最大间隔 最大2s一个I帧
en->param.b_annexb = 1; //1前面为0x00000001,0为nal长度
en->param.b_repeat_headers = 0; //关键帧前面是否放sps跟pps帧,0 否 1,放
en->param.i_csp = X264_CSP_I422; //指定输入图像的色彩空间位YUV422--YUYV
en->param.i_fps_den = 1; //帧率分母 帧率:是用来描述视频中每秒显示的帧数的
en->param.i_fps_num = 30; //帧率分子 常见的帧率:24 25 30 60
en->param.i_timebase_num = 1; //设置每帧时长:1/1000s 也就是每毫秒1帧
en->param.i_timebase_den = 1000;
//设置B帧属性
en->param.i_bframe = 0; //禁止使用B帧,不进行前后帧参考
en->param.b_open_gop = 0; //不使用开放式GOP
en->param.i_bframe_pyramid = 0; //关闭改进B帧预测模式
en->param.i_bframe_adaptive = X264_B_ADAPT_FAST; //B帧快速自适应模式
//设置速率控制参数
en->param.rc.i_bitrate = 800; //设置比特率 单位时间发送的比特数量 kbps
en->param.rc.i_lookahead = 0; //禁止下一帧预测
en->param.rc.i_rc_method = X264_RC_ABR; //码率控制,CQP(恒定质量), CRF(恒定码率),ABR(平均码率)
en->param.rc.i_vbv_max_bitrate = 800; //平均码率下,最大瞬时码率,默认0
en->param.rc.i_vbv_buffer_size = 800;//VBV Video Buffering Verifier 视频缓存检验器
//实例化一个编码器
en->handle = x264_encoder_open(&en->param);
//初始化图像信息
x264_picture_init(&en->picture);
//申请图像空间
int ret = x264_picture_alloc(&en->picture, X264_CSP_I422, WIDTH, HEIGHT);
if(ret < 0)
{
printf("x264_picture_alloc\n");
return -1;
}
//获取SPS和PPS
en->picture.i_pts = 0; //存放用于整个流的SPS、PPS、SEI
int pi_nal = 0; //存放NAL单元的个数
x264_encoder_headers(en->handle,&en->nal,&pi_nal);
if(pi_nal > 0) //pi_nal=2
{
int i = 0;
for(i = 0; i < pi_nal; i++)
{
//如果获取到的帧类型是SPS
if (en->nal[i].i_type == NAL_SPS) //SPS数据 0x67&1F NAL:startcode
{
sp->sps = malloc(en->nal[i].i_payload - 4); //去掉0x00 00 00 01 的空间大小
sp->sps_len = en->nal[i].i_payload - 4; //SPS的NAL去掉0x00 00 00 01的大小
memcpy(sp->sps, en->nal[i].p_payload + 4, sp->sps_len);//得到SPS的内容
}
/*PPS*/
if (en->nal[i].i_type == NAL_PPS)
{
sp->pps = malloc(en->nal[i].i_payload - 4);
sp->pps_len = en->nal[i].i_payload - 4;
memcpy(sp->pps, en->nal[i].p_payload + 4, sp->pps_len);
}
}
}
}
/**********************************************
函数功能:转码为x264
函数参数:en Encode结构体指针 cam_buf nal_num
函数返回值:成功返回0 失败返回-1
**********************************************/
int X264_Encode(Encode * en,unsigned char * cam_buf,int * nal_num)
{
//提取yuv分量值
unsigned char * y = en->picture.img.plane[0]; //y指向picture 的Y分量存储位置
unsigned char * u = en->picture.img.plane[1]; //u指向picture 的u分量存储位置
unsigned char * v = en->picture.img.plane[2]; //v指向picture 的v分量存储位置
unsigned char * frame_ = cam_buf;
int index_y=0,index_u=0,index_v=0;
int num = (WIDTH*HEIGHT)*2; //图像的字节数 图像的宽*高*2 每个像素需要2个字节来存储
int i = 0;
for(i=0;i<num;i=i+4)
{
*(y+(index_y++)) = *(frame_+i);
*(u+(index_u++)) = *(frame_+i+1);
*(y+(index_y++)) = *(frame_+i+2);
*(v+(index_v++)) = *(frame_+i+3);
}
//转码
x264_picture_t pic_out;
x264_picture_init(&pic_out);
int ret = x264_encoder_encode(en->handle,&en->nal,nal_num,&en->picture,&pic_out);
if(ret < 0)
{
printf("x264_encoder_encode\n");
return -1;
}
return 0;
}
/**********************************************
函数功能:pps 和SPS打包
函数参数:sp sps_pps结构体指针(存放sps pps内容的)buff sps_pps_buf结构体指针
函数返回值:
**********************************************/
void X264_GetSPS_PPS_Buff(sps_pps * sp, sps_pps_buf * buff)
{
buff->buf = malloc(sp->sps_len+sp->pps_len+8);
int j = 0;
//填充SPS startcode
buff->buf[j++]=0x00;
buff->buf[j++]=0x00;
buff->buf[j++]=0x00;
buff->buf[j++]=0x01;
memcpy(&buff->buf[j],sp->sps,sp->sps_len);
//填充PPS startcode
j = j+sp->sps_len;
buff->buf[j++]=0x00;
buff->buf[j++]=0x00;
buff->buf[j++]=0x00;
buff->buf[j++]=0x01;
memcpy(&buff->buf[j],sp->pps,sp->pps_len);
j +=sp->pps_len;
buff->length = j;
}
/**********************************************
函数功能:资源释放
函数参数:en Encode结构体指针 sp sps_pps结构体指针(存放sps pps内容的) buff sps_pps_buf结构体指针
函数返回值:
**********************************************/
int X264_Release(Encode * en,sps_pps * sp,sps_pps_buf * buff)
{
free(sp->sps);
free(sp->pps);
free(buff->buf);
buff->buf=NULL;
sp->sps=NULL;
sp->pps=NULL;
//清空编码图像
x264_picture_clean(&en->picture);
//关闭编码器
x264_encoder_close(en->handle);
}
4.main.c
#include "cam.h"
#include "X264_Code.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char * argv[])
{
//初始化摄像头
unsigned int size1;
unsigned int width=640,height=480 ,ismjpeg=0;
int cam_fd = camera_init(CAMERA_USB,&width, &height,&size1,&ismjpeg);
if(cam_fd < 0)
{
printf("camera_init\n");
return -1;
}
//初始化编码器
Encode en;
sps_pps sp;
int ret = X264_Iinit(&en,&sp);
if(ret < 0)
{
printf("X264_Iinit\n");
camera_exit(cam_fd);
return -1;
}
//获取SPS和PPS的NAL单元,打包成SPS_NAL+PPS_NAL
sps_pps_buf buff;
X264_GetSPS_PPS_Buff(&sp,&buff);
//打开文件
int file_fd = open("test.h264",O_RDWR | O_CREAT,0666);
//SPS_NAL+PPS_NAL写入文件
write(file_fd,buff.buf,buff.length);
//开启摄像头
ret = camera_start(cam_fd);
if(ret < 0)
{
printf("camera_start\n");
camera_exit(cam_fd);
close(file_fd);
return -1;
}
unsigned char * cam_buf = NULL; //摄像头图像的指针
int size = 0;
unsigned int index = 0;
//循环采集图像
for(int i=0;i<100;i++)
{
int nal_num = 0;
//出队
camera_dqbuf(cam_fd, (void **)&cam_buf, &size, &index);
//入队
camera_eqbuf(cam_fd, index);
//编码
X264_Encode(&en,cam_buf,&nal_num);
if(nal_num > 0)
{
for(int i = 0; i< nal_num;i++)
{
write(file_fd,en.nal[i].p_payload,en.nal[i].i_payload);
}
}
}
close(file_fd);
camera_stop(cam_fd);
camera_exit(cam_fd);
X264_Release(&en,&sp,&buff);
return 0;
}
五、测试
edu@edu:~/Nginx/code/V4L2_X264$ gcc *.c -lx264
edu@edu:~/Nginx/code/V4L2_X264$ ./a.out
目前项目已开源至 gitee:流媒体项目: 通过V4L2接口从摄像头采集YUYV格式的视频;用H264对视频数据进行编码;然后通过RTMP协议发送至支持了rtmp的nginx流媒体服务器;用客户端使用VLC从服务器拉流显示。