当前位置: 首页 > news >正文

v4l2子系统学习(一)V4L2应用程序编程

文章目录

  • 1、声明
  • 2、前言
  • 3、数据采集流程
    • 3.1、buffer的管理
    • 3.2、完整的使用流程
  • 4、应用程序编写
  • 5、测试

1、声明

本文是在学习韦东山《驱动大全》V4L2子系统时,为梳理知识点和自己回看而记录,全部内容高度复制粘贴。

韦老师的《驱动大全》:商品详情

其对应的讲义资料:https://e.coding.net/weidongshan/linux/doc_and_source_for_drivers.git

2、前言

本章只从应用的角度去介绍如何使用v4l2的接口来获取图像。在v4l2应用编程中,主要都是通过ioctl命令来完成一系列任务,如查询设备能力使用VIDIOC_QUERYCAP,申请buffer使用VIDIOC_REQBUFS等等。

3、数据采集流程

3.1、buffer的管理

使用摄像头时,核心是"获得数据"。所以先讲如何获取数据,即如何得到buffer。

摄像头采集数据时,是一帧又一帧地连续采集。所以需要申请若干个buffer,驱动程序把数据放入buffer,APP从buffer得到数据。这些buffer可以使用链表来管理。

驱动程序周而复始地做如下事情:

  • 从硬件采集到数据。
  • 把"空闲链表"取出buffer,把数据存入buffer。
  • 把含有数据的buffer放入"完成链表"。

APP也会周而复始地做如下事情:

  • 监测"完成链表",等待它含有buffer。
  • 从"完成链表"中取出buffer。
  • 处理数据。
  • 把buffer放入"空闲链表"

链表操作示意图如下:

3.2、完整的使用流程

下面列出一个获取图像的完成流程:

  • open:打开设备节点/dev/videoX。
  • ioctl VIDIOC_QUERYCAP:查询能力,比如
    • 确认它是否是"捕获设备",因为有些节点是输出设备。
    • 确认它是否支持mmap操作,还是仅支持read/write操作。
  • ioctl VIDIOC_ENUM_FMT:枚举所支持的图像格式。
  • ioctl VIDIOC_S_FMT:在上面枚举出来的格式里,选择一个来设置格式。
  • ioctl VIDIOC_REQBUFS:通知驱动程序申请buffer,APP中可以表明需要申请多少个buffer,但驱动程序不一定能申请到。
  • ioctl VIDIOC_QUERYBUF和mmap:查询驱动程序中所申请到的buffer的信息,并在用户空间进行映射。
    • 如果申请到了N个buffer,这个ioctl就应该执行N次。
    • 执行mmap后,APP就可以直接读写这些buffer。
  • ioctl VIDIOC_QBUF:通知驱动程序,把buffer放入"空闲链表"。
    • 如果申请到了N个buffer,这个ioctl就应该执行N次。
  • ioctl VIDIOC_STREAMON:启动摄像头。这里是一个循环,使用poll/select监测buffer,然后从"完成链表"中取出buffer,处理后再放入"空闲链表"
    • poll/select
    • ioctl VIDIOC_DQBUF:从"完成链表"中取出buffer。
    • 处理:前面使用mmap映射了每个buffer的地址,处理时就可以直接使用地址来访问buffer。
    • ioclt VIDIOC_QBUF:把buffer放入"空闲链表"。
  • ioctl VIDIOC_STREAMOFF:停止摄像头。

4、应用程序编写

/* v4l2_test.c */

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

int main(int argc, char **argv)
{
	int fd;
	struct v4l2_fmtdesc fmtdesc;
    struct v4l2_frmsizeenum fsenum;
    int fmt_index = 0;
    int frame_index = 0;
	void *bufs[32];
    int buf_cnt;
	struct pollfd fds[1];
	int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	char filename[32];
    int file_cnt = 0;
	int i;
	
	if(argc != 2)
	{
		printf("Usage : %s </dev/videoX>\n", argv[0]);
		return -1;
	}

	/* 1、打开vedio设备节点 */
	fd = open(argv[1], O_RDWR);
	if (fd < 0)
    {
        printf("can not open %s\n", argv[1]);
        return -1;
    }

	/* 2、查询能力 */ 
	struct v4l2_capability cap;
    memset(&cap, 0, sizeof(struct v4l2_capability));
	
	if (0 == ioctl(fd, VIDIOC_QUERYCAP, &cap))
	{
		if((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0) 
		{
            fprintf(stderr, "Error opening device %s: video capture not supported.\n",
                    argv[1]);
            return -1;
        }
        
        if((cap.capabilities & V4L2_CAP_STREAMING) == 0) 
		{
            fprintf(stderr, "%s does not support streaming i/o\n", argv[1]);
            return -1;
        }
	}
	else
	{
		printf("can not get capability\n");
        return -1;
	}

	while(1)
	{
		/* 3、枚举格式 */
		fmtdesc.index = fmt_index;	// 从0开始枚举
		fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		if(0 != ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc))
			break;

		frame_index = 0;
		printf("format %s,%d\n", fmtdesc.description, fmtdesc.pixelformat);
		while(1)
		{
			/* 4、枚举这种格式所支持的帧大小 */
			memset(&fsenum, 0, sizeof(struct v4l2_frmsizeenum));
            fsenum.pixel_format = fmtdesc.pixelformat;
            fsenum.index = frame_index;

			if (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &fsenum) == 0)
            {
                printf("\tframesize %d: %d x %d\n", frame_index, fsenum.discrete.width, fsenum.discrete.height);
            }
            else
            {
                break;
            }

			frame_index++;
		}

		fmt_index++;
	}

	/* 5、设置格式 */
	struct v4l2_format fmt;
	memset(&fmt, 0, sizeof(struct v4l2_format));
	fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = 1280;
    fmt.fmt.pix.height = 720;
	fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
    fmt.fmt.pix.field = V4L2_FIELD_ANY;
	if (0 == ioctl(fd, VIDIOC_S_FMT, &fmt))
	{
        printf("set format ok: %d x %d\n", fmt.fmt.pix.width, fmt.fmt.pix.height);
    }
    else
    {
        printf("can not set format\n");
        return -1;
    }

	/* 6、申请buffer */
	struct v4l2_requestbuffers rb;
	memset(&rb, 0, sizeof(struct v4l2_requestbuffers));
	rb.count = 32;
    rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    rb.memory = V4L2_MEMORY_MMAP;
	
	if (0 == ioctl(fd, VIDIOC_REQBUFS, &rb))	// 向驱动程序申请32个buffer,只是申请,不一定成功
	{
		/* 7、申请完成后,mmap这些buffer */
		buf_cnt = rb.count;
        for (i = 0; i < rb.count; i++) 			// 枚举这32个buffer
		{
            struct v4l2_buffer buf;
            memset(&buf, 0, sizeof(struct v4l2_buffer));
            buf.index = i;
            buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            buf.memory = V4L2_MEMORY_MMAP;
            if (0 == ioctl(fd, VIDIOC_QUERYBUF, &buf))	// 查询第n个buffer的信息
            {
                bufs[i] = mmap(0, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);	// 使用mmap映射到用户空间
                if(bufs[i] == MAP_FAILED) 				// 映射失败
				{
                    perror("Unable to map buffer");
                    return -1;
                }
            }
            else
            {
                printf("can not query buffer\n");
                return -1;
            }            
        }

        printf("map %d buffers ok\n", buf_cnt);
	}
	else
    {
        printf("can not request buffers\n");
        return -1;
    }

	/* 8、把所有buffer放入空闲链表 */
	for (i = 0; i < buf_cnt; ++i) 
	{
        struct v4l2_buffer buf;
        memset(&buf, 0, sizeof(struct v4l2_buffer));
        buf.index = i;
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        if (0 != ioctl(fd, VIDIOC_QBUF, &buf))	// 这里只是通知驱动程序,把buffer放入空闲链表。这里的buffer是驱动程序里的。
        {
            perror("Unable to queue buffer");
            return -1;
        }
    }
    printf("queue buffers ok\n");

	/* 9、启动摄像头 */
    if (0 != ioctl(fd, VIDIOC_STREAMON, &type))
    {
        perror("Unable to start capture");
        return -1;
    }
    printf("start capture ok\n");

	while (1)
	{
		/* 10、使用poll监听是否有buffer可以取出 */
		memset(fds, 0, sizeof(fds));
        fds[0].fd = fd;
        fds[0].events = POLLIN;
		if (poll(fds, 1, -1) > 0)
		{
			/* 11、把buffer取出队列 */
            struct v4l2_buffer buf;
            memset(&buf, 0, sizeof(struct v4l2_buffer));
            buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            buf.memory = V4L2_MEMORY_MMAP;
			if (0 != ioctl(fd, VIDIOC_DQBUF, &buf))		// 这里只是通知驱动程序,把buffer从完成链表中取出。这里的buffer是驱动程序里的。
            {
                perror("Unable to dequeue buffer");
                return -1;
            }

			/* 12、把buffer的数据存为文件 */
            sprintf(filename, "video_raw_data_%04d.jpg", file_cnt++);
            int fd_file = open(filename, O_RDWR | O_CREAT, 0666);
            if (fd_file < 0)
            {
                printf("can not create file : %s\n", filename);
            }
            printf("capture to %s\n", filename);
            write(fd_file, bufs[buf.index], buf.bytesused);	// 前面已经对驱动程序里申请的buffer进行了mmap,所以这里将用户空间的buffer写入文件中
            close(fd_file);

			/* 13、重新把buffer放入空闲队列 */
			if (0 != ioctl(fd, VIDIOC_QBUF, &buf))
            {
                perror("Unable to queue buffer");
                return -1;
            }
		}
			
	}

	if (0 != ioctl(fd, VIDIOC_STREAMOFF, &type))
    {
        perror("Unable to stop capture");
        return -1;
    }
    printf("stop capture ok\n");
    close(fd);
	
	return 0;
}

5、测试

编译应用程序,插入usb摄像头,执行应用程序:

# 1、编译应用程序
gcc -o v4l2_test v4l2_test.c

# 2、插入摄像头

# 3、执行应用程序
./v4l2_test /dev/video10

相关文章:

  • Python3测试开发面试题2
  • 在echarts的tooltip组件中使用vue3自定义组件
  • Spring Bean生命周期通俗讲解
  • VScode C语言学习开发环境;运行提示“#Include错误,无法打开源文件stdio.h”
  • php文件包含
  • C extern在函数声明中的作用
  • 各类数据质量等相关学习地址
  • vmware centos 10 stream boot 安装
  • 【算法】快排-786. 第k个数
  • 23. AI-大语言模型-DeepSeek
  • MySQL登录问题总结
  • 【Reasoning】LLaMA-Berry: Pairwise Optimization for O1-like Olympiad-Level Mathematical Reasoning
  • Linux 内核中的 container_of 宏:以 ipoib_rx_poll_rss 函数为例
  • Langchain vs. LlamaIndex:哪个在集成MongoDB并分析资产负债表时效果更好?
  • android,flutter 混合开发,pigeon通信,传参
  • RDMA ibverbs_API功能说明
  • 【蓝桥杯集训·每日一题2025】 AcWing 6122. 农夫约翰的奶酪块 python
  • Rust编程语言入门教程(五)猜数游戏:生成、比较神秘数字并进行多次猜测
  • javaSE学习笔记22-线程(thread)-线程通信、线程池
  • MySQL(1)基础篇
  • 五一假期上海口岸出入境客流总量预计达59.4万人,同比增约30%
  • 澎湃回声丨23岁小伙“被精神病”8年续:今日将被移出“重精”管理系统
  • 奈雪的茶叫停“能喝奶茶就不要喝水”宣传,当地市监称不要误导消费者
  • 南部战区位南海海域进行例行巡航
  • 逛了6个小时的上海车展。有些不太成熟的感受。与你分享。
  • 企业取消“大小周”引热议,半月谈:不能将显性加班变为隐性加班