【linux】v4l2应用编程(一)图片(jpg)采集操作流程

在这里插入图片描述

1. 打开设备

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
    // 1. 打开设备
    int fd = open("/dev/video0", O_RDWR);
    if (fd < 0) {
        perror("failed open");
        return -1;
    }
	// 2. 关闭设备    
    close(fd);
    return 0;
}

返回一个文件描述符。

2. 获取/配置设备支持格式

2.1 主要函数

要实现功能,最重要的是以下这个函数:

int ioctl(int fd, unsinged long request, ...);
// 即ioctl(文件描述符,操作命令,与命令的对应的结构体)
// 查看方式man ioctl

2.2 unsinged long request

ioctl函数参数的查看方式:

  • https://v4l.videotechnology.com/dwg/v4l2.html
  • /usr/include/linux/videodev2.h
关键宏 功能 对应的结构体
VIDIOC_QUERYCAP 视频设备支持的功能查询 struct v4l2_capability(传出参数)
VIDIOC_ENUM_FMT 查看usb摄像头支持的图片格式 struct v4l2_fmtdesc (传入传出参数)
VIDIOC_G_FMT 获取图片格式(图像高宽、像素格式等) struct v4l2_format(传入传出参数)
VIDIOC_S_FMT 设置图像格式 struct v4l2_format(传入传出参数)

对应结构体具体内容

struct v4l2_capability {
	__u8	driver[16];	 /* i.e. "bttv" */
	__u8	card[32];	 /* i.e. "Hauppauge WinTV" */
	__u8	bus_info[32];	/* "PCI:" + pci_name(pci_dev) */
	__u32   version;        /* should use KERNEL_VERSION() */
	__u32	capabilities;	/* Device capabilities */
	__u32	reserved[4];
};
struct v4l2_fmtdesc {
        __u32               index;             /* Format number        设置好index*/
        __u32               type;              /* enum v4l2_buf_type, 设置好类型 */
        __u32               flags;
        __u8                description[32];   /* Description string */
        __u32               pixelformat;       /* Format fourcc      */
        __u32               reserved[4];
};
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,
        V4L2_BUF_TYPE_META_OUTPUT          = 14,
        /* Deprecated, do not use */
        V4L2_BUF_TYPE_PRIVATE              = 0x80,
};
struct v4l2_format {
        __u32    type;   // 设置好类型
        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 */
                struct v4l2_meta_format         meta;    /* V4L2_BUF_TYPE_META_CAPTURE */
                __u8    raw_data[200];                   /* user-defined */
        } fmt;
};

2.3 代码具体实现

#include <sys/fcntl.h> 
#include <sys/stat.h>
#include <sys/ioctl.h>      
#include <unistd.h>     
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#include <linux/videodev2.h>

int v4l2_check_cap(int fd) {
    struct v4l2_capability cap = {};
    if (-1 == ioctl(fd, VIDIOC_QUERYCAP, &cap)) {
        printf("this device unable to query capability.");
        return -1;
    }
    // 查询是否是视频设备
    if (0 == cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) {
        printf("this device does not support capture.\n");
        return -1;
    }
    // 查询是否支持IO数据流
    if (0 == cap.capabilities & V4L2_CAP_STREAMING) {
        printf("this device does not support I/O stream.\n");
        return -1;
    }
    return 0;
}
void v4l2_enum_fmt(int fd) {
    struct v4l2_fmtdesc fmtd = {};
    fmtd.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmtd.index = 0;	// 取第几个格式
    while (0 == ioctl(fd, VIDIOC_ENUM_FMT, &fmtd)) {
        printf("pixel format: %s \n", fmtd.description);
        fmtd.index ++;
    }
}
int v4l2_get_format(int fd) {
    struct v4l2_format fmt = {};
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (-1 == ioctl(fd, VIDIOC_G_FMT, &fmt)) {
        printf("cannot get pixel format");
        return -1;
    }
    printf("format: %u x %u, pixeformat:%c%c%c%c\n",
            fmt.fmt.pix.width, fmt.fmt.pix.height,
            fmt.fmt.pix.pixelformat & 0xFF, (fmt.fmt.pix.pixelformat >> 8) & 0xFF,
            (fmt.fmt.pix.pixelformat >> 16) & 0xFF, (fmt.fmt.pix.pixelformat >> 24) & 0xFF);
    
}

int v4l2_set_format(int fd) {
    struct v4l2_format fmt = {};
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = 320;
    fmt.fmt.pix.height = 240;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;

    if (-1 == ioctl(fd, VIDIOC_S_FMT, &fmt)) {
        printf("cannot get pixel format");
        return -1;
    }
    return 0;
}

int main () {
    int fd, rval;
    fd = open("/dev/video0", O_RDWR);
    if (fd == -1) {
        perror("cannot open device.");
        exit(1);
    }
    // 1. 视频设备支持的功能查询
    rval = v4l2_check_cap(fd);
    if (rval == -1) {
        close(fd);
        return rval;
    }
    
    // 2. 查看usb摄像头支持的图片格式
    v4l2_enum_fmt(fd);

    // 3. 获取、设置摄像头的图像格式
    // 获取图片格式
    rval = v4l2_get_format(fd);
    if (rval < 0) {
        close(fd);
        return rval;
    }
    // 设置图像格式
    rval = v4l2_set_format(fd);
    if (rval < 0) {
        close(fd);
        return rval;
    }
    v4l2_get_format(fd);
    close(fd);
    return 0;
}

3. 申请内核缓冲区队列

内核中使用缓存队列对图像数据进行管理,用户空间获取图像数据有两种方式,一种是通过read、write方式读取内核空间的缓存(多次读写速度慢),一种是将内核空间的缓存映射到用户空间。

关键宏 功能 对应的结构体
VIDIOC_REQBUFS Initiate Memory Mapping or User Pointer I/O struct v4l2_requestbuffers(传入参数)
struct v4l2_requestbuffers {
        __u32                   count;			// 设置缓冲区的数量						
        __u32                   type;           /* enum v4l2_buf_type */ // 设置类型
        __u32                   memory;         /* enum v4l2_memory */   //设置映射方式
        __u32                   capabilities;
        __u32                   reserved[1];
};
int v4l2_request_kernel_buffer(int fd) {
	struct v4l2_requestbuffers reqbuffer;
	reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	reqbuffer.count = 4; //申请4个缓冲区
	reqbuffer.memory = V4L2_MEMORY_MMAP ;//映射方式
	ret  = ioctl(fd, VIDIOC_REQBUFS, &reqbuffer);
	if(ret < 0)
	{
		perror("申请队列空间失败");
        return -1;
	}
	return 0;
}

4. 映射队列空间到用户空间

先前配置了四个缓冲区,依次查询出一个缓冲区进行映射。

内存映射

用户空间的一段内存区域映射到内核空间,映射成功后,用户对这段内存区域的修改可以直接反映到内核空间,同样,内核空间对这段区域的修改也直接反映用户空间。

mmap函数是unix/linux下的系统调用,mmap系统调用使得进程之间通过映射同一个普通文件实现共享内存。mmap并不分配空间, 只是将文件映射到调用进程的地址空间里(但是会占掉你的 virutal memory), 然后你就可以用memcpy等操作写文件, 而不用write()了.写完后,内存中的内容并不会立即更新到文件中,而是有一段时间的延迟,你可以调用msync()来显式同步一下, 这样你所写的内容就能立即保存到文件里了.这点应该和驱动相关。 不过通过mmap来写文件这种方式没办法增加文件的长度, 因为要映射的长度在调用mmap()的时候就决定了.如果想取消内存映射,可以调用munmap()来取消内存映射。

扫描二维码关注公众号,回复: 14996398 查看本文章

void * mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset)

  • start:要映射到的内存区域的起始地址,通常都是用NULL(NULL即为0)。NULL表示由内核来指定该内存地址

  • length:要映射的内存区域的大小

  • prot:内存映射区的权限。期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起

    PROT_EXEC //页内容可以被执行

    PROT_READ //页内容可以被读取

    PROT_WRITE //页可以被写入

    PROT_NONE //页不可访问

  • flags:指定映射对象的类型,内存上的修改是否反映到磁盘上。映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体。

    MAP_SHARED :对映射区域的写入数据会复制回文件内, 而且允许其他映射该文件的进程共享。

    MAP_PRIVATE:映射区做的修改不会反映到物理设备。

  • fd:文件描述符(由open函数返回)

  • offset:映射文件的偏移(4k的整数倍, 4096字节,linux一页的大小),表示被映射对象(即文件)从那里开始对映,通常都是用0。 该值应该为大小为PAGE_SIZE的整数倍

返回说明:成功执行时,mmap()返回被映射区的首地址。失败时,mmap()返回MAP_FAILED[其值为(void *)-1]

买一送一:munmap(void* addr, size_t length)关闭释放映射区

  • addr:mmap返回值对应的指针
  • length:与mmap的length对应

mmap函数的应用:父子进程通信;匿名映射;无血缘关系进程间通信

映射实现

关键宏 功能 对应的结构体
VIDIOC_QUERYBUF Query the status of a buffer struct v4l2_buffer(传入传出)
VIDIOC_QBUF Exchange a buffer with the driver struct v4l2_buffer(传入)
struct v4l2_buffer {
        __u32                   index;	// 设置好索引
        __u32                   type;   // 设置好类型
        __u32                   bytesused;
        __u32                   flags;
        __u32                   field;
        struct timeval          timestamp;
        struct v4l2_timecode    timecode;
        __u32                   sequence;

        /* memory location */
        __u32                   memory;
        union {
                __u32           offset;
                unsigned long   userptr;
                struct v4l2_plane *planes;
                __s32           fd;
        } m;
        __u32                   length;
        __u32                   reserved2;
        union {
                __s32           request_fd;
                __u32           reserved;
        };
};

	unsigned char *mptr[4];//保存映射后用户空间的首地址,之后通过该指针取数据即可
    unsigned int  size[4];
	struct v4l2_buffer mapbuffer;
	//初始化type, index
	mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	for(int i=0; i<4; i++)
	{
		mapbuffer.index = i;
		ret = ioctl(fd, VIDIOC_QUERYBUF, &mapbuffer);//从内核空间中查询一个空间做映射
		if(ret < 0)
		{
			perror("查询内核空间队列失败");
		}
		mptr[i] = (unsigned char *)mmap(NULL, mapbuffer.length, PROT_READ|PROT_WRITE, 
                                            MAP_SHARED, fd, mapbuffer.m.offset);
        size[i]=mapbuffer.length;

		//通知使用完毕--‘放回去’
		ret  = ioctl(fd, VIDIOC_QBUF, &mapbuffer);
		if(ret < 0)
		{
			perror("放回失败");
		}
	}

5. 数据采集

此部分包含开始采集、采集数据、停止采集

关键宏 功能 对应的结构体
VIDIOC_STREAMON 开始采集,写数据到队列 const int *
VIDIOC_DQBUF 告诉内核我要某一数据,内核不可以修改 struct v4l2_buffer
VIDIOC_QBUF 告诉内核我已经使用完毕 struct v4l2_buffer
VIDIOC_STREAMOFF 停止采集,不再向队列中写数据 const int *
// 开始采集
	int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	ret = ioctl(fd, VIDIOC_STREAMON, &type);
	if(ret < 0)
	{
		perror("开启失败");
	}
//从队列中提取一帧数据
	struct v4l2_buffer  readbuffer;
	readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	ret = ioctl(fd, VIDIOC_DQBUF, &readbuffer);
	if(ret < 0)
	{
		perror("提取数据失败");
	}

	FILE *file=fopen("my.jpg", "w+");
	fwrite(mptr[readbuffer.index], readbuffer.length, 1, file);
	fclose(file);
	
	//通知内核已经使用完毕
	ret = ioctl(fd, VIDIOC_QBUF, &readbuffer);
	if(ret < 0)
	{
		perror("放回队列失败");
	}
// 停止采集	
	ret = ioctl(fd, VIDIOC_STREAMOFF, &type);

6. 释放映射

for(int i=0; i<4; i++)
	munmap(mptr[i], size[i]);

7. 关闭设备

close(fd);

参考链接

V4L2采集视频显示

猜你喜欢

转载自blog.csdn.net/weixin_42442319/article/details/126052294