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


    在这里插入图片描述

    1. 打开设备

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    int main() {
        // 1. 打开设备
        int fd = open("/dev/video0", O_RDWR);
        if (fd < 0) {
            perror("failed open");
            return -1;
        }
    	// 2. 关闭设备    
        close(fd);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    返回一个文件描述符。

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

    2.1 主要函数

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

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

    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];
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    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];
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    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,
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    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;
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2.3 代码具体实现

    #include  
    #include 
    #include       
    #include      
    #include 
    #include 
    #include 
    #include 
    
    #include 
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100

    3. 申请内核缓冲区队列

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

    关键宏功能对应的结构体
    VIDIOC_REQBUFSInitiate Memory Mapping or User Pointer I/Ostruct v4l2_requestbuffers(传入参数)
    struct v4l2_requestbuffers {
            __u32                   count;			// 设置缓冲区的数量						
            __u32                   type;           /* enum v4l2_buf_type */ // 设置类型
            __u32                   memory;         /* enum v4l2_memory */   //设置映射方式
            __u32                   capabilities;
            __u32                   reserved[1];
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

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

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

    内存映射

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

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

    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_QUERYBUFQuery the status of a bufferstruct v4l2_buffer(传入传出)
    VIDIOC_QBUFExchange a buffer with the driverstruct 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;
            };
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    	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("放回失败");
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    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("开启失败");
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    //从队列中提取一帧数据
    	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("放回队列失败");
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    // 停止采集	
    	ret = ioctl(fd, VIDIOC_STREAMOFF, &type);
    
    • 1
    • 2

    6. 释放映射

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

    7. 关闭设备

    close(fd);
    
    • 1

    参考链接

    V4L2采集视频显示

  • 相关阅读:
    php mysql 后台 操作
    NVR对接三方相机预览黑屏问题案例
    【资损】资损防控的系统规范-渠道网关类设计
    2019 ccpc女生赛 Tree
    事务,不只ACID
    YGG GAP 第一季 NFT 背后的设计理念
    VTK9.3 编译debug链接问题
    Java第9章 异常
    AtCoder Beginner Contest 262 部分题解
    Stream常用操作以及原理探索
  • 原文地址:https://blog.csdn.net/weixin_42442319/article/details/126052294