v4l2框架

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
云数据库 RDS MySQL Serverless,价值2615元额度,1个月
简介: v4l2框架

框架

1.硬件相关层

driver/media/usb/uvc/uvc_driver.c

/*
 * UVC 驱动结构体
 */
struct uvc_driver uvc_driver = {
    .driver = {
        .name       = "uvcvideo", // 驱动名
        .probe      = uvc_probe, // 探测函数
        .disconnect = uvc_disconnect, // 断开连接函数
        .suspend    = uvc_suspend, // 挂起函数
        .resume     = uvc_resume, // 恢复函数
        .reset_resume   = uvc_reset_resume, // 重置恢复函数
        .id_table   = uvc_ids, // 设备 ID 表
        .supports_autosuspend = 1, // 支持自动挂起
    },
};

接入摄像头

driver/media/usb/uvc/uvc_driver.c

uvc_probe

int uvc_probe(struct usb_interface *intf,
             const struct usb_device_id *id)
/* ------------------------------------------------------------------------
 * USB probe, disconnect, suspend and resume
 */
static int uvc_probe(struct usb_interface *intf,
             const struct usb_device_id *id)
{
    // 获取 USB 设备
    struct usb_device *udev = interface_to_usbdev(intf);
    // 定义 UVC 设备
    struct uvc_device *dev;
    int ret;
    // 如果 idVendor 和 idProduct 都存在,则打印已知 UVC 设备的信息
    if (id->idVendor && id->idProduct)
        uvc_trace(UVC_TRACE_PROBE, "Probing known UVC device %s "
                "(%04x:%04x)\n", udev->devpath, id->idVendor,
                id->idProduct);
    // 否则打印通用 UVC 设备的信息
    else
        uvc_trace(UVC_TRACE_PROBE, "Probing generic UVC device %s\n",
                udev->devpath);
    // 为设备分配内存并初始化
    if ((dev = kzalloc(sizeof *dev, GFP_KERNEL)) == NULL)
        return -ENOMEM;
// 初始化设备的各个链表
    INIT_LIST_HEAD(&dev->entities);
    INIT_LIST_HEAD(&dev->chains);
    INIT_LIST_HEAD(&dev->streams);
    atomic_set(&dev->nstreams, 0);
    atomic_set(&dev->nmappings, 0);
    mutex_init(&dev->lock);
    // 获取 USB 设备
    dev->udev = usb_get_dev(udev);
    dev->intf = usb_get_intf(intf);
    dev->intfnum = intf->cur_altsetting->desc.bInterfaceNumber;
    dev->quirks = (uvc_quirks_param == -1)
            ? id->driver_info : uvc_quirks_param;
    // 如果 USB 设备有产品名称,则使用该名称,否则使用默认名称
    if (udev->product != NULL)
        strlcpy(dev->name, udev->product, sizeof dev->name);
    else
        snprintf(dev->name, sizeof dev->name,
            "UVC Camera (%04x:%04x)",
            le16_to_cpu(udev->descriptor.idVendor),
            le16_to_cpu(udev->descriptor.idProduct));
    // 解析 Video Class 控制描述符
    if (uvc_parse_control(dev) < 0) {
        uvc_trace(UVC_TRACE_PROBE, "Unable to parse UVC "
            "descriptors.\n");
        goto error;
    }
    // 打印 UVC 设备信息
    uvc_printk(KERN_INFO, "Found UVC %u.%02x device %s (%04x:%04x)\n",
        dev->uvc_version >> 8, dev->uvc_version & 0xff,
        udev->product ? udev->product : "<unnamed>",
        le16_to_cpu(udev->descriptor.idVendor),
        le16_to_cpu(udev->descriptor.idProduct));
if (dev->quirks != id->driver_info) {
        // 如果设备 quirks 不等于驱动程序信息,则打印信息
        uvc_printk(KERN_INFO, "Forcing device quirks to 0x%x by module "
            "parameter for testing purpose.\n", dev->quirks);
        // 打印信息
        uvc_printk(KERN_INFO, "Please report required quirks to the "
            "linux-uvc-devel mailing list.\n");
    }
    /* Register the media and V4L2 devices. */
#ifdef CONFIG_MEDIA_CONTROLLER
    // 设置 media 设备的信息
    dev->mdev.dev = &intf->dev;
    strlcpy(dev->mdev.model, dev->name, sizeof(dev->mdev.model));
    if (udev->serial)
        strlcpy(dev->mdev.serial, udev->serial,
            sizeof(dev->mdev.serial));
    strcpy(dev->mdev.bus_info, udev->devpath);
    dev->mdev.hw_revision = le16_to_cpu(udev->descriptor.bcdDevice);
    dev->mdev.driver_version = LINUX_VERSION_CODE;
    // 注册 media 设备
    if (media_device_register(&dev->mdev) < 0)
        goto error;
    // 设置 v4l2 设备的信息
    dev->vdev.mdev = &dev->mdev;
#endif
// 注册 v4l2 设备
    if (v4l2_device_register(&intf->dev, &dev->vdev) < 0)
        goto error;
    /* Initialize controls. */
    // 初始化控制器
    if (uvc_ctrl_init_device(dev) < 0)
        goto error;
    /* Scan the device for video chains. */
    // 扫描设备的视频链
    if (uvc_scan_device(dev) < 0)
        goto error;
    /* Register video device nodes. */
    // 注册视频设备节点
    if (uvc_register_chains(dev) < 0)
        goto error;
    /* Save our data pointer in the interface data. */
    // 在接口数据中保存数据指针
    usb_set_intfdata(intf, dev);
    /* Initialize the interrupt URB. */
    // 初始化中断 URB
    if ((ret = uvc_status_init(dev)) < 0) {
        uvc_printk(KERN_INFO, "Unable to initialize the status "
            "endpoint (%d), status interrupt will not be "
            "supported.\n", ret);
    }
    // 打印 UVC 设备初始化信息
    uvc_trace(UVC_TRACE_PROBE, "UVC device initialized.\n");
    // 启用 USB 自动挂起
    usb_enable_autosuspend(udev);
    return 0;
error:
    // 注销视频设备
    uvc_unregister_video(dev);
    return -ENODEV;
}

uvc_register_chains

uvc_probe

uvc_register_chains

/*
 * 注册所有的视频链
 */
static int uvc_register_chains(struct uvc_device *dev)
{
    struct uvc_video_chain *chain;
    int ret;
    // 遍历所有的视频链
    list_for_each_entry(chain, &dev->chains, list) {
        // 注册视频链中的所有终端
        ret = uvc_register_terms(dev, chain);
        if (ret < 0)
            return ret;
#ifdef CONFIG_MEDIA_CONTROLLER
        // 如果支持 media controller,则注册实体
        ret = uvc_mc_register_entities(chain);
        if (ret < 0) {
            uvc_printk(KERN_INFO, "Failed to register entites "
                "(%d).\n", ret);
        }
#endif
    }
    return 0;
}

uvc_register_terms

uvc_probe

uvc_register_chains

uvc_register_terms

/*
 * Register all video devices in all chains.
 */
/*
 * 注册视频链中的所有终端
 */
static int uvc_register_terms(struct uvc_device *dev,
    struct uvc_video_chain *chain)
{
    struct uvc_streaming *stream;
    struct uvc_entity *term;
    int ret;
    // 遍历视频链中的所有实体
    list_for_each_entry(term, &chain->entities, chain) {
        // 如果实体不是流式传输终端,则跳过
        if (UVC_ENTITY_TYPE(term) != UVC_TT_STREAMING)
            continue;
        // 根据终端 ID 获取流式传输结构体
        stream = uvc_stream_by_id(dev, term->id);
        if (stream == NULL) {
            uvc_printk(KERN_INFO, "No streaming interface found "
                   "for terminal %u.", term->id);
            continue;
        }
        // 将流式传输结构体与视频链关联
        stream->chain = chain;
        // 注册视频设备
        ret = uvc_register_video(dev, stream);
        if (ret < 0)
            return ret;
        // 将视频设备与终端关联
        term->vdev = &stream->vdev;
    }
    return 0;
}

uvc_register_video

uvc_probe

uvc_register_chains

uvc_register_terms

uvc_register_video

static int uvc_register_video(struct uvc_device *dev,
        struct uvc_streaming *stream)
{
    // 初始化视频缓冲区队列
    int ret = uvc_queue_init(&stream->queue, stream->type, !uvc_no_drop_param);
    if (ret)
        return ret;
    // 使用默认的流式传输参数初始化流式传输接口
    ret = uvc_video_init(stream);
    if (ret < 0) {
        uvc_printk(KERN_ERR, "Failed to initialize the device "
            "(%d).\n", ret);
        return ret;
    }
    // 初始化调试文件系统
    uvc_debugfs_init_stream(stream);
    // 在 V4L 中注册设备
    // 我们已经持有对 dev->udev 的引用。在引用被释放之前,视频设备将被注销,因此我们不需要获取另一个引用。
    struct video_device *vdev = &stream->vdev;
    vdev->v4l2_dev = &dev->vdev;
    vdev->fops = &uvc_fops;
    vdev->ioctl_ops = &uvc_ioctl_ops;
    vdev->release = uvc_release;
    vdev->prio = &stream->chain->prio;
    if (stream->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
        vdev->vfl_dir = VFL_DIR_TX;
    strlcpy(vdev->name, dev->name, sizeof vdev->name);
    // 在调用 video_register_device 之前设置驱动程序数据,否则 uvc_v4l2_open 可能会与我们竞争。
    video_set_drvdata(vdev, stream);
    ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
    if (ret < 0) {
        uvc_printk(KERN_ERR, "Failed to register video device (%d).\n",
               ret);
        return ret;
    }
    if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
        stream->chain->caps |= V4L2_CAP_VIDEO_CAPTURE;
    else
        stream->chain->caps |= V4L2_CAP_VIDEO_OUTPUT;
    atomic_inc(&dev->nstreams);
    return 0;
}

2.核心层

driver/media/v4l2-core.c

__video_register_device

uvc_probe

uvc_register_chains

uvc_register_terms

uvc_register_video

__video_register_device

/*
 __video_register_device函数用于在内核中注册video4linux设备。它根据请求的类型分配
 次要号和设备节点号,并将新的设备节点注册到内核中。
  */
int __video_register_device(struct video_device *vdev, int type, int nr,
        int warn_if_nr_in_use, struct module *owner)
{
    // 初始化变量
    int i = 0;
    int ret;
    int minor_offset = 0;
    int minor_cnt = VIDEO_NUM_DEVICES;
    const char *name_base;
    // A minor value of -1 marks this video device as never having been registered
    // 初始化 minor 值为 -1
    vdev->minor = -1;
    // the release callback MUST be present
    // 检查 release 回调是否存在
    if (WARN_ON(!vdev->release))
        return -EINVAL;
    // the v4l2_dev pointer MUST be present
    // 检查 v4l2_dev 指针是否存在
    if (WARN_ON(!vdev->v4l2_dev))
        return -EINVAL;
    /* v4l2_fh support */
    spin_lock_init(&vdev->fh_lock); // 初始化锁
    INIT_LIST_HEAD(&vdev->fh_list); // 初始化链表
    /* Part 1: check device type */
    switch (type) { // 根据设备类型选择设备名
    case VFL_TYPE_GRABBER:
        name_base = "video";
        break;
    case VFL_TYPE_VBI:
        name_base = "vbi";
        break;
    case VFL_TYPE_RADIO:
        name_base = "radio";
        break;
    case VFL_TYPE_SUBDEV:
        name_base = "v4l-subdev";
        break;
    case VFL_TYPE_SDR:
        /* Use device name 'swradio' because 'sdr' was already taken. */
        name_base = "swradio";
        break;
    default:
        printk(KERN_ERR "%s called with unknown type: %d\n",
               __func__, type); // 打印错误信息
        return -EINVAL;
    }
    // 设置设备类型
    vdev->vfl_type = type;
    // 初始化字符设备指针
    vdev->cdev = NULL;
    // 如果设备的父设备指针为空,则将其指向 v4l2_dev 的设备指针
    if (vdev->dev_parent == NULL)
        vdev->dev_parent = vdev->v4l2_dev->dev;
    // 如果控制处理程序指针为空,则将其指向 v4l2_dev 的控制处理程序指针
    if (vdev->ctrl_handler == NULL)
        vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;
    /* 如果优先级状态指针为空,则使用 v4l2_device 的优先级状态。*/
    if (vdev->prio == NULL)
        vdev->prio = &vdev->v4l2_dev->prio;
    /* Part 2: find a free minor, device node number and device index. */
#ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES
    /* 为前四种类型保留范围,出于历史原因。
     * 新设备(尚未就位)应使用范围
     * 128-191,只需在那里选择第一个空闲的次要设备
     * (新样式)。 */
    switch (type) {
    case VFL_TYPE_GRABBER: // 如果设备类型为 VFL_TYPE_GRABBER
        minor_offset = 0; // 次设备号偏移量为 0
        minor_cnt = 64; // 次设备号数量为 64
        break;
    case VFL_TYPE_RADIO: // 如果设备类型为 VFL_TYPE_RADIO
        minor_offset = 64; // 次设备号偏移量为 64
        minor_cnt = 64; // 次设备号数量为 64
        break;
    case VFL_TYPE_VBI: // 如果设备类型为 VFL_TYPE_VBI
        minor_offset = 224; // 次设备号偏移量为 224
        minor_cnt = 32; // 次设备号数量为 32
        break;
    default: // 如果设备类型为其他类型
        minor_offset = 128; // 次设备号偏移量为 128
        minor_cnt = 64; // 次设备号数量为 64
        break;
    }
#endif
    /* 选择设备节点号 */
    mutex_lock(&videodev_lock); // 加锁
    nr = devnode_find(vdev, nr == -1 ? 0 : nr, minor_cnt); // 查找设备节点号
    if (nr == minor_cnt) // 如果找不到空闲的设备节点号
        nr = devnode_find(vdev, 0, minor_cnt); // 从头开始查找
    if (nr == minor_cnt) { // 如果还是找不到空闲的设备节点号
        printk(KERN_ERR "could not get a free device node number\n"); // 打印错误信息
        mutex_unlock(&videodev_lock); // 解锁
        return -ENFILE; // 返回错误码
    }
#ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES
    /* 1-on-1 mapping of device node number to minor number */
    i = nr; // 将设备节点号赋值给 i
#else
    /* The device node number and minor numbers are independent, so
       we just find the first free minor number. */
    for (i = 0; i < VIDEO_NUM_DEVICES; i++) // 遍历所有设备
        if (video_device[i] == NULL) // 如果设备未被占用
            break; // 跳出循环
    if (i == VIDEO_NUM_DEVICES) { // 如果所有设备都被占用
        mutex_unlock(&videodev_lock); // 解锁
        printk(KERN_ERR "could not get a free minor\n"); // 打印错误信息
        return -ENFILE; // 返回错误码
    }
#endif
    vdev->minor = i + minor_offset; // 设置设备节点号
    vdev->num = nr; // 设置设备编号
    devnode_set(vdev); // 设置设备节点
    /* Should not happen since we thought this minor was free */
    WARN_ON(video_device[vdev->minor] != NULL); // 如果设备节点已被占用,打印警告信息
    vdev->index = get_index(vdev); // 获取设备索引
    video_device[vdev->minor] = vdev; // 将设备添加到 video_device 数组中
    mutex_unlock(&videodev_lock); // 解锁
    if (vdev->ioctl_ops)
        determine_valid_ioctls(vdev); // 确定设备支持的 ioctl 操作
    /* Part 3: Initialize the character device */
    vdev->cdev = cdev_alloc(); // 分配 cdev 结构体
    if (vdev->cdev == NULL) { // 如果分配失败
        ret = -ENOMEM; // 返回错误码
        goto cleanup; // 跳转到 cleanup 标签处
    }
    vdev->cdev->ops = &v4l2_fops; // 设置 cdev 的操作函数
    vdev->cdev->owner = owner; // 设置 cdev 的 owner
    ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1); // 添加 cdev
    if (ret < 0) { // 如果添加失败
        printk(KERN_ERR "%s: cdev_add failed\n", __func__); // 打印错误信息
        kfree(vdev->cdev); // 释放 cdev
        vdev->cdev = NULL; // 将 cdev 置为空
        goto cleanup; // 跳转到 cleanup 标签处
    }
    /* Part 4: register the device with sysfs */
    vdev->dev.class = &video_class; // 设置设备的 class
    vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor); // 设置设备的 devt
    vdev->dev.parent = vdev->dev_parent; // 设置设备的 parent
    dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num); // 设置设备的名字
    ret = device_register(&vdev->dev); // 注册设备
    if (ret < 0) { // 如果注册失败
        printk(KERN_ERR "%s: device_register failed\n", __func__); // 打印错误信息
        goto cleanup; // 跳转到 cleanup 标签处
    }
    /* Register the release callback that will be called when the last
       reference to the device goes away. */
    vdev->dev.release = v4l2_device_release; // 设置设备的 release 回调函数
    if (nr != -1 && nr != vdev->num && warn_if_nr_in_use)
        printk(KERN_WARNING "%s: requested %s%d, got %s\n", __func__,
            name_base, nr, video_device_node_name(vdev)); // 打印警告信息
    /* Increase v4l2_device refcount */
    v4l2_device_get(vdev->v4l2_dev); // 增加 v4l2_device 的引用计数
#if defined(CONFIG_MEDIA_CONTROLLER)
    /* Part 5: Register the entity. */
    if (vdev->v4l2_dev->mdev && // 如果 v4l2_dev 的 mdev 不为空
        vdev->vfl_type != VFL_TYPE_SUBDEV) { // 如果 vfl_type 不是 VFL_TYPE_SUBDEV
        vdev->entity.type = MEDIA_ENT_T_DEVNODE_V4L; // 设置 entity 的 type
        vdev->entity.name = vdev->name; // 设置 entity 的 name
        vdev->entity.info.dev.major = VIDEO_MAJOR; // 设置 entity 的 major
        vdev->entity.info.dev.minor = vdev->minor; // 设置 entity 的 minor
        ret = media_device_register_entity(vdev->v4l2_dev->mdev, // 注册 entity
            &vdev->entity);
        if (ret < 0) // 如果注册失败
            printk(KERN_WARNING
                   "%s: media_device_register_entity failed\n",
                   __func__); // 打印警告信息
    }
#endif
    /* Part 6: Activate this minor. The char device can now be used. */
    set_bit(V4L2_FL_REGISTERED, &vdev->flags); // 设置设备已注册标志位
    return 0; // 返回 0
cleanup:
    mutex_lock(&videodev_lock);
    if (vdev->cdev)
        cdev_del(vdev->cdev);
    video_device[vdev->minor] = NULL;
    devnode_clear(vdev);
    mutex_unlock(&videodev_lock);
    /* Mark this video device as never having been registered. */
    vdev->minor = -1;
    return ret;
}
    /* Part 3: Initialize the character device */
    vdev->cdev = cdev_alloc(); // 分配 cdev 结构体
    if (vdev->cdev == NULL) { // 如果分配失败
        ret = -ENOMEM; // 返回错误码
        goto cleanup; // 跳转到 cleanup 标签处
    }
    vdev->cdev->ops = &v4l2_fops; // 设置 cdev 的操作函数
    vdev->cdev->owner = owner; // 设置 cdev 的 owner
    ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1); // 添加 cdev
    if (ret < 0) { // 如果添加失败
        printk(KERN_ERR "%s: cdev_add failed\n", __func__); // 打印错误信息
        kfree(vdev->cdev); // 释放 cdev
        vdev->cdev = NULL; // 将 cdev 置为空
        goto cleanup; // 跳转到 cleanup 标签处
    }

移除摄像头

3.虚拟视频驱动vivid分析

drivers/media/platform/vivd/vivid-core.c

平台设备驱动

static struct platform_driver vivid_pdrv = {
    .probe        = vivid_probe,
    .remove        = vivid_remove,
    .driver        = {
        .name    = "vivid",
    },
};

入口vivid_init注册vivid平台驱动

static int __init vivid_init(void)
{
    int ret;
    // 注册vivid平台设备
    ret = platform_device_register(&vivid_pdev);
    if (ret)
        return ret;
    // 注册vivid平台驱动
    ret = platform_driver_register(&vivid_pdrv);
    if (ret)
        platform_device_unregister(&vivid_pdev);
    return ret;
}

vivid_probe

vivid_init

platform_driver_register

匹配后调用vivid_probe

/* This routine allocates from 1 to n_devs virtual drivers.
   The real maximum number of virtual drivers will depend on how many drivers
   will succeed. This is limited to the maximum number of devices that
   videodev supports, which is equal to VIDEO_NUM_DEVICES.
 */
static int vivid_probe(struct platform_device *pdev)
{
    // 查找字体
    const struct font_desc *font = find_font("VGA8x16");
    // 如果找不到字体,返回错误
    if (font == NULL) {
        pr_err("vivid: could not find font\n");
        return -ENODEV;
    }
    // 设置字体
    tpg_set_font(font->data);
    // 限制设备数量在1到VIVID_MAX_DEVS之间
    n_devs = clamp_t(unsigned, n_devs, 1, VIVID_MAX_DEVS);
    // 创建n_devs个设备实例
    for (unsigned i = 0; i < n_devs; i++) {
        // 创建设备实例
        int ret = vivid_create_instance(pdev, i);
        // 如果创建失败,如果已经创建了一些实例,保留这些实例,否则返回错误
        if (ret) {
            if (i)
                ret = 0;
            break;
        }
    }
    // 如果创建失败,返回错误
    if (ret < 0) {
        pr_err("vivid: error %d while loading driver\n", ret);
        return ret;
    }
    // n_devs将反映实际分配的设备数量
    n_devs = i;
    return ret;
}

vivid_create_instance

vivid_probe

vivid_create_instance创建设备实例

static int vivid_create_instance(struct platform_device *pdev, int inst)
{
    // 定义默认的视频格式
    static const struct v4l2_dv_timings def_dv_timings =
                    V4L2_DV_BT_CEA_1280X720P60;
    // 计数器
    unsigned in_type_counter[4] = { 0, 0, 0, 0 };
    unsigned out_type_counter[4] = { 0, 0, 0, 0 };
    // 是否支持ccs_cap_mode和ccs_out_mode
    int ccs_cap = ccs_cap_mode[inst];
    int ccs_out = ccs_out_mode[inst];
    // 是否有调谐器和调制器
    bool has_tuner;
    bool has_modulator;
    // 定义vivid_dev和video_device结构体
    struct vivid_dev *dev;
    struct video_device *vfd;
    // 定义vb2_queue结构体
    struct vb2_queue *q;
    // 节点类型
    unsigned node_type = node_types[inst];
    // 视频标准
    v4l2_std_id tvnorms_cap = 0, tvnorms_out = 0;
    int ret;
    int i;
    // 分配vivid_dev结构体
    dev = kzalloc(sizeof(*dev), GFP_KERNEL);
    if (!dev)
        return -ENOMEM;
    dev->inst = inst;
    // 注册v4l2_device
    snprintf(dev->v4l2_dev.name, sizeof(dev->v4l2_dev.name),
            "%s-%03d", VIVID_MODULE_NAME, inst);
    ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev);
    if (ret) {
        kfree(dev);
        return ret;
    }
    dev->v4l2_dev.release = vivid_dev_release;
    /* start detecting feature set */
/* do we use single- or multi-planar? */
    // 判断是否使用多平面
    dev->multiplanar = multiplanar[inst] > 1;
    // 输出使用的格式
    v4l2_info(&dev->v4l2_dev, "using %splanar format API\n",
            dev->multiplanar ? "multi" : "single ");
    /* how many inputs do we have and of what type? */
    // 获取输入的数量和类型
    dev->num_inputs = num_inputs[inst];
    if (dev->num_inputs < 1)
        dev->num_inputs = 1;
    if (dev->num_inputs >= MAX_INPUTS)
        dev->num_inputs = MAX_INPUTS;
    for (i = 0; i < dev->num_inputs; i++) {
        // 获取输入的类型
        dev->input_type[i] = (input_types[inst] >> (i * 2)) & 0x3;
        // 获取输入的名称计数器
        dev->input_name_counter[i] = in_type_counter[dev->input_type[i]]++;
    }
    // 判断是否有音频输入
    dev->has_audio_inputs = in_type_counter[TV] && in_type_counter[SVID];
    /* how many outputs do we have and of what type? */
    // 获取输出的数量和类型
    dev->num_outputs = num_outputs[inst];
    if (dev->num_outputs < 1)
        dev->num_outputs = 1;
    if (dev->num_outputs >= MAX_OUTPUTS)
        dev->num_outputs = MAX_OUTPUTS;
    for (i = 0; i < dev->num_outputs; i++) {
        // 获取输出的类型
        dev->output_type[i] = ((output_types[inst] >> i) & 1) ? HDMI : SVID;
        // 获取输出的名称计数器
        dev->output_name_counter[i] = out_type_counter[dev->output_type[i]]++;
    }
    // 判断是否有音频输出
    dev->has_audio_outputs = out_type_counter[SVID];
/* do we create a video capture device? */
    // 判断是否创建视频捕获设备
    dev->has_vid_cap = node_type & 0x0001;
    /* do we create a vbi capture device? */
    // 判断是否创建VBI捕获设备
    if (in_type_counter[TV] || in_type_counter[SVID]) {
        dev->has_raw_vbi_cap = node_type & 0x0004;
        dev->has_sliced_vbi_cap = node_type & 0x0008;
        dev->has_vbi_cap = dev->has_raw_vbi_cap | dev->has_sliced_vbi_cap;
    }
    /* do we create a video output device? */
    // 判断是否创建视频输出设备
    dev->has_vid_out = node_type & 0x0100;
    /* do we create a vbi output device? */
    // 判断是否创建VBI输出设备
    if (out_type_counter[SVID]) {
        dev->has_raw_vbi_out = node_type & 0x0400;
        dev->has_sliced_vbi_out = node_type & 0x0800;
        dev->has_vbi_out = dev->has_raw_vbi_out | dev->has_sliced_vbi_out;
    }
    /* do we create a radio receiver device? */
    // 判断是否创建无线电接收器设备
    dev->has_radio_rx = node_type & 0x0010;
    /* do we create a radio transmitter device? */
    // 判断是否创建无线电发射器设备
    dev->has_radio_tx = node_type & 0x1000;
    /* do we create a software defined radio capture device? */
    // 判断是否创建软件定义无线电捕获设备
    dev->has_sdr_cap = node_type & 0x0020;
    /* do we have a tuner? */
    // 判断是否有调谐器
    has_tuner = ((dev->has_vid_cap || dev->has_vbi_cap) && in_type_counter[TV]) ||
            dev->has_radio_rx || dev->has_sdr_cap;
    /* do we have a modulator? */
    // 判断是否有调制器
    has_modulator = dev->has_radio_tx;
    if (dev->has_vid_cap)
        /* do we have a framebuffer for overlay testing? */
        // 判断是否有用于叠加测试的帧缓冲区
        dev->has_fb = node_type & 0x10000;
/* do we create a video capture device? */
// 判断是否创建视频捕获设备
dev->has_vid_cap = node_type & 0x0001;
/* do we create a vbi capture device? */
// 判断是否创建VBI捕获设备
if (in_type_counter[TV] || in_type_counter[SVID]) {
    dev->has_raw_vbi_cap = node_type & 0x0004;
    dev->has_sliced_vbi_cap = node_type & 0x0008;
    dev->has_vbi_cap = dev->has_raw_vbi_cap | dev->has_sliced_vbi_cap;
}
/* do we create a video output device? */
// 判断是否创建视频输出设备
dev->has_vid_out = node_type & 0x0100;
/* do we create a vbi output device? */
// 判断是否创建VBI输出设备
if (out_type_counter[SVID]) {
    dev->has_raw_vbi_out = node_type & 0x0400;
    dev->has_sliced_vbi_out = node_type & 0x0800;
    dev->has_vbi_out = dev->has_raw_vbi_out | dev->has_sliced_vbi_out;
}
/* do we create a radio receiver device? */
// 判断是否创建无线电接收器设备
dev->has_radio_rx = node_type & 0x0010;
/* do we create a radio transmitter device? */
// 判断是否创建无线电发射器设备
dev->has_radio_tx = node_type & 0x1000;
/* do we create a software defined radio capture device? */
// 判断是否创建软件定义无线电捕获设备
dev->has_sdr_cap = node_type & 0x0020;
/* do we have a tuner? */
// 判断是否有调谐器
has_tuner = ((dev->has_vid_cap || dev->has_vbi_cap) && in_type_counter[TV]) ||
        dev->has_radio_rx || dev->has_sdr_cap;
/* do we have a modulator? */
// 判断是否有调制器
has_modulator = dev->has_radio_tx;
if (dev->has_vid_cap)
    /* do we have a framebuffer for overlay testing? */
    // 判断是否有用于叠加测试的帧缓冲区
    dev->has_fb = node_type & 0x10000;
/* can we do crop/compose/scaling while capturing? */
// 判断是否可以在捕获时进行裁剪/组合/缩放
if (no_error_inj && ccs_cap == -1)
    ccs_cap = 7;
/* if ccs_cap == -1, then the use can select it using controls */
// 如果ccs_cap == -1,则用户可以使用控件进行选择
if (ccs_cap != -1) {
    dev->has_crop_cap = ccs_cap & 1;
    dev->has_compose_cap = ccs_cap & 2;
    dev->has_scaler_cap = ccs_cap & 4;
    v4l2_info(&dev->v4l2_dev, "Capture Crop: %c Compose: %c Scaler: %c\n",
        dev->has_crop_cap ? 'Y' : 'N',
        dev->has_compose_cap ? 'Y' : 'N',
        dev->has_scaler_cap ? 'Y' : 'N');
}
/* can we do crop/compose/scaling with video output? */
// 判断是否可以在视频输出时进行裁剪/组合/缩放
if (no_error_inj && ccs_out == -1)
    ccs_out = 7;
/* if ccs_out == -1, then the use can select it using controls */
// 如果ccs_out == -1,则用户可以使用控件进行选择
if (ccs_out != -1) {
    dev->has_crop_out = ccs_out & 1;
    dev->has_compose_out = ccs_out & 2;
    dev->has_scaler_out = ccs_out & 4;
    v4l2_info(&dev->v4l2_dev, "Output Crop: %c Compose: %c Scaler: %c\n",
        dev->has_crop_out ? 'Y' : 'N',
        dev->has_compose_out ? 'Y' : 'N',
        dev->has_scaler_out ? 'Y' : 'N');
}
/* end detecting feature set */
    // 如果有视频捕获设备
    if (dev->has_vid_cap) {
        /* set up the capabilities of the video capture device */
        // 设置视频捕获设备的功能
        dev->vid_cap_caps = dev->multiplanar ?
            V4L2_CAP_VIDEO_CAPTURE_MPLANE :
            V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OVERLAY;
        dev->vid_cap_caps |= V4L2_CAP_STREAMING | V4L2_CAP_READWRITE;
        // 如果有音频输入
        if (dev->has_audio_inputs)
            dev->vid_cap_caps |= V4L2_CAP_AUDIO;
        // 如果有电视输入
        if (in_type_counter[TV])
            dev->vid_cap_caps |= V4L2_CAP_TUNER;
    }
    // 如果有视频输出设备
    if (dev->has_vid_out) {
        /* set up the capabilities of the video output device */
        // 设置视频输出设备的功能
        dev->vid_out_caps = dev->multiplanar ?
            V4L2_CAP_VIDEO_OUTPUT_MPLANE :
            V4L2_CAP_VIDEO_OUTPUT;
        // 如果有帧缓存
        if (dev->has_fb)
            dev->vid_out_caps |= V4L2_CAP_VIDEO_OUTPUT_OVERLAY;
        dev->vid_out_caps |= V4L2_CAP_STREAMING | V4L2_CAP_READWRITE;
        // 如果有音频输出
        if (dev->has_audio_outputs)
            dev->vid_out_caps |= V4L2_CAP_AUDIO;
    }
    // 如果有VBI捕获设备
    if (dev->has_vbi_cap) {
        /* set up the capabilities of the vbi capture device */
        // 设置VBI捕获设备的功能
        dev->vbi_cap_caps = (dev->has_raw_vbi_cap ? V4L2_CAP_VBI_CAPTURE : 0) |
                    (dev->has_sliced_vbi_cap ? V4L2_CAP_SLICED_VBI_CAPTURE : 0);
        dev->vbi_cap_caps |= V4L2_CAP_STREAMING | V4L2_CAP_READWRITE;
        // 如果有音频输入
        if (dev->has_audio_inputs)
            dev->vbi_cap_caps |= V4L2_CAP_AUDIO;
        // 如果有电视输入
        if (in_type_counter[TV])
            dev->vbi_cap_caps |= V4L2_CAP_TUNER;
    }
    // 如果有VBI输出设备
    if (dev->has_vbi_out) {
        /* set up the capabilities of the vbi output device */
        // 设置VBI输出设备的功能
        dev->vbi_out_caps = (dev->has_raw_vbi_out ? V4L2_CAP_VBI_OUTPUT : 0) |
                    (dev->has_sliced_vbi_out ? V4L2_CAP_SLICED_VBI_OUTPUT : 0);
        dev->vbi_out_caps |= V4L2_CAP_STREAMING | V4L2_CAP_READWRITE;
        // 如果有音频输出
        if (dev->has_audio_outputs)
            dev->vbi_out_caps |= V4L2_CAP_AUDIO;
    }
    // 如果有SDR捕获设备
    if (dev->has_sdr_cap) {
        /* set up the capabilities of the sdr capture device */
        // 设置SDR捕获设备的功能
        dev->sdr_cap_caps = V4L2_CAP_SDR_CAPTURE | V4L2_CAP_TUNER;
        dev->sdr_cap_caps |= V4L2_CAP_STREAMING | V4L2_CAP_READWRITE;
    }
    /* set up the capabilities of the radio receiver device */
    // 设置无线电接收设备的功能
    if (dev->has_radio_rx)
        dev->radio_rx_caps = V4L2_CAP_RADIO | V4L2_CAP_RDS_CAPTURE |
                     V4L2_CAP_HW_FREQ_SEEK | V4L2_CAP_TUNER |
                     V4L2_CAP_READWRITE;
    /* set up the capabilities of the radio transmitter device */
    // 设置无线电发射设备的功能
    if (dev->has_radio_tx)
        dev->radio_tx_caps = V4L2_CAP_RDS_OUTPUT | V4L2_CAP_MODULATOR |
                     V4L2_CAP_READWRITE;
/* initialize the test pattern generator */
    // 初始化测试图案生成器
    tpg_init(&dev->tpg, 640, 360);
    if (tpg_alloc(&dev->tpg, MAX_ZOOM * MAX_WIDTH))
        goto free_dev;
    dev->scaled_line = vzalloc(MAX_ZOOM * MAX_WIDTH);
    if (!dev->scaled_line)
        goto free_dev;
    dev->blended_line = vzalloc(MAX_ZOOM * MAX_WIDTH);
    if (!dev->blended_line)
        goto free_dev;
    /* load the edid */
    // 加载EDID
    dev->edid = vmalloc(256 * 128);
    if (!dev->edid)
        goto free_dev;
    /* create a string array containing the names of all the preset timings */
    // 创建一个包含所有预设时间名称的字符串数组
    while (v4l2_dv_timings_presets[dev->query_dv_timings_size].bt.width)
        dev->query_dv_timings_size++;
    dev->query_dv_timings_qmenu = kmalloc(dev->query_dv_timings_size *
                       (sizeof(void *) + 32), GFP_KERNEL);
    if (dev->query_dv_timings_qmenu == NULL)
        goto free_dev;
    for (i = 0; i < dev->query_dv_timings_size; i++) {
        const struct v4l2_bt_timings *bt = &v4l2_dv_timings_presets[i].bt;
        char *p = (char *)&dev->query_dv_timings_qmenu[dev->query_dv_timings_size];
        u32 htot, vtot;
        p += i * 32;
        dev->query_dv_timings_qmenu[i] = p;
        htot = V4L2_DV_BT_FRAME_WIDTH(bt);
        vtot = V4L2_DV_BT_FRAME_HEIGHT(bt);
        snprintf(p, 32, "%ux%u%s%u",
            bt->width, bt->height, bt->interlaced ? "i" : "p",
            (u32)bt->pixelclock / (htot * vtot));
    }
    /* disable invalid ioctls based on the feature set */
    if (!dev->has_audio_inputs) {
        v4l2_disable_ioctl(&dev->vid_cap_dev, VIDIOC_S_AUDIO);
        v4l2_disable_ioctl(&dev->vid_cap_dev, VIDIOC_G_AUDIO);
        v4l2_disable_ioctl(&dev->vid_cap_dev, VIDIOC_ENUMAUDIO);
        v4l2_disable_ioctl(&dev->vbi_cap_dev, VIDIOC_S_AUDIO);
        v4l2_disable_ioctl(&dev->vbi_cap_dev, VIDIOC_G_AUDIO);
        v4l2_disable_ioctl(&dev->vbi_cap_dev, VIDIOC_ENUMAUDIO);
    }
    if (!dev->has_audio_outputs) {
        v4l2_disable_ioctl(&dev->vid_out_dev, VIDIOC_S_AUDOUT);
        v4l2_disable_ioctl(&dev->vid_out_dev, VIDIOC_G_AUDOUT);
        v4l2_disable_ioctl(&dev->vid_out_dev, VIDIOC_ENUMAUDOUT);
        v4l2_disable_ioctl(&dev->vbi_out_dev, VIDIOC_S_AUDOUT);
        v4l2_disable_ioctl(&dev->vbi_out_dev, VIDIOC_G_AUDOUT);
        v4l2_disable_ioctl(&dev->vbi_out_dev, VIDIOC_ENUMAUDOUT);
    }
    if (!in_type_counter[TV] && !in_type_counter[SVID]) {
        v4l2_disable_ioctl(&dev->vid_cap_dev, VIDIOC_S_STD);
        v4l2_disable_ioctl(&dev->vid_cap_dev, VIDIOC_G_STD);
        v4l2_disable_ioctl(&dev->vid_cap_dev, VIDIOC_ENUMSTD);
        v4l2_disable_ioctl(&dev->vid_cap_dev, VIDIOC_QUERYSTD);
    }
    if (!out_type_counter[SVID]) {
        v4l2_disable_ioctl(&dev->vid_out_dev, VIDIOC_S_STD);
        v4l2_disable_ioctl(&dev->vid_out_dev, VIDIOC_G_STD);
        v4l2_disable_ioctl(&dev->vid_out_dev, VIDIOC_ENUMSTD);
    }
    if (!has_tuner && !has_modulator) {
        v4l2_disable_ioctl(&dev->vid_cap_dev, VIDIOC_S_FREQUENCY);
        v4l2_disable_ioctl(&dev->vid_cap_dev, VIDIOC_G_FREQUENCY);
        v4l2_disable_ioctl(&dev->vbi_cap_dev, VIDIOC_S_FREQUENCY);
        v4l2_disable_ioctl(&dev->vbi_cap_dev, VIDIOC_G_FREQUENCY);
    }
    if (!has_tuner) {
        v4l2_disable_ioctl(&dev->vid_cap_dev, VIDIOC_S_TUNER);
        v4l2_disable_ioctl(&dev->vid_cap_dev, VIDIOC_G_TUNER);
        v4l2_disable_ioctl(&dev->vbi_cap_dev, VIDIOC_S_TUNER);
        v4l2_disable_ioctl(&dev->vbi_cap_dev, VIDIOC_G_TUNER);
    }
    if (in_type_counter[HDMI] == 0) {
        v4l2_disable_ioctl(&dev->vid_cap_dev, VIDIOC_S_EDID);
        v4l2_disable_ioctl(&dev->vid_cap_dev, VIDIOC_G_EDID);
        v4l2_disable_ioctl(&dev->vid_cap_dev, VIDIOC_DV_TIMINGS_CAP);
        v4l2_disable_ioctl(&dev->vid_cap_dev, VIDIOC_G_DV_TIMINGS);
        v4l2_disable_ioctl(&dev->vid_cap_dev, VIDIOC_S_DV_TIMINGS);
        v4l2_disable_ioctl(&dev->vid_cap_dev, VIDIOC_ENUM_DV_TIMINGS);
        v4l2_disable_ioctl(&dev->vid_cap_dev, VIDIOC_QUERY_DV_TIMINGS);
    }
    if (out_type_counter[HDMI] == 0) {
        v4l2_disable_ioctl(&dev->vid_out_dev, VIDIOC_G_EDID);
        v4l2_disable_ioctl(&dev->vid_out_dev, VIDIOC_DV_TIMINGS_CAP);
        v4l2_disable_ioctl(&dev->vid_out_dev, VIDIOC_G_DV_TIMINGS);
        v4l2_disable_ioctl(&dev->vid_out_dev, VIDIOC_S_DV_TIMINGS);
        v4l2_disable_ioctl(&dev->vid_out_dev, VIDIOC_ENUM_DV_TIMINGS);
    }
    if (!dev->has_fb) {
        v4l2_disable_ioctl(&dev->vid_out_dev, VIDIOC_G_FBUF);
        v4l2_disable_ioctl(&dev->vid_out_dev, VIDIOC_S_FBUF);
        v4l2_disable_ioctl(&dev->vid_out_dev, VIDIOC_OVERLAY);
    }
    v4l2_disable_ioctl(&dev->vid_cap_dev, VIDIOC_S_HW_FREQ_SEEK);
    v4l2_disable_ioctl(&dev->vbi_cap_dev, VIDIOC_S_HW_FREQ_SEEK);
    v4l2_disable_ioctl(&dev->sdr_cap_dev, VIDIOC_S_HW_FREQ_SEEK);
    v4l2_disable_ioctl(&dev->vid_out_dev, VIDIOC_S_FREQUENCY);
    v4l2_disable_ioctl(&dev->vid_out_dev, VIDIOC_G_FREQUENCY);
    v4l2_disable_ioctl(&dev->vid_out_dev, VIDIOC_ENUM_FRAMESIZES);
    v4l2_disable_ioctl(&dev->vid_out_dev, VIDIOC_ENUM_FRAMEINTERVALS);
    v4l2_disable_ioctl(&dev->vbi_out_dev, VIDIOC_S_FREQUENCY);
    v4l2_disable_ioctl(&dev->vbi_out_dev, VIDIOC_G_FREQUENCY);
/* configure internal data */
    // 设置内部数据
    dev->fmt_cap = &vivid_formats[0];
    // 设置输入格式
    dev->fmt_out = &vivid_formats[0];
    // 设置输出格式
    if (!dev->multiplanar)
        // 如果不是多平面,则数据偏移为0
        vivid_formats[0].data_offset[0] = 0;
    dev->webcam_size_idx = 1;
    // 设置webcam的大小
    dev->webcam_ival_idx = 3;
    // 设置webcam的间隔
    tpg_s_fourcc(&dev->tpg, dev->fmt_cap->fourcc);
    // 设置tpg的fourcc
    dev->std_cap = V4L2_STD_PAL;
    // 设置输入标准为PAL
    dev->std_out = V4L2_STD_PAL;
    // 设置输出标准为PAL
    if (dev->input_type[0] == TV || dev->input_type[0] == SVID)
        // 如果输入类型为TV或SVID,则tvnorms_cap为V4L2_STD_ALL
        tvnorms_cap = V4L2_STD_ALL;
    if (dev->output_type[0] == SVID)
        // 如果输出类型为SVID,则tvnorms_out为V4L2_STD_ALL
        tvnorms_out = V4L2_STD_ALL;
    dev->dv_timings_cap = def_dv_timings;
    // 设置输入dv_timings为默认值
    dev->dv_timings_out = def_dv_timings;
    // 设置输出dv_timings为默认值
    dev->tv_freq = 2804 /* 175.25 * 16 */;
    // 设置tv的频率
    dev->tv_audmode = V4L2_TUNER_MODE_STEREO;
    // 设置tv的音频模式为立体声
    dev->tv_field_cap = V4L2_FIELD_INTERLACED;
    // 设置tv的场为交错场
    dev->tv_field_out = V4L2_FIELD_INTERLACED;
    // 设置输出tv的场为交错场
    dev->radio_rx_freq = 95000 * 16;
    // 设置收音机的接收频率
    dev->radio_rx_audmode = V4L2_TUNER_MODE_STEREO;
    // 设置收音机的音频模式为立体声
    if (dev->has_radio_tx) {
        dev->radio_tx_freq = 95500 * 16;
        // 如果有收音机发射,则设置发射频率
        dev->radio_rds_loop = false;
    }
    dev->radio_tx_subchans = V4L2_TUNER_SUB_STEREO | V4L2_TUNER_SUB_RDS;
    // 设置收音机的子通道
    dev->sdr_adc_freq = 300000;
    // 设置sdr的adc频率
    dev->sdr_fm_freq = 50000000;
    // 设置sdr的fm频率
    dev->edid_max_blocks = dev->edid_blocks = 2;
    // 设置edid的最大块数和块数
    memcpy(dev->edid, vivid_hdmi_edid, sizeof(vivid_hdmi_edid));
    // 复制vivid_hdmi_edid到edid
    ktime_get_ts(&dev->radio_rds_init_ts);
    /* create all controls */
    // 创建所有控件
    ret = vivid_create_controls(dev, ccs_cap == -1, ccs_out == -1, no_error_inj,
            in_type_counter[TV] || in_type_counter[SVID] ||
            out_type_counter[SVID],
            in_type_counter[HDMI] || out_type_counter[HDMI]);
    if (ret)
        goto unreg_dev;
/*
     * update the capture and output formats to do a proper initial
     * configuration.
     */
    // 更新捕获和输出格式以进行正确的初始配置。
    vivid_update_format_cap(dev, false);
    vivid_update_format_out(dev);
    v4l2_ctrl_handler_setup(&dev->ctrl_hdl_vid_cap);
    v4l2_ctrl_handler_setup(&dev->ctrl_hdl_vid_out);
    v4l2_ctrl_handler_setup(&dev->ctrl_hdl_vbi_cap);
    v4l2_ctrl_handler_setup(&dev->ctrl_hdl_vbi_out);
    v4l2_ctrl_handler_setup(&dev->ctrl_hdl_radio_rx);
    v4l2_ctrl_handler_setup(&dev->ctrl_hdl_radio_tx);
    v4l2_ctrl_handler_setup(&dev->ctrl_hdl_sdr_cap);
    /* initialize overlay */
    // 初始化叠加层
    dev->fb_cap.fmt.width = dev->src_rect.width;
    dev->fb_cap.fmt.height = dev->src_rect.height;
    dev->fb_cap.fmt.pixelformat = dev->fmt_cap->fourcc;
    dev->fb_cap.fmt.bytesperline = dev->src_rect.width * tpg_g_twopixelsize(&dev->tpg, 0) / 2;
    dev->fb_cap.fmt.sizeimage = dev->src_rect.height * dev->fb_cap.fmt.bytesperline;
    /* initialize locks */
    // 初始化锁
    spin_lock_init(&dev->slock);
    mutex_init(&dev->mutex);
    /* init dma queues */
    // 初始化dma队列
    INIT_LIST_HEAD(&dev->vid_cap_active);
    INIT_LIST_HEAD(&dev->vid_out_active);
    INIT_LIST_HEAD(&dev->vbi_cap_active);
    INIT_LIST_HEAD(&dev->vbi_out_active);
    INIT_LIST_HEAD(&dev->sdr_cap_active);
    /* start creating the vb2 queues */
    // 开始创建vb2队列
    if (dev->has_vid_cap) {
        /* initialize vid_cap queue */
        // 初始化vid_cap队列
        q = &dev->vb_vid_cap_q;
        q->type = dev->multiplanar ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE :
            V4L2_BUF_TYPE_VIDEO_CAPTURE;
        q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ;
        q->drv_priv = dev;
        q->buf_struct_size = sizeof(struct vivid_buffer);
        q->ops = &vivid_vid_cap_qops;
        q->mem_ops = &vb2_vmalloc_memops;
        q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
        q->min_buffers_needed = 2;
        q->lock = &dev->mutex;
        ret = vb2_queue_init(q);
        if (ret)
            goto unreg_dev;
    }
if (dev->has_vid_out) {
        /* 初始化vid_out队列 */
        q = &dev->vb_vid_out_q;
        q->type = dev->multiplanar ? V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE :
            V4L2_BUF_TYPE_VIDEO_OUTPUT;
        q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_WRITE;
        q->drv_priv = dev;
        q->buf_struct_size = sizeof(struct vivid_buffer);
        q->ops = &vivid_vid_out_qops;
        q->mem_ops = &vb2_vmalloc_memops;
        q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
        q->min_buffers_needed = 2;
        q->lock = &dev->mutex;
        ret = vb2_queue_init(q);
        if (ret)
            goto unreg_dev;
    }
    if (dev->has_vbi_cap) {
        /* 初始化vbi_cap队列 */
        q = &dev->vb_vbi_cap_q;
        q->type = dev->has_raw_vbi_cap ? V4L2_BUF_TYPE_VBI_CAPTURE :
                          V4L2_BUF_TYPE_SLICED_VBI_CAPTURE;
        q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ;
        q->drv_priv = dev;
        q->buf_struct_size = sizeof(struct vivid_buffer);
        q->ops = &vivid_vbi_cap_qops;
        q->mem_ops = &vb2_vmalloc_memops;
        q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
        q->min_buffers_needed = 2;
        q->lock = &dev->mutex;
        ret = vb2_queue_init(q);
        if (ret)
            goto unreg_dev;
    }
    if (dev->has_vbi_out) {
        /* 初始化vbi_out队列 */
        q = &dev->vb_vbi_out_q;
        q->type = dev->has_raw_vbi_out ? V4L2_BUF_TYPE_VBI_OUTPUT :
                          V4L2_BUF_TYPE_SLICED_VBI_OUTPUT;
        q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_WRITE;
        q->drv_priv = dev;
        q->buf_struct_size = sizeof(struct vivid_buffer);
        q->ops = &vivid_vbi_out_qops;
        q->mem_ops = &vb2_vmalloc_memops;
        q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
        q->min_buffers_needed = 2;
        q->lock = &dev->mutex;
        ret = vb2_queue_init(q);
        if (ret)
            goto unreg_dev;
    }
if (dev->has_sdr_cap) {
        /* 初始化sdr_cap队列 */
        q = &dev->vb_sdr_cap_q;
        q->type = V4L2_BUF_TYPE_SDR_CAPTURE;
        q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ;
        q->drv_priv = dev;
        q->buf_struct_size = sizeof(struct vivid_buffer);
        q->ops = &vivid_sdr_cap_qops;
        q->mem_ops = &vb2_vmalloc_memops;
        q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
        q->min_buffers_needed = 8;
        q->lock = &dev->mutex;
        ret = vb2_queue_init(q);
        if (ret)
            goto unreg_dev;
    }
    if (dev->has_fb) {
        /* 为测试捕获/输出叠加创建帧缓冲区 */
        ret = vivid_fb_init(dev);
        if (ret)
            goto unreg_dev;
        v4l2_info(&dev->v4l2_dev, "Framebuffer device registered as fb%d\n",
                dev->fb_info.node);
    }
    /* 最后开始创建设备节点 */
    if (dev->has_vid_cap) {
        vfd = &dev->vid_cap_dev;
        strlcpy(vfd->name, "vivid-vid-cap", sizeof(vfd->name));
        vfd->fops = &vivid_fops;
        vfd->ioctl_ops = &vivid_ioctl_ops;
        vfd->release = video_device_release_empty;
        vfd->v4l2_dev = &dev->v4l2_dev;
        vfd->queue = &dev->vb_vid_cap_q;
        vfd->tvnorms = tvnorms_cap;
        /*
         * 提供一个互斥锁给v4l2核心。它将用于保护所有fops和v4l2 ioctls。
         */
        vfd->lock = &dev->mutex;
        video_set_drvdata(vfd, dev);
        ret = video_register_device(vfd, VFL_TYPE_GRABBER, vid_cap_nr[inst]);
        if (ret < 0)
            goto unreg_dev;
        v4l2_info(&dev->v4l2_dev, "V4L2 capture device registered as %s\n",
                      video_device_node_name(vfd));
    }
// 如果设备支持视频采集
    if (dev->has_vid_out) {
        vfd = &dev->vid_out_dev;
        strlcpy(vfd->name, "vivid-vid-out", sizeof(vfd->name));
        vfd->vfl_dir = VFL_DIR_TX;
        vfd->fops = &vivid_fops;
        vfd->ioctl_ops = &vivid_ioctl_ops;
        vfd->release = video_device_release_empty;
        vfd->v4l2_dev = &dev->v4l2_dev;
        vfd->queue = &dev->vb_vid_out_q;
        vfd->tvnorms = tvnorms_out;
        /*
         * 提供一个互斥锁给v4l2核心。它将用于保护所有fops和v4l2 ioctls。
         */
        vfd->lock = &dev->mutex;
        video_set_drvdata(vfd, dev);
        ret = video_register_device(vfd, VFL_TYPE_GRABBER, vid_out_nr[inst]);
        if (ret < 0)
            goto unreg_dev;
        v4l2_info(&dev->v4l2_dev, "V4L2 output device registered as %s\n",
                      video_device_node_name(vfd));
    }
    // 如果设备支持VBI采集
    if (dev->has_vbi_cap) {
        vfd = &dev->vbi_cap_dev;
        strlcpy(vfd->name, "vivid-vbi-cap", sizeof(vfd->name));
        vfd->fops = &vivid_fops;
        vfd->ioctl_ops = &vivid_ioctl_ops;
        vfd->release = video_device_release_empty;
        vfd->v4l2_dev = &dev->v4l2_dev;
        vfd->queue = &dev->vb_vbi_cap_q;
        vfd->lock = &dev->mutex;
        vfd->tvnorms = tvnorms_cap;
        video_set_drvdata(vfd, dev);
        ret = video_register_device(vfd, VFL_TYPE_VBI, vbi_cap_nr[inst]);
        if (ret < 0)
            goto unreg_dev;
        v4l2_info(&dev->v4l2_dev, "V4L2 capture device registered as %s, supports %s VBI\n",
                      video_device_node_name(vfd),
                      (dev->has_raw_vbi_cap && dev->has_sliced_vbi_cap) ?
                      "raw and sliced" :
                      (dev->has_raw_vbi_cap ? "raw" : "sliced"));
    }
if (dev->has_vbi_out) {
        // 如果设备支持VBI输出
        vfd = &dev->vbi_out_dev;
        // 设置设备名称
        strlcpy(vfd->name, "vivid-vbi-out", sizeof(vfd->name));
        // 设置设备方向为发送
        vfd->vfl_dir = VFL_DIR_TX;
        // 设置文件操作
        vfd->fops = &vivid_fops;
        // 设置ioctl操作
        vfd->ioctl_ops = &vivid_ioctl_ops;
        // 设置设备释放函数
        vfd->release = video_device_release_empty;
        // 设置v4l2设备
        vfd->v4l2_dev = &dev->v4l2_dev;
        // 设置队列
        vfd->queue = &dev->vb_vbi_out_q;
        // 设置锁
        vfd->lock = &dev->mutex;
        // 设置视频标准
        vfd->tvnorms = tvnorms_out;
        // 设置设备私有数据
        video_set_drvdata(vfd, dev);
        // 注册设备
        ret = video_register_device(vfd, VFL_TYPE_VBI, vbi_out_nr[inst]);
        if (ret < 0)
            goto unreg_dev;
        // 打印设备信息
        v4l2_info(&dev->v4l2_dev, "V4L2 output device registered as %s, supports %s VBI\n",
                      video_device_node_name(vfd),
                      (dev->has_raw_vbi_out && dev->has_sliced_vbi_out) ?
                      "raw and sliced" :
                      (dev->has_raw_vbi_out ? "raw" : "sliced"));
    }
    if (dev->has_sdr_cap) {
        // 如果设备支持SDR采集
        vfd = &dev->sdr_cap_dev;
        // 设置设备名称
        strlcpy(vfd->name, "vivid-sdr-cap", sizeof(vfd->name));
        // 设置文件操作
        vfd->fops = &vivid_fops;
        // 设置ioctl操作
        vfd->ioctl_ops = &vivid_ioctl_ops;
        // 设置设备释放函数
        vfd->release = video_device_release_empty;
        // 设置v4l2设备
        vfd->v4l2_dev = &dev->v4l2_dev;
        // 设置队列
        vfd->queue = &dev->vb_sdr_cap_q;
        // 设置锁
        vfd->lock = &dev->mutex;
        // 设置设备私有数据
        video_set_drvdata(vfd, dev);
        // 注册设备
        ret = video_register_device(vfd, VFL_TYPE_SDR, sdr_cap_nr[inst]);
        if (ret < 0)
            goto unreg_dev;
        // 打印设备信息
        v4l2_info(&dev->v4l2_dev, "V4L2 capture device registered as %s\n",
                      video_device_node_name(vfd));
    }
if (dev->has_radio_rx) {
        // 如果设备支持无线电接收
        vfd = &dev->radio_rx_dev;
        // 设置设备名称
        strlcpy(vfd->name, "vivid-rad-rx", sizeof(vfd->name));
        // 设置文件操作
        vfd->fops = &vivid_radio_fops;
        // 设置ioctl操作
        vfd->ioctl_ops = &vivid_ioctl_ops;
        // 设置设备释放函数
        vfd->release = video_device_release_empty;
        // 设置v4l2设备
        vfd->v4l2_dev = &dev->v4l2_dev;
        // 设置锁
        vfd->lock = &dev->mutex;
        // 设置设备私有数据
        video_set_drvdata(vfd, dev);
        // 注册设备
        ret = video_register_device(vfd, VFL_TYPE_RADIO, radio_rx_nr[inst]);
        if (ret < 0)
            goto unreg_dev;
        // 打印设备信息
        v4l2_info(&dev->v4l2_dev, "V4L2 receiver device registered as %s\n",
                      video_device_node_name(vfd));
    }
    if (dev->has_radio_tx) {
        // 如果设备支持无线电发送
        vfd = &dev->radio_tx_dev;
        // 设置设备名称
        strlcpy(vfd->name, "vivid-rad-tx", sizeof(vfd->name));
        // 设置vfl_dir
        vfd->vfl_dir = VFL_DIR_TX;
        // 设置文件操作
        vfd->fops = &vivid_radio_fops;
        // 设置ioctl操作
        vfd->ioctl_ops = &vivid_ioctl_ops;
        // 设置设备释放函数
        vfd->release = video_device_release_empty;
        // 设置v4l2设备
        vfd->v4l2_dev = &dev->v4l2_dev;
        // 设置锁
        vfd->lock = &dev->mutex;
        // 设置设备私有数据
        video_set_drvdata(vfd, dev);
        // 注册设备
        ret = video_register_device(vfd, VFL_TYPE_RADIO, radio_tx_nr[inst]);
        if (ret < 0)
            goto unreg_dev;
        // 打印设备信息
        v4l2_info(&dev->v4l2_dev, "V4L2 transmitter device registered as %s\n",
                      video_device_node_name(vfd));
    }
    /* 现在一切都很好,让我们将其添加到设备列表中 */
    vivid_devs[inst] = dev;
    return 0;
unreg_dev:
    // 取消注册设备
    video_unregister_device(&dev->radio_tx_dev);
    video_unregister_device(&dev->radio_rx_dev);
    video_unregister_device(&dev->sdr_cap_dev);
    video_unregister_device(&dev->vbi_out_dev);
    video_unregister_device(&dev->vbi_cap_dev);
    video_unregister_device(&dev->vid_out_dev);
    video_unregister_device(&dev->vid_cap_dev);
free_dev:
    // 释放设备
    v4l2_device_put(&dev->v4l2_dev);
    return ret;
}


相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
2月前
|
前端开发 JavaScript
框架
框架
13 3
|
8月前
|
机器学习/深度学习 计算机视觉
AIGM 框架
AIGM (Adaptive Image Generation and Manipulation) 是一个基于深度学习的图像生成和处理框架。它使用先进的生成对抗网络 (GAN) 和变分自编码器 (VAE) 技术,可以实现图像的自动生成、转换、编辑和增强等功能。
145 8
|
9月前
|
SQL XML 前端开发
1.1 初识框架
思考:框架是什么?我们为什么要学习框架呢?“框架(Framework)”一词最早出现在建筑领域,指的是在建造房屋前期构建的建筑骨架。在编程领域,框架就是应用程序的骨架,开发人员可以在这个骨架上加入自己的东西,搭建出符合自己需求的应用系统。实际开发中,随着业务的发展,软件系统变得越来越复杂,如果所有的软件都从底层功能开始开发,那将是一个漫长而繁琐的过程。此外,团队协作开发时,由于没有统一的调用规范,系统会出现大量的重复功能的代码,给系统的二次开发和维护带来不便。为解决上述问题,框架应运而生。
52 0
|
9月前
|
存储 Java 应用服务中间件
|
10月前
|
IDE Linux 开发工具
C++之openFrameworks框架
openFrameworks(简称 oF)是一个基于C++的开源库。 它提供了丰富的功能库和工具,用于快速开发多媒体、交互性和艺术创作相关的应用程序,如艺术装置、互动艺术、音视频作品、实时图形等。oF 的设计目标是让创意编程变得更加简单、直观和灵活,使艺术家、设计师、创意工作者等能够利用编程进行创作和表达。oF提供了丰富的图形、音频、输入输出、计算机视觉等功能库,并支持跨平台开发,适用于Windows、Mac OSX、Linux等操作系统。oF的社区活跃,有大量的用户和开发者共享和贡献了各种扩展、插件和示例代码。
89 0
|
程序员 测试技术
【提高自己】正确的工作方法,形成自己的思考框架
在学习过程中,将老师的知识用脑图的形式记录下来,在这里做个分享,不足之处欢迎大家指出。
|
SQL JSON 前端开发
|
传感器
CoreMotion 框架
CoreMotion框架(一)—— 基础理论CoreMotion框架(三)—— 获取陀螺仪数据CoreMotion框架(二)—— 利用加速度计获取设备的方向CoreMotion框架(四)—— 仿摩拜贴纸小球碰撞动画 ...
942 0