Linux之V4L2驱动框架

2023-05-16

目录

一、V4L2简介

二、V4L2操作流程

 1.打开摄像头

2.查询设备的属性/能力/功能

3.获取摄像头支持的格式

4.设置摄像头的采集通道

5.设置/获取摄像头的采集格式和参数

6.申请帧缓冲、内存映射、入队

(1)申请帧缓冲

(2)内存映射

(3)入队

7.开启视频采集

8.读取数据、对数据进行处理

9.结束视频采集

三、应用编程


一、V4L2简介

V4L2(Video for linux two)是 Linux 内核中视频类设备的一套驱动框架,为视频类设备驱动开发和应用层提供了一套统一的接口规范。使用 V4L2 设备驱动框架注册的设备会在 Linux 系统/dev/目录下生成对应的设备节点文件,设备节点的名称通常为 videoX(X 标准一个数字编号:/dev/videox),每一个 videoX 设备文件就代表一个视频类设备。应用程序通过对 videoX 设备文件进行 I/O 操作来配置、使用设备类设备。
 

二、V4L2操作流程

V4L2摄像头驱动框架的访问是通过系统IO的接口 ------ ioctl函数,ioctl专用于硬件控制的系统IO的接口

#include <sys/ioctl.h>     //包含头文件
int ioctl(int fd, unsigned long request, ...);
fd: 文件描述符
request: 此参数与具体要操作的对象有关, 表示向文件描述符请求相应的操作
...: 可变参函数, 第三个参数需要根据 request 参数来决定,配合 request 来使用
返回值: 成功返回 0,失败返回-1

ioctl()是一个文件 IO 操作的杂物箱,可以处理的事情非常杂、不统一,一般用于操作特殊文件或硬件外设,可以通过 ioctl 获取外设相关信息。通过 ioctl()来完成,搭配不同的 V4L2 指令(request
参数)请求不同的操作,这些指令定义在头文件 linux/videodev2.h 中,常用的如下图。

 1.打开摄像头

视频类设备对应的设备节点为/dev/videoX, X 为数字编号,通常从 0 开始,调用 open 打开,得到文件描述符 fd。

int fd = -1;
fd = open("/dev/video0", O_RDWR);
if (0 > fd) 
{
    perror( "open error");
    exit(-1);
}

2.查询设备的属性/能力/功能
 

打开设备后,需要查询设备的属性,确定该设备是否是一个视频采集类设备。通过 ioctl()将获取到一个 struct v4l2_capability 类型数据, struct v4l2_capability 数据结构描述了设备的一些属性,结构体定义如下:

struct v4l2_capability {
    __u8 driver[16]; /* 驱动的名字 */
    __u8 card[32]; /* 设备的名字 */
    __u8 bus_info[32]; /* 总线的名字 */
    __u32 version; /* 版本信息 */
    __u32 capabilities; /* 设备拥有的能力 */
    __u32 device_caps;
    __u32 reserved[3]; /* 保留字段 */
};

这里关注capabilities 字段,该字段描述了设备拥有的能力,该字段的值如下

所以可以通过判断 capabilities字段是否包含 V4L2_CAP_VIDEO_CAPTURE、 来确定它是否是一个摄像头设备

struct v4l2_capability cap = {};
ioctl(fd,VIDIOC_QUERYCAP,&cap);
if(cap.capabilities&V4L2_CAP_VIDEO_CAPTURE)
{
    //是一个摄像头
}

3.获取摄像头支持的格式

获取支持的像素格式使用 VIDIOC_ENUM_FMT 指令

struct v4l2_fmtdesc {
	__u32		    index;             /*格式编号*/
	__u32		    type;              /*摄像头的格式  V4L2_BUF_TYPE_VIDEO_CAPTURE*/
	__u32               flags;
	__u8		    description[32];   /*描述信息:描述 pixelformat 像素格式。*/
	__u32		    pixelformat;       /*类型格式 --- 4字节:像素格式编号*/
	__u32		    reserved[4];
};

pixelfoemat像素格式:

//定义格式的宏
#define v4l2_fourcc(a, b, c, d)\
	((__u32)(a) | ((__u32)(b) << 8) | ((__u32)(c) << 16) | ((__u32)(d) << 24))
 
#define V4L2_PIX_FMT_YUYV    v4l2_fourcc('Y', 'U', 'Y', 'V')  /* 16 YUV 4:2:2 */ 
#define V4L2_PIX_FMT_YVYU    v4l2_fourcc('Y', 'V', 'Y', 'U') /* 16 YVU 4:2:2 */
#define V4L2_PIX_FMT_MJPEG   v4l2_fourcc('M', 'J', 'P', 'G') /* Motion-JPEG */
#define V4L2_PIX_FMT_JPEG    v4l2_fourcc('J', 'P', 'E', 'G') /* JFIF JPEG   */ 

v4l2_fourcc是 宏定义,通过这个宏以及对应的参数合成的一个u32 类型数据。

type类型:

获取设备的哪种功能对应的像素格式, 因为有些设备它可能即支持视频采集功能、又支持视频输出等其它的功能;

enum v4l2_buf_type {
    V4L2_BUF_TYPE_VIDEO_CAPTURE = 1, //视频采集
    V4L2_BUF_TYPE_VIDEO_OUTPUT = 2, //视频输出
    V4L2_BUF_TYPE_VIDEO_OVERLAY = 3,
    V4L2_BUF_TYPE_VBI_CAPTURE = 4,
    V4L2_BUF_TYPE_VBI_OUTPUT = 5,
    V4L2_BUF_TYPE_SLICED_VBI_CAPTURE = 6,
    V4L2_BUF_TYPE_SLICED_VBI_OUTPUT = 7,
    V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY = 8,
    V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE = 9,
    V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE = 10,
    V4L2_BUF_TYPE_SDR_CAPTURE = 11,
    V4L2_BUF_TYPE_SDR_OUTPUT = 12,
    V4L2_BUF_TYPE_META_CAPTURE = 13,
    /* Deprecated, do not use */
    V4L2_BUF_TYPE_PRIVATE = 0x80,
};

type 字 段 需 要 在 调 用 ioctl() 之 前 设 置 它 的 值 , 对 于 摄 像 头 , 需 要 将 type 字 段 设 置 为V4L2_BUF_TYPE_VIDEO_CAPTURE,指定将要获取的是视频采集的像素格式

struct v4l2_fmtdesc fmt = {};
fmt.index = 0;//第一种格式
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//获取摄像头的格式
ioctl(fd,VIDIOC_ENUM_FMT,&fmt);

4.设置摄像头的采集通道

int index = 0;//使用通道0
ioctl(fd,VIDIOC_S_INPUT,&index);

5.设置/获取摄像头的采集格式和参数

使用 VIDIOC_G_FMT 指令查看设备当期的格式和使用 VIDIOC_S_FMT 指令设置设备的格式;

int ioctl(int fd, VIDIOC_G_FMT, struct v4l2_format *fmt);
int ioctl(int fd, VIDIOC_S_FMT, struct v4l2_format *fmt);

ioctl()会将获取到的数据写入到 fmt 指针所指向的对象中或者会使用 fmt 所指对象的数据去设置设备的格式, struct v4l2_format 结构体描述了格式相关的信息

struct v4l2_format {
	__u32	 type;//V4L2_BUF_TYPE_VIDEO_CAPTURE
	union {
		struct v4l2_pix_format		pix;     /* V4L2_BUF_TYPE_VIDEO_CAPTURE */
		struct v4l2_pix_format_mplane	pix_mp;  /* V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE */
		struct v4l2_window		win;     /* V4L2_BUF_TYPE_VIDEO_OVERLAY */
		struct v4l2_vbi_format		vbi;     /* V4L2_BUF_TYPE_VBI_CAPTURE */
		struct v4l2_sliced_vbi_format	sliced;  /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */
		struct v4l2_sdr_format		sdr;     /* V4L2_BUF_TYPE_SDR_CAPTURE */
		__u8	raw_data[200];                   /* user-defined */
	} fmt;
};

struct v4l2_pix_format {
	__u32         		width;//像素宽度
	__u32			height;//像素高度
	__u32			pixelformat;//采集格式 V4L2_PIX_FMT_YVYU
	__u32			field;		/* V4L2_FIELD_NONE */
	__u32            	bytesperline;	/* for padding, zero if unused */
	__u32          		sizeimage;
	__u32			colorspace;	/* enum v4l2_colorspace */
	__u32			priv;		/* private data, depends on pixelformat */
	__u32			flags;		/* format flags (V4L2_PIX_FMT_FLAG_*) */
	__u32			ycbcr_enc;	/* enum v4l2_ycbcr_encoding */
	__u32			quantization;	/* enum v4l2_quantization */
	__u32			xfer_func;	/* enum v4l2_xfer_func */
};

type 字段依然与前面介绍的结构体中的 type 字段意义相同,不管是获取格式、还是设置格式都需要在调用 ioctl()函数之前设置它的值。接下来是一个 union 共用体,当 type 被设置为V4L2_BUF_TYPE_VIDEO_CAPTURE 时, pix 变量生效,它是一个 struct v4l2_pix_format 类型变量,记录了视频帧格式相关的信息。

获取当前的格式、并设置格式:

struct v4l2_format format = {};
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
format.fmt.pix.width = 640;
format.fmt.pix.height = 480;
format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
format.fmt.pix.field= V4L2_FIELD_NONE ;

ioctl(fd,VIDIOC_S_FMT,&format); //S:set


6.申请帧缓冲、内存映射、入队

(1)申请帧缓冲

读取摄像头数据的方式有两种:一种是 read 方式,直接通过 read()系统调用读取摄像头采集到的数据;另一种是 streaming 方式。使用 VIDIOC_QUERYCAP 指令查询设备的属性、得到一个 struct v4l2_capability 类型数据, 其中 capabilities 字段记录了设备拥有的能力,当该字段包含
V4L2_CAP_READWRITE 时,表示设备支持 read I/O 方式读取数据;当该字段包含V4L2_CAP_STREAMING时,表示设备支持 streaming I/O 方式;使用streaming I/O 方式,需要向设备申请帧缓冲,并将帧缓冲映射到应用程序进程地址空间中

 使用 VIDIOC_REQBUFS 指令可申请帧缓冲:

ioctl(int fd, VIDIOC_REQBUFS, struct v4l2_requestbuffers *reqbuf);

调用 ioctl()需要传入一个 struct v4l2_requestbuffers *指针, struct v4l2_requestbuffers 结构体描述了申请帧缓冲的信息, ioctl()会根据 reqbuf 所指对象填充的信息进行申请。
 

struct v4l2_requestbuffers {
	__u32			count;//缓冲区块数 ----- 4
	__u32			type;		/* enum v4l2_buf_type */
	__u32			memory;		/* enum v4l2_memory */
	__u32			reserved[2];
};

type 字段与前面所提及到的 type 字段意义相同,count 字段用于指定申请帧缓冲的数量
memory 字段可取值如下:

enum v4l2_memory {
    V4L2_MEMORY_MMAP = 1,
    V4L2_MEMORY_USERPTR = 2,
    V4L2_MEMORY_OVERLAY = 3,
    V4L2_MEMORY_DMABUF = 4,
};

通常将 memory 设置为 V4L2_MEMORY_MMAP
申请缓存:

struct v4l2_requestbuffers req = {};
req.count = 4;     //申请4个帧缓存
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;

ioctl(fd,VIDIOC_REQBUFS,&req);

streaming I/O 方式会在内核空间中维护一个帧缓冲队列, 驱动程序会将从摄像头读取的一帧数据写入到队列中的一个帧缓冲,接着将下一帧数据写入到队列中的下一个帧缓冲;当应用程序需要读取一帧数据时,需要从队列中取出一个装满一帧数据的帧缓冲,这个取出过程就叫做出队;当应用程序处理完这一帧数据后,需要再把这个帧缓冲加入到内核的帧缓冲队列中,这个过程叫做入队。

使用 VIDIOC_REQBUFS 指令申请帧缓冲, 该缓冲区是由内核所维护的,应用程序不能直接读取该缓冲区的数据,需要将其映射到用户空间中,应用程序读取映射区的数据实际上是读取内核维护的帧缓冲中的数据。

(2)内存映射

在映射之前,需要查询帧缓冲的信息:帧缓冲的长度、偏移量。使用VIDIOC_QUERYBUF指令查询:

ioctl(int fd, VIDIOC_QUERYBUF, struct v4l2_buffer *buf);

调用 ioctl()需要传入一个 struct v4l2_buffer *指针, struct v4l2_buffer 结构体描述了帧缓冲的信息, ioctl()会将获取到的数据写入到 buf 指针所指的对象中。

struct v4l2_buffer {
	__u32			index;//编号
	__u32			type;//V4L2_BUF_TYPE_VIDEO_CAPTURE
	__u32			bytesused;//使用的字节数
	__u32			flags;
	__u32			field;
	struct timeval		timestamp;
	struct v4l2_timecode	timecode;
	__u32			sequence;

	/* memory location */
	__u32			memory;//V4L2_MEMORY_MMAP
	union {
		__u32           offset;//偏移
		unsigned long   userptr;
		struct v4l2_plane *planes;
		__s32		fd;
	} m;
	__u32			length;//长度
	__u32			reserved2;
	__u32			reserved;
};


index 字段表示一个编号, 申请的多个帧缓冲、 每一个帧缓冲都有一个编号,从 0 开始。一次 ioctl()调用只能获取指定编号对应的帧缓冲的信息,所以要获取多个帧缓冲的信息,需要重复调用多次,每调用一次ioctl()、 index 加 1,指向下一个帧缓冲。

type 字段和memory 字段与前面介绍的一样。length 字段表示帧缓冲的长度,共同体中的 offset 表示帧缓冲的偏移量。因为应用程序通过 VIDIOC_REQBUFS 指令申请帧缓冲时,内核会向操作系统申请一块内存空间作为帧缓冲区,内存空间的大小就等于申请的帧缓冲数量 * 每一个帧缓冲的大小,每一个帧缓冲对应到这一块内存空间的某一段,所以它们都有一个地址偏移量。
 

申请帧缓冲后、调用 mmap()将帧缓冲映射到用户地址空间
 

struct v4l2_buffer buf = {};
buf.index = xxx;//0~3
but.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;

ioctl(fd,VIDIOC_QUERYBUF,&buf);
//映射到用户空间
mmap(NULL,buf.length,.......,fd,buf.m.offset);

(3)入队

使用 VIDIOC_QBUF 指令将帧缓冲放入到内核的帧缓冲队列中,调用 ioctl()之前,需要设置 struct v4l2_buffer 类型对象的 memory、 type 字段

ioctl(fd,VIDIOC_QBUF,&buf);

7.开启视频采集

使用 VIDIOC_DQBUF 指令开启视频采集,

ioctl(int fd, VIDIOC_STREAMON, int *type); //开启视频采集
ioctl(int fd, VIDIOC_STREAMOFF, int *type); //停止视频采集
enum v4l2_buf_type buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd,VIDIOC_STREAMON,&buf_type);

8.读取数据、对数据进行处理

开启视频采集之后,便可以去读取数据 ,直接读取每一个帧缓冲的在用户空间的映射区即可读取到摄像头采集的每一帧图像数据。在读取数据之前,需要将帧缓冲从内核的帧缓冲队列中取出,这个操作叫做帧缓冲出队。帧缓冲出队,便可读取数据,对数据进行处理:将摄像头采集的图像显示到 LCD屏上;数据处理完成之后,再将帧缓冲入队,往复操作。

使用 VIDIOC_DQBUF 指令执行出队操作

ioctl(int fd, VIDIOC_DQBUF, struct v4l2_buffer *buf);
while(1){
    //从采集队列中取出一帧
    ioctl(fd,VIDIOC_DQBUF,&buf);
    //将该帧的数据拷贝走
    memcpy(....);
    //将取出的一帧放回队列
    ioctl(fd,VIDIOC_QBUF,&buf);
    
    //显示,存储......
}

9.结束视频采集

结束视频采集,使用 VIDIOC_STREAMOFF 指令

enum v4l2_buf_type buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd,VIDIOC_STREAMOFF,&buf_type);

//解除映射
//关闭设备文件

三、应用编程

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <sys/mman.h>
#include <string.h>

typedef struct{
	char *start;
	size_t length;
}buffer_t;

buffer_t buffer[4];
buffer_t current;//保存当前取出的一帧

int lcd_fd;
int *memp;

unsigned int sign3 = 0;

int yuyv2rgb(int y, int u, int v)
{
     unsigned int pixel24 = 0;
     unsigned char *pixel = (unsigned char *)&pixel24;
     int r, g, b;
     static int  ruv, guv, buv;

     if(sign3)
     {
         sign3 = 0;
         ruv = 1159*(v-128);
         guv = 380*(u-128) + 813*(v-128);
         buv = 2018*(u-128);
     }

     r = (1164*(y-16) + ruv) / 1000;
     g = (1164*(y-16) - guv) / 1000;
     b = (1164*(y-16) + buv) / 1000;

     if(r > 255) r = 255;
     if(g > 255) g = 255;
     if(b > 255) b = 255;
     if(r < 0) r = 0;
     if(g < 0) g = 0;
     if(b < 0) b = 0;

     pixel[0] = r;
     pixel[1] = g;
     pixel[2] = b;

     return pixel24;
}


int yuyv2rgb0(unsigned char *yuv, unsigned char *rgb, unsigned int width, unsigned int height)
{
     unsigned int in, out;
     int y0, u, y1, v;
     unsigned int pixel24;
     unsigned char *pixel = (unsigned char *)&pixel24;
     unsigned int size = width*height*2;

     for(in = 0, out = 0; in < size; in += 4, out += 6)
     {
          y0 = yuv[in+0];
          u  = yuv[in+1];
          y1 = yuv[in+2];
          v  = yuv[in+3];

          sign3 = 1;
          pixel24 = yuyv2rgb(y0, u, v);
          rgb[out+0] = pixel[0];    
          rgb[out+1] = pixel[1];
          rgb[out+2] = pixel[2];

          pixel24 = yuyv2rgb(y1, u, v);
          rgb[out+3] = pixel[0];
          rgb[out+4] = pixel[1];
          rgb[out+5] = pixel[2];

     }
     return 0;
}

//LCD初始化
void lcd_init()
{
	lcd_fd = open("/dev/fb0",O_RDWR);
	if(lcd_fd==-1){
		perror("open");;
		exit(-1);
	}
	
	//映射
	memp = mmap(NULL,800*480*4,PROT_READ|PROT_WRITE,MAP_SHARED,lcd_fd,0);
	if(memp==MAP_FAILED){
		perror("mmap");;
		exit(-1);
	}
}

void lcd_uninit()
{
	munmap(memp,800*480*4);
	close(lcd_fd);
}

int main()
{
	lcd_init();
	
	//1.打开摄像头
	int fd = open("/dev/video7",O_RDWR);
	if(fd==-1){
		perror("open");
		exit(-1);
	}
	
	//2.获取功能参数
	struct v4l2_capability cap = {};
	int res = ioctl(fd,VIDIOC_QUERYCAP,&cap);
	if(res==-1){
		perror("ioctl cap");
		exit(-1);
	}
	
	if(cap.capabilities&V4L2_CAP_VIDEO_CAPTURE){
		//设备是一个摄像头
		printf("capture device!\n");
	}
	else{
		printf("not a capture device!\n");
		exit(-1);
	}
	
	//3.获取摄像头支持的格式
	struct v4l2_fmtdesc fmt = {};
	fmt.index = 0;//第一种格式
	fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//获取摄像头的格式
	
	while((res=ioctl(fd,VIDIOC_ENUM_FMT,&fmt))==0){
		printf("pixformat=%c%c%c%c,description=%s\n",fmt.pixelformat&0xff,(fmt.pixelformat>>8)&0xff,
			   (fmt.pixelformat>>16)&0xff,(fmt.pixelformat>>24)&0xff,fmt.description);
		fmt.index++;
	}
	
	//4.设置采集通道
	int index = 0;//使用通道0
	res = ioctl(fd,VIDIOC_S_INPUT,&index);
	if(res==-1){
		perror("ioctl s_input");
		exit(-1);
	}
	
	//5.设置摄像头的采集格式
	struct v4l2_format format = {};
	format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	format.fmt.pix.width = 640;
	format.fmt.pix.height = 480;
	format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;//YUYV格式
	format.fmt.pix.field= V4L2_FIELD_NONE;
	res = ioctl(fd,VIDIOC_S_FMT,&format);
	if(res==-1){
		perror("ioctl s_fmt");
		exit(-1);
	}
	
	//6.申请缓存空间
	struct v4l2_requestbuffers req = {};
	req.count = 4;
	req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	req.memory = V4L2_MEMORY_MMAP;
	res = ioctl(fd,VIDIOC_REQBUFS,&req);
	if(res==-1){
		perror("ioctl reqbufs");
		exit(-1);
	}
	
	//7.分配,映射,入队
	size_t i,max_len = 0;
	for(i=0;i<4;i++){
		struct v4l2_buffer buf = {};
		buf.index = i;//0~3
		buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		buf.memory = V4L2_MEMORY_MMAP;
		res = ioctl(fd,VIDIOC_QUERYBUF,&buf);
		if(res==-1){
			perror("ioctl querybuf");
			exit(-1);
		}
		
		//记录最大长度
		if(buf.length>max_len)
			max_len = buf.length;
		
		//映射
		buffer[i].length = buf.length;
		buffer[i].start = mmap(NULL,buf.length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,buf.m.offset);
		if(buffer[i].start==MAP_FAILED){
			perror("mmap");
			exit(-1);
		}
		
		//入队
		res = ioctl(fd,VIDIOC_QBUF,&buf);
		if(res==-1){
			perror("ioctl qbuf");
			exit(-1);
		}
	}
	
	//申请临时缓冲区
	current.start = malloc(max_len);
	if(current.start==NULL){
		perror("malloc");
		exit(-1);
	}
	
	//8.启动摄像头
	enum v4l2_buf_type buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	res = ioctl(fd,VIDIOC_STREAMON,&buf_type);
	if(res==-1){
			perror("ioctl streamon");
			exit(-1);
	}
	
	//延时
	sleep(1);
	
	//RGB缓冲区
	char rgb[640*480*3];
	//9.采集数据
	while(1){
		struct v4l2_buffer buf = {};
		buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		buf.memory = V4L2_MEMORY_MMAP;
		//出队
		res = ioctl(fd,VIDIOC_DQBUF,&buf);
		if(res==-1){
			perror("ioctl dqbuf");
		}
		
		//拷贝数据
		memcpy(current.start,buffer[buf.index].start,buf.bytesused);
		current.length = buf.bytesused;
		
		//入队
		res = ioctl(fd,VIDIOC_QBUF,&buf);
		if(res==-1){
			perror("ioctl qbuf");
		}
		
		//显示 保存 传输.....
		//YUYV转RGB
		yuyv2rgb0(current.start,rgb,640,480);
		//显示到LCD
		int x,y;
		for(y=0;y<480;y++){
			for(x=0;x<640;x++){
				*(memp+y*800+x) = rgb[3*(y*640+x)]<<16 | rgb[3*(y*640+x)+1]<<8 | rgb[3*(y*640+x)+2];
			}
		}
	}
	
	//10.关闭摄像头采集
	buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	res = ioctl(fd,VIDIOC_STREAMOFF,&buf_type);
	if(res==-1){
			perror("ioctl streamoff");
			exit(-1);
	}
	
	//解除映射
	for(i=0;i<4;i++){
		munmap(buffer[i].start,buffer[i].length);
	}
	free(current.start);
	
	close(fd);
	lcd_uninit();
	return 0;
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux之V4L2驱动框架 的相关文章

  • 域套接字“sendto”遇到“errno 111,连接被拒绝”

    我正在使用域套接字从另一个进程获取值 就像 A 从 B 获取值一样 它可以运行几个月 但最近 A 向 B 发送消息时偶尔会失败 出现 errno 111 连接被拒绝 我检查了B域套接字绑定文件 它是存在的 我也在另一台机器上做了一些测试 效
  • Android 时钟滴答数 [赫兹]

    关于 proc pid stat 中应用程序的总 CPU 使用率 https stackoverflow com questions 16726779 total cpu usage of an application from proc
  • 如何在数组中存储包含双引号的命令参数?

    我有一个 Bash 脚本 它生成 存储和修改数组中的值 这些值稍后用作命令的参数 对于 MCVE 我想到了任意命令bash c echo 0 0 echo 1 1 这解释了我的问题 我将用两个参数调用我的命令 option1 without
  • 所有平台上的java

    如果您想用 java 为 Windows Mac 和 Linux 编写桌面应用程序 那么所有这些代码都相同吗 您只需更改 GUI 即可使 Windows 应用程序更像 Windows 等等 如果不深入细节 它是如何工作的 Java 的卖点之
  • 如何使用GDB修改内存内容?

    我知道我们可以使用几个命令来访问和读取内存 例如 print p x 但是如何更改任何特定位置的内存内容 在 GDB 中调试时 最简单的是设置程序变量 参见GDB 分配 http sourceware org gdb current onl
  • linux perf:如何解释和查找热点

    我尝试了linux perf https perf wiki kernel org index php Main Page今天很实用 但在解释其结果时遇到了困难 我习惯了 valgrind 的 callgrind 这当然是与基于采样的 pe
  • 添加要在给定命令中运行的 .env 变量

    我有一个 env 文件 其中包含如下变量 HELLO world SOMETHING nothing 前几天我发现了这个很棒的脚本 它将这些变量放入当前会话中 所以当我运行这样的东西时 cat env grep v xargs node t
  • arm64和armhf有什么区别?

    Raspberry Pi Type 3 具有 64 位 CPU 但其架构不是arm64 but armhf 有什么区别arm64 and armhf armhf代表 arm hard float 是给定的名称Debian 端口 https
  • 如何在apache 2.4.6上安装apxs模块

    我刚刚用过apt get update我的 apache 已更新为2 4 6 我想安装 apxs 来编译模块 但收到此错误 The following packages have unmet dependencies apache2 pre
  • Linux 中什么处理 ping?

    我想覆盖 更改 linux 处理 ping icmp echo 请求数据包的方式 这意味着我想运行自己的服务器来回复传入的 icmp 回显请求或其他 数据包 但为了使其正常工作 我想我需要禁用 Linux 的默认 ping icmp 数据包
  • 为什么我可以直接从 bash 执行 JAR?

    我是一个长期从事 Java 工作的人 并且知道运行带有主类的 JAR 的方法MANIFEST MFJar 中的文件很简单 java jar theJar jar 我用它来启动 Fabric3 服务器 包含在bin server jar在其标
  • 为arm构建WebRTC

    我想为我的带有arm926ej s处理器的小机器构建webrtc 安装 depot tools 后 我执行了以下步骤 gclient config http webrtc googlecode com svn trunk gclient s
  • 多处理:仅使用物理核心?

    我有一个函数foo它消耗大量内存 我想并行运行多个实例 假设我有一个有 4 个物理核心的 CPU 每个核心有两个逻辑核心 我的系统有足够的内存来容纳 4 个实例foo并行但不是 8 个 此外 由于这 8 个核心中的 4 个是逻辑核心 我也不
  • 如何使用 GOPATH 的 Samba 服务器位置?

    我正在尝试将 GOPATH 设置为共享网络文件夹 当我进入 export GOPATH smb path to shared folder I get go GOPATH entry is relative must be absolute
  • 内核模式下的线程(和进程)与用户模式下的线程(和进程)有什么区别?

    我的问题 1 书中现代操作系统 它说线程和进程可以处于内核模式或用户模式 但没有明确说明它们之间有什么区别 2 为什么内核态线程和进程的切换比用户态线程和进程的切换花费更多 3 现在 我正在学习Linux 我想知道如何在LINUX系统中分别
  • 如何在 Bash 中给定超时后终止子进程?

    我有一个 bash 脚本 它启动一个子进程 该进程时不时地崩溃 实际上是挂起 而且没有明显的原因 闭源 所以我对此无能为力 因此 我希望能够在给定的时间内启动此进程 如果在给定的时间内没有成功返回 则将其终止 有没有simple and r
  • Linux 上的用户空间能否实现本机代码的抢占式多任务处理?

    我想知道是否可以在 Linux 用户空间的单个进程中实现本机代码的抢占式多任务处理 也就是说 从外部暂停一些正在运行的本机代码 保存上下文 交换到不同的上下文 然后恢复执行 所有这些都由用户空间精心安排 但使用可能进入内核的调用 我认为这可
  • 如何在基于 Linux 的系统上的 C 程序中使用 mqueue?

    如何在基于 Linux 的系统上的 C 程序中使用 mqueue 消息队列 我正在寻找一些好的代码示例 可以展示如何以正确且正确的方式完成此操作 也许是一个操作指南 下面是一个服务器的简单示例 该服务器接收来自客户端的消息 直到收到告诉其停
  • php exec 返回的结果比直接进入命令行要少

    我有一个 exec 命令 它的行为与通过 Penguinet 给 linux 的相同命令不同 res exec cd mnt mydirectory zcat log file gz echo res 当将命令直接放入命令行时 我在日志文件
  • 在两次之间每分钟执行一次 Cronjob

    我需要在 crontab 中每分钟运行一个 bash 脚本8 45am and 9 50am每天的 Code 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 8 home pull sh gt ho

随机推荐

  • FreeRTOS-ARM架构、TCB结构体,调度机制

    ARM架构 对于ARM架构来说 xff0c 主要有3部分构成 xff1a CPU xff0c RAM xff0c FLASH CPU内部主要是运算单元和寄存器单元 xff0c 可以读写RAM xff0c 修改内存 xff0c 也可以读取FL
  • FreeRTOS--队列

    在讲队列前 xff0c 先思考一下这个问题 xff1a 下面这个程序 xff0c 如果用RTOS实现会出问题吗 xff1f c span class token punctuation span span class token keywo
  • 新手村——如何改变CSDN代码块背景样式

    1 在电脑上登录自己CSDN账号 xff1b 2 按如下指示操作 xff1a 鼠标滑动至头像处 xff0c 点击下方的内容管理 xff1b 3 在左方中一直下滑 xff0c 找到设置中的博客设置 xff1b 4 在代码样式片中选择自己喜欢的
  • antd的Form实例控制表格,表格可收集/回显数据

    大概就是如图的效果 xff0c 但是需要控制数据的收集和回显 首先看到这个页面 xff0c 第一眼就想到table表格 但是我翻阅了下antd的Table组件 xff0c 发现只能展示数据 也有可编辑单元格 但是我需要这里的表格 xff0c
  • 网络字节序、大小端模式

    多字节数据的存储顺序称为字节序 分为大 小端模式 xff1a 大端 xff1a 数据高位对应低地址 小端 xff1a 数据高位对应高地址 可以通过下面的程序 测试自己的机器是大端字节序还是小端字节序 用共用体的方式来测试 xff1a spa
  • C++运算符重载详解

    目录 什么是运算符重载 运算符重载的意义 运算符重载的语法格式 简单例子 43 运算符的重载 代码分析 运算符重载的两种方式 1 重载为类的成员函数 1 双目运算符 2 单目运算符 2 重载为类的友元函数 1 重载格式 2 调用格式 两种重
  • 微信登录老是间歇式失败

    在微信小程的开发中 xff0c 登录问题 xff0c 一定要按照这样的顺序 1 小程序请求login xff0c 拿到code 然后传给服务端 xff1b 2 服务端拿到code 到微信服务器拿到sessionKey xff1b 3 然后小
  • Python爬虫编程7——多线程爬虫

    目录 一 多线程基本介绍 程序中模拟多任务 二 多线程的创建 三 主线程与子线程的执行关系 四 查看线程数量 五 线程间的通信 xff08 多线程共享全局变量 xff09 六 线程间的资源竞争 互斥锁和死锁 互斥锁 死锁 七 Queue线程
  • 嵌入式蓝桥杯客观题总结(4.19)

    1 DMA xff08 1 xff09 DMA是否需要经过MCU访问内存 xff1f xff08 2 xff09 在STM32处理器中一个DMA请求 xff0c 至少占用 xff08 xff09 个周期的CPU访问系统总线时间 A 1 B
  • PixHawk 4用作四旋翼DIY调试部分笔记

    PixHawk 4用作四旋翼DIY调试部分笔记 序所使用的设备器件问题整理短暂结语 序 这是用于记录DIY组装的基于PixHawk 4的四旋翼时所遇到的部分有趣的而又比较耗时才能查找或者仍未能查找到解决方案 xff0c 自行针对当下所处环境
  • CentOS虚拟机可以ping通主机,但是主机ping虚拟机请求超时解决办法

    我用ssh连不上虚拟机 xff0c 找了好多博客 xff0c 试了好多方法 xff0c ip都背得了 xff0c 方法也都差不多千篇一律 xff0c 还是没能解决 xff0c 卡了我好几个小时 xff0c 最后在一次无意的尝试中解决了 xf
  • 麦克纳姆轮PID控制原理

    目录 前言 一 什么是麦克纳姆轮 二 运动原理 三 pid控制 一 什么是pid xff1f 二 什么是串级PID xff1f 三 麦克娜姆轮的控制思想 总结 前言 目前很多大学生比赛里面经常都会出现麦克娜姆轮 xff0c 并且麦克娜姆轮在
  • Matlab在线运行网址

    下载正版matlab麻烦 xff0c 耗内存 xff0c 在线的matlab运行器不香吗 xff1f 在线运行网址 打开后是这样的一个界面 然后我们把左边框的内容换成自己的代码之后点击Execute运行就可以在右边显示代码的运行结果了
  • 《手把手教你学嵌入式无人机》——入门航模遥控器使用(MC6C)

    一 MC6C入门航模遥控器简介 六通道MC6C迈克遥控器是普遍使用的一款入门航模遥控器 xff0c 价格较为低廉 xff0c 同时性能比较稳定 xff0c 性价比较高 遥控器与接收机 1 基本参数 xff1a 遥控器 xff1a 遥控范围
  • (一)Jetson Agx Xavier 装系统Ubuntu

    买不到nvidia Agx Xavier 所以用的是rtimes代替 目前是暑假打算做一辆无人车玩玩 拿到手后和树莓派 TX2感觉很像 xff0c 可以直接插鼠标键盘 显示器网线等还是很方便 虽然不是原厂 xff0c 看手册应该是 nvid
  • (三)Jetson Agx Xavier 串口使用

    暂时没有需要串口的地方 xff0c 不过先写在这备用 xff0c 主要就是配置一下 dev ttyTHS1这里的串口 xff0c 波特率 xff0c 奇偶校验这些 xff0c 比较简单 xff0c 和正常的电脑配置串口差不多 uart py
  • Ubuntu18.04实现ROS安装及小海龟实例

    目录 一 ROS简介1 含义2 发展 二 ROS安装1 添加软件源2 添加密钥3 安装ROS4 初始化rosdep5 设置环境变量 三 小海龟实例1 运行小海龟2 话题器查看节点信息3 控制量移动海龟4 C 43 43 程序编写小海龟圆形
  • iviewadmin本地调试线上接口

    修改vue config js文件 xff1a devServer proxy 39 adminapi 39 target 39 这里写自己的域名 api 39 changeOrigin true pathRewrite function
  • ESP32之FreeRTOS--任务的创建和运行

    文章目录 前言一 创建任务和删除函数1 xTaskCreate 2 xTaskCreateStatic 3 xTaskCreateRestricted 4 vTaskDelete 二 任务函数和任务控制块TCB1 任务函数模板2 TCB 三
  • Linux之V4L2驱动框架

    目录 一 V4L2简介 二 V4L2操作流程 1 打开摄像头 2 查询设备的属性 能力 功能 3 获取摄像头支持的格式 4 设置摄像头的采集通道 5 设置 获取摄像头的采集格式和参数 6 申请帧缓冲 内存映射 入队 xff08 1 xff0