v4l2读取图像样例

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 <sys/mman.h>
#include <poll.h>

int main(int argc, char **argv)
{
    int ret = 0;
    int fd;
    struct v4l2_fmtdesc fmtdesc;    // 该变量是用来与v4l2设备节点交互,从而获取支持的格式的变量
    struct v4l2_frmsizeenum fsenum; // 该变量是指定了format的index后,从而继续遍历获取当前v4l2设备支持的大小格式
    int i = 0;
    void *bufs[32]; // 定义了一个32个大小的数组,每个元素都是一个void*,也就是任意类型的指针
    if (argc != 2)
    {
        printf("Usage: %s </dev/videoX>, print format detail for video device\n", argv[0]);
        return -1;
    }

    fd = open(argv[1], O_RDWR);
    if (fd < 0)
    {
        printf("can not open %s\n", argv[1]);
        return -1;
    }

    fmtdesc.index = 0;                          // 该变量即当输出又当输入,所以指定从0开始,它别的成员变量会被内核中的驱动修改
    fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 制定获取的format的type为捕获,即视频输入
    while (1)
    {

        if (ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc))
        {
            break;
        }
        fsenum.pixel_format = fmtdesc.pixelformat; // 指定当前要遍历哪个format的size
        fsenum.index = 0;                          // 每次进format的循环前,都默认从第一个size开始获取
        while (1)
        {
            if (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &fsenum))
            {
                break;
            }
            printf("format %s,%d   framesize %d: %d x %d\n", fmtdesc.description, fmtdesc.pixelformat, fsenum.index, fsenum.discrete.width, fsenum.discrete.height);
            fsenum.index++;
        }
        fmtdesc.index++;
    }

    //===============设置需要的图像格式=================

    struct v4l2_format fmt;
    memset(&fmt, 0, sizeof(struct v4l2_format));
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = 1024;
    fmt.fmt.pix.height = 720;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
    fmt.fmt.pix.field = V4L2_FIELD_ANY; // 扫描方式
    if (ioctl(fd, VIDIOC_S_FMT, &fmt) == 0)
    {
        // 驱动会修改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;
    }

    struct v4l2_requestbuffers rb;
    memset(&rb, 0, sizeof(struct v4l2_requestbuffers));
    rb.count = 32;                         // 表示app想申请32个buffer
    rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 用于视频输入的buf
    rb.memory = V4L2_MEMORY_MMAP;          // 表示buffer是由内核驱动申请的,然后后续会通过mmap方式映射到用户空间

    if (ioctl(fd, VIDIOC_REQBUFS, &rb) == 0)
    {
        // 同样的,这个结构体也会被驱动修改,会返回成功申请到的buffer的信息,不一定和app发送给它的完全一致,尤其是数量可能有限制

        for (i = 0; i < rb.count; 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 (ioctl(fd, VIDIOC_QUERYBUF, &buf) == 0)
            {
                // 查询申请的第index个buff的信息
                // 将这些buff逐个映射到用户空间
                // 第一个参数是指定虚拟地址,0则是交给内核自由选择
                bufs[i] = mmap(0, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
                if (bufs[i] == MAP_FAILED)
                {
                    perror("Unable to map buffer");
                    return -1;
                }
            }
            else
            {
                printf("can not query buffer\n");
                return -1;
            }
        }
    }
    else
    {
        printf("can not request buffers\n");
        return -1;
    }

    for (i = 0; i < rb.count; 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))
        {
            printf("queue buffer%d ok\n" buf.index);
        }
        else
        {
            perror("Unable to queue buffer");
            return -1;
        }
    }
    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (ioctl(fd, VIDIOC_STREAMON, &type) != 0)
    {
        perror("Unable to start capture");
        return -1;
    }
    printf("start capture ok\n");

    struct pollfd fds[1];

    int file_cnt = 0;
    while (1)
    {
        memset(fds, 0, sizeof(fds));
        fds[0].fd = fd;
        fds[0].events = POLLIN; // 有数据输入
        if (poll(fds, 1, -1))   // poll 1个,时间无限
        {
            /* 把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 (ioctl(fd, VIDIOC_DQBUF, &buf) != 0)
            {
                perror("Unable to dequeue buffer");
                return -1;
            }
            char filename[32];
            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("capature to %s\n", filename);
            write(fd_file, bufs[buf.index], buf.bytesused); // 将buf中真正用了的大小写到文件里面
            close(fd_file);

            // 记得将buffer放回空闲队列
            if (ioctl(fd, VIDIOC_QBUF, &buf) != 0)
            {
                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;
}

步骤如下:

1.打开v4l2注册的设备节点,即videox,剩下的所有和设备节点的操作都是通过ioctl这个驱动暴露出来的接口和用户APP交互的 ioctl带进去的结构体有的也会被内核中的驱动修改

2.使用ioctl传递v4l2_fmtdesc结构体和VIDIOC_ENUM_FMT命令查询当前video节点支持的图像格式,然后使用v4l2_frmsizeenum结构体和VIDIOC_ENUM_FRAMESIZES枚举该格式支持的分辨率大小

3.使用ioctl传递v4l2_format结构体和VIDIOC_S_FMT命令设置要使用的格式与分辨率

4.使用v4l2_requestbuffers结构体和VIDIOC_REQBUFS命令,申请缓冲区,缓冲区一般使用内核申请,然后映射到用户空间的方式

5.使用v4l2_buffer结构体和VIDIOC_QUERYBUF命令轮询所有申请好的buffer,然后将buffer映射到用户空间,可以使用一个指针数组来存储

6.再次使用4l2_buffer结构体,通过index轮询的方式和VIDIOC_QBUF命令将buffer加入到空闲队列里面去

7.使用VIDIOC_STREAMON和指定的type(捕获),开启video设备的捕获

8.使用poll机制和v4l2_buffer结构体,等poll等来数据的时候,就出队一个buffer,然后使用该buffer的index去映射好的用户空间里面读取对应的数据,读取的长度为bytesused

9.读取完数据后,要将用完的buffer再次放入空闲队列中

10.使用VIDIOC_STREAMOFF命令和type关闭video设备的捕获

相关推荐
xlp666hub2 小时前
从零点亮 RK3568 的 LED:设备树,平台总线,现代gpio子系统全解析(附完整代码)
linux·面试
哼?~2 小时前
Linux线程基本概念
linux
姓王名礼3 小时前
一份 Windows/macOS/Linux 完整安装 + 运行 + 对接 WebUI 的步骤
linux·windows·macos
idolao4 小时前
CentOS 7 安装 nginx-1.3.15.tar.gz 详细步骤(从源码编译到启动配置)
linux·运维·数据库
yaoxin5211234 小时前
358. Java IO API - 使用 relativize() 创建路径之间的相对关系
java·linux·python
亚林瓜子4 小时前
linux账号强制密码过期导致私钥文件登录异常问题——(current) UNIX password:
linux·运维·服务器·ssh·aws·ec2·chage
Code_LT4 小时前
【AIGC】Claude Code Rules配置
linux·ubuntu·aigc
LXY_BUAA4 小时前
《嵌入式操作系统》_驱动框架_20260318
linux·运维·服务器
淮北也生橘125 小时前
Linux应用开发:全链路 OTA 升级架构
linux·架构·ota·linux应用开发