Chinaunix首页 | 论坛 | 博客
  • 博客访问: 311348
  • 博文数量: 101
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 774
  • 用 户 组: 普通用户
  • 注册时间: 2018-10-15 14:13
个人简介

搭建一个和linux开发者知识共享和学习的平台

文章分类

全部博文(101)

文章存档

2024年(15)

2023年(24)

2022年(27)

2019年(8)

2018年(27)

分类: 嵌入式

2024-10-16 10:40:01

struct v4l2_device:硬件设备可能包含多个子设备,例如电视卡以及捕获设备,可能还有 VBI 设备或 FM 调谐器。v4l2_device是所有这些设备的根节点,负责管理所有子设备。

struct video_device:此结构的主要目的是提供众所周知的/dev/videoX或/dev/v4l-subdevX设备节点。此结构主要抽象了捕获接口,也称为/dev/v4l-subdevX节点及其文件操作。在子设备驱动程序中,只有核心访问底层子设备中的这个结构。

struct vb2_queue:对我来说,这是视频驱动程序中的主要数据结构,因为它在数据流逻辑和 DMA 操作的中心部分中使用,以及struct vb2_v4l2_buffer。

struct v4l2_subdev:这是负责实现特定功能并在 SoC 的视频系统中抽象特定功能的子设备。

struct video_device 可以被视为所有设备和子设备的基类。

初始化和注册 V4L2 设备


在被使用或成为系统的一部分之前,V4L2 设备必须被初始化和注册。V4L2 设备是struct v4l2_device结构的一个实例。这是媒体框架中的{BANNED}{BANNED}{BANNED}{BANNED}{BANNED}最佳佳佳佳佳高数据结构,维护着媒体管道由哪些子设备组成,并充当桥接设备的父级。
struct v4l2_device {
    struct device *dev;
    struct media_device *mdev;
    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);
};

dev是指向此 V4L2 设备的父struct device的指针。这将在注册时自动设置,dev->driver_data将指向这个v4l2结构。

mdev是指向此 V4L2 设备所属的struct media_device对象的指针。这个字段涉及媒体控制器框架,并将在相关部分介绍。如果不需要与媒体控制器框架集成,则可能为NULL。

subdevs是此 V4L2 设备的子设备列表。

lock是保护对此结构的访问的锁。

name是此 V4L2 设备的唯一名称。默认情况下,它是从驱动程序名称加上总线 ID 派生的。

notify是指向通知回调的指针,由子设备调用以通知此 V4L2 设备某些事件。

ctrl_handler是与此设备关联的控制处理程序。它跟踪此 V4L2 设备拥有的所有控件。如果没有控件,则可能为NULL。

prio是设备的优先级状态。

ref是核心用于引用计数的内部使用。

release是当此结构的{BANNED}最佳佳佳佳佳后一个用户退出时要调用的回调函数。


这个顶层结构通过相同的函数v4l2_device_register()初始化并注册到核心,其原型如下:
int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev);

{BANNED}中国第一个dev参数通常是桥接总线相关设备数据结构的 struct device 指针。即pci_dev、usb_device或platform_device。

如果dev->driver_data字段为NULL,此函数将使其指向正在注册的实际v4l2_dev对象。此外,如果v4l2_dev->name为空,则将设置为从dev driver name + dev device name的连接结果。

另一方面,可以使用 v4l2_device_unregister() 注销先前注册的 V4L2 设备,如下所示:
v4l2_device_unregister(struct v4l2_device *v4l2_dev);

调用此函数时,所有子设备也将被注销。这一切都与 V4L2 设备有关。但是,您应该记住,它是顶层结构,维护媒体设备的子设备列表,并充当桥接设备的父级。

桥接驱动程序

桥接驱动程序直接处理的主要数据结构之一是 struct video_device。此结构嵌入了执行视频流所需的整个元素,它与用户空间的{BANNED}中国第一个交互之一是在 /dev/ 目录中创建设备文件。

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;
    u32 device_caps;
    struct device dev; struct cdev *cdev;
    struct v4l2_device *v4l2_dev;
    struct device *dev_parent;
    struct v4l2_ctrl_handler *ctrl_handler;
    struct vb2_queue *queue;
    struct v4l2_prio_state *prio;
    char name[32];
    enum vfl_devnode_type vfl_type;
    enum vfl_devnode_direction vfl_dir;
    int minor;
    u16 num;
    unsigned long flags; int index;
    spinlock_t fh_lock;
    struct list_head fh_list;
    void (*release)(struct video_device *vdev);
    const struct v4l2_ioctl_ops *ioctl_ops;
    DECLARE_BITMAP(valid_ioctls, BASE_VIDIOC_PRIVATE);
    struct mutex *lock;
};
entity、intf_node 和 pipe 是与媒体框架集成的一部分,我们将在同名部分中看到。前者从媒体框架内部抽象出视频设备(成为实体),而 intf_node 表示媒体接口设备节点,pipe 表示实体所属的流水线。

fops 表示视频设备文件节点的文件操作。V4L2 核心通过一些子系统所需的额外逻辑覆盖虚拟设备文件操作。

cdev 是字符设备结构,抽象出底层的 /dev/videoX 文件节点。vdev->cdev->ops 由 V4L2 核心设置为 v4l2_fops(在 drivers/media/v4l2-core/v4l2-dev.c 中定义)。v4l2_fops 实际上是一个通用的(在实现的操作方面)和面向 V4L2 的(在这些操作所做的方面)文件操作,分配给每个 /dev/videoX 字符设备,并包装在 vdev->fops 中定义的视频设备特定操作。在它们的返回路径上,v4l2_fops 中的每个回调将调用 vdev->fops 中的对应项。v4l2_fops 回调在调用 vdev->fops 中的真实操作之前执行一些合理性检查。例如,在用户空间对 /dev/videoX 文件发出的 mmap() 系统调用上,将首先调用 v4l2_fops->mmap,这将确保在调用之前设置了 vdev->fops->mmap,并在需要时打印调试消息。

ctrl_handler:默认值为 vdev->v4l2_dev->ctrl_handler。

queue 是与此设备节点关联的缓冲区管理队列。这是桥接驱动程序唯一可以操作的数据结构之一。这可能是 NULL,特别是当涉及非桥接视频驱动程序(例如子设备)时。

prio 是指向具有设备优先级状态的 &struct v4l2_prio_state 的指针。如果此状态为 NULL,则将使用 v4l2_dev->prio。

name 是视频设备的名称。

vfl_type 是 V4L 设备类型。可能的值由 enum vfl_devnode_type 定义,包括以下内容:

– VFL_TYPE_GRABBER:用于视频输入/输出设备
– VFL_TYPE_VBI:用于垂直空白数据(未解码)
– VFL_TYPE_RADIO:用于无线电卡
– VFL_TYPE_SUBDEV:用于 V4L2 子设备
– VFL_TYPE_SDR:软件定义无线电
– VFL_TYPE_TOUCH:用于触摸传感器

vfl_dir 是一个 V4L 接收器、发射器或内存到内存(表示为 m2m 或 mem2mem)设备。可能的值由 enum vfl_devnode_direction 定义,包括以下内容:
– VFL_DIR_RX:用于捕获设备
– VFL_DIR_TX:用于输出设备
– VFL_DIR_M2M:应该是 mem2mem 设备(读取内存到内存,也称为内存到内存设备)。mem2mem 设备是使用用户空间应用程序传递的内存缓冲区作为源和目的地的设备。这与当前和现有的仅使用其中一个的内存缓冲区的驱动程序不同。这样的设备在 V4L2 框架中不存在,但是存在对这种模型的需求,例如,用于 ‘调整器设备’ 或 V4L2 回环驱动程序。

v4l2_dev 是此视频设备的 v4l2_device 父设备。

dev_parent 是此视频设备的设备父级。如果未设置,核心将使用 vdev->v4l2_dev->dev 进行设置。

ioctl_ops 是指向 &struct v4l2_ioctl_ops 的指针,它定义了一组 ioctl 回调。

release 是核心在视频设备的{BANNED}最佳后一个用户退出时调用的回调。这必须是非-NULL。

lock 是一个互斥锁,用于串行访问此设备。这是主要的串行化锁,通过它所有的 ioctls 都被串行化。桥接驱动程序通常会使用相同的互斥锁设置此字段,就像 queue->lock 一样,这是用于串行化访问队列的锁(串行化流)。但是,如果设置了 queue->lock,那么流 ioctls 将由单独的锁串行化。

num 是核心分配的实际设备节点索引。它对应于 /dev/videoX 中的 X。

flags 是视频设备的标志。您应该使用位操作来设置/清除/测试标志。它们包含一组 &enum v4l2_video_device_flags 标志。

fh_list 是一个 struct v4l2_fh 列表,描述了一个 V4L2 文件处理程序,可以跟踪为此视频设备打开的文件句柄的数量。fh_lock 是与此列表关联的锁。

class 对应于 sysfs 类。它由核心分配。此类条目对应于 /sys/video4linux/ sysfs 目录。

初始化和注册视频设备

在注册之前,视频设备可以动态分配,使用 video_device_alloc()(简单调用 kzalloc()),或者静态嵌入到动态分配的结构中,这是大多数情况下的设备状态结构。

视频设备是使用 video_device_alloc() 动态分配的,就像以下示例中一样:
struct video_device * vdev;
vdev = video_device_alloc();
if (!vdev)
    return ERR_PTR(-ENOMEM);
vdev->release = video_device_release;

{BANNED}最佳后一行提供了视频设备的 release 方法,因为 .release 字段必须是非-NULL。内核提供了 video_device_release() 回调。它只调用 kfree() 来释放分配的内存。

我们已经完成了分配。在这一点上,我们可以使用 video_register_device() 来注册视频设备。
int video_register_device(struct video_device *vdev,  enum vfl_devnode_type type, int nr)

上述原型中,type 指定了要注册的桥接设备的类型。它将被分配给 vdev->vfl_type 字段。在本章的其余部分,我们将考虑将其设置为 VFL_TYPE_GRABBER,因为我们正在处理视频捕获接口。nr 是所需的设备节点号(0 == /dev/video0,1 == /dev/video1,…)。但是,将其值设置为 -1 将指示内核选择{BANNED}中国第一个空闲索引并使用它。指定固定索引可能对构建复杂的 udev 规则很有用,因为设备节点名称是预先知道的。为了使注册成功,必须满足以下要求:

首先,必须 设置 vdev->release 函数,因为它不能是空的。如果不需要它,可以传递 V4L2 核心的空释放方法。

其次,必须 设置 vdev->v4l2_dev 指针;它应该指向视频设备的 V4L2 父设备。

{BANNED}最佳后,但不是强制的,您应该设置 vdev->fops 和 vdev->ioctl_ops。

video_register_device() 在成功时返回 0。但是,如果没有空闲的次要设备,找不到设备节点号,或者设备节点的注册失败,它可能会失败。在任何错误情况下,它都会返回一个负的错误号。每个注册的视频设备都会在 /sys/class/video4linux 中创建一个目录条目,并在其中包含一些属性。

如果注册失败,vdev->release() 回调将永远不会被调用。在这种情况下,如果动态分配了 video_device 结构,您需要调用 video_device_release() 来释放它,或者如果 video_device 被嵌入其中,则释放您自己的结构。

在驱动程序卸载路径上,或者当不再需要视频节点时,您应该调用 video_unregister_device() 来注销视频设备,以便其节点可以被移除:

void video_unregister_device(struct video_device *vdev)

在上述调用之后,设备的 sysfs 条目将被移除,导致 udev 移除 /dev/ 中的节点。

到目前为止,我们只讨论了注册过程中{BANNED}最佳简单的部分,但是视频设备中还有一些复杂的字段需要在注册之前初始化。这些字段通过提供视频设备文件操作、一致的一组 ioctl 回调以及{BANNED}最佳重要的是媒体队列和内存管理接口来扩展驱动程序的功能。

视频设备文件操作

视频设备(通过其驱动程序)旨在作为 /dev/ 目录中的特殊文件暴露给用户空间,用户空间可以使用它与底层设备进行交互:流式传输数据。为了使视频设备能够响应用户空间查询(通过系统调用),必须从驱动程序内部实现一组标准回调。这些回调形成了今天所知的 struct v4l2_file_operations 类型,定义在 include/media/v4l2-dev.h 中,如下所示:
struct v4l2_file_operations {
    struct module *owner;
    ssize_t (*read) (struct file *file, char user *buf, size_t, loff_t *ppos);
    ssize_t (*write) (struct file *file, const char user *buf, size_t, loff_t *ppos);
    poll_t (*poll) (struct file *file, struct poll_table_struct *);
    long (*unlocked_ioctl) (struct file *file, unsigned int cmd, unsigned long arg);
#ifdef CONFIG_COMPAT
     long (*compat_ioctl32) (struct file *file, unsigned int cmd, unsigned long arg);
#endif
    unsigned long (*get_unmapped_area) (struct file *file,
                              unsigned long, unsigned long, unsigned long, unsigned long);
    int (*mmap) (struct file *file, struct vm_area_struct *vma);
    int (*open) (struct file *file);
    int (*release) (struct file *file);
};
这些可以被视为顶层回调,因为它们实际上是由另一个低级设备文件操作调用的(当然,经过一些合理性检查),这次是与 vdev->cdev 字段相关联的低级设备文件操作,设置为 vdev->cdev->ops = &v4l2_fops; 在文件节点创建时。这允许内核实现额外的逻辑并强制执行合理性:

owner 是指向模块的指针。大多数情况下,它是 THIS_MODULE。

open应包含实现open()系统调用所需的操作。大多数情况下,这可以设置为v4l2_fh_open,这是一个 V4L2 助手,简单地分配和初始化一个v4l2_fh结构,并将其添加到vdev->fh_list列表中。但是,如果您的设备需要一些额外的初始化,请在内部执行初始化,然后调用v4l2_fh_open(struct file * filp)。无论如何,您必须处理v4l2_fh_open。

release应包含实现close()系统调用所需的操作。这个回调必须处理v4l2_fh_release。它可以设置为以下之一:

vb2_fop_release,这是一个 videobuf2-V4L2 释放助手,将清理任何正在进行的流。这个助手将调用v4l2_fh_release。

撤销.open中所做的工作的自定义回调,并且必须直接或间接调用v4l2_fh_release(例如,使用_vb2_fop_release()助手),以便 V4L2 核心处理任何正在进行的流的清理。

read应包含实现read()系统调用所需的操作。大多数情况下,videobuf2-V4L2 助手vb2_fop_read就足够了。

write在我们的情况下不需要,因为它是用于输出类型设备。但是,在这里使用vb2_fop_write可以完成工作。

如果您使用v4l2_ioctl_ops,则必须将unlocked_ioctl设置为video_ioctl2。下一节将详细解释这一点。这个 V4L2 核心助手是__video_do_ioctl()的包装器,它处理真正的逻辑,并将每个 ioctl 路由到vdev->ioctl_ops中的适当回调,这是单独的 ioctl 处理程序定义的地方。

mmap应包含实现mmap()系统调用所需的操作。大多数情况下,videobuf2-V4L2 助手vb2_fop_mmap就足够了,除非在执行映射之前需要额外的元素。内核中的视频缓冲区(响应于VIDIOC_REQBUFSioctl 而分配)在被访问用户空间之前必须单独映射。这就是这个.mmap回调的目的,它只需要将一个视频缓冲区映射到用户空间。查询将缓冲区映射到用户空间所需的信息是使用VIDIOC_QUERYBUFioctl 向内核查询的。

poll应包含实现poll()系统调用所需的操作。大多数情况下,videobuf2-V4L2 助手vb2_fop_call就足够了。如果这个助手不知道如何锁定(queue->lock和vdev->lock都没有设置),那么您不应该使用它,而应该编写自己的助手,可以依赖于不处理锁定的vb2_poll()助手。

在这两个回调中,您可以使用v4l2_fh_is_singular_file()助手来检查给定的文件是否是关联video_device的唯一文件句柄。它的替代方法是v4l2_fh_is_singular(),这次依赖于v4l2_fh。

V4L2 ioctl 处理

让我们再谈谈v4l2_file_operations.unlocked_ioctl回调。正如我们在前一节中所看到的,它应该设置为video_ioctl2。video_ioctl2负责在内核和用户空间之间进行参数复制,并在将每个单独的ioctl()调用分派到驱动程序之前执行一些合理性检查(例如,ioctl 命令是否有效),这{BANNED}{BANNED}{BANNED}最佳佳佳终会进入video_device->ioctl_ops字段中的回调条目,该字段是struct v4l2_ioctl_ops类型。

struct v4l2_ioctl_ops结构包含了 V4L2 框架中每个可能的 ioctl 的回调。然而,你应该根据你的设备类型和驱动程序的能力来设置这些回调。
struct v4l2_ioctl_ops {
    /* VIDIOC_QUERYCAP handler */
    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_expbuf)(struct file *file, void *fh, struct v4l2_exportbuffer *e);
    int (*vidioc_dqbuf)(struct file *file, void *fh, struct v4l2_buffer *b);
    int (*vidioc_create_bufs)(struct file *file, void *fh, struct v4l2_create_buffers *b);
    int (*vidioc_prepare_buf)(struct file *file, void *fh, struct v4l2_buffer *b);
    int (*vidioc_overlay)(struct file *file, void *fh, unsigned int i);
[...]
};

videobuf2 接口和 API

videobuf2 框架用于连接 V4L2 驱动程序层和用户空间层,提供了一个数据交换通道,可以分配和管理视频帧数据。videobuf2 内存管理后端是完全模块化的。这允许为具有非标准内存管理要求的设备和平台插入自定义内存管理例程,而无需更改高级缓冲区管理函数和 API。该框架提供以下功能:
  • 实现流式 I/O V4L2 ioctls 和文件操作

  • 高级视频缓冲区、视频队列和状态管理功能

  • 视频缓冲区内存分配和管理

Videobuf2(或者只是 vb2)促进了驱动程序的开发,减少了驱动程序的代码大小,并有助于在驱动程序中正确和一致地实现 V4L2 API。然后 V4L2 驱动程序负责从传感器(通常通过某种 DMA 控制器)获取视频数据,并将其提供给由 vb2 框架管理的缓冲区。

这个框架实现了许多 ioctl 函数,包括缓冲区分配、入队、出队和数据流控制。

缓冲区的概念

缓冲区是在 vb2 和用户空间之间进行单次数据交换的单位。从用户空间代码的角度来看,V4L2 缓冲区代表与视频帧对应的数据(例如,在捕获设备的情况下)。流式传输涉及在内核和用户空间之间交换缓冲区。vb2 使用struct vb2_buffer数据结构来描述视频缓冲区。
struct vb2_buffer {
    struct vb2_queue *vb2_queue;
    unsigned int index;
    unsigned int type;
    unsigned int memory;
    unsigned int num_planes;
    u64 timestamp;
    /* private: internal use only
     *
     * state: current buffer state; do not change
     * queued_entry: entry on the queued buffers list, which
     * holds all buffers queued from userspace
     * done_entry: entry on the list that stores all buffers
     * ready to be dequeued to userspace
     * vb2_plane: per-plane information; do not change
     */
    enum vb2_buffer_state state;
    struct vb2_plane planes[VB2_MAX_PLANES];
    struct list_head queued_entry;
    struct list_head done_entry;
[...]
};

vb2_queue是这个缓冲区所属的vb2队列。

index是这个缓冲区的 ID。

type是缓冲区的类型。它由vb2在分配时设置。它与其所属队列的类型匹配:vb->type = q->type。

memory是用于使缓冲区在用户空间可见的内存模型类型。此字段的值是enum vb2_memory类型,与其 V4L2 用户空间对应项enum v4l2_memory相匹配。此字段由vb2在缓冲区分配时设置,并报告了与vIDIOC_REQBUFS给定的v4l2_requestbuffers的.memory字段分配的用户空间值的 vb2 等价项。可能的值包括以下内容:

VB2_MEMORY_MMAP:其在用户空间分配的等价物是V4L2_MEMORY_MMAP,表示缓冲区用于内存映射 I/O。

VB2_MEMORY_USERPTR:其在用户空间分配的等价物是V4L2_MEMORY_USERPTR,表示用户在用户空间分配缓冲区,并通过v4l2_buffer的buf.m.userptr成员传递指针。V4L2 中USERPTR的目的是允许用户直接通过malloc()或静态方式传递在用户空间分配的缓冲区。

VB2_MEMORY_DMABUF。其在用户空间分配的等价物是V4L2_MEMORY_DMABUF,表示内存由驱动程序分配并导出为 DMABUF 文件处理程序。这个 DMABUF 文件处理程序可以在另一个驱动程序中导入。

state是enum vb2_buffer_state类型,表示此视频缓冲区的当前状态。驱动程序可以使用void vb2_buffer_done(struct vb2_buffer *vb, enum vb2_buffer_state state) API 来更改此状态。可能的状态值包括以下内容:

B2_BUF_STATE_DEQUEUED 表示缓冲区在用户空间控制之下。这是由 videobuf2 核心在VIDIOC_REQBUFS ioctl 的执行路径中设置的。

VB2_BUF_STATE_PREPARING 表示缓冲区正在 videobuf2 中准备。这个标志是由 videobuf2 核心在支持的驱动程序的VIDIOC_PREPARE_BUF ioctl 的执行路径中设置的。

VB2_BUF_STATE_QUEUED 表示缓冲区在 videobuf 中排队,但尚未在驱动程序中。这是由 videobuf2 核心在VIDIOC_QBUF ioctl 的执行路径中设置的。然而,如果驱动程序无法启动流,则驱动程序必须将所有缓冲区的状态设置为VB2_BUF_STATE_QUEUED。这相当于将缓冲区返回给 videobuf2。

VB2_BUF_STATE_ACTIVE 表示缓冲区实际上在驱动程序中排队,并可能在硬件操作(例如 DMA)中使用。驱动程序无需设置此标志,因为在调用缓冲区.buf_queue回调之前,核心会设置此标志。

VB2_BUF_STATE_DONE 表示驱动程序应在此缓冲区的 DMA 操作成功路径上设置此标志,以将缓冲区传递给 vb2。这意味着 videobuf2 核心从驱动程序返回缓冲区,但尚未将其出队到用户空间。

VB2_BUF_STATE_ERROR 与上述相同,但是对缓冲区的操作以错误结束,当它被出队时将向用户空间报告。

平面的概念 有些设备要求每个输入或输出视频帧的数据放在不连续的内存缓冲区中。在这种情况下,一个视频帧必须使用多个内存地址来寻址,换句话说,每个“平面”有一个指针。平面是当前帧的子缓冲区(或帧的一部分)。

因此,在单平面系统中,一个平面代表整个视频帧,而在多平面系统中,一个平面只代表视频帧的一部分。由于内存是不连续的,多平面设备使用 Scatter/Gather DMA。

队列的概念

队列是流处理的中心元素,是桥接驱动程序的 DMA 引擎相关部分。实际上,它是驱动程序向 videobuf2 介绍自己的元素。它帮助我们在驱动程序中实现数据流管理模块。

struct vb2_queue {
    unsigned int type;
    unsigned int io_modes;
    struct device *dev;
    struct mutex *lock;
    const struct vb2_ops *ops;
    const struct vb2_mem_ops *mem_ops;
    const struct vb2_buf_ops *buf_ops;
    u32 min_buffers_needed;
    gfp_t gfp_flags;
    void *drv_priv;
    struct vb2_buffer *bufs[VB2_MAX_FRAME];
    unsigned int num_buffers;
    /* Lots of private and debug stuff omitted */
    [...]
};
type 是缓冲区类型。这应该使用include/uapi/linux/videodev2.h中定义的enum v4l2_buf_type中的一个值进行设置。在我们的情况下,这必须是V4L2_BUF_TYPE_VIDEO_CAPTURE。

io_modes是描述可以处理的缓冲区类型的位掩码。可能的值包括以下内容:

VB2_MMAP:在内核中分配并通过mmap()访问的缓冲区;vmalloc’ed 和连续 DMA 缓冲区通常属于这种类型。

VB2_USERPTR:这是为用户空间分配的缓冲区。通常,只有可以进行散射/聚集 I/O 的设备才能处理用户空间缓冲区。

VB2_READ, VB2_WRITE:这些是通过read()和write()系统调用提供的用户空间缓冲区。

lock是用于流 ioctls 的串行化锁的互斥体。通常将此锁与video_device->lock相同,这是主要的串行化锁。但是,如果一些非流 ioctls 需要很长时间才能执行,那么您可能希望在这里使用不同的锁,以防止VIDIOC_DQBUF在等待另一个操作完成时被阻塞。

ops代表特定于驱动程序的回调,用于设置此队列和控制流操作。它是struct vb2_ops类型。

mem_ops字段是驱动程序告诉 videobuf2 它实际使用的缓冲区类型的地方;它应该设置为vb2_vmalloc_memops、vb2_dma_contig_memops或vb2_dma_sg_memops中的一个。这是 videobuf2 实现的三种基本类型的缓冲区分配:

  • {BANNED}中国第一种是vmalloc(),因此在内核空间中是虚拟连续的,不保证在物理上是连续的。

  • 第二种是vb2_mem_ops,以满足这种需求。没有限制。

您可能不关心buf_ops,因为如果未设置,它由vb2核心提供。但是,它包含了在用户空间和内核空间之间传递缓冲区信息的回调。

min_buffers_needed是在开始流之前需要的{BANNED}最佳小缓冲区数量。如果这个值不为零,那么只有用户空间排队了至少这么多的缓冲区,vb2_queue->ops->start_streaming才会被调用。换句话说,它表示 DMA 引擎在启动之前需要有多少可用的缓冲区。

bufs是此队列中缓冲区的指针数组。它的{BANNED}最佳大值是VB2_MAX_FRAME,这对应于vb2核心允许每个队列的{BANNED}最佳大缓冲区数量。它被设置为32,这已经是一个相当可观的值。

num_buffers是队列中已分配/已使用的缓冲区数量。

特定于驱动程序的流回调

桥接驱动程序需要公开一系列函数来管理缓冲区队列,包括队列和缓冲区初始化。这些函数将处理来自用户空间的缓冲区分配、排队和与流相关的请求。
struct vb2_ops {
    int (*queue_setup)(struct vb2_queue *q, unsigned int *num_buffers,   unsigned int *num_planes, unsigned int sizes[],  struct device *alloc_devs[]);
    void (*wait_prepare)(struct vb2_queue *q);
    void (*wait_finish)(struct vb2_queue *q);
    int (*buf_init)(struct vb2_buffer *vb);
    int (*buf_prepare)(struct vb2_buffer *vb);
    void (*buf_finish)(struct vb2_buffer *vb);
    void (*buf_cleanup)(struct vb2_buffer *vb);
    int (*start_streaming)(struct vb2_queue *q,  unsigned int count);
    void (*stop_streaming)(struct vb2_queue *q);
    void (*buf_queue)(struct vb2_buffer *vb);
};

queue_setup:此回调函数由驱动程序的v4l2_ioctl_ops.vidioc_reqbufs()方法调用(响应VIDIOC_REQBUFS和VIDIOC_CREATE_BUFS ioctls),以调整缓冲区计数和大小。此回调的目标是通知 videobuf2-core 需要多少个缓冲区和每个缓冲区的平面,以及每个平面的大小和分配器上下文。换句话说,所选的 vb2 内存分配器调用此方法与驱动程序协商在流媒体期间使用的缓冲区和每个缓冲区的平面数量。3是一个很好的选择作为{BANNED}最佳小缓冲区数量,因为大多数 DMA 引擎至少需要队列中的2个缓冲区。此回调的参数定义如下:

q是vb2_queue指针。

num_buffers是应用程序请求的缓冲区数量的指针。然后,驱动程序应在此*num_buffers字段中设置分配的缓冲区数量。由于此回调在协商过程中可能会被调用两次,因此应检查queue->num_buffers以了解在设置此值之前已分配的缓冲区数量。

num_planes包含保存帧所需的不同视频平面的数量。这应该由驱动程序设置。

sizes包含每个平面的大小(以字节为单位)。对于单平面系统,只需设置size[0]。

alloc_devs是一个可选的每平面分配器特定设备数组。将其视为分配上下文的指针。

buf_init在为缓冲区分配内存后或在新的USERPTR缓冲区排队后会被调用一次。例如,可以用来固定页面,验证连续性,并设置 IOMMU 映射。

buf_prepare在VIDIOC_QBUF ioctl 的执行路径上被调用。它应该准备好缓冲区以排队到 DMA 引擎。缓冲区被准备好,并且用户空间虚拟地址或用户地址被转换为物理地址。

buf_finish在每个DQBUF ioctl 上被调用。例如,可以用于缓存同步和从反弹缓冲区复制回来。

buf_cleanup在释放内存之前调用。可以用于取消映射内存等。

buf_queue:videobuf2 核心在调用此回调之前在缓冲区中设置VB2_BUF_STATE_ACTIVE标志。但是,它是代表VIDIOC_QBUF ioctl 调用的。用户空间逐个排队缓冲区,一个接一个。此外,缓冲区可能会比桥接设备从捕获设备抓取数据到缓冲区的速度更快。与此同时,在发出VIDIOC_DQBUF之前可能会多次调用VIDIOC_QBUF。建议驱动程序维护一个排队用于 DMA 的缓冲区列表,以便在任何 DMA 完成时,填充的缓冲区被移出列表,同时通过填充其时间戳并将缓冲区添加到 videobuf2 的完成缓冲区列表中,如果需要,则更新 DMA 指针。粗略地说,此回调函数应将缓冲区添加到驱动程序的 DMA 队列中,并在该缓冲区上启动 DMA。与此同时,驱动程序通常会重新实现自己的缓冲区数据结构,建立在通用的vb2_v4l2_buffer结构之上,但添加一个列表以解决我们刚才描述的排队问题。

start_streaming启动了流式传输的 DMA 引擎。在开始流式传输之前,必须首先检查是否已排队了{BANNED}最佳少数量的缓冲区。如果没有,应返回-ENOBUFS,vb2框架将在下次缓冲区排队时再次调用此函数,直到有足够的缓冲区可用于实际启动 DMA 引擎。如果支持以下操作,还应在子设备上启用流式传输:v4l2_subdev_call(subdev, video, s_stream, 1)。应从缓冲区队列中获取下一帧并在其上启动 DMA。通常,在捕获新帧后会发生中断。处理程序的工作是从内部缓冲区中删除新帧(使用list_del())并将其返回给vb2框架(通过vb2_buffer_done()),同时更新序列计数字段和时间戳。

stop_streaming停止所有待处理的 DMA 操作,停止 DMA 引擎,并释放 DMA 通道资源。如果支持以下操作,还应在子设备上禁用流式传输:v4l2_subdev_call(subdev, video, s_stream, 0)。如有必要,禁用中断。由于驱动程序维护了排队进行 DMA 的缓冲区列表,因此必须将该列表中排队的所有缓冲区以错误状态返回给 vb2。

初始化和释放 vb2 队列

为了使驱动程序完成队列初始化,应调用vb2_queue_init()函数,给定队列作为参数。但是,vb2_queue结构应首先由驱动程序分配。此外,驱动程序必须清除其内容并为一些必需的条目设置初始值,然后才能调用此函数。这些必需的值是q->ops、q->mem_ops、q->type和q->io_modes。否则,队列初始化将失败,如下所示的vb2_core_queue_init()函数将会被调用,并且从vb2_queue_init()中检查其返回值。

子设备的概念

处理管道中的每个 IP 块(除了桥接设备)都被视为一个子设备,甚至包括摄像头传感器本身。桥接视频设备节点采用/dev/videoX模式,而子设备则采用/dev/v4l-subdevX模式(假设它们在创建节点之前已设置了适当的标志)。

为了更好地理解桥接设备和子设备之间的区别,可以将桥接设备视为处理管道中的{BANNED}最佳终元素

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;
[...]
    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 device_node *of_node;
    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;
};

list是list_head类型,并由核心用于将当前子设备插入v4l2_device维护的子设备列表中。

owner由核心设置,表示拥有此结构的模块。

flags表示驱动程序可以设置的子设备标志,可以具有以下值:

如果此子设备实际上是 I2C 设备,则应设置V4L2_SUBDEV_FL_IS_I2C标志。

如果此子设备是 SPI 设备,则应设置V4L2_SUBDEV_FL_IS_SPI。

如果子设备需要设备节点(著名的/dev/v4l-subdevX条目),则应设置V4L2_SUBDEV_FL_HAS_DEVNODE。使用此标志的 API 是v4l2_device_register_subdev_nodes(),稍后将讨论并由桥接调用以创建子设备节点条目。

V4L2_SUBDEV_FL_HAS_EVENTS表示此子设备生成事件。

v4l2_dev由核心在子设备注册时设置,并指向此子设备所属的struct 4l2_device的指针。

ops是可选的。这是指向struct v4l2_subdev_ops的指针,由驱动程序设置以提供核心可以依赖的此子设备的回调。

ctrl_handler是指向struct v4l2_ctrl_handler的指针。它表示此子设备提供的控件列表,我们将在V4L2 控件基础设施部分中看到。

name是子设备的唯一名称。在子设备初始化后,驱动程序应设置它。对于 I2C 变体的初始化,核心分配的默认名称是("%s %d-%04x", driver->name, i2c_adapter_id(client->adapter), client->addr)。在包括媒体控制器支持时,此名称用作媒体实体名称。

grp_id是驱动程序特定的,在异步模式下由核心提供,并用于对类似的子设备进行分组。

dev_priv是设备的私有数据指针(如果有的话)。

host_priv是指向设备的私有数据的指针,用于连接子设备的设备。

devnode是此子设备的设备节点,由核心在调用v4l2_device_register_subdev_nodes()时设置,不要与基于相同结构构建的桥接设备混淆。您应该记住,每个v4l2元素(无论是子设备还是桥接)都是视频设备。

dev是指向物理设备的指针(如果有的话)。驱动程序可以使用void v4l2_set_subdevdata(struct v4l2_subdev *sd, void *p)设置此值,或者可以使用void *v4l2_get_subdevdata(const struct v4l2_subdev *sd)获取它。

fwnode是此子设备的固件节点对象句柄。在较旧的内核版本中,此成员曾经是struct device_node *of_node,并指向struct fwnode_handle,因为它允许根据在平台上使用的设备树节点/ACPI 设备进行切换。换句话说,它是dev->of_node->fwnode或dev->fwnode,以非NULL的方式。

async_list:当与异步核心注册时,此成员由核心用于将此子设备链接到全局subdev_list(这是一个孤立子设备的列表,不属于任何通知程序,这意味着此子设备在其父级桥之前注册)或其父级桥的notifier->done列表。

asd:此字段是struct v4l2_async_subdev类型,并在异步核心中抽象了这个子设备。

subdev_notifier:这是由此子设备隐式注册的通知程序,以防需要通知其他子设备的探测。它通常用于涉及多个子设备的流水线的系统,其中子设备 N 需要被通知子设备 N-1 的探测。

notifier:这是由异步核心设置的,并对应于其底层的.asd异步子设备匹配的通知程序。

pdata:这是子设备平台数据的常见部分。

子设备初始化

在被访问之前,V4L2 子设备需要使用v4l2_subdev_init() API 进行初始化。然而,当涉及到具有基于 I2C 或 SPI 的控制接口(通常是摄像头传感器)的子设备时,内核提供v4l2_spi_subdev_init()和v4l2_i2c_subdev_init()变体。

拥有指向子设备对象的指针,您可以使用v4l2_get_subdevdata()来获取底层特定于总线的结构。

子设备操作

子设备是以某种方式连接到主桥设备的设备。在整个媒体设备中,每个 IP(子设备)都有其自己的功能集。这些功能必须通过内核开发人员为常用功能定义的回调来向核心公开。这就是struct v4l2_subdev_ops的目的。

然而,一些子设备可以执行如此多不同和不相关的事情,以至于甚至 struct v4l2_subdev_ops 已经被分成小的和分类的一致的子结构操作,每个子结构操作都收集相关的功能,以便 struct v4l2_subdev_ops 成为顶级操作结构,描述如下:
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;
    const struct v4l2_subdev_pad_ops           *pad;
};

操作应该只为用户空间公开的子设备提供,通过底层字符设备文件节点。注册时,该设备文件节点将具有与前面讨论的相同的文件操作,即 v4l2_fops。然而,正如我们之前所看到的,这些低级操作只是包装(处理)video_device->fops。因此,为了达到 v4l2_subdev_ops,核心使用 subdev->video_device->fops 作为中间,并在初始化时分配另一个文件操作(subdev->vdev->fops = &v4l2_subdev_fops;),它将包装并调用真正的子设备操作。这里的调用链是 v4l2_fops ==> v4l2_subdev_fops ==> our_subdev_ops。

v4l2_subdev_core_ops 类型的 core:这是核心操作类别,提供通用的回调,比如日志记录和调试。它还允许提供额外和自定义的 ioctls(特别是当 ioctl 不适用于任何类别时非常有用)。

v4l2_subdev_video_ops 类型的 video:.s_stream 在流媒体开始时被调用。它根据所选择的帧大小和格式向摄像头的寄存器写入不同的配置值。

v4l2_subdev_pad_ops 类型的 pad:对于支持多个帧大小和图像采样格式的摄像头,这些操作允许用户从可用选项中进行选择。

v4l2_subdev_sensor_ops 类型的 sensor:这涵盖了摄像头传感器操作,通常用于已知有错误的传感器,需要跳过一些帧或行,因为它们已损坏。

每个类别结构中的每个回调对应一个 ioctl。路由实际上是由 subdev_do_ioctl() 在低级别执行的,该函数在 drivers/media/v4l2-core/v4l2-subdev.c 中定义,并间接地由 subdev_ioctl() 调用,对应于 v4l2_subdev_fops.unlocked_ioctl。真正的调用链应该是 v4l2_fops ==>v4l2_subdev_fops.unlocked_ioctl ==> our_subdev_ops。

这个顶级 struct v4l2_subdev_ops 结构的性质只是确认了 V4L2 可能支持的设备范围有多广。对于子设备驱动程序不感兴趣的操作类别可以保持 NULL。还要注意,.core 操作对所有子设备都是通用的。这并不意味着它是强制性的;它只是意味着任何类别的子设备驱动程序都可以实现 .core 操作,因为它的回调是与类别无关的。

struct v4l2_subdev_core_ops

这个结构实现了通用的回调

struct v4l2_subdev_core_ops {
    int (*log_status)(struct v4l2_subdev *sd);
    int (*load_fw)(struct v4l2_subdev *sd);
    long (*ioctl)(struct v4l2_subdev *sd, unsigned int cmd, void *arg);
[...]
#ifdef CONFIG_COMPAT
    long (*compat_ioctl32)(struct v4l2_subdev *sd,  unsigned int cmd,  unsigned long arg);
#endif
#ifdef CONFIG_VIDEO_ADV_DEBUG
   int (*g_register)(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg);
   int (*s_register)(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg);
#endif
   int (*s_power)(struct v4l2_subdev *sd, int on);
   int (*interrupt_service_routine)(struct v4l2_subdev *sd, u32 status,  bool *handled);
   int (*subscribe_event)(struct v4l2_subdev *sd,  struct v4l2_fh *fh, struct v4l2_event_subscription *sub);
   int (*unsubscribe_event)(struct v4l2_subdev *sd, struct v4l2_fh *fh,  struct v4l2_event_subscription *sub);
};

.log_status 用于记录目的。您应该使用 v4l2_info() 宏来实现这一点。

.s_power 将子设备(例如摄像头)置于省电模式(on==0)或正常操作模式(on==1)。

.load_fw 操作必须被调用以加载子设备的固件。

如果子设备提供额外的 ioctl 命令,应该定义 .ioctl。

.g_register 和 .s_register 仅用于高级调试,需要设置内核配置选项 CONFIG_VIDEO_ADV_DEBUG。这些操作允许读取和写入硬件寄存器,以响应 VIDIOC_DBG_G_REGISTER 和 VIDIOC_DBG_S_REGISTER ioctls。reg 参数(类型为 v4l2_dbg_register,在 include/uapi/linux/videodev2.h 中定义)由应用程序填充和提供。

.interrupt_service_routine由桥接器在其 IRQ 处理程序中调用(应使用v4l2_subdev_call),当由于此子设备而引发中断状态时,以便子设备处理详细信息。handled是桥接驱动程序提供的输出参数,但必须由子设备驱动程序填充,以便通知(作为true 或 false)其处理结果。我们处于 IRQ 上下文中,因此不能休眠。位于 I2C/SPI 总线后面的子设备可能应该在线程化的上下文中安排其工作。

.subscribe_event.unsubscribe_event用于订阅或取消订阅控制更改事件。请查看其他实现此功能的 V4L2 驱动程序,以了解如何实现您的驱动程序。

struct v4l2_subdev_video_ops 或 struct v4l2_subdev_pad_ops

当 V4L2 设备以视频模式打开时,struct v4l2_subdev_video_ops结构的回调被使用。媒体控制器框架通过实体对象(稍后我们将看到)抽象了子设备,通过 PAD 连接到其他元素。在这种情况下,使用与 PAD 相关的功能而不是与子设备相关的功能是有意义的,因此,使用struct v4l2_subdev_pad_ops而不是struct v4l2_subdev_video_ops。

 struct v4l2_subdev_video_ops {
    int (*querystd)(struct v4l2_subdev *sd, v4l2_std_id *std);
[...]
    int (*s_stream)(struct v4l2_subdev *sd, int enable);
    int (*g_frame_interval)(struct v4l2_subdev *sd,
                  struct v4l2_subdev_frame_interval *interval);
    int (*s_frame_interval)(struct v4l2_subdev *sd,
                  struct v4l2_subdev_frame_interval *interval);
[...]
};

querystd:这是VIDIOC_QUERYSTD()ioctl 处理程序代码的回调。

s_stream:用于通知驱动程序视频流将开始或已停止,取决于enable参数的值。

g_frame_interval:这是VIDIOC_SUBDEV_G_FRAME_INTERVAL()ioctl 处理程序代码的回调。

s_frame_interval:这是VIDIOC_SUBDEV_S_FRAME_INTERVAL()ioctl 处理程序代码的回调。

struct v4l2_subdev_sensor_ops

当传感器开始流式传输时,有些传感器会产生初始垃圾帧。这样的传感器可能需要一些时间来确保其某些属性的稳定性。该结构使得可以通知核心跳过多少帧以避免垃圾。此外,一些传感器可能始终在顶部产生一定数量的损坏行的图像,或者在这些行中嵌入它们的元数据。在这两种情况下,它们产生的帧始终是损坏的。该结构还允许我们指定在抓取每帧之前要跳过的行数。

struct v4l2_subdev_sensor_ops {
    int (*g_skip_top_lines)(struct v4l2_subdev *sd,  u32 *lines);
    int (*g_skip_frames)(struct v4l2_subdev *sd, u32 *frames);
};

g_skip_top_lines用于指定传感器每幅图像中要跳过的行数,而g_skip_frames允许我们指定要跳过的初始帧数,以避免垃圾。

调用子设备操作

{BANNED}最佳后,如果提供了subdev回调,则打算调用它们。也就是说,调用 ops 回调就像直接调用它一样简单。
err = subdev->ops->video->s_stream(subdev, 1);

然而,有一种更方便和更安全的方法可以实现这一点,即使用v4l2_subdev_call()宏。
err = v4l2_subdev_call(subdev, video, s_stream, 1);

还可以调用所有或部分子设备:
v4l2_device_call_all(dev, 0, core, g_chip_ident, &chip);

不支持此回调的任何子设备都将被跳过,错误结果将被忽略。如果要检查错误,请使用以下命令。
err = v4l2_device_call_until_err(dev, 0, core,  g_chip_ident, &chip);

V4L2 控件基础设施

一些设备具有可由用户设置的控件,以修改一些定义的属性。其中一些控件可能支持预定义值列表、默认值、调整等。问题是,不同的设备可能提供具有不同值的不同控件。此外,虽然其中一些控件是标准的,但其他可能是特定于供应商的。控件框架的主要目的是向用户呈现控件,而不假设其目的。

控件框架依赖于两个主要对象,都在include/media/v4l2- ctrls.h中定义,就像该框架提供的其他数据结构和 API 一样。{BANNED}中国第一个是struct v4l2_ctrl。这个结构描述了控件的属性,并跟踪控件的值。第二个和{BANNED}最佳后一个是struct v4l2_ctrl_handler,它跟踪所有的控件。

struct v4l2_ctrl_handler {
    [...]
    struct mutex *lock;
    struct list_head ctrls;
    v4l2_ctrl_notify_fnc notify;
    void *notify_priv;
    [...]
};

在struct v4l2_ctrl_handler的前述定义摘录中,ctrls表示此处理程序拥有的控件列表。notify是一个通知回调,每当控件更改值时都会被调用。这个回调在持有处理程序的lock时被调用。{BANNED}最佳后,notify_priv是作为参数给出的上下文数据。接下来是struct v4l2_ctrl,定义如下:

struct v4l2_ctrl {
    struct list_head node;
    struct v4l2_ctrl_handler *handler;
    unsigned int is_private:1;
    [...]
    const struct v4l2_ctrl_ops *ops;
    u32 id;
    const char *name;
    enum v4l2_ctrl_type type;
    s64 minimum, maximum, default_value;
    u64 step;
    unsigned long flags; [...]
}

node用于将控件插入处理程序的控件列表中。

handler是此控件所属的处理程序。

ops是struct v4l2_ctrl_ops类型,并表示此控件的获取/设置操作。

id 是此控件的 ID。

name 是控件的名称。

minimum和maximum分别是控件接受的{BANNED}最佳小值和{BANNED}最佳大值。

default_value是控件的默认值。

step是非菜单控件的递增/递减步长。

flags涵盖了控件的标志。虽然整个标志列表在include/uapi/linux/videodev2.h中定义,但一些常用的标志如下:
– V4L2_CTRL_FLAG_DISABLED,表示控件被禁用

– V4L2_CTRL_FLAG_READ_ONLY,用于只读控件

– V4L2_CTRL_FLAG_WRITE_ONLY,用于只写控件

– V4L2_CTRL_FLAG_VOLATILE,用于易失性控件

is_private,如果设置,将阻止此控件被添加到任何其他处理程序中。它使得此控件对{BANNED}最佳初添加它的处理程序私有。这可以用来防止将subdev控件可用于 V4L2 驱动程序控件。

enum通常像一种菜单,因此称为菜单控件

V4L2 控件由唯一的 ID 标识。它们以V4L2_CID_为前缀,并且都在include/uapi/linux/v4l2-controls.h中可用。视频捕获设备支持的常见标准控件如下

#define V4L2_CID_BRIGHTNESS        (V4L2_CID_BASE+0)
#define V4L2_CID_CONTRAST          (V4L2_CID_BASE+1)
#define V4L2_CID_SATURATION        (V4L2_CID_BASE+2)
#define V4L2_CID_HUE (V4L2_CID_BASE+3)
#define V4L2_CID_AUTO_WHITE_BALANCE      (V4L2_CID_BASE+12)
#define V4L2_CID_DO_WHITE_BALANCE  (V4L2_CID_BASE+13)
#define V4L2_CID_RED_BALANCE (V4L2_CID_BASE+14)
#define V4L2_CID_BLUE_BALANCE      (V4L2_CID_BASE+15)
#define V4L2_CID_GAMMA       (V4L2_CID_BASE+16)
#define V4L2_CID_EXPOSURE    (V4L2_CID_BASE+17)
#define V4L2_CID_AUTOGAIN    (V4L2_CID_BASE+18)
#define V4L2_CID_GAIN  (V4L2_CID_BASE+19)
#define V4L2_CID_HFLIP (V4L2_CID_BASE+20)
#define V4L2_CID_VFLIP (V4L2_CID_BASE+21)
[...]
#define V4L2_CID_VBLANK  (V4L2_CID_IMAGE_SOURCE_CLASS_BASE + 1) #define V4L2_CID_HBLANK  (V4L2_CID_IMAGE_SOURCE_CLASS_BASE + 2) #define V4L2_CID_LINK_FREQ (V4L2_CID_IMAGE_PROC_CLASS_BASE + 1)

前面的列表只包括标准控件。要支持自定义控件,你应该根据控件的基类描述符添加其 ID,并确保 ID 不重复。要向驱动程序添加控件支持,控件处理程序应首先使用v4l2_ctrl_handler_init()宏进行初始化。这个宏接受要初始化的处理程序以及此处理程序可以引用的控件数量,如下原型所示:

v4l2_ctrl_handler_init(hdl, nr_of_controls_hint)

完成控件处理程序后,你可以调用v4l2_ctrl_handler_free()释放此控件处理程序的资源。一旦控件处理程序被初始化,就可以创建控件并将其添加到其中。对于标准的 V4L2 控件,你可以使用v4l2_ctrl_new_std()来分配和初始化新的控件:

struct v4l2_ctrl *v4l2_ctrl_new_std( struct v4l2_ctrl_handler *hdl, const struct v4l2_ctrl_ops *ops,  u32 id, s64 min, s64 max,  u64 step, s64 def);

这个函数在大多数字段上都是基于控件 ID 的。然而对于自定义控件(这里不讨论),你应该使用v4l2_ctrl_new_custom()辅助函数。在前面的原型中,以下元素被定义如下:

hdl表示先前初始化的控件处理程序。

ops是struct v4l2_ctrl_ops类型,并表示控件操作。

id是控件 ID,定义为V4L2_CID_*。

min是此控件可以接受的{BANNED}最佳小值。根据控件 ID,这个值可能会被核心修改。

max是此控件可以接受的{BANNED}最佳大值。根据控件 ID,这个值可能会被核心修改。

step 是控件的步进值。

def 是控件的默认值。

控件的目的是设置/获取。这是前面的 ops 参数的目的。这意味着在初始化控件之前,您应该首先定义将在设置/获取此控件的值时调用的操作。也就是说,整个控件列表可以由相同的操作处理。在这种情况下,操作回调将必须使用 switch ... case 来处理不同的控件。

struct v4l2_ctrl_ops {
    int (*g_volatile_ctrl)(struct v4l2_ctrl *ctrl);
    int (*try_ctrl)(struct v4l2_ctrl *ctrl);
    int (*s_ctrl)(struct v4l2_ctrl *ctrl);
};

g_volatile_ctrl 获取给定控件的新值。只有在对易失性控件(由硬件自身更改,并且大部分时间是只读的,例如信号强度或自动增益)提供此回调才有意义。

try_ctrl,如果设置,将被调用来测试要应用的控件值是否有效。只有在通常的{BANNED}最佳小/{BANNED}最佳大/步长检查不足以时,提供此回调才有意义。

s_ctrl 被调用来设置控件的值。

可选地,您可以在控件处理程序上调用 v4l2_ctrl_handler_setup() 来设置此处理程序的控件为它们的默认值。这有助于确保硬件和驱动程序的内部数据结构保持同步:
int v4l2_ctrl_handler_setup(struct v4l2_ctrl_handler *hdl);

此函数遍历给定处理程序中的所有控件,并使用每个控件的默认值调用 s_ctrl 回调。


阅读(69) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~