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

相关推荐
FoldWinCard4 小时前
D5 Linux 网络及端口命令
linux·运维·服务器
零涂毕业设计5 小时前
毕业设计模块开发-OLED显示屏(IIC协议0.96寸)STM32 ESP32 FPGA Linux驱动免费分享
linux·stm32·单片机·嵌入式·esp32·fpga·oled
tedcloud1235 小时前
OmniRoute怎么部署?开源AI模型路由平台Linux部署教程
linux·服务器·人工智能·开源·音视频
霸道流氓气质6 小时前
KMS 密钥管理服务(Key Management Service)原理与实践
linux·服务器·数据库
SakitamaX6 小时前
LVS(Linux Virtual Server)概念详解
linux·运维·lvs
小张同学a.6 小时前
Linux系统管理
linux·运维
阿龙的工作记录6 小时前
深入理解Linux设备驱动模型
linux·嵌入式硬件·工作记录
代码村新手7 小时前
Linux的基本指令
linux·运维·服务器
hkj88087 小时前
Ubuntu 切换java版本
java·linux·ubuntu
撩得Android一次心动9 小时前
Linux编程笔记3【个人用】
linux·笔记