Android Camera 一 源码路径

Android Camera 二 JNI JAVA和C/CPP图像数据传输流程分析

Android Camera 三 CameraService 和 Client 链接到 HAL

Android Camera 四 Camera HAL 分析

Linux v4l2 一 应用层

Linux v4l2 二 驱动和 usb 摄像头

源码目录

kernel/fs  : 文件系统

kernel/drivers\usb\core :usb 核心

kernel/drivers/media/v4l2-core : v4l2 核心

kernel/drivers/media/usb/uvc  :  usb 视频设备类

字符设备驱动简介

内核提供了三个函数来注册一组字符设备

  • register_chrdev()             # 旧接口,不做讨论
  • register_chrdev_region()  # 静态分配主次设备号, 用 cdev_init 和 cdev_add 完成注册
  • alloc_chrdev_region()       # 动态分配主次设备号, 用 cdev_init 和 cdev_add 完成注册

register_chrdev  比较老的内核注册的形式,不做分析。
register_chrdev_region/alloc_chrdev_region + cdev  新的驱动形式。

  • register_chrdev_region()  : 注册一个字符设备,事先知道要使用的主、次设备号时使用
  • alloc_chrdev_region() :       注册一个字符设备,来让内核自动给我们分配设备号

字符设备注册和卸载的顺序如下: 
注册: register_chrdev_region()  → cdev_add()       // 此过程在加载模块中 
卸载: cdev_del() → unregister_chrdev_region()     // 此过程在卸载模块中

register_chrdev_region/alloc_chrdev_region 的源码如下:

// kernel/fs/char_dev.c
/*** register_chrdev_region()  : 注册一个字符设备,事先知道要使用的主、次设备号时使用的;*                             要先查看cat /proc/devices去查看没有使用的* @from:  要分配的设备编号范围的初始值, 这组连续设备号的起始设备号, 相当于register_chrdev()中主设备号* @count: 连续编号范围. 是这组设备号的大小(也是次设备号的个数)* @name:  编号相关联的设备名称. (/proc/devices); 本组设备的驱动名称** 返回值在成功时为 0,失败时为负错误代码*/
int register_chrdev_region(dev_t from, unsigned count, const char *name)
{struct char_device_struct *cd;dev_t to = from + count;dev_t n, next;for(n = from; n < to; n = next){next = MKDEV(MAJOR(n)+1, 0);if(next > to)next = to;cd = __register_chrdev_region(MAJOR(n), MINOR(n),next - n, name);if(IS_ERR(cd))goto fail;}return 0;
fail:to = n;for(n = from; n < to; n = next){next = MKDEV(MAJOR(n)+1, 0);kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));}return PTR_ERR(cd);
}/*** alloc_chrdev_region() : 注册一个字符设备,来让内核自动给我们分配设备号* @dev: 是输出参数,获得一个分配到的设备号。可以用MAJOR宏和MINOR宏,获得主设备号和次设备号;*       在mknod创建节点和设备文件时用到主设备号和次设备号* @baseminor: 次设备号的基准,从第几个次设备号开始分配* @count: 次设备号的个数* @name: 驱动的名字** 返回值在成功时为 0,失败时为负错误代码*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)
{struct char_device_struct *cd;cd = __register_chrdev_region(0, baseminor, count, name);if(IS_ERR(cd))return PTR_ERR(cd);*dev = MKDEV(cd->major, cd->baseminor);return 0;
}

cdev 字符设备

cdev是表示字符设备的结构体,注册字符设备时,实例化 struct cdev 结构体的内部成员

// kernel/include/linux/cdev.h#ifndef _LINUX_CDEV_H
#define _LINUX_CDEV_H#include <linux/kobject.h>
#include <linux/kdev_t.h>
#include <linux/list.h>struct file_operations;
struct inode;
struct module;struct cdev
{struct kobject kobj;       //struct module *owner;      // 填充时,值要为 THIS_MODULE,表示模块const struct file_operations *ops;  // file_operations结构体是注册驱动的关键,要填充这个结构体的成员,实例化一系列回调函数struct list_head list;dev_t dev;                 // 设备号,主设备号+次设备号,可以用MAJOR宏和MINOR宏,提取主设备号和次设备号unsigned int count;        // 次设备号个数
};void cdev_init(struct cdev *, const struct file_operations *); // 将struct cdev类型的结构体变量和file_operations结构体进行绑定的struct cdev *cdev_alloc(void);                                 // 动态申请一个 cdev 内存void cdev_put(struct cdev *p);                                 // 释放一个 cdev 内存int cdev_add(struct cdev *, dev_t, unsigned);                  // 向内核里面添加一个驱动,注册驱动void cdev_del(struct cdev *);                                  // 从内核中注销掉一个驱动。注销驱动void cd_forget(struct inode *);extern struct backing_dev_info directly_mappable_cdev_bdi;#endif

V4L2 驱动分析

我们知道 video 设备是在 v4l2 中注册字符驱动。

在 Linux 内核的 v4l2 源码目录中执行搜索注册字符设备的函数名 register_chrdev_region

# 查找哪个文件注册 v4l2 设备
grep -R "register_chrdev_region" kernel/drivers/media/v4l2-core/

打印如下: 定位到 v4l2-dev.c

下面来分析: v4l2-dev.c 中的与设备注册相关的函数 :

设备注册和卸载 : __init和 __exit

// kernel/drivers/media/v4l2-core/v4l2-dev.c/**  Initialise video for linux*/
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;
}static void __exit videodev_exit(void)
{dev_t dev = MKDEV(VIDEO_MAJOR, 0);class_unregister(&video_class);unregister_chrdev_region(dev, VIDEO_NUM_DEVICES);
}

在 Linux 系统中添加一个 v4l2 设备节点,如下函数 __video_register_device()  添加一个视频类字符设备

// kernel/drivers/media/v4l2-core/v4l2-dev.cint __video_register_device(struct video_device *vdev, int type, int nr,int warn_if_nr_in_use, struct module *owner)
{....../* Part 1: 检查设备类型 */....../* Part 2: 找到一个空闲次设备号、设备节点号和设备索引 */....../* Part 3: 添加一个字符设备 */vdev->cdev = cdev_alloc();if(vdev->cdev == NULL){ret = -ENOMEM;goto cleanup;}vdev->cdev->ops = &v4l2_fops;vdev->cdev->owner = owner;ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);if(ret < 0){printk(KERN_ERR "%s: cdev_add failed\n", __func__);kfree(vdev->cdev);vdev->cdev = NULL;goto cleanup;}/* Part 4: 将设备注册到sysfs */vdev->dev.class = &video_class;vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor);if(vdev->parent)vdev->dev.parent = vdev->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;}/* 注册释放回调函数 */vdev->dev.release = v4l2_device_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 */if(vdev->v4l2_dev)v4l2_device_get(vdev->v4l2_dev);#if defined(CONFIG_MEDIA_CONTROLLER)/* Part 5: 注册实例 */if(vdev->v4l2_dev && vdev->v4l2_dev->mdev &&vdev->vfl_type != VFL_TYPE_SUBDEV){vdev->entity.type = MEDIA_ENT_T_DEVNODE_V4L;vdev->entity.name = vdev->name;vdev->entity.info.v4l.major = VIDEO_MAJOR;vdev->entity.info.v4l.minor = vdev->minor;ret = media_device_register_entity(vdev->v4l2_dev->mdev,&vdev->entity);if(ret < 0)printk(KERN_WARNING"%s: media_device_register_entity failed\n",__func__);}
#endif/* Part 6: 在video_device 数组中添加当前 vdev 设备,通过次设备号索引并使用该设备 */set_bit(V4L2_FL_REGISTERED, &vdev->flags);mutex_lock(&videodev_lock);video_device[vdev->minor] = vdev;mutex_unlock(&videodev_lock);return 0;cleanup:mutex_lock(&videodev_lock);if(vdev->cdev)cdev_del(vdev->cdev);devnode_clear(vdev);mutex_unlock(&videodev_lock);/* Mark this video device as never having been registered. */vdev->minor = -1;return ret;
}

用 usb 摄像头来分析硬件层

usb core 中读取设备描述符和解析描述符中的的协议,调用 uvc 类来注册一个 usb 摄像头设备。

usb 驱动内容很多,不做展开,仅分析 uvc 驱动。

uvc 驱动调用 uvc_probe() 函数来探测并加载 usb 摄像头;

// kernel/drivers/media/usb/uvc/uvc_driver.c
// usb 摄像头设备结构体
struct uvc_driver uvc_driver =
{.driver = {.name       = "uvcvideo",.probe      = uvc_probe,        // probe 方法,探测并加载 usb 摄像头.disconnect = uvc_disconnect,.suspend    = uvc_suspend,.resume     = uvc_resume,.reset_resume   = uvc_reset_resume, // 支持的设备id列表.id_table   = uvc_ids,.supports_autosuspend = 1,},
};// init 初始化函数,注册结构体 uvc_driver
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;
}

接下来分析 probe() 中的函数调用:

// kernel/drivers/media/usb/uvc/uvc_driver.c/* ------------------------------------------------------------------------* USB probe, disconnect, suspend and resume*/static int uvc_probe(struct usb_interface *intf,const struct usb_device_id *id)
{struct usb_device *udev = interface_to_usbdev(intf);struct uvc_device *dev;int ret;if(id->idVendor && id->idProduct)uvc_trace(UVC_TRACE_PROBE, "Probing known UVC device %s ""(%04x:%04x)\n", udev->devpath, id->idVendor,id->idProduct);elseuvc_trace(UVC_TRACE_PROBE, "Probing generic UVC device %s\n",udev->devpath);/* 内核为设备分配一个 struct usb_device 大小的内存并初始化 */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->users, 0);atomic_set(&dev->nmappings, 0);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;if(udev->product != NULL)strlcpy(dev->name, udev->product, sizeof dev->name);elsesnprintf(dev->name, sizeof dev->name,"UVC Camera (%04x:%04x)",le16_to_cpu(udev->descriptor.idVendor),le16_to_cpu(udev->descriptor.idProduct));/* Parse the Video Class control descriptor. */if(uvc_parse_control(dev) < 0){uvc_trace(UVC_TRACE_PROBE, "Unable to parse UVC ""descriptors.\n");goto error;}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){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_CONTROLLERdev->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;if(media_device_register(&dev->mdev) < 0)goto error;dev->vdev.mdev = &dev->mdev;
#endif/* 注册一个媒体类设备 */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. *//* 注册 video 设备节点 */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. */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_trace(UVC_TRACE_PROBE, "UVC device initialized.\n");usb_enable_autosuspend(udev);return 0;error:uvc_unregister_video(dev);return -ENODEV;
}

阅读源码通过注释,得知 uvc_register_chains() 时注册设备节点的,来重点分析这个函数

    /* Register video device nodes. *//* 注册 video 设备节点 */if(uvc_register_chains(dev) < 0)goto error;

uvc_probe() →  uvc_register_chains() → uvc_register_terms() → uvc_register_video()

uvc_register_video() 调用 v4l2 核心层的v4l2-dev.c 来注册 video 字符设备:  

  •  video_device_alloc()     
  • video_register_device()  调用  __video_register_device()

在上文中分析了  __video_register_device() 函数中注册了 video 字符设备。  到此 v4l2 框架的注册的过程分析完成。

// kernel/drivers/media/usb/uvc/uvc_v4l2.c
// v4l2 驱动中 ioctl 操作,被用户层调用
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
};// kernel/drivers/media/usb/uvc/uvc_driver.c
static int uvc_register_video(struct uvc_device *dev,struct uvc_streaming *stream)
{struct video_device *vdev;int ret;/* Initialize the streaming interface with default streaming* parameters.*/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);/* Register the device with V4L. */vdev = video_device_alloc();if(vdev == NULL){uvc_printk(KERN_ERR, "Failed to allocate video device (%d).\n",ret);return -ENOMEM;}/* We already hold a reference to dev->udev. The video device will be* unregistered before the reference is released, so we don't need to* get another one.*/vdev->v4l2_dev = &dev->vdev;vdev->fops = &uvc_fops;                    //  注册 struct v4l2_file_operations uvc_fops , v4l2 open/close/ioctl 等函数vdev->release = uvc_release;vdev->prio = &stream->chain->prio;set_bit(V4L2_FL_USE_FH_PRIO, &vdev->flags);if(stream->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)vdev->vfl_dir = VFL_DIR_TX;strlcpy(vdev->name, dev->name, sizeof vdev->name);/* Set the driver data before calling video_register_device, otherwise* uvc_v4l2_open might race us.*/stream->vdev = vdev;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);stream->vdev = NULL;video_device_release(vdev);return ret;}if(stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)stream->chain->caps |= V4L2_CAP_VIDEO_CAPTURE;elsestream->chain->caps |= V4L2_CAP_VIDEO_OUTPUT;atomic_inc(&dev->nstreams);return 0;
}

Linux v4l2 二 驱动和 usb 摄像头相关推荐

  1. linux下基于qt和v4l2驱动的usb摄像头视频采集与显示,用v4l2和framebuffer实现usb摄像头视频采集并显示...

    Windows程序是基于消息的,不管其封装形式如何,最后都要包含如下代码MSG msg;while(GetMesssage(msg)){TranslateMessage(msg);DispatchMe ...

  2. Linux系统下自动搜索USB摄像头地址及设备信息

    Linux系统下自动搜索USB摄像头地址及设备信息 在进行到多个USB摄像头开发时,会涉及到获取摄像头地址及设备信息问题. 一般USB摄像头的设备信息是固定的,我们基于设备信息,就可以在软件开发时进行 ...

  3. Linux下基于XScale的USB摄像头图像采集

    1.引言 摄像头分为数字摄像头和模拟摄像头两大类.传统的模拟摄像头,获取图像信息需要先将视频采集设备产生的模拟视频信号经过特定的视频捕捉卡转换成数字信号,进而才能进行存储等处理.数字摄像头可以直接捕捉 ...

  4. Linux下用FFMPEG采集usb摄像头到RTMP

    Linux下用 FFMPEG 采集 usb摄像头视频 和 摄像头内置麦克风音频 到RTMP服务 ffmpeg -f video4linux2 -qscale 10 -r 12 -s 640x480 - ...

  5. Linux基础入门--驱动开发--USB

    Linux基础入门--驱动开发--USB 1.基本概念 2.组成结构 2.1 设备描述符 2.2 配置描述符 2.3 接口描述符 2.4 端点描述符 2.5 字符串描述符 3.管道 4.端点分类 4. ...

  6. linux下uvc协议访问usb摄像头,Ubuntu调用USB摄像头

    FreeBSD Webcam:传送门 1 查看摄像头USB驱动 CMD ls /dev/v* Result /dev/vcs /dev/vcs4 /dev/vcsa1 /dev/vcsa5 /dev/ ...

  7. linux添加ax88772b驱动,佳能 USB 2.0 to Fast Ethernet AX88772B 驱动程序下载-更新佳能软件(以太网控制器)...

    ASIX USB 2.0 to Fast Ethernet AX88772B 驱动程序下载 如何手动下载和更新: 你可以通过 %%os%% 或通过执行 Windows® 更新获取基本的 USB 2.0 ...

  8. linux v4l2系统详解,Linux摄像头驱动学习之:(一)V4L2_框架分析

    这段时间开始搞安卓camera底层驱动了,把以前的的Linux视频驱动回顾一下,本篇主要概述一下vfl2(video for linux 2). 一. V4L2框架: video for linux ...

  9. 【原创】IP摄像头技术纵览(一)---linux 内核编译,USB摄像头设备识别

    IP摄像头技术纵览(一)- linux 内核编译,USB摄像头设备识别 开始正文之前先来认识一下我的开发环境: 系统:ubuntu 10.04 开发板:AT91SAM9260 + Linux-2.6. ...

最新文章

  1. GitHub 上 6 款牛哄哄的后台模板
  2. 用workbench给表重命名_MySQL Workbench的使用方法(图文)
  3. Log4j MDC Tomcat下报异常org.apache.log4j.helpers.ThreadLocalMap
  4. canal数据同步(客户端代码编写)
  5. 计组—中央处理器(CPU)
  6. php 根据数量创建数组,php实现根据字符串生成对应数组的方法
  7. 【Python3网络爬虫开发实战】1.4.1-MySQL的安装
  8. linux十分钟调度一次,linux系统任务调度命令crontab
  9. 1121. Damn Single (25)-PAT甲级真题
  10. 【Spring Boot】3.Spring Boot的配置
  11. 已经安装mysql xampp_windows 7 本机已安装mysql5的情况上 安装XAMPP
  12. Codeforces Round #168 (Div. 2)
  13. 图像检索 - 评价指标
  14. Monster: half man, half beast and very scary.
  15. 标题:深度分销的方向和尺度 内容:Pnbsp;深度分销的方向和尺度BRnbsp;nbsp; 所谓深度分销,有人也称之为通路精耕细作,是通过减少原有渠道层次,并增强中间商分销能力或通过企
  16. 学习 Linux 有哪些好处?
  17. tcl/tk实例详解——eval
  18. nginx平台初探(100%)
  19. 第一讲 Matlab/Simulink入门——简单系统模型的Simulink仿真
  20. QQ空间获取指定QQ号信息接口

热门文章

  1. Java项目:JSP汉服服饰租赁展示商城项目
  2. TypeScript中的范型
  3. jpeg压缩简单介绍及huffman table
  4. 360声明 腾讯要挟用户卸载360 360将保证和QQ同时正常使用
  5. Word简历中如何插入头像?
  6. 阿里面试官:MySQL如何设计索引更高效?
  7. 地图下钻、地图点聚合
  8. iteritems python3_Python3下错误AttributeError: ‘dict’ object has no attribute’iteritems‘的分析与解决...
  9. 高维向量相似搜索插件 pgvector
  10. php 抓取网页数据