Linux V4L2子系统-应用层访问video设备

2023-08-31 10:51:13 浏览数 (2)

一、概述:

V4L2子系统向上提供了很多访问Video设备的接口,应用程序可以通过系统调用访问Video设备。但由于Video设备千差万别,很少有设备驱动程序能支持所有的接口功能,因此在使用之前,需要了解设备驱动程序支持的功能。

二、访问流程:

2.1.打开设备文件

视频设备与其他设备一样可以视为一个文件,所以使用open打开文件。可以是阻塞打开,也可以是非阻塞打开,非阻塞打开,若没有数据,则会返回错误。

代码语言:javascript复制
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    ......
    int camera_fd;
    camera_fd = open("/dev/video0", O_RDWR);  // 阻塞打开
    ......
    camera_fd = open("/dev/video0", O_RDWR | O_NONBLOCK);  // 非阻塞打开
    ......


2.2.获取设备支持的功能:

在使用Video设备之前,需要了解Video设备支持哪些功能,如是图像捕获设备还是图像输出设备、可对视频信号进行何种调制。支持VBI、是否具有音频功能等。可通过VIDIOC_QUERYCAP命令获取Video设备支持的功能。最终通过检查struct v4l2_capability中的capabilities变量获取设备支持的功能。

代码语言:javascript复制
    #include <sys/ioctl.h>
    #include <linux/videodev2.h>
    ......
    struct v4l2_capability cap = {0};
    int ret = ioctl(camera_fd, VIDIOC_QUERYCAP, &capability);
    ......
    // 判断是否支持某些功能
    if(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)
        printf("v4l2 device support video capturen");
    if(cap.capabilities & V4L2_CAP_VIDEO_OUTPUT)
        printf("v4l2 device support video outputn");
    ......

设备的功能保存在struct v4l2_capability结构体中,capabilities变量具体表示了设备具有的功能,功能由宏定义V4L2_CAP_XXXX表示:

代码语言:javascript复制
    // 描述V4L2设备的功能,对应ioctl命令VIDIOC_QUERYCAP
    struct v4l2_capability {    
        __u8 driver[16];    // 驱动模块的名称,如"bttv"
        __u8 card[32];      // 品牌名称,如"Hauppauge WinTV"
        __u8 bus_info[32];  // 总线名称,如"PCI:"   pci_name(pci_dev)
        __u32   version;       // 版本信息,KERNEL_VERSION
        __u32 capabilities;  // 设备整体的功能
        __u32 device_caps;  
        __u32 reserved[3];
    };
    // capabilities字段值的宏定义
    #define V4L2_CAP_VIDEO_CAPTURE  0x00000001  // 图像捕获设备
    #define V4L2_CAP_VIDEO_OUTPUT  0x00000002  // 图像输出设备
    #define V4L2_CAP_VIDEO_OVERLAY  0x00000004  // 支持预览功能
    #define V4L2_CAP_VBI_CAPTURE  0x00000010  /* Is a raw VBI capture device */
    #define V4L2_CAP_VBI_OUTPUT      0x00000020  /* Is a raw VBI output device */
    #define V4L2_CAP_SLICED_VBI_CAPTURE 0x00000040  /* Is a sliced VBI capture device */
    #define V4L2_CAP_SLICED_VBI_OUTPUT 0x00000080  /* Is a sliced VBI output device */
    #define V4L2_CAP_RDS_CAPTURE  0x00000100  /* RDS data capture */
    #define V4L2_CAP_VIDEO_OUTPUT_OVERLAY 0x00000200  /* Can do video output overlay */
    #define V4L2_CAP_HW_FREQ_SEEK  0x00000400  /* Can do hardware frequency seek  */
    #define V4L2_CAP_RDS_OUTPUT  0x00000800      /* Is an RDS encoder */
    /* Is a video capture device that supports multiplanar formats */
    #define V4L2_CAP_VIDEO_CAPTURE_MPLANE 0x00001000
    /* Is a video output device that supports multiplanar formats */
    #define V4L2_CAP_VIDEO_OUTPUT_MPLANE 0x00002000
    /* Is a video mem-to-mem device that supports multiplanar formats */
    #define V4L2_CAP_VIDEO_M2M_MPLANE 0x00004000
    #define V4L2_CAP_VIDEO_M2M  0x00008000  /* Is a video mem-to-mem device */
    #define V4L2_CAP_TUNER   0x00010000  // 有高频头
    #define V4L2_CAP_AUDIO   0x00020000  // 支持音频
    #define V4L2_CAP_RADIO   0x00040000  /* is a radio device */
    #define V4L2_CAP_MODULATOR  0x00080000  /* has a modulator */
    #define V4L2_CAP_SDR_CAPTURE    0x00100000  /* Is a SDR capture device */
    #define V4L2_CAP_EXT_PIX_FORMAT 0x00200000  /* Supports the extended pixel format */
    #define V4L2_CAP_READWRITE      0x01000000  // 支持read、write系统调用
    #define V4L2_CAP_ASYNCIO        0x02000000  // 支持异步I/O
    #define V4L2_CAP_STREAMING      0x04000000  // 支持流式I/O ioctl功能
    #define V4L2_CAP_DEVICE_CAPS    0x80000000  /* sets device capabilities field */

2.3.选择设备输入:

Video设备可能有多个输入。如某些芯片上,摄像头控制器可以接多个摄像头,则需要选择哪一个摄像头作为输入源。若只有一个输入,则无需选择。VIDIOC_ENUMINPUT命令可以列出对应编号输入设备的信息,信息存放在struct v4l2_input结构体中。VIDIOC_S_INPUT可以通过编号指定当前的输入设备。

代码语言:javascript复制
    #include <sys/ioctl.h>
    #include <linux/videodev2.h>
    ......
    struct v4l2_input input = {0};
    // 枚举Video设备所有输入
    while (!ioctl(camera_fd, VIDIOC_ENUMINPUT, &input)) {
        printf("video input name: %sn", input.name);
          input.index;        
    }
    ......
    input.index = XX;  // 指定输入的编号为XX
    ret = ioctl(camera_fd, VIDIOC_S_INPUT, &input);  // 设置编号为XX的输入为当前的输入设备
    ......

设备的输入信息保存在struct v4l2_input结构体中。

代码语言:javascript复制
    struct v4l2_input {
        __u32      index;  /*  Which input,用来确定输入设备 */
        __u8      name[32]; /*  Label */
        __u32      type;  /*  Type of input */
        __u32      audioset; /*  Associated audios (bitfield) */
        __u32        tuner;     /*  enum v4l2_tuner_type */
        v4l2_std_id  std;
        __u32      status;
        __u32      capabilities;
        __u32      reserved[3];
    };
    /*  Values for the 'type' field */
    #define V4L2_INPUT_TYPE_TUNER  1
    #define V4L2_INPUT_TYPE_CAMERA  2

    /* field 'status' - general */
    #define V4L2_IN_ST_NO_POWER    0x00000001  /* Attached device is off */
    #define V4L2_IN_ST_NO_SIGNAL   0x00000002
    #define V4L2_IN_ST_NO_COLOR    0x00000004

    /* field 'status' - sensor orientation */
    /* If sensor is mounted upside down set both bits */
    #define V4L2_IN_ST_HFLIP       0x00000010 /* Frames are flipped horizontally */
    #define V4L2_IN_ST_VFLIP       0x00000020 /* Frames are flipped vertically */

    /* field 'status' - analog */
    #define V4L2_IN_ST_NO_H_LOCK   0x00000100  /* No horizontal sync lock */
    #define V4L2_IN_ST_COLOR_KILL  0x00000200  /* Color killer is active */

    /* field 'status' - digital */
    #define V4L2_IN_ST_NO_SYNC     0x00010000  /* No synchronization lock */
    #define V4L2_IN_ST_NO_EQU      0x00020000  /* No equalizer lock */
    #define V4L2_IN_ST_NO_CARRIER  0x00040000  /* Carrier recovery failed */

    /* field 'status' - VCR and set-top box */
    #define V4L2_IN_ST_MACROVISION 0x01000000  /* Macrovision detected */
    #define V4L2_IN_ST_NO_ACCESS   0x02000000  /* Conditional access denied */
    #define V4L2_IN_ST_VTR         0x04000000  /* VTR time constant */

    /* capabilities flags */
    #define V4L2_IN_CAP_DV_TIMINGS   0x00000002 /* Supports S_DV_TIMINGS */
    #define V4L2_IN_CAP_CUSTOM_TIMINGS V4L2_IN_CAP_DV_TIMINGS /* For compatibility */
    #define V4L2_IN_CAP_STD       0x00000004 /* Supports S_STD */
    #define V4L2_IN_CAP_NATIVE_SIZE  0x00000008 /* Supports setting native size */

2.4.获取和设置像素格式

有些摄像头支持多个像素格式,有的摄像头只支持一种像素格式。因此在设置像素格式之前需要了解摄像头支持的像素格式,然后再进行设置。VIDIOC_ENUM_FMT命令枚举设备支持的像素格式,VIDIOC_S_FMT命令设置设备当前采用的像素格式。

代码语言:javascript复制
    #include <sys/ioctl.h>
    #include <linux/videodev2.h>
    struct v4l2_fmtdesc fmtdesc = {0};
    ......
    // 获取支持的像素格式
    while (!ioctl(camera_fd, VIDIOC_ENUM_FMT, &fmtdesc)) {
        printf("fmt: %sn", fmtdesc.description);
        fmtdesc.index  ;
    }
    ......
    // 设置像素格式
    struct v4l2_format fmt = {0};
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  // 视频捕获模式
    v4l2_fmt.fmt.pix.width = 720;            // 视频宽度
    v4l2_fmt.fmt.pix.height = 576;           // 视频高度
    v4l2_fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; // YUYV像素格式
    v4l2_fmt.fmt.pix.field = V4L2_FIELD_ANY;
    ret = ioctl(camera_fd, VIDIOC_S_FMT, &fmt);
    ......

下面是用到的结构体和枚举定义。

代码语言:javascript复制
    // 用于获取像素格式
    struct v4l2_fmtdesc {
        __u32      index;             /* Format number      */
        __u32      type;              /* enum v4l2_buf_type */
        __u32           flags;
        __u8      description[32];   /* 描述像素格式的字符串   */
        __u32      pixelformat;       /* Format fourcc      */
        __u32      reserved[4];
    };
    // 用于设置像素格式
    struct v4l2_format {
        __u32  type;  // 数据流类型,由enum v4l2_buf_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 */
            __u8 raw_data[200];                /* user-defined */
        } fmt;
    };
    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,
        ......
    };
    struct v4l2_pix_format {
        __u32          width;       // 宽度
        __u32   height;      // 高度
        __u32   pixelformat; // 像素格式
        __u32   field;      /* enum v4l2_field */
        __u32           bytesperline; /* for padding, zero if unused */
        __u32           sizeimage;
        __u32   colorspace; /* enum v4l2_colorspace */
        __u32   priv;  /* private data, depends on pixelformat */
        __u32   flags;  /* format flags (V4L2_PIX_FMT_FLAG_*) */
        __u32   ycbcr_enc; /* enum v4l2_ycbcr_encoding */
        __u32   quantization; /* enum v4l2_quantization */
    };

    enum v4l2_field {
        V4L2_FIELD_ANY           = 0, /* driver can choose from none,
                        top, bottom, interlaced
                        depending on whatever it thinks
                        is approximate ... */
        V4L2_FIELD_NONE          = 1, /* this device has no fields ... */
        V4L2_FIELD_TOP           = 2, /* top field only */
        V4L2_FIELD_BOTTOM        = 3, /* bottom field only */
        V4L2_FIELD_INTERLACED    = 4, /* both fields interlaced */
        V4L2_FIELD_SEQ_TB        = 5, /* both fields sequential into one
                        buffer, top-bottom order */
        V4L2_FIELD_SEQ_BT        = 6, /* same as above   bottom-top order */
        V4L2_FIELD_ALTERNATE     = 7, /* both fields alternating into
                        separate buffers */
        V4L2_FIELD_INTERLACED_TB = 8, /* both fields interlaced, top field
                        first and the top field is
                        transmitted first */
        V4L2_FIELD_INTERLACED_BT = 9, /* both fields interlaced, top field
                        first and the bottom field is
                        transmitted first */
    };

2.5.申请缓冲区:

Video设备捕获的视频数据应该存放在预先分配的缓冲区中。使用VIDIOC_REQBUFS命令向内核申请缓冲区。在申请之前,需要设置申请的缓冲区数量nr_bufs、缓冲区数据流类型type和缓冲区内存使用方式memory。

代码语言:javascript复制
    #include <sys/ioctl.h>
    #include <linux/videodev2.h>
    struct v4l2_requestbuffers req = {0};
    req.count = nr_bufs; //缓存数量
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;    // 内存映射的方式,可提高效率,减少内存占用
    ......
    ret = ioctl(camera_fd, VIDIOC_REQBUFS, &req);
    ......

下面是struct v4l2_requestbuffers结构体的具体定义。

代码语言:javascript复制
    struct v4l2_requestbuffers {
        __u32   count;    // 缓冲区数量
        __u32   type;   // 数据流类型,通常为V4L2_BUF_TYPE_VIDEO_CAPTURE
        __u32   memory;   // 缓冲区内存使用方式,通常为V4L2_MEMORY_MMAP
        __u32   reserved[2];
    };
    enum v4l2_memory {
        V4L2_MEMORY_MMAP             = 1,
        V4L2_MEMORY_USERPTR          = 2,
        V4L2_MEMORY_OVERLAY          = 3,
        V4L2_MEMORY_DMABUF           = 4,
    };

2.6.缓冲区映射:

使用read会导致数据在用户空间和内核空间之间来回复制,效率低,且在用户空间和内核空间都占用内存,开销大。通常情况下缓冲区使用内存映射的方式,mmap把驱动程序中videobuf2管理的内存映射到用户空间,应用程序可直接访问videobuf2管理的内存,不发生数据拷贝,这种方式效率高,内存占用低。VIDIOC_QUERYBUF命令获取缓冲区信息,VIDIOC_QBUF将缓冲区加入到内核队列中。

代码语言:javascript复制
    #include <stddef.h>
    #include <string.h>
    #include <sys/ioctl.h>
    #include <sys/mman.h>
    #include <linux/videodev2.h>

    struct video_buf {
        void* start;
        size_t length;
    }
    struct v4l2_buffer v4l2_buf;
    struct video_buf* buf = calloc(req.count, sizeof(struct video_buf));
    ......
    for (i = 0; i < req.count; i  ) {  // 将申请的缓冲区全部映射
        memset(&v4l2_buf, 0, sizeof(v4l2_buf));
        buf.index = i;
        // 获取编号为i的缓冲区信息
        ret = ioctl(camera_fd, VIDIOC_QUERYBUF, &v4l2_buf);
        ......
        buf[i].length = v4l2_buf.length;  // 记录缓冲区长度
        // 开始映射,映射完成后,应用通过该地址获取视频数据
        buf[i].start = mmap(NULL, v4l2_buf.length, PORT_READ | PORT_WRITE, 
                MAP_SHARED, camera_fd, v4l2_buf.m.offset);
        ......
        // 将缓冲区加入到内核的缓冲队列中
        ret = ioctl(camera_fd, VIDIOC_QBUF, v4l2_buf);
        ......
    }


struct v4l2_buffer的详细定义如下。

代码语言:javascript复制
    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;
        __u32   reserved;
    };

2.7.开始采集视频:

经过前面的准备工作,现在可以使能设备,开始采集视频数据了。VIDIOC_STREAMON命令使能设备,开始采集视频。

代码语言:javascript复制
    #include <sys/ioctl.h>  
    #include <linux/videodev2.h>
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ......
    ret = ioctl(camera_fd, VIDIOC_STREAMON, &type);
    ......

2.8.处理视频数据:

内核中以环形队列的形式组织缓冲区,处理数据的时候从环形队列中获取一个缓冲区,处理完毕,将缓冲区放入环形队列。应用程序通过VIDIOC_DQBUF命令从队列中获取一个缓冲区,使用VIDIOC_QBUF将缓冲区加入到队列中。整个过程中不发生数据的拷贝,比传统的read和write I/O方式效率高很多,内存占用也大大降低了。

代码语言:javascript复制
    #include <sys/ioctl.h>  
    #include <linux/videodev2.h>
    void* data = NULL;
    size_t length = 0;
    struct v4l2_buffer v4l2_buf = {0};
    for (;;) {
     ret = ioctl(camera_fd, VIDIOC_DQBUF, &v4l2_buf);  // 从内核队列中获取一个缓冲区
        ......
        data = buf[v4l2_buf.index].start;    // 缓冲区地址
        length = buf[v4l2_buf.index].length  // 缓冲区数据长度
        ......
        ret = ioctl(camera_fd, VIDIOC_QBUF, &v4l2_buf);  // 将缓冲区放入内核队列中
        ......
    }

2.9.停止采集视频:

应用可使用VIDIOC_STREAMOFF命令停止视频采集,同时禁止设备。

代码语言:javascript复制
    #include <sys/ioctl.h>  
    #include <linux/videodev2.h>
    ......
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ret = ioctl(camera_fd, VIDIOC_STREAMOFF, &type);
    ......

2.10.关闭设备:

不再使用设备时,可使用close关闭设置,close调用后之前申请的缓冲区会被释放。

参考资料:

  • https://blog.csdn.net/weixin_42462202/article/details/96826526
  • Android驱动开发权威指南
  • Android驱动开发与移植实战详解

文章参考:http://t.csdn.cn/nkIMu

0 人点赞