V4L2引入(含浅析UVC)

Posted zongzi10010

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了 V4L2引入(含浅析UVC)相关的知识,希望对你有一定的参考价值。


title: V4L2引入(含浅析UVC)
date: 2019/4/23 19:00:00
toc: true
---

V4L2引入(含浅析UVC)

基本框架

  • V4L2全名是video for linux 2之前还有个老版本v4l,也就是video for linux 1.0版本

  • V4L2不仅仅用于摄像头,也用于视频输出接口,收音机接口等,完整的框架可以参考这里

基本框架图如下:摘录自 Linux摄像头驱动1——vivid

技术图片

代码入手

我们插入USB,使用dmesg查看usb的输出信息

uvcvideo: Found UVC 1.00 device USB 2.0 Camera (05a3:9310)

搜索内核源码可以找到相关函数

cd linux-3.4.2/
cd drivers/
$ grep "Found UVC" *  -nR
media/video/uvc/uvc_driver.c:1848:      uvc_printk(KERN_INFO, "Found UVC %u.%02x device %s 

这里就涉及到UVC这个名词了,所谓的UVC也就是usb video class类的驱动,也就是USB硬件相关的驱动,也就是应该会向上注册这个驱动程序

UVC流程简述

这里简单分析下UVC驱动的流程,详细分析到USB摄像头那里,对于UVC驱动也是一个驱动,从入口函数分析下

static int __init uvc_init(void)
{
    int ret;

    uvc_debugfs_init();

    ret = usb_register(&uvc_driver.driver);
    if (ret < 0) {
        uvc_debugfs_cleanup();
        return ret;
    }

    printk(KERN_INFO DRIVER_DESC " (" DRIVER_VERSION ")\n");
    return 0;
}

注册了一个结构体,看下这个结构体的成员

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,
        .supports_autosuspend = 1,
    },
};

按照以往的经验,应该就是先匹配id_table,然后执行probe来初始化,查看下这个probe,深入分析最后是调用cdev注册了一个v4l2_fopsfile_operation,也就是说最终app的操作函数就是这个啦.

v4l2_fops > 具体的驱动

简单流程一览,抓住关键注册函数是uvc_register_chains,而v4l2_device_register并不重要在整体上

// drivers\media\video\uvc\uvc_driver.c
uvc_probe
    v4l2_device_register  //这个不重要,只是进行一些初始化
    ..
    uvc_register_chains  //这里才是实际的注册v4l2管理结构
        >uvc_register_terms
            >uvc_register_video(dev, stream)
                >video_device_alloc                     //分配
                >vdev->v4l2_dev = &dev->vdev;
----            >vdev->fops = &uvc_fops;        //这个是最终的读写函数
                >vdev->release = uvc_release;
                >video_set_drvdata(vdev, stream);       // 设置
                >video_register_device(vdev, VFL_TYPE_GRABBER, -1) //注册
                    // \include\media\v4l2-dev.h
                    static inline int __must_check video_register_device(struct video_device *vdev,
                    int type, int nr)
                    {
                        return __video_register_device(vdev, type, nr, 1, vdev->fops->owner);
                    }
                    
----这个实际的       __video_register_device 注册函数是在  drivers\media\video\v4l2-dev.c  也就是它为下层提供注册接口的  
__video_register_device
    >case VFL_TYPE_GRABBER:
            name_base = "video";
    > 获得一个空的次设备号    
        for (i = 0; i < VIDEO_NUM_DEVICES; i++)
            if (video_device[i] == NULL) break;
    > 这里就有字符设备驱动的了
    >vdev->cdev = cdev_alloc();
    >vdev->cdev->ops = &v4l2_fops;  //这个也就是具体的 /dev/video的fileoperation
    >vdev->cdev->owner = owner;
    >ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
    

调用流程

appopen,read,write 最终就会调用到vdev->cdev->ops=v4l2_fops

// \drivers\media\video\v4l2-dev.c
static const struct file_operations v4l2_fops = {
    .owner = THIS_MODULE,
    .read = v4l2_read,
    .write = v4l2_write,
    .open = v4l2_open,
    .get_unmapped_area = v4l2_get_unmapped_area,
    .mmap = v4l2_mmap,
    .unlocked_ioctl = v4l2_ioctl,
#ifdef CONFIG_COMPAT
    .compat_ioctl = v4l2_compat_ioctl32,
#endif
    .release = v4l2_release,
    .poll = v4l2_poll,
    .llseek = no_llseek,
};

open

vdev = video_devdata(filp);  //return video_device[iminor(file->f_path.dentry->d_inode)];
    >vdev->fops->open(filp)

read

这里调用的最终就是vdev->fops->read

video_device *vdev = video_devdata(filp);
                    > return video_device[iminor(file->f_path.dentry->d_inode)];
vdev->fops->read(filp, buf, sz, off)

这个是在uvc_register_video中设置的

vdev->fops = &uvc_fops;

const struct v4l2_file_operations uvc_fops = {
    .owner      = THIS_MODULE,
    .open       = uvc_v4l2_open,
    .release    = uvc_v4l2_release,
    .unlocked_ioctl = uvc_v4l2_ioctl,
#ifdef CONFIG_COMPAT
    .compat_ioctl32 = uvc_v4l2_compat_ioctl32,
#endif
    .read       = uvc_v4l2_read,
    .mmap       = uvc_v4l2_mmap,
    .poll       = uvc_v4l2_poll,
#ifndef CONFIG_MMU
    .get_unmapped_area = uvc_v4l2_get_unmapped_area,
#endif
};

v4l2_ioctl

也就是调用了`uvc_v4l2_ioctl>uvc_v4l2_do_ioctl

struct video_device *vdev = video_devdata(filp);
vdev->fops->unlocked_ioctl(filp, cmd, arg)

videodev_init(字符设备驱动注册)

这个是v4l2-dev.c的入口,这里就是常规的字符设备驱动,这里使用了主设备号81

static int __init videodev_init(void)
{
    dev_t dev = MKDEV(VIDEO_MAJOR, 0);
    int ret;

    printk(KERN_INFO "Linux video capture interface: v2.00\n");
    ret = register_chrdev_region(dev, VIDEO_NUM_DEVICES, VIDEO_NAME);
    if (ret < 0) {
        printk(KERN_WARNING "videodev: unable to get major %d\n",
                VIDEO_MAJOR);
        return ret;
    }

    ret = class_register(&video_class);
    if (ret < 0) {
        unregister_chrdev_region(dev, VIDEO_NUM_DEVICES);
        printk(KERN_WARNING "video_dev: class_register failed\n");
        return -EIO;
    }

    return 0;
}

以上是关于 V4L2引入(含浅析UVC)的主要内容,如果未能解决你的问题,请参考以下文章

v4l2 UVC 设置摄像头获取摄像头性能

V4l2视频输出实现流程

V4L2学习流程

Linux 下UVC&amp;V4L2技术简单介绍

NanoPi NEO Air使用十五:使用V4L2驱动USB摄像头

NanoPi NEO Air使用十五:使用V4L2驱动USB摄像头