深入学习Linux摄像头(二)v4l2驱动框架

深入学习Linux摄像头系列深入学习Linux摄像头(一)v4l2应用编程深入学习Linux摄像头(二)v4l2驱动框架深入学习Linux摄像头(三)虚拟摄像头驱动分析深入学习Linux摄像头(五)三星平台fimc驱动详解一深入学习Linux摄像头(六)三星平台fimc驱动详解二深入学习Linux摄像头(二)v4l2驱动框架文章目录深入学习Linux摄像头(二)v4l2驱动框架一、V…

大家好,又见面了,我是你们的朋友全栈君。

深入学习Linux摄像头系列

深入学习Linux摄像头(一)v4l2应用编程

深入学习Linux摄像头(二)v4l2驱动框架

深入学习Linux摄像头(三)虚拟摄像头驱动分析

深入学习Linux摄像头(四)三星平台fimc驱动详解

深入学习Linux摄像头(二)v4l2驱动框架

一、V4L2 框架

1.1 相关对象

v4l2驱动框架主要的对象有video_devicev4l2_devicev4l2_subdevvideobuf

  • video_device

    一个字符设备,为用户空间提供设备节点(/dev/videox),提供系统调用的相关操作(open、ioctl…)

  • v4l2_device

    嵌入到video_device中,表示一个v4l2设备的实例

  • v4l2_subdev

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

  • videobuf

    v4l2驱动的缓存管理

下面有必要对v4l2_device和v4l2_subdev来进行说明

subdev的设计目的是为了多路复用,就是用一个v4l2_device可以服务多个v4l2_subdev

下面以我们手机的摄像头来举例

  • CMOS摄像头

    对于一款CMOS摄像头来说,有两个接口,一个是摄像头接口,一个是I2C接口

    摄像头接口负责传输图像数据,I2C接口负责传输控制信息,所以又可以将CMOS摄像头看作是一个I2C模块

    如下图所示

    在这里插入图片描述

  • 芯片片上资源

    在一款芯片上面,摄像头相关的有摄像头控制器摄像头接口I2C总线

    SOC上可以有多个摄像头控制器,多个摄像头接口,多个I2C总线

    摄像头控制器负责接收和处理摄像头数据,摄像头接口负责传输图像数据,I2C总线负责传输控制信息

    如下图所示

    在这里插入图片描述

对于手机而言,一般都有两个摄像头,一个前置摄像头,一个后置摄像头,其接发下图所示

在这里插入图片描述

我们可以选择让控制器去操作哪一个摄像头,这就做到了使用一个摄像头控制器来控制多个摄像头,这就是多路复用

上面说要使用一个摄像头控制器去操作多个摄像头,这是我们的目的,那么在软件中是怎么实现的呢?

我们回到V4L2来,再来谈v4l2_devicev4l2_subdev

上面我们介绍到v4l2_device表示一个v4l2实例

在V4L2驱动中,使用v4l2_device来表示摄像头控制器

使用v4l2_subdev来表示具体的某一个摄像头的I2C控制模块,进而通过其控制摄像头

v4l2_device里有一个v4l2_subdev链表,可以选择v4l2_device去控制哪一个v4l2_subdev

相信到此,你对v4l2_devicev4l2_subdev就有所了解了

当然某些驱动是没有v4l2_subdev,只有video_device

经过上面的讲解,我们用一张图来总结

在这里插入图片描述

前面说video_device是一个字符设备,从图中可以看出,video_device内含一个cdev

v4l2_device是一个v4l2实例,嵌入到video_device中

v4l2_device维护者一个链表管理v4l2_subdev,v4l2_subdev表示摄像头的I2C控制模块

1.2 V4L2 框架

在理清楚V4L2中的主要对象后,我们来介绍V4L2的框架

在介绍V4L2驱动框架前,我们先回顾一下简单的字符设备的编写

  • 分配一个字符设备(cdev)
  • 设置一个fops
  • 注册字符设备

复杂的字符设备

对于复杂的字符设备,内核都是采用分层的方法,一般分驱动核心层还有硬件相关层

核心层会帮你完成字符设备的分配,fops的设置,注册字符设备,并向硬件相关层提供一个相应的对象和注册接口

硬件相关层则需要分配相应的对象,设置对象和对象的fops,并注册到核心层中

当应用层发生系统调用,会先来到核心层,核心层再通过回调函数调用到硬件相关层的驱动

对于V4L2的驱动框架也是如此,可分为V4L2驱动核心层硬件相关层

下面先用一张图来总结大致V4L2的驱动框架

在这里插入图片描述

从图中可以看出V4L2分为核心层还有硬件相关层

核心层负责注册字符设备,然后提供video_device对象和相应的注册接口给硬件相关层使用

硬件相关层需要分配一个video_device并设置它,然后向核心层注册,核心层会为其注册字符设备并且创建设备节点(/dev/videox)。同时硬件相关层还需要分配和设置相应的v4l2_devicev4l2_subdev,其中v4l2_device的一个比较重要的意义就是管理v4l2_subdev,当然有一些驱动并不需要实现v4l2_subdev,此时v4l2_device的意义就不是很大了

当应用层通过/dev/video来操作设备的时候,首先会来到V4L2的核心层,核心层通过注册进的video_device的回调函数调用相应的操作函数,video_device可以直接操作硬件或者是通过v4l2_subdev来操作硬件

二、V4L2的数据结构

介绍完V4L2的驱动框架后,来看一看内核中各对象的数据结构

2.1 V4L2主要对象的数据结构

  • video_device

    struct video_device
    { 
         
        /* character device */
    	struct cdev *cdev;
        
        /* v4l2_device parent */
    	struct v4l2_device *v4l2_dev;
    
       	/* device ops */
    	const struct v4l2_file_operations *fops;
        
    	/* ioctl callbacks */
    	const struct v4l2_ioctl_ops *ioctl_ops;
    };
    

    可以看到video_device中含有一个cdev还有v4l2_device,此外还有fops和ioctl_ops,从应用层进行系统调用会经过v4l2的核心层回调到这里

    其中v4l2_file_operationsv4l2_ioctl_ops如下

    struct v4l2_file_operations { 
         
    	struct module *owner;
    	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    	unsigned int (*poll) (struct file *, struct poll_table_struct *);
    	long (*ioctl) (struct file *, unsigned int, unsigned long);
    	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    	unsigned long (*get_unmapped_area) (struct file *, unsigned long,
    				unsigned long, unsigned long, unsigned long);
    	int (*mmap) (struct file *, struct vm_area_struct *);
    	int (*open) (struct file *);
    	int (*release) (struct file *);
    };
    

    熟悉v4l2应用编程的应该都知道v4l2有很多ioctl操作,具体实现都在这里

    struct v4l2_ioctl_ops { 
         
    	int (*vidioc_querycap)(struct file *file, void *fh, struct v4l2_capability *cap);
    	/* Buffer handlers */
    	int (*vidioc_reqbufs) (struct file *file, void *fh, struct v4l2_requestbuffers *b);
    	int (*vidioc_querybuf)(struct file *file, void *fh, struct v4l2_buffer *b);
    	int (*vidioc_qbuf)    (struct file *file, void *fh, struct v4l2_buffer *b);
    	int (*vidioc_dqbuf)   (struct file *file, void *fh, struct v4l2_buffer *b);
        /* Stream on/off */
    	int (*vidioc_streamon) (struct file *file, void *fh, enum v4l2_buf_type i);
    	int (*vidioc_streamoff)(struct file *file, void *fh, enum v4l2_buf_type i);
       	...
    };
    
  • v4l2_device

    struct v4l2_device { 
         
    	/* used to keep track of the registered subdevs */
    	struct list_head subdevs;
        ...
    };
    

    可以看到v4l2_device中有一个v4l2_subdev的链表,v4l2_device的主要目的时用来管理v4l2_subdev

  • v4l2_subdev

    struct v4l2_subdev { 
         
    	struct list_head list;
    	struct v4l2_device *v4l2_dev;
    	const struct v4l2_subdev_ops *ops;
    };
    

    v4l2_subdev中有一个v4l2_subdev_ops,实现了一系列的操作,供v4l2_device调用

    struct v4l2_subdev_ops { 
         
    	const struct v4l2_subdev_core_ops	*core;
    	const struct v4l2_subdev_tuner_ops	*tuner;
    	const struct v4l2_subdev_audio_ops	*audio;
    	const struct v4l2_subdev_video_ops	*video;
    	const struct v4l2_subdev_vbi_ops	*vbi;
    	const struct v4l2_subdev_ir_ops		*ir;
    	const struct v4l2_subdev_sensor_ops	*sensor;
    };
    
    struct v4l2_subdev_core_ops { 
         
        ...
    	int (*s_config)(struct v4l2_subdev *sd, int irq, void *platform_data);
    	int (*init)(struct v4l2_subdev *sd, u32 val);
    	int (*s_gpio)(struct v4l2_subdev *sd, u32 val);
    	int (*g_ctrl)(struct v4l2_subdev *sd, struct v4l2_control *ctrl);
    	int (*s_ctrl)(struct v4l2_subdev *sd, struct v4l2_control *ctrl);
    	int (*s_std)(struct v4l2_subdev *sd, v4l2_std_id norm);
    	long (*ioctl)(struct v4l2_subdev *sd, unsigned int cmd, void *arg);
    	...
    };
    
    struct v4l2_subdev_video_ops { 
         
    	...
        int (*enum_fmt)(struct v4l2_subdev *sd, struct v4l2_fmtdesc *fmtdesc);
    	int (*g_fmt)(struct v4l2_subdev *sd, struct v4l2_format *fmt);
    	int (*try_fmt)(struct v4l2_subdev *sd, struct v4l2_format *fmt);
    	int (*s_fmt)(struct v4l2_subdev *sd, struct v4l2_format *fmt);
    	int (*cropcap)(struct v4l2_subdev *sd, struct v4l2_cropcap *cc);
    	int (*g_crop)(struct v4l2_subdev *sd, struct v4l2_crop *crop);
    	int (*s_crop)(struct v4l2_subdev *sd, struct v4l2_crop *crop);
    	int (*g_parm)(struct v4l2_subdev *sd, struct v4l2_streamparm *param);
    	int (*s_parm)(struct v4l2_subdev *sd, struct v4l2_streamparm *param);
    	...
    };
    

2.2 V4L2提供的注册接口

  • video_device

    注册

    int video_register_device(struct video_device *vdev, int type, int nr);
    

    注销

    void video_unregister_device(struct video_device *vdev);
    
  • 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_subdev(struct v4l2_subdev *sd);
    

三、源码剖析

3.1 V4L2驱动模板

此示例中并没有设计到v4l2_subdev,这部分将会在后面分析具体的驱动中出现

#include <...>

static struct video_device* video_dev;
static struct v4l2_device v4l2_dev;

/* 实现各种系统调用 */
static const struct v4l2_file_operations video_dev_fops = { 
   
	.owner		    = THIS_MODULE,
	.release        = vdev_close,
	.read           = vdev_read,
	.poll		    = vdev_poll,
	.ioctl          = video_ioctl2,
	.mmap           = vdev_mmap,
};

/* 实现各种系统调用 */
static const struct v4l2_ioctl_ops video_dev_ioctl_ops = { 
   
	.vidioc_querycap      = vidioc_querycap,
	.vidioc_enum_fmt_vid_cap  = vidioc_enum_fmt_vid_cap,
	.vidioc_g_fmt_vid_cap     = vidioc_g_fmt_vid_cap,
	.vidioc_try_fmt_vid_cap   = vidioc_try_fmt_vid_cap,
	.vidioc_s_fmt_vid_cap     = vidioc_s_fmt_vid_cap,
	.vidioc_reqbufs       = vidioc_reqbufs,
	.vidioc_querybuf      = vidioc_querybuf,
	.vidioc_qbuf          = vidioc_qbuf,
	.vidioc_dqbuf         = vidioc_dqbuf,
	.vidioc_enum_input    = vidioc_enum_input,
	.vidioc_g_input       = vidioc_g_input,
	.vidioc_s_input       = vidioc_s_input,
	.vidioc_streamon      = vidioc_streamon,
	.vidioc_streamoff     = vidioc_streamoff,
};

static int __init video_init(void)
{ 
   
    /* 分配并设置一个video_device */
    video_dev = video_device_alloc();
    video_dev->fops = &video_dev_fops;
    video_dev->ioctl_ops = &video_dev_ioctl_ops;
    video_dev->release = video_device_release;
    video_dev->tvnorms = V4L2_STD_525_60;
    video_dev->current_norm = V4L2_STD_NTSC_M;

    /* 注册一个v4l2_device */
    v4l2_device_register(video_dev->dev, &v4l2_dev);    
    video_dev->v4l2_dev = &video_dev;

    /* 注册一个video_device字符设备 */
    video_register_device(video_dev, VFL_TYPE_GRABBER, -1);

    return 0;
}

static void __exit video_exit(void)
{ 
   
    video_unregister_device(video_dev);
    v4l2_device_unregister(&v4l2_dev);
    video_device_release(video_dev);
}


module_init(video_init);
module_exit(video_exit);

如果你熟悉v4l2应用编程的话,你应该知道v4l2有许多ioctl操作,上面的模板就实现了很多ioctl操作

3.2 V4L2源码剖析

下面我们来分析分析源码

在上面的video_init中,我们分配并设置了video_dev,注册了v4l2_device(v4l2_device_register),然后向v4l2核心层注册video_device(video_register_device)

我们先来看v4l2_device_register

int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
{ 
   
    INIT_LIST_HEAD(&v4l2_dev->subdevs);
    spin_lock_init(&v4l2_dev->lock);
    dev_set_drvdata(dev, v4l2_dev);
    ...
}

从源码中可以看出v4l2_device_register并没有做什么事,只是初始化链表,自旋锁,还有设置数据,这函数并不是我们的重点

下面来仔细分析video_register_device

int video_register_device(struct video_device *vdev, int type, int nr)
{ 
      
    /* 分配字符设备 */
    vdev->cdev = cdev_alloc();
    
    /* 设置fops */
    vdev->cdev->ops = &v4l2_fops;
    
    /* 注册字符设备 */
    cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);

	/* 生成设备节点 */
	dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);
    device_register(&vdev->dev);
    
    /* 设置全局数组 */
    video_device[vdev->minor] = vdev;
}

可以看到这个函数会为video_device分配一个cdev,然后设置fops,向内核注册字符设备,再者生成设备节点

然后设置video_device全局数组,video_device一个全局数组

static struct video_device *video_device[VIDEO_NUM_DEVICES];

保存着注册的video_device

接下来看一下其中设置的fops(v4l2_fops)

static const struct file_operations v4l2_fops = { 
   
	.owner = THIS_MODULE,
	.read = v4l2_read,
	.write = v4l2_write,
	.open = v4l2_open,
	.get_unmapped_area = v4l2_get_unmapped_area,
	.mmap = v4l2_mmap,
	.ioctl = v4l2_ioctl,
	.release = v4l2_release,
	.poll = v4l2_poll,
	.llseek = no_llseek,
};

这个是video_device中的字符设备对应的fops,应用层发生系统调用会率先调用到这里,我们来好好看一看这些调用

  • v4l2_open

    static int v4l2_open(struct inode *inode, struct file *filp)
    { 
         
        struct video_device *vdev;
        
        /* 根据次设备获得video_device */
        vdev = video_devdata(filp);
        
        /* 回调video_device的fops */
    	if (vdev->fops->open)
    		ret = vdev->fops->open(filp); //回调video
    }
    

    从这个函数可以看到,发生系统调用首先来到v4l2核心层的字符设备,然后再回调到对应的video_device,video_device在前面已经实现了v4l2_file_operationsv4l2_ioctl_ops一系列回调

  • v4l2_ioctl

    V4L2的应用编程会有非常多的ioctl,会先调用到此处

    static int v4l2_ioctl(struct inode *inode, struct file *filp,
    		unsigned int cmd, unsigned long arg)
    { 
         
    	struct video_device *vdev = video_devdata(filp);
    
        /* 回调到video_device中 */
    	return vdev->fops->ioctl(filp, cmd, arg);
    }
    

    下面来看一看video_device怎么实现ioctl

    static const struct v4l2_file_operations video_dev_fops = { 
         
    	.owner		    = THIS_MODULE,
    	.release        = vdev_close,
    	.read           = vdev_read,
    	.poll		    = vdev_poll,
    	.ioctl          = video_ioctl2,
    	.mmap           = vdev_mmap,
    };
    

    从上面驱动程序的编写,我们可以知道video_device对应ioctl就是video_ioctl2,这个函数是内核提供的,我们看一看这个函数的内容

    long video_ioctl2(struct file *file,
    	       unsigned int cmd, unsigned long arg)
    { 
         
        __video_do_ioctl(file, cmd, parg);
    }
    
    static long __video_do_ioctl(struct file *file,
    		unsigned int cmd, void *arg)
    { 
         
        /* 获取video_device */
        struct video_device *vfd = video_devdata(file);
        
        /* 获取video_device的ioctl_ops */
        const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops;
    
        switch (cmd) { 
         
    	case VIDIOC_QUERYCAP:
            ops->vidioc_querycap(file, fh, cap);
        case VIDIOC_ENUM_FMT:
             ops->vidioc_enum_fmt_vid_cap(file, fh, f);
        ...
        }
    }
    

    可以看出,最终会调用到video_device实现的v4l2_ioctl_ops

    /* 实现各种系统调用 */
    static const struct v4l2_ioctl_ops video_dev_ioctl_ops = { 
         
    	.vidioc_querycap      = vidioc_querycap,
    	.vidioc_enum_fmt_vid_cap  = vidioc_enum_fmt_vid_cap,
    	.vidioc_g_fmt_vid_cap     = vidioc_g_fmt_vid_cap,
    	.vidioc_try_fmt_vid_cap   = vidioc_try_fmt_vid_cap,
    	.vidioc_s_fmt_vid_cap     = vidioc_s_fmt_vid_cap,
    	.vidioc_reqbufs       = vidioc_reqbufs,
    	.vidioc_querybuf      = vidioc_querybuf,
    	.vidioc_qbuf          = vidioc_qbuf,
    	.vidioc_dqbuf         = vidioc_dqbuf,
    	.vidioc_enum_input    = vidioc_enum_input,
    	.vidioc_g_input       = vidioc_g_input,
    	.vidioc_s_input       = vidioc_s_input,
    	.vidioc_streamon      = vidioc_streamon,
    	.vidioc_streamoff     = vidioc_streamoff,
    };
    

    所以系统调用最先都会调用到字符设备的fops,然后经过v4l2核心层最终调用到video_device这里

    关于v4l2的驱动框架就分析到这里,关于更加详细的实现v4l2驱动,将在后续文章中通过实例讲解

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/126617.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(4)


相关推荐

  • 链式求导法则公式_链式法则求导基础题

    链式求导法则公式_链式法则求导基础题原题链接“计算图”(computational graph)是现代深度学习系统的基础执行引擎,提供了一种表示任意数学表达式的方法,例如用有向无环图表示的神经网络。 图中的节点表示基本操作或输入变量,边表示节点之间的中间值的依赖性。 例如,下图就是一个函数 ( 的计算图。现在给定一个计算图,请你根据所有输入变量计算函数值及其偏导数(即梯度)。 例如,给定输入,,上述计算图获得函数值 (;并且根据微分链式法则,上图得到的梯度 ∇。知道你已经把微积分忘了,所以这里只要求你处理几个简单的算子:加法、减法、乘

  • vue组件之间的传值通信(vue props 对象 默认值)

    Vue通信、传值的多种方式,详解(都是干货):一、通过路由带参数进行传值①两个组件A和B,A组件通过query把orderId传递给B组件(触发事件可以是点击事件、钩子函数等)this.$router.push({path:’/conponentsB’,query:{orderId:123}})//跳转到B②在B组件中获取A组件传递过来的参数…

  • Android之Http通信——3.Android HTTP请求方式:HttpURLConnection

    Android之Http通信——3.Android HTTP请求方式:HttpURLConnection

  • python开发h5页面_python读取excel

    python开发h5页面_python读取excel广告关闭腾讯云11.11云上盛惠,精选热门产品助力上云,云服务器首年88元起,买的越多返的越多,最高返5000元!腾讯云api全新升级3.0,该版本进行了性能优化且全地域部署、支持就近和按地域接入、访问时延下降显著,接口描述更加详细、错误码描述更加全面、sdk增加接口级注释,让您更加方便快捷的使用腾讯云产品。这里针对pythonapi调用方式进行简单说明。现已支持云服务器(cv…

  • datagrip 2021 mac 激活码(在线激活)

    datagrip 2021 mac 激活码(在线激活),https://javaforall.cn/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

  • 插件8:拼写检查

    插件8:拼写检查&lt;?php//Plug-in8:SpellCheck//Thisisanexecutableexamplewithadditionalcodesupplie

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号