v4l2框架

news2024/11/24 8:27:29

v4l2框架


文章目录

  • v4l2框架
  • 框架
  • 1.硬件相关层
    • uvc_probe
    • uvc_register_chains
    • uvc_register_terms
    • uvc_register_video
  • 2.核心层
    • __video_register_device
  • 3.虚拟视频驱动vivid分析
    • 入口vivid_init注册vivid平台驱动
    • vivid_probe
    • vivid_create_instance


框架

在这里插入图片描述

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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/489731.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023 hnust 大三下 嵌入式 期中考试复习笔记

前言 ★&#xff1a;重点※&#xff1a;补充内容❓&#xff1a;还没搞懂的内容主要来源&#xff1a;教材、PPT、百度百科、AI重点来源&#xff1a;4-6班感谢&#xff1a;lyf总结得很草率&#xff0c;因为没听过课&#xff0c;也玩不懂 重点汇总 考试题型 选择&#xff08;40…

C#_Struct与Class的差异

简述 struct属于值类型&#xff0c;class属于引用类型 存储地址 struct储存于栈&#xff0c;class储存于堆&#xff08;class于栈中储存引用&#xff09; 传参性质 struct属于值传递&#xff0c;在函数内对参数进行修改&#xff0c;不会修改struct class处于引用传递&…

day40—选择题

文章目录 1.上网的时候&#xff0c;访问某个网页却突然出现了某个运营商的网页&#xff08;如联通、电信&#xff09;。出现此问题可能的原因是&#xff08;A&#xff09;2.某浏览器发出的HTTP 请求报文如下&#xff0c;下列叙述中&#xff0c;错误的是&#xff08;C&#xff0…

阿里云CentOS服务器安装Redis教程(一步步操作)

使用阿里云服务器ECS安装Redis数据库流程&#xff0c;操作系统为CentOS 7.6镜像&#xff0c;在CentOS上安装Redis 4.0.14&#xff0c;云服务器选择的是持久内存型re6p实例&#xff0c;新手站长分享阿里云CentOS服务器安装Redis流程方法&#xff1a; 目录 在CentOS系统中部署R…

【内网】面试·HVV专题

【内网】面试HVV专题 1.目标主机没有nc时如何获取反向shell2.说说你了解的隧道技术SSH隧道HTTP/HTTPS协议DNS协议1.目标主机没有nc时如何获取反向shell Python反向shell执行如下命令,在VPS上监听本地2222端口 nc -lvp 2222在目标主机上执行如下命令: bash反向shell执行如下命…

数据结构/栈实现队列

前言 在学习数据结构的过程当中&#xff0c;我们会学到栈和队列&#xff0c;在本篇文章中&#xff0c;重点讲解的是栈实现队列&#xff0c;故关于栈和队列的讲解只是简单带过。 一、栈 栈是一种后进先出的线性表&#xff0c;即只能在固定的一端进行插入和删除。 栈 方法 Stac…

(2)Qt的UI入门

目录 1. QWidget类&#xff08;重点&#xff09; 2. 子组件 1. QWidget类&#xff08;重点&#xff09; QWidget类是Qt中所有组件和窗口的基类&#xff0c;其内部规定了组件和窗口的基本规则和功能。 Qt中每个属性的文档中都有Access functions部分&#xff0c;表示其支持的读写…

机器学习实战教程(十二):聚类算法Kmeans

聚类概念 聚类是一种无监督的机器学习方法&#xff0c;它主要是通过在数据集中找到相似的样本并将它们分组来发现数据中的模式和结构。聚类算法可以将数据分成具有相似特征的组&#xff0c;每个组被称为一个簇。 常见的聚类算法有以下几种&#xff1a; K-means聚类算法&#…

python面向对象三大特性详解 - 封装 继承 多态

前言 面向对象编程有三大特性&#xff1a;封装、继承、多态&#xff0c;本文带大家来认识和了解这三个特性~ 补充 - 新式类 & 经典类 在python2.x中&#xff0c;新式类是继承了object类的子类&#xff0c;以及该子类的子类 子子类...&#xff1b;经典类就是没有继承没有…

java基础入门-05

Java基础入门-05 13、面向对象进阶&#xff08;static&继承&#xff09;1.1 如何定义类1.2 如何通过类创建对象1.3 封装1.3.1 封装的步骤1.3.2 封装的步骤实现 1.4 构造方法1.4.1 构造方法的作用1.4.2 构造方法的格式1.4.3 构造方法的应用 1.5 this关键字的作用1.5.1 this关…

MapReduce 源码分析-1

源码追踪 Class Job 作为使用Java语言编写的MapReduce城西&#xff0c;其入口方法位main方法&#xff0c;在MapReduce Main方法中&#xff0c;整个核心都在围绕着job类&#xff0c;中文称之为作业。 public class WordDriver {public static void main(String[] args) throw…

解决vue中父组件通过props向子组件传递数据,子组件接收不到

问题&#xff1a;父组件在挂载时向后端发起请求获取数据&#xff0c;然后将获取到的数据传递给子组件&#xff0c;子组件想要在挂载时获取数据&#xff0c;获取不到。 代码示例&#xff1a; //父组件 <template><div><HelloWorld :message"message"…

Mysql数据库中的用户管理与授权

1.登录用户的管理 1.1 查看用户密码的信息 用户信息存放在 mysql 数据库下的 user 表&#xff08;MySQL 服务下存在一个系统自带的 mysql 数据库&#xff09;。 经常使用的查看密码信息的命令&#xff1a; 能看到密码信息&#xff1a;是经过加密后的密码信息 select user…

真题详解(关系模型)-软件设计(六十六)

真题详解(ICMP)-软件设计&#xff08;六十五)https://blog.csdn.net/ke1ying/article/details/130475620 2017年下半年 内存按字节编址&#xff0c;若存储容量为32K*8bit的存储芯片构成地址从A0000H到 DFFFFH的内存&#xff0c;至少需要____片芯片。 解析&#xff1a; DFF…

《Netty》从零开始学netty源码(五十二)之PoolThreadCache

PoolThreadCache Netty有一个大的公共内存容器PoolArena&#xff0c;用来管理从操作系统中获得的内存&#xff0c;在高并发下如果所有线程都去这个大容器获取内存它的压力是非常大的&#xff0c;所以Netty为每个线程建立了一个本地缓存&#xff0c;即PoolThreadCache&#xff…

ReentrantLock实现原理-非公平锁

在线程间通信方式2一节中&#xff0c;我们了解了Lock&#xff0c;Condition和ReentrantLock&#xff0c;学习了简单使用Condition和RentrantLock完成线程间通信&#xff0c;从文章中我们了解到ReentrantLock是Lock接口的一个最常用的实现类&#xff0c;ReentrantLock是独占锁&a…

WEBPACK和ROLLUP构建前端工具库

1. WEBPACK webpack 提供了构建和打包不同模块化规则的库&#xff0c;只是需要自己去搭建开发底层架构。vue-cli&#xff0c;基于 webpack &#xff0c; vue-cli 脚手架工具可以快速初始化一个 vue 应用&#xff0c;它也可以初始化一个构建库。 2. ROLLUP rollup 是一个专门…

prusa2.6.0 树形支撑(有机支撑)Organic体验测试 3d打印及下载失败解决

目前官网没有这个2.6版本&#xff0c;只有2.5.2下载&#xff0c;是没有树形支撑的。如果试用2.6版本&#xff0c;需要从GitHub下载。地址为&#xff1a; https://github.com/prusa3d/PrusaSlicer/releases/tag/version_2.6.0-alpha6 或者点击链接&#xff1a; Release PrusaS…

aop切面调用失效问题排查

应用里有较多的地方访问外部供应商接口&#xff0c;由于公网网络不稳定或者外部接口不稳定&#xff08;重启&#xff0c;发版&#xff0c;ip切换&#xff09;的原因&#xff0c;经常报502或者504错误。为了解决HTTP调用的500报错&#xff0c;选择使用spring的retryable注解进行…

Leetcode292. Nim 游戏

Every day a leetcode 题目来源&#xff1a;292. Nim 游戏 解法1&#xff1a;数学推理 让我们考虑一些小例子。 显而易见的是&#xff0c;如果石头堆中只有一块、两块、或是三块石头&#xff0c;那么在你的回合&#xff0c;你就可以把全部石子拿走&#xff0c;从而在游戏中…