实时监控系统/直播系统(二)- 用H264对视频数据进行编码

一、项目思路

实时监控系统/直播系统,通过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从服务器拉流显示。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值