实时监控系统/直播系统(一)- V4L2打开摄像头

一、项目思路

实时监控系统/直播系统,通过V4L2接口从摄像头采集YUYV格式的视频;用H264对视频数据进行编码;然后通过RTMP协议发送至支持了rtmp的nginx流媒体服务器;用客户端使用VLC从服务器拉流显示。

V4L2----->H264----->RTMP------>nginx------>VLC

V4L2:linux下摄像头操作的框架(主要用来采集图像视频)

YUYV:图像格式

H264:音视频编码格式(图像的压缩算法)

RTMP:用来传输视频、音频流数据

nginx:反向代理服务器

VLC:一款开源的跨平台多媒体播放器和服务器

二、配置环境

参考:

Ubuntu16.04使用笔记本电脑摄像头操作方式_ubuntu16.04找不到摄像头图标-CSDN博客

三、相关技术

3.1、V4L2

V4L2(Video for Linux 2)是用于Linux操作系统的视频捕获框架,允许开发者访问和控制视频设备,如摄像头、摄像机和其他视频输入设备。
应用程序通过V4L2框架,对摄像头进行操作,如 设置摄像头的频率、图像参数、查看摄像头支持的配置等等;在使用V4L2框架采集摄像头时,主要步骤 1、打开设备;2、对设备进行配置;3、设置数据采集方式;4、处理数据;5、关闭设备。

摄像头属性相关结构体:

struct v4l2_format {
    __u32 type;                     // 数据流类型(例如:V4L2_BUF_TYPE_VIDEO_CAPTURE)
    union {
        struct v4l2_pix_format pix; // 像素格式
        // 更多格式类型可以通过 union 扩展
    } fmt;
};

struct v4l2_pix_format {
    __u32 width;                    // 图像宽度
    __u32 height;                   // 图像高度
    __u32 pixelformat;              // 像素格式(例如:V4L2_PIX_FMT_YUV420)
    // 更多参数可以根据不同的像素格式类型进行扩展
};

struct v4l2_capability {
    char driver[16];      // 驱动程序名称
    char card[32];        // 设备名称或描述
    char bus_info[32];    // 连接总线信息
    __u32 version;        // V4L2 规范的版本号
    __u32 capabilities;   // 设备支持的能力标志
};

/*ioctl 查看支持的驱动*/
    ret = ioctl(fd, VIDIOC_QUERYCAP, &capability);
    if (ret == -1) {
        perror("camera->init");
        close(fd);
        return -1;
    }

3.2、YUV

YUV 是一种将彩色图像分离为亮度(Y)和色度(UV)信息的图像编码格式。常用有YUV422格式和YUV420格式。与RGB相比YUV422的大小是其2/3,而YUV420是其1/2。在YUV422格式中,按照U、V分量在时空上的排列顺序不同,可以将他们分为YUYV、YVYU、UYVY、VYUY四种不同的排列方式;在YUV420格式中,又分为I420(YU12)、YV12、NV12、NV21。

四、源代码

1.cam.h
#ifndef __CAM_H__
#define	__CAM_H__

#define CAMERA_USB "/dev/video0"//填写自己video0文件路径

int camera_init(char *devpath, unsigned int *width, unsigned int *height, unsigned int *size, unsigned int *ismjpeg);
int camera_start(int fd);
int camera_dqbuf(int fd, void **buf, unsigned int *size, unsigned int *index);
int camera_eqbuf(int fd, unsigned int index);
int camera_stop(int fd);
int camera_exit(int fd);
#endif
2.cam.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include "cam.h"

/*
   函数功能:用于进行设备和文件的输入/输出控制操作,可以向设备驱动程序发送特定的命令以及相关参数
   函数名:int ioctl(int fd, unsigned long request, ...);
   函数参数:fd 设备文件的文件描述符 request 表示要执行的 ioctl 命令或操作。
*/



#define	REQBUFS_COUNT	4

struct cam_buf {
	void *start;
	size_t length;
};

struct v4l2_requestbuffers reqbufs;//用于请求视频缓冲区
struct cam_buf bufs[REQBUFS_COUNT];//用于存储视频缓冲区的信息

/*
  功能:  初始化摄像头
  函数名:camera_init
  参数:devpath   摄像头的路径名     width采集图像的宽度   height采集图像的高度   size图像大小   ismjpeg图像的格式(1表示支持jpeg格式,非1表示其它格式)
  返回值:成功返回摄像头文件描述符,失败返回-1
*/
int camera_init(char *devpath, unsigned int *width, unsigned int *height, unsigned int *size, unsigned int *ismjpeg)
{
	int i;
	int fd = -1;
	int ret;
	struct v4l2_buffer vbuf;//用于表示视频缓冲区的信息
	
	struct v4l2_format format;//用于配置视频设备的格式
	
	struct v4l2_capability capability;//获取视频设备相关信息
	
	/*open 打开设备文件*/
	if((fd = open(devpath, O_RDWR)) == -1){
		perror("open:");
		close(fd);
		return -1;		
	}
	/*ioctl 查看支持的驱动*/
	ret = ioctl(fd, VIDIOC_QUERYCAP, &capability);//VIDIOC_QUERYCAP摄像头设备的基本信息
	if (ret == -1) {
		perror("camera->init");
		close(fd);
		return -1;
	}

	/*判断设备是否支持视频采集*/
	if(!(capability.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
		fprintf(stderr, "camera->init: device can not support V4L2_CAP_VIDEO_CAPTURE\n");
		close(fd);
		return -1;
	}
	/*判断设备是否支持视频流采集,*/
	if(!(capability.capabilities & V4L2_CAP_STREAMING)) {
		fprintf(stderr, "camera->init: device can not support V4L2_CAP_STREAMING\n");
		close(fd);
		return -1;
	}
    /*V4L2_CAP_VIDEO_CAPTURE,则可以使用它来捕获视频帧;V4L2_CAP_STREAMING,则可以配置它以进行实时视频流捕获。*/

	if (*ismjpeg != 1){
		/*设置捕获的视频格式 YUYV*/
		memset(&format, 0, sizeof(format));//视频设备的格式format清空
		format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//视频捕获相关的参数
		format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;   //原始图像格式V4L2_PIX_FMT_YUYV
		format.fmt.pix.width = *width;// 填充所需的宽度
		format.fmt.pix.height = *height; // 填充所需的高度
		format.fmt.pix.field = V4L2_FIELD_ANY;//表示图像字段可以是任何类型
		ret = ioctl(fd, VIDIOC_S_FMT, &format);     //通过ioctl函数将设置信息设置到驱动程序
		if(ret == -1) {
			perror("camera init");
			close(fd);
			return -1;
		} else {
			*ismjpeg = 0;
			fprintf(stdout, "camera->init: picture format is yuyv\n");
			goto get_fmt;
		}
	} else {
		/*设置捕获的视频格式 MYJPEG*/
		memset(&format, 0, sizeof(format));
		format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//永远都是这个类型
		format.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;//设置采集图片的格式V4L2_PIX_FMT_MJPEG
		format.fmt.pix.width = *width;
		format.fmt.pix.height = *height;
		format.fmt.pix.field = V4L2_FIELD_ANY;	//设置图片一行一行的采集
		ret = ioctl(fd, VIDIOC_S_FMT, &format);	//ioctl	是设置生效
		if(ret == -1){
			perror("camera init");
			close(fd);
			return -1;
		}else {
			fprintf(stdout, "camera->init: picture format is mjpeg\n");
			*ismjpeg = 1;
			goto get_fmt;
		}
	}
//获取摄像头属性信息
get_fmt:
	ret = ioctl(fd, VIDIOC_G_FMT, &format);
	if (ret == -1) {
		perror("camera init");
		close(fd);
		return -1;
	}
	
	
	/*向驱动申请缓存*/
	memset(&reqbufs, 0, sizeof(struct v4l2_requestbuffers));//清空请求视频缓冲区
	reqbufs.count	= REQBUFS_COUNT;		//缓存区个数
	reqbufs.type	= V4L2_BUF_TYPE_VIDEO_CAPTURE;
	reqbufs.memory	= V4L2_MEMORY_MMAP;		//设置操作申请缓存的方式:映射 MMAP
	ret = ioctl(fd, VIDIOC_REQBUFS, &reqbufs);//用于分配4个内存映射类型的视频捕获缓冲区			
	if (ret == -1) {	
		perror("camera init");
		close(fd);
		return -1;
	}
	/*循环映射并入队*/
	for (i = 0; i < reqbufs.count; i++){
		/*真正获取缓存的地址大小*/
		memset(&vbuf, 0, sizeof(struct v4l2_buffer));
		vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		vbuf.memory = V4L2_MEMORY_MMAP;//设置操作申请缓存的方式:映射 MMAP
		vbuf.index = i;//查询的缓冲区的索引号
		ret = ioctl(fd, VIDIOC_QUERYBUF, &vbuf);
		if (ret == -1) {
			perror("camera init");
			close(fd);
			return -1;
		}
		/*映射缓存到用户空间,通过mmap讲内核的缓存地址映射到用户空间,并切和文件描述符fd相关联*/
		bufs[i].length = vbuf.length;
		bufs[i].start = mmap(NULL, vbuf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, vbuf.m.offset);
		if (bufs[i].start == MAP_FAILED) {
			perror("camera init");
			close(fd);
			return -1;
		}
		/*每次映射都会入队,放入缓冲队列*/
		vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		vbuf.memory = V4L2_MEMORY_MMAP;
		ret = ioctl(fd, VIDIOC_QBUF, &vbuf);
		if (ret == -1) {
			perror("camera init");
			close(fd);
			return -1;
		}
	}
	/*VIDIOC_QUERYBUF 用于查询缓冲区的属性和状态,而 VIDIOC_QBUF 用于将缓冲区添加到捕获队列中*/

	/*返回真正设置成功的宽.高.大小*/
	*width = format.fmt.pix.width;
	*height = format.fmt.pix.height;
	*size = bufs[0].length;

	return fd;
}

/*
   函数功能:启动摄像头视频捕获
   函数名:camera_start
   函数参数:fd 摄像头文件描述符
*/

int camera_start(int fd)
{
	int ret;
	
	enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	/*ioctl控制摄像头开始采集*/
	ret = ioctl(fd, VIDIOC_STREAMON, &type);//VIDIOC_STREAMON 启动视频捕获流程
	if (ret == -1) {
		perror("camera->start");
		return -1;
	}
	fprintf(stdout, "camera->start: start capture\n");

	return 0;
}

/*
   函数功能:摄像头数据出队
   函数名:camera_dqbuf
   函数参数:fd 摄像头文件描述符  buf 输出的图像数据    size输出图像数据大小   index 输出的哪一片缓冲队列
*/

int camera_dqbuf(int fd, void **buf, unsigned int *size, unsigned int *index)
{
	int ret;
	fd_set fds;
	struct timeval timeout;    //为IO多路复用设置时间的
	struct v4l2_buffer vbuf;

	while (1) {
		FD_ZERO(&fds);
		FD_SET(fd, &fds);   //将摄像头添加到监听队列
		timeout.tv_sec = 2;
		timeout.tv_usec = 0;
		ret = select(fd + 1, &fds, NULL, NULL, &timeout);	//使用select机制,保证fd有图片的时候才能出对
		if (ret == -1) {
			perror("camera->dbytesusedqbuf");
			if (errno == EINTR)
				continue;
			else
				return -1;
		} else if (ret == 0) {
			fprintf(stderr, "camera->dqbuf: dequeue buffer timeout\n");
			return -1;
		} else {
			vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
			vbuf.memory = V4L2_MEMORY_MMAP;
			ret = ioctl(fd, VIDIOC_DQBUF, &vbuf);	//出队,也就是从用户空间取出图片
			if (ret == -1) {
				perror("camera->dqbuf");
				return -1;
			}
			*buf = bufs[vbuf.index].start;
			*size = vbuf.bytesused;
			*index = vbuf.index;   //当前读走的缓冲区下标

			return 0;
		}
	}
}

/*
   函数功能:摄像头数据入队
   函数名称:camera_eqbuf
   函数参数:fd   摄像头文件描述符    index  下一帧数据入队缓冲区下标
*/

int camera_eqbuf(int fd, unsigned int index)
{
	int ret;
	struct v4l2_buffer vbuf;

	vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	vbuf.memory = V4L2_MEMORY_MMAP;
	vbuf.index = index;
	ret = ioctl(fd, VIDIOC_QBUF, &vbuf);		//入队
	if (ret == -1) {
		perror("camera->eqbuf");
		return -1;
	}

	return 0;
}

/*
函数功能:停止摄像头采集图片
函数名称:camera_stop
函数参数:fd    文件描述符
函数返回值:成功返回0,失败返回-1

*/

int camera_stop(int fd)
{
	int ret;
	enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

	ret = ioctl(fd, VIDIOC_STREAMOFF, &type);
	if (ret == -1) {
		perror("camera->stop");
		return -1;
	}
	fprintf(stdout, "camera->stop: stop capture\n");

	return 0;
}

/*
函数功能:关闭摄像头
函数名称:camera_exit
函数参数:fd  摄像头文件描述符
函数返回值:成功返回0,失败返回-1
注:退出之前需要将内核中的数据清空(出队),解除映射关系
*/

int camera_exit(int fd)
{
	int i;
	int ret;
	struct v4l2_buffer vbuf;
	vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	vbuf.memory = V4L2_MEMORY_MMAP;
	for (i = 0; i < reqbufs.count; i++) {
		ret = ioctl(fd, VIDIOC_DQBUF, &vbuf);
		if (ret == -1)
			break;
	}
	for (i = 0; i < reqbufs.count; i++)
		munmap(bufs[i].start, bufs[i].length);
	fprintf(stdout, "camera->exit: camera exit\n");
	return close(fd);
}
3.main.c
#include <stdio.h>
#include <string.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "cam.h"

int main()
{
	unsigned int width = 640;
	unsigned int height = 480;
	unsigned int size;	//存储图片大小的变量,由出队函数赋值
	unsigned int ismjpeg = 1;
	unsigned int index = -1;
	
	//保存图片内容的地址。
	char *jpg_buf = NULL;
	

	//初始化摄像头
	int  fd = camera_init(CAMERA_USB,&width,&height,&size,&ismjpeg);
	if (fd < 0){
		return -1;
	} 
	
	//开始采集
	if (camera_start(fd) < 0){
		return -1;
	}
	
	jpg_buf = (char *)malloc(width * height);
	if (jpg_buf == NULL){
		printf("malloc failed\n");
		return -1;
	}
	
	int file_fd = -1;
   for(int i = 0; i<100;i++)
   {
	//出队
	camera_dqbuf(fd, &jpg_buf, &size, &index);
	
	//入队
	camera_eqbuf(fd,index);
	
	 file_fd= open("1.jpeg", O_CREAT | O_RDWR, 0640);
	if (file_fd < 0){
		perror("open file");
		return -1;
	}
	printf( "write: %d\n", write(file_fd,jpg_buf, size));
   }
	close(file_fd);
	camera_stop(fd);
	camera_exit(fd);

}

五、测试

edu@edu:~/Nginx/code/V4L2$ gcc *.c
edu@edu:~/Nginx/code/V4L2$ ./a.out

目前项目已开源至 gitee:流媒体项目: 通过V4L2接口从摄像头采集YUYV格式的视频;用H264对视频数据进行编码;然后通过RTMP协议发送至支持了rtmp的nginx流媒体服务器;用客户端使用VLC从服务器拉流显示。

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值