【V4L2】V4L2框架浅析

概述

V4L2(Video4Linux的缩写)是Linux下关于视频采集相关设备的驱动框架,为驱动和应用程序提供了一套统一的接口规范。

V4L2支持的设备十分广泛,但是其中只有很少一部分在本质上是真正的视频设备。

按照v4l2的标准,它会将一个数据流设备抽象成一个videoX节点,从属主设备都对应着各自的v4l2_subdev实现,并且按照media controller进行统一管理。

V4L2框架下面就是相机驱动部分,驱动部分控制着上下电逻辑以及寄存器读取时序并按照I2C协议与硬件进行通信,和根据MIPI CSI协议传递数据,从而达到控制各个硬件设备,并且获取图像数据的目的

V4L2的主要数据结构

v4l2_device:嵌入到video_device中,表示一个v4l2设备的实例,内部通过一个链表管理着整个从属的所有子设备。(/include/media)

struct v4l2_device {
    struct device *dev;
#if defined(CONFIG_MEDIA_CONTROLLER)
    struct media_device *mdev;
#endif
    struct list_head subdevs;
    spinlock_t lock;
    char name[V4L2_DEVICE_NAME_SIZE];
    void (*notify)(struct v4l2_subdev *sd,
        unsigned int notification, void *arg);
    struct v4l2_ctrl_handler *ctrl_handler;
    struct v4l2_prio_state prio;
    struct kref ref;
    void (*release)(struct v4l2_device *v4l2_dev);
};

v4l2_subdev:依附在v4l2_device之下,表示一个v4l2设备的子设备,一个v4l2_device下可以有多个sub_device

subdevice的设计目的是为了多路复用,用一个v4l2_device可以服务多个v4l2_subdev,而有些驱动没有v4l2_subdev,只有video_device。

struct v4l2_subdev {
#if defined(CONFIG_MEDIA_CONTROLLER)
    struct media_entity entity;
#endif
    struct list_head list;
    struct module *owner;
    bool owner_v4l2_dev;
    u32 flags;
    struct v4l2_device *v4l2_dev;
    const struct v4l2_subdev_ops *ops;  // subdev的操作方法
    const struct v4l2_subdev_internal_ops *internal_ops;
    struct v4l2_ctrl_handler *ctrl_handler;
    char name[V4L2_SUBDEV_NAME_SIZE];
    u32 grp_id;
    void *dev_priv;
    void *host_priv;
    struct video_device *devnode;
    struct device *dev;
    struct fwnode_handle *fwnode;
    struct list_head async_list;
    struct v4l2_async_subdev *asd;
    struct v4l2_async_notifier *notifier;
    struct v4l2_async_notifier *subdev_notifier;
    struct v4l2_subdev_platform_data *pdata;

    struct v4l2_subdev_state *state;
};

video_device:是一个字符设备,video_device包含一个cdev,v4l2_device是一个v4l2实例,嵌入到video_device中,v4l2维护着一张链表管理v4l2_subdevice,v4l2_subdev表示摄像头的I2C控制模块

struct video_device
{
#if defined(CONFIG_MEDIA_CONTROLLER)
    struct media_entity entity;
    struct media_intf_devnode *intf_devnode;
    struct media_pipeline pipe;
#endif
    const struct v4l2_file_operations *fops;   // v4l2设备操作接口
    u32 device_caps;
    /* sysfs */
    struct device dev;
    struct cdev *cdev;                  // 字符设备
    struct v4l2_device *v4l2_dev;       // v4l2设备
    struct device *dev_parent;

    struct v4l2_ctrl_handler *ctrl_handler;
    struct vb2_queue *queue;
    struct v4l2_prio_state *prio;

    /* device info */
    char name[32];
    enum vfl_devnode_type vfl_type;
    enum vfl_devnode_direction vfl_dir;
    int minor;
    u16 num;
    unsigned long flags;
    int index;

    /* V4L2 file handles */
    spinlock_t      fh_lock;
    struct list_head    fh_list;
    int dev_debug;
    v4l2_std_id tvnorms;

    /* callbacks */
    void (*release)(struct video_device *vdev);
    const struct v4l2_ioctl_ops *ioctl_ops;  // v4l2d的ioctl操作集
    DECLARE_BITMAP(valid_ioctls, BASE_VIDIOC_PRIVATE);

    struct mutex *lock;
};

v4l2驱动框架:

【V4L2】V4L2框架浅析

v4l2主要注册接口

  • v4l2_device
int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev);
void v4l2_device_unregister(struct v4l2_device *v4l2_dev);
  • v4l2_subdev
int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev,struct v4l2_subdev *sd);
void v4l2_device_unregister(struct v4l2_device *v4l2_dev);
  • video_device
int video_register(struct video_device *vdev,int type,int nr);
void video_unregister_device(struct video_device *vdev);

V4L2执行流程

【V4L2】V4L2框架浅析

1.打开video设备

在需要进行视频数据流操作之前,首先通过标准的字符设备操作接口open方法来打开一个video设备,并且将返回设备句柄存在本地,之后的一系列操作都是基于对此句柄的操作。在打开的过程中,会给每一个子设备进行各自的一系列初始化操作。

int vfd;

if((vfd = open("/dev/video0",O_RDWR)) < 0)
{
    perror("DEVICE open error");
    return -1;
}

2.查看并设置设备

在打开设备获取其文件句柄后,就需要查询设备的属性,主要通过ioctl传入VIDIOC_QUERYCAP参数来完成,设备属性通过v4l2_capability结构体来表达,另外还可以通过传入VIDIOC_ENUM_FMT来枚举支持的数据格式,通过传入VIDIOC_G_FMT/VIDIOC_S_FMT来分别获取和设置数据格式,通过传入VIDIOC_G_PARM/VIDIOC_S_PARM来分别获取和设置参数。

// init device
struct v4l2_format fmt = {0};
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width  = 1280;   // 设置相机宽度
fmt.fmt.pix.height = 720;    // 设置相机高度
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;   // 像素格式:YUYV
fmt.fmt.pix.field  = V4L2_FIELD_NONE;

if(-1 == xioctl(vfd , VIDIOC_S_FMT, &fmt)) {
    perror("VIDIOC_S_FMT");
    return errno;
}

3.向驱动申请帧缓冲区

完成设备的配置后,可以向设备申请多个用于盛装图像数据的帧缓冲区,通过调用ioctl传入VIDIOC_REQBUFS命令来完成最后将缓冲区通过mmap方式映射到用户空间

struct v4l2_requestbuffers *request = &config->request;
request->type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
request->memory = V4L2_MEMORY_MMAP;
request->count  = 4;

if(-1 == ioctl(config->vfd, VIDIOC_REQBUFS, request)){
    perror("VIDIOC_REQBUFS");
    return errno;
}

if(request->count < 2) {
    fprintf(stderr, "Not enough buffer memory.\n");
    return EXIT_FAILURE;
}

4.将帧缓冲区入队

申请好缓冲区后,通过调用ioctl方法传入VIDIOC_QBUF命令来将帧缓冲区加入到V4L2框架中的缓冲区队列中,静等硬件模块将图像数据填充到缓冲区中。

struct v4l2_buffer buffer;

// config->buffer_count为4,VIDIOC_REQBUFS时已指定count
for(i = 0; i < config->buffer_count; i++) {
    memset(&buffer, 0, sizeof(buffer));
    buffer.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buffer.memory = V4L2_MEMORY_MMAP;
    buffer.index  = i;

    if(-1 == xioctl(config->vfd, VIDIOC_QBUF, &buffer)) {
        perror("VIDIOC_QBUF");
        return errno;
    }
}

5.开启数据流

将所有的缓冲区都加入到队列之后,就可以调用ioctl并且传入VIDIOC_STREAMON命令,来通知整个框架开始进行数据传输,其中大致包括了通知各个子设备开始进行工作,最终将数据填充到V4L2框架中的缓冲区队列中。

enum v4l2_buf_type type;

type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if(-1 == xioctl(config->vfd, VIDIOC_STREAMON, &type)) {
    perror("VIDIOC_STREAMON");
    return errno;
}

6.将帧缓冲区出队

一旦数据流开始进行流转,就可以通过调用ioctk下发VIDIOC_DQBUF命令来获取帧缓冲区,并且将缓冲区的图像数据取出,进行预览、拍照或者录像的处理。(此处一般配合epoll/poll/select使用)

struct v4l2_buffer buffer;
{
    memset(&buffer, 0, sizeof(buffer));
    buffer.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buffer.memory = V4L2_MEMORY_MMAP;

    if(-1 == xioctl(config->vfd, VIDIOC_DQBUF, &buffer)) {
        switch(errno) {
        case EAGAIN:
            return 0;
        case EIO:
        default:
            perror("VIDIOC_DQBUF");
            return errno;
        }
    }
    // swap buffer pointer.
    v4l2_capture_t *capture = (v4l2_capture_t *)config->refs;
    capture->data = config->buffers[buffer.index].data;
    capture->len  = config->buffers[buffer.index].len;
}

7.将缓冲重新入列

通过调用ioctk下发VIDIOC_QBUF命令将缓冲区重新入列,这样可以实现循环采集。

// &#x5C06;&#x5DF2;&#x7ECF;&#x53D6;&#x5B8C;&#x7684;&#x5E27;&#x7F13;&#x51B2;&#x533A;&#xFF0C;&#x6E05;&#x96F6;
memset(&buffer, 0, sizeof(buffer));

// &#x91CD;&#x65B0;&#x538B;&#x5165;v4l2&#x6846;&#x67B6;&#x7684;&#x961F;&#x5217;&#x4E2D;
if(-1 == xioctl(config->vfd, VIDIOC_QBUF, &buffer)){
     perror("VIDIOC_QBUF");
     return errno;
}

8.停止视频的采集

enum v4l2_buf_type type;

if(-1 == xioctl(config->vfd, VIDIOC_STREAMOFF, &type)) {
    perror("VIDIOC_STREAMOFF");
    return errno;
}

9.关闭视频设备

close(vfd);

调用流程:

【V4L2】V4L2框架浅析

V4L2常用API及命令标识符

1.常用数据结构体在内核目录include/linux/videodev2.h中定义

struct v4l2_requestbuffers reqbufs;   // &#x5411;&#x9A71;&#x52A8;&#x7533;&#x8BF7;&#x5E27;&#x7F13;&#x51B2;&#x7684;&#x60C5;&#x6C42;&#xFF0C;&#x91CC;&#x9762;&#x5305;&#x542B;&#x7533;&#x8BF7;&#x7684;&#x4E2A;&#x6570;
struct v4l2_capability cap;       // &#x8BBE;&#x5907;&#x7684;&#x529F;&#x80FD;&#xFF0C;&#x6BD4;&#x5982;&#x662F;&#x5426;&#x662F;&#x89C6;&#x9891;&#x8F93;&#x5165;&#x8BBE;&#x5907;
struct v4l2_input input;          // &#x89C6;&#x9891;&#x8F93;&#x5165;
struct v4l2_standard std;         // &#x89C6;&#x9891;&#x7684;&#x5236;&#x5F0F;&#xFF0C;&#x6BD4;&#x5982;PAL&#xFF0C;NTSC&#x5236;
struct v4l2_fromat fmt;           // &#x5E27;&#x7684;&#x683C;&#x5F0F;&#xFF0C;&#x6BD4;&#x5982;&#x5BBD;&#x5EA6;&#xFF0C;&#x9AD8;&#x5EA6;&#x7B49;
struct v4l2_buffer buf;           // &#x4EE3;&#x8868;&#x9A71;&#x52A8;&#x4E2D;&#x7684;&#x5E27;&#x7F13;&#x5B58;

2.常用ioctl接口命令include/linux/videodev2.h定义

VIDIOC_REQBUFS&#xFF1A;&#x8BF7;&#x6C42;&#x7F13;&#x5B58;&#x7684;&#x6570;&#x91CF;&#xFF0C;&#x9A71;&#x52A8;&#x4F1A;&#x636E;&#x6B64;&#x7533;&#x8BF7;&#x5BF9;&#x5E94;&#x6570;&#x91CF;&#x7684;&#x89C6;&#x9891;&#x7F13;&#x5B58;&#x3002;
VIDIOC_QUERYBUF&#xFF1A;&#x628A;VIDIOC_REQBUFS&#x4E2D;&#x5206;&#x914D;&#x7684;&#x6570;&#x636E;&#x7F13;&#x5B58;&#x8F6C;&#x6362;&#x6210;&#x7269;&#x7406;&#x5730;&#x5740;
VIDIOC_QUERYCAP&#xFF1A;&#x901A;&#x8FC7; struct v4l2_buffer &#x7ED3;&#x6784;&#x4F53;&#x7684; index&#xFF0C;&#x8BBF;&#x95EE;&#x5BF9;&#x5E94;&#x5E8F;&#x53F7;&#x7684; buffer&#xFF0C;&#x83B7;&#x53D6;&#x5230;&#x5BF9;&#x5E94; buffer &#x7684;&#x7F13;&#x5B58;&#x4FE1;&#x606F;&#x3002;
VIDIOC_ENUM_FMT&#xFF1A;&#x83B7;&#x53D6;&#x5F53;&#x524D;&#x9A71;&#x52A8;&#x652F;&#x6301;&#x7684;&#x89C6;&#x9891;&#x683C;&#x5F0F;
VIDIOC_S_FMT&#xFF1A;&#x8BBE;&#x7F6E;&#x5F53;&#x524D;&#x9A71;&#x52A8;&#x7684;&#x89C6;&#x9891;&#x6355;&#x83B7;&#x683C;&#x5F0F;&#xFF0C;&#x8BBE;&#x7F6E;&#x4E4B;&#x524D;&#x6700;&#x597D;&#x8C03;&#x7528;VIDIOC_TRY_FMT&#x5224;&#x65AD;&#x662F;&#x5426;&#x652F;&#x6301;
VIDIOC_G_FMT&#xFF1A;&#x8BFB;&#x53D6;&#x5F53;&#x524D;&#x9A71;&#x52A8;&#x7684;&#x89C6;&#x9891;&#x6355;&#x83B7;&#x683C;&#x5F0F;&#xFF0C;&#x6BD4;&#x5982;&#xFF1A;width,height,sizeimage&#x7B49;&#x53C2;&#x6570;
VIDIOC_TRY_FMT&#xFF1A;&#x9A8C;&#x8BC1;&#x5F53;&#x524D;&#x9A71;&#x52A8;&#x7684;&#x683C;&#x5F0F;&#x662F;&#x5426;&#x88AB;&#x9A71;&#x52A8;&#x652F;&#x6301;&#xFF0C;&#x4E0D;&#x4F1A;&#x6539;&#x53D8;&#x4EFB;&#x4F55;&#x786C;&#x4EF6;&#x8BBE;&#x7F6E;&#x786C;&#x4EF6;&#x8BBE;&#x7F6E;
VIDIOC_CROPCAP&#xFF1A;&#x67E5;&#x8BE2;&#x9A71;&#x52A8;&#x7684;&#x4FEE;&#x526A;&#x80FD;&#x529B;
VIDIOC_S_CROP&#xFF1A;&#x8BBE;&#x7F6E;&#x89C6;&#x9891;&#x4FE1;&#x53F7;&#x7684;&#x8FB9;&#x6846;
VIDIOC_G_CROP&#xFF1A;&#x8BFB;&#x53D6;&#x89C6;&#x9891;&#x4FE1;&#x53F7;&#x7684;&#x8FB9;&#x6846;
VIDIOC_QBUF&#xFF1A;&#x5C06;User&#x7A7A;&#x95F4;&#x5DF2;&#x7ECF;&#x5904;&#x7406;&#x8FC7;&#x7684;buffer&#xFF0C;&#x91CD;&#x65B0;&#x5165;&#x961F;&#xFF0C;&#x79FB;&#x4EA4;&#x7ED9; driver&#xFF0C;&#x7B49;&#x5F85;&#x586B;&#x5145;&#x6570;&#x636E;&#x3002;
VIDIOC_DQBUF&#xFF1A;&#x5C06;driver&#x5DF2;&#x7ECF;&#x586B;&#x5145;&#x597D;&#x6570;&#x636E;&#x7684; buffer &#x51FA;&#x5217;&#xFF0C;&#x4F9B;&#x5E94;&#x7528;&#x4F7F;&#x7528;&#x3002;
VIDIOC_STREAMON&#xFF1A;&#x5F00;&#x59CB;&#x89C6;&#x9891;&#x663E;&#x793A;&#x51FD;&#x6570;
VIDIOC_STREAMOFF&#xFF1A;&#x7ED3;&#x675F;&#x89C6;&#x9891;&#x663E;&#x793A;&#x51FD;&#x6570;
VIDIOC_QUERYSTD&#xFF1A;&#x68C0;&#x67E5;&#x5F53;&#x524D;&#x89C6;&#x9891;&#x8BBE;&#x5907;&#x652F;&#x6301;&#x7684;&#x6807;&#x51C6;&#xFF0C;&#x4F8B;&#x5982;PAL&#x6216;NTSC&#x3002;
VIDIOC_OVERLAY&#xFF1A;&#x8BBE;&#x7F6E;&#x4F7F;&#x80FD;osd&#x53C2;&#x6570;&#xFF0C;&#x4F20;&#x5165;1&#x8868;&#x793A;&#x4F7F;&#x80FD;&#xFF0C;0&#x8868;&#x793A;&#x5173;&#x95ED;

总结

在linux端开发视频设备大部分使用场景是通过ioctl发送命令符到V4L2框架中,实现获取设备配置信息,设置设备数据格式,拉取设备视频流,获取视频帧进行处理等过程。

此篇仅为v4l2框架简介,还有event,mem2mem,videobuf2等部分,详情请看Linux源码,V4L2部分完全开源。

参考文章:

https://blog.csdn.net/weixin_42462202/article/details/99680969
https://www.cnblogs.com/unreal/articles/1820295.html
https://blog.csdn.net/thisway_diy/article/details/128459836

Original: https://www.cnblogs.com/Wangzx000/p/16652601.html
Author: _Wangzx
Title: 【V4L2】V4L2框架浅析

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/607525/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 一篇文章Shell脚本快速入门

    Shell脚本基础入门 Bash注释 Bash只支持单行注释,使用 #开头的都被当作注释语句: 整行注释 echo hello world # 行尾注释 通过Bash的一些特性,可…

    Linux 2023年5月28日
    073
  • mysql order by语句流程是怎么样的

    order by流程是怎么样的 注意点: select id, name,age,city from t1 where city=’&#x676D;&#x5DDE;…

    Linux 2023年6月8日
    0114
  • Linux安装cmatrix代码雨教程

    一:介绍 cmatrix代码雨是Linux的系统屏保界面;执行cmatrix不仅可以练习简单的编译安装软件三部曲,还可以执行cmatrix命令做出代码雨,提升文化实力 二:成品演示…

    Linux 2023年5月27日
    0115
  • maven安装及导入本地jar包

    一、maven的安装方法 1.去maven官网下载适合的版本 下载地址: 官方下载地址 2.下载后解压到任意目录 3.配置系统环境变量 M2_HOME ,值为maven解压后的目录…

    Linux 2023年6月14日
    0131
  • DNS 查询原理详解

    你可能会问,难道 DNS 服务器(比如 1.1.1.1)保存了世界上所有域名(包括二级域名、三级域名)的 IP 地址? 当然不是。DNS 是一个分布式系统,1.1.1.1 只是用户…

    Linux 2023年6月8日
    069
  • redis 学习指南

    2、 redis.windows.conf各项配置参数介绍 默认情况下,redis不是在后台模式运行的,如果需要在后台进程运行,把该项的值更改为yes,默认为no daemoniz…

    Linux 2023年5月28日
    090
  • HTTP状态码1XX深入理解

    前段时间看了《御赐小仵作》,里面有很多细节很有心。看了一些评论都是:终于在剧里能够看到真正在搞事业、发了工资第一时间还钱的正常人了。我印象比较深的是王府才能吃上的葡萄。觉得非常合理…

    Linux 2023年6月13日
    0102
  • Java基础系列–04_数组

    一维数组:(1)数组:存储同一种数据类型的多个元素的容器。(2)特点: 每一个元素都有编号,从0开始,最大编号是数组的长度-1。编号的专业叫法: 索引(3)定义格式A:数据类型[]…

    Linux 2023年6月7日
    097
  • JAVA设计模式-建造者模式

    JAVA设计模式-建造者模式 介绍 建造者模式是通过一步一步的步骤构建一个包含多个部件的对象,每个不同的对象都是具有相同的构建过程。适用于复杂对象的构建,用户不需要知道具体的构建细…

    Linux 2023年6月6日
    099
  • MAC Book: Operation not permitted

    背景: 最近清理系统上的一些无用的文件后,为了release出可用空间,所以还要把.Trash目录下的文件清理才真正清理完,但是ls 查看该目录时发现一直报”opera…

    Linux 2023年6月7日
    093
  • VirtualBox网络模式 宿主机无法访问虚拟机问题记录

    问题背景 最近在公司使用VirtualBox虚拟机,使用虚拟的ubuntu系统,家里的路由能使用桥接模式,然后能双向ping通(宿主机 现在经常要打开虚拟Linux跑一些脚本和编译…

    Linux 2023年6月6日
    0109
  • 《Redis开发与运维》——(五)Redis持久化(脑图)

    posted @2021-01-09 15:04 雪山上的蒲公英 阅读(122 ) 评论() 编辑 / 返回顶部代码 / Original: https://www.cnblogs…

    Linux 2023年5月28日
    0101
  • redis用法介绍

    Jedis常用方法API Redis命令用scan代替keys、smembers等命令 Java Spring 与 Redis 操作封装源码 Redis API 必杀解读:引入Re…

    Linux 2023年5月28日
    0100
  • IOC容器模拟实现

    运用反射机制和自定义注解模拟实现IOC容器,使其具有自动加载、自动装配和根据全限定类名获取Bean的功能。 1-1 IOC容器的本质 IOC容器可理解为是一个map,其中的一个en…

    Linux 2023年6月8日
    097
  • 访问github 与 mac修改hosts并刷新DNS

    加速githubhttps://ipaddress.com/website/github.global.ssl.fastly.nethttps://ipaddress.com/we…

    Linux 2023年6月8日
    0117
  • margin-top塌陷

    一、问题描述 ​ 在两个及以上的盒子嵌套时候,内部的盒子设置的 margin-top 的效果会加到最外边的盒子上,导致内部的盒子margin-top设置失败。 – 示例…

    Linux 2023年6月14日
    0110
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球