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设备的捕获

相关推荐
琥珀色糖2 小时前
Linux守护进程
linux·终端·守护进程·会话·进程组
开心大爆炸6 小时前
xrdp 连接 登录对话框输入密码后闪退
linux·运维·服务器
oushaojun26 小时前
大厂 C++ 面试:深度剖析 Linux Signal 信号机制(转)
linux
苏生Susheng7 小时前
【软件实施】Linux系统Shell脚本教程
linux·运维·服务器·chrome·spring boot·学习·实施
Ruiery7 小时前
Linux 6.6内核 IOMMU 深度解析(七):DMA API 与 IOMMU 集成 — 从 dma_map_single 到 iommu_map
linux·运维·服务器
分支预测失败7 小时前
RISC-V AIA 中断架构实战:从 PLIC 到 APLIC 与 IMSIC 的迁移
linux·后端
GeW7 小时前
RHCE学习拿证计划:锁定核心考点,实现备考效率最大化
linux
snow@li7 小时前
服务器运维:Alibaba Cloud Linux 4 LTS 64位 根目录全景深度解析文章
linux·运维·服务器
苏生Susheng8 小时前
【软件实施】Linux企业运维常用命令手册
java·linux·运维·服务器·springboot·springcloud·软件实施
额额额对了8 小时前
Linux 进程间通信(IPC)核心机制:消息队列、共享内存与信号量详解
linux·网络·网络协议