[linux] v4l2 application programming (1) picture (jpg) collection operation process

insert image description here

1. Turn on the device

#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;
}

Return a file descriptor.

2. Obtain/configure device support format

2.1 Main functions

To realize the function, the most important thing is the following function:

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

2.2 unsinged long request

How to view ioctl function parameters:

  • https://v4l.videotechnology.com/dwg/v4l2.html
  • /usr/include/linux/videodev2.h
key macro Function corresponding structure
VIDIOC_QUERYCAP Function query supported by video equipment struct v4l2_capability (outgoing parameters)
VIDIOC_ENUM_FMT View the picture formats supported by the usb camera struct v4l2_fmtdesc (incoming and outgoing parameters)
VIDIOC_G_FMT Get image format (image height and width, pixel format, etc.) struct v4l2_format (incoming and outgoing parameters)
VIDIOC_S_FMT set image format struct v4l2_format (incoming and outgoing parameters)

Corresponding to the specific content of the structure

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 Code implementation

#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. Apply for kernel buffer queue

The cache queue is used in the kernel to manage image data. There are two ways for user space to obtain image data. One is to read the cache of the kernel space through read and write (multiple reads and writes are slow), and the other is to use the kernel space The cache is mapped to user space.

key macro Function corresponding structure
VIEWER_REQBUFS Initiate Memory Mapping or User Pointer I/O struct v4l2_requestbuffers (incoming parameters)
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. Map queue space to user space

Four buffers were previously configured, and one buffer is queried in turn for mapping.

memory map

A section of memory area in user space is mapped to kernel space. After the mapping is successful, the modification of this memory area by the user can be directly reflected in the kernel space. Similarly, the modification of this area in kernel space is also directly reflected in user space.

The mmap function is a system call under unix/linux. The mmap system call enables processes to share memory by mapping the same ordinary file. mmap does not allocate space, but only maps the file to the address space of the calling process (but it will take up your virtual memory), and then you can use memcpy and other operations to write files instead of write(). After writing, The content in memory will not be updated to the file immediately, but there is a delay for a period of time. You can call msync() to explicitly synchronize, so that the content you write can be saved to the file immediately. This point Should be related to the driver. However, there is no way to increase the length of the file by writing the file through mmap, because the length to be mapped is determined when mmap() is called. If you want to cancel the memory mapping, you can call munmap() to cancel the memory mapping.

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

  • start: The starting address of the memory area to be mapped to, usually NULL (NULL is 0). NULL means that the memory address is specified by the kernel

  • length: the size of the memory area to be mapped

  • prot: The permission of the memory mapping area. Desired memory protection flags, must not conflict with the file's open mode. Is one of the following values, which can be reasonably combined through the OR operation

    PROT_EXEC //page content can be executed

    PROT_READ //page content can be read

    PROT_WRITE //page can be written

    PROT_NONE //page not accessible

  • flags: specifies the type of the mapped object, and whether the modification on the memory is reflected on the disk. Whether mapped options and mapped pages can be shared. Its value can be a combination of one or more of the following bits.

    MAP_SHARED : The data written to the mapped area will be copied back to the file and allowed to be shared by other processes that map the file.

    MAP_PRIVATE: Modifications made in the mapping area will not be reflected on the physical device.

  • fd: file descriptor (returned by the open function)

  • offset: The offset of the mapped file (an integer multiple of 4k, 4096 bytes, the size of a Linux page), indicating where the mapped object (that is, the file) starts mapping, usually using 0. The value should be an integer multiple of PAGE_SIZE

Return description: When successfully executed, mmap() returns the first address of the mapped area. On failure, mmap() returns MAP_FAILED [its value is (void *)-1]

Buy one get one free: munmap(void* addr, size_t length)close the release mapping area

  • addr: the pointer corresponding to the return value of mmap
  • length: corresponds to the length of mmap

Application of mmap function: parent-child process communication; anonymous mapping; communication between processes without blood relationship

Mapping implementation

key macro Function corresponding structure
VIDIOC_QUERYBUF Query the status of a buffer struct v4l2_buffer (incoming and outgoing)
VIDIOC_QBUF Exchange a buffer with the driver struct v4l2_buffer (incoming)
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. Data Acquisition

This section contains start collection, collection data, stop collection

key macro Function corresponding structure
VIDIOC_STREAMON Start collecting, write data to the queue const int *
VIDIOC_DQBUF Tell the kernel that I want a certain data, and the kernel cannot modify it struct v4l2_buffer
VIDIOC_QBUF tell the kernel that I'm done using struct v4l2_buffer
VIDIOC_STREAMOFF Stop collecting and no longer write data to the queue 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. Release mapping

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

7. Turn off the device

close(fd);

reference link

V4L2 capture video display

Guess you like

Origin blog.csdn.net/weixin_42442319/article/details/126052294
Recommended