一:将ucv摄像头驱动编译生成uvc模块,我这边采用的是海思3536提供的linux系统时linux-3.10
模块生成参考一下地址
http://www.coin163.com/it/1305375058925494518:
其中模块生成是:
Device Drivers -> Multimedia support -> Video capture adapters -> V4L USB Devices -> USB Video Class (UVC),这个选项就是在make menuconfig的时候对应的Kconfig中的内容,在编译内核是采用该命令生成uvc 内核模块,还有其他的配置选项,主要文件(driver/media/v4l2-core driver/media/usb/uvc)(如果是默认的,需要配置多个选项)然后通过config去配置make menuconfig
注意:刚开始老不能生成.ko文件,最后查找资料发现在make uIamge之后生成然后采用make modules生成模块,才能生成.ko文件。
主要是需要添加ucv摄像头的id_table。
内核中的驱动对应的文件夹是:\linux-3.10.y\drivers\media\usb\uvc在该文件夹下就是用来生成uvc模块的程序。
linux-3.10.y\drivers\media\v4l2-core文件夹程序是基础的uvc程序,用来进行itocl以及内存的分配函数。最好用两个文件夹参考程序。
二:查看摄像头参数(操作平台:虚拟机中查看usb设备参数)
lsusb:用来查看摄像头设备。
lsusb -s 003(Bus):004(device) -v(查看usb设备的详细参数)
https://www.cnblogs.com/tureno/articles/6888794.html 用来分析一个usb的描述符参数
参考程序:dev/gadget、uvc_v412
我们先找一个切入点:v4l2_file_operations结构体
以上是准备工作。下面才是我们正常的uvc摄像头流程分析。
找到ioctal的设置,
1:一些基本结构体:获取设备支持的操作模式struct v4l2_capability 与 VIDIOC_QUERYCA
struct v4l2_capability {
__u8 driver[16]; /* i.e. "bttv" */
__u8 card[32]; /* i.e. "Hauppauge WinTV" */
__u8 bus_info[32]; /* "PCI:" + pci_name(pci_dev) */
__u32 version; /* should use KERNEL_VERSION() */
__u32 capabilities; /* Device capabilities */
__u32 reserved[4];
};
2:通过结构 v4l2_format 初始化捕获视频的格式
struct v4l2_format {
__u32 type;
union {
struct v4l2_pix_format pix; /* V4L2_BUF_TYPE_VIDEO_CAPTURE */
struct v4l2_pix_format_mplane pix_mp; /* V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE */
struct v4l2_window win; /* V4L2_BUF_TYPE_VIDEO_OVERLAY */
struct v4l2_vbi_format vbi; /* V4L2_BUF_TYPE_VBI_CAPTURE */
struct v4l2_sliced_vbi_format sliced; /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */
__u8 raw_data[200]; /* user-defined */
} fmt;
};
3:VIDIOC_REQBUFS 命令通过结构 v4l2_requestbuffers 请求驱动申请一片连续的内存用于缓存视频信息:
struct v4l2_requestbuffers {
__u32 count;
__u32 type; /* enum v4l2_buf_type */
__u32 memory; /* enum v4l2_memory */
__u32 reserved[2];
};
4:
struct uvc_video
{
struct usb_ep *ep;
/* Frame parameters */
u8 bpp;
u32 fcc;
unsigned int width;
unsigned int height;
unsigned int imagesize;
/* Requests */
unsigned int req_size;
struct usb_request *req[UVC_NUM_REQUESTS];
__u8 *req_buffer[UVC_NUM_REQUESTS];
struct list_head req_free;
spinlock_t req_lock;
void (*encode) (struct usb_request *req, struct uvc_video *video,
struct uvc_buffer *buf);
/* Context data used by the completion handler */
__u32 payload_size;
__u32 max_payload_size;
struct uvc_video_queue queue;
unsigned int fid;
};
struct uvc_device //uvc的设备描述,用来配置摄像头的接口参数,用来配置基本的usb的配置参数以及terminal和unit
{
struct usb_device *udev; //usb的设备指针
struct usb_interface *intf; //usb的接口指针
unsigned long warnings;
__u32 quirks;
int intfnum; //usb的接口数
char name[32]; //设备名
enum uvc_device_state state; //uvc的设备状态
atomic_t users;
atomic_t nmappings;
#ifdef CONFIG_MEDIA_CONTROLLER
struct media_device mdev;
#endif
struct v4l2_device vdev; //uvc的子设备v4l2设备(一个父设备分为好几个子设备)
__u16 uvc_version;
__u32 clock_frequency; //时钟频率
struct list_head entities; //uvc实体链表头(挂着uvc设备的terminal和unit)
struct list_head chains; //uvc视频链表头
struct list_head streams; //uvc的视频流链表头 (结构体对应的设备接口)
atomic_t nstreams; //视频流个数
struct usb_host_endpoint *int_ep;//usb_host_endpoint的对象
struct urb *int_urb; //中断urb
__u8 *status; //uvc的设备状态
struct input_dev *input; //输入设备
char input_phys[64]; //输入设备节点路径
}
三:uvc_probe函数:
3.1:当插入摄像头后会根据:通过usb_register注册usb摄像头struct uvc_driver uvc_driver程序中的id_table来判断该驱动是否支持不同类型的usb 设备,,如果没有该变量,则usb中的探测回调函数则不会被调用,(有兴趣的朋友可以仔细分析该探测函数)。
当UVC摄像头被正常调用后,通过.probe = uvc_probe,调用uvc的处理函数:传递两个参数(usb接口参数,USB的id_table参数)。static struct usb_device_id uvc_ids[]中有一个id_table: { USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, 0) },可以配置所有的摄像头参数
struct usb_device *udev = interface_to_usbdev(intf);(这个时候已经得到usb的设备的基本参数了,我们要做的就是分析usb的参数的信息),
注意一个比较重要的结构体:usb_interface,一个usb_interface代表一个功能,,每个usb驱动控制器控制一个usb_interface,所有的驱动控制器都是由usb_interface和usb_core惊醒通信,usb_interface就是我们的入口参数。usb_device通常具备一个或多个usb_host_config;一个usb_host_config通常具备一个或多个usb_interface。(usb_device是爷爷,usb_host_config是爸爸,usb_interface是孩子),我们可以自己打印查看这些usb的配置的 过程描述符
3.2:摄像头的接口参数配置
if (uvc_parse_control(dev) < 0)用来解析usb的参数;(这里注意数据的排列顺序不一样:简称的大头小头)。
https://blog.csdn.net/eternity9255/article/details/53522966 用来介绍unit和terminal的参数
unit可以理解为构建UVC设备功能的各功能单元,多个unit按照一定的规则链接后就是一个完整的uvc功能设备,unit有一个或者多个入口,一个出口。
Processing Unit代表对采集单元的数据进行处理的单元,负责对采集到的图像进行调整,当host端要求设定这些Processing Unit负责范围内的功能特性时,SPEC定义要求PU单元负责相应Host端对图像特性的调整。
Terminal指整个UVC功能的入口和出口,Input Terminal可以理解为整个UVC功能的数据源头,它仅有一个outPin,可以连接到其他Unit的Input Pin作为该Unit的输入源。Output Terminal则可以理解为当图像数据流程UVC设备内部的整个处理流程后的输出点,它仅有一个Input Pin, 整个UVC处理流程上的最后一个Unit会将它的Output Pin与Output Terminal的Input Pin连接在一起。
我们可以看出该USB Camera大致有三个逻辑功能意义上的Entity:CT(代表硬件上的图像数据采集源,Sensor),PU(代表Sensor中可对采集所得图像数据进行加工的功能单元),OT(代表实际中USB模块的的ISO In端点),并无SU单元来控制图像数据采集源的切换和选择(只有一个输入源),此外也没有对采集所得的数据进行个性化处理的功能,故也没有EU单元的存在(无EU描述符)。
通过usb的host得到接口配置参数,通过该参数填充所需的配置格式,配置
https://blog.csdn.net/u010783226/article/details/93298993 用来分析各种接口配置描述符,联合接口描述符,配置描述符的功能。
接口描述符的参数:bInterfaceNumber 用来描述接口号,根据接口号,配置不同的接口,eg:vc控制接口。vc流接口
uvc摄像头有四个接口,接口号0:控制接口,接口1:流接口,用来设置接口格式,帧大小等参数。(H264,MJPEG,颜色域)
if ((ret = uvc_parse_standard_control(dev, buffer, buflen)) < 0)解析标准的uvc设备。是uvc的特殊的一连串的接口描述符,是用来描述设备功能的描述符,比如unit和terminal的描述,通过:bDescriptorSubtype参数来描述不同的控制接口功能描述符。
uvc_parse_standard_control:用来解析控制接口描述符,一个设备需要一个联合接口描述符来描述视频接口集合,对于每个设备功能需要一个视频控制接口或者多个视频流接口,标准的视频接口联合描述符。
端点号:0x80(输入)端点号:0x81(输出),用来控制vc头,输入terminal,输出terminal,unit,扩展unit参数功能。
UVC_VC_HEADER:控制接口描述头,uvc_parse_format:根据不同的参数解析不同的格式
UVC_VC_INPUT_TERMINAL,输入terminal,UVC_VC_OUTPUT_TERMINAL:输出list_add_tail(&term->list, &dev->entities);根据不同的操作terminal对uvc不同的操作
terminal,UVC_VC_SELECTOR_UNIT:选择unit UVC_VC_PROCESSING_UNIT:扩展unit
根据buffer[2]参数的不同解析不同的uvc功能:uvc_parse_streaming(dev, intf);解析uvc的流接口,添加数据流的头
struct usb_host_interface *alts = &intf->altsetting[0];用来得到流接口描述符。
videostreaming:VS Interface Descriptors:主要报货视频流接口input header output headerformat frame描述符组成,每个视频流接口都有一个单独的input或者output描述符,,得到摄像头为捕捉设备
streaming->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
根据输入头描述符(INPUT_HEAD 1的到format的个数为2(配置为2个,配置bDescriptorSubtype 为4(得到帧格式数为5(类型,格式通道号1),9(个数))13(颜色帧格式配置符),6(MJPEG format的格式,得到2个format格式类型 7,个数(11)格式通道号2)))
端点号:0x82(输入)
对支持的每个format都有一个单独的format描述符,对于每一个format描述符有多个frame描述符,format和frame描述符仅定义在接口的第0号设置中
streaming->header.bNumFormats = p; //得到的格式种类号
streaming->header.bEndpointAddress = buffer[6];//得到的端点地址
uvc_parse_streaming(struct uvc_device *dev, struct usb_interface *intf):用来解析视频流接口参数
list_add_tail(&streaming->list, &dev->streams);;最终将参数添加到该数据链表中
(其中主要就是一些参数的填充,根据不同的参数,执行不同的流程,一些uvc定义好的死规定,有兴趣有兴趣的还可以打印参数)。
最终解析完流参数,填充到:struct uvc_streaming *streaming(该结构体中)
以上为uvc接口解析的主要函数。
if (media_device_register(&dev->mdev) < 0)media设备的注册注册一个流媒体设备,用于运行时设备的控制,运行时设备能达到控制节点的效果
if (v4l2_device_register(&intf->dev, &dev->vdev) < 0)注册一个v4l2结构体。
if (uvc_ctrl_init_device(dev) < 0)初始化一个控制结构体
if (uvc_register_chains(dev) < 0):注册uvc视频流
if ((ret = uvc_status_init(dev)) < 0):初始化中断urb
uvc_register_chains->ret = uvc_register_terms(dev, chain);->ret = uvc_register_video(dev, stream);主要功能呢注册uvc_stream流
ret = uvc_video_init(stream);接口初始化:
vdev = video_device_alloc(); //分配一个video_device结构体,1:队列初始化,2:设置和得到摄像头的参数uvc_get_video_ctrl->__uvc_query_ctrl通过该函数来设置数据图像格式等摄像头的参数。
vdev->fops = &uvc_fops; //操作函数
ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1); //注册video_device结构体
通过填充的video_device结构体中的fops函数来操作摄像头
摄像头主要是填充该操作函数:
const struct v4l2_file_operations uvc_fops = {
.owner = THIS_MODULE,
.open = uvc_v4l2_open,
.release = uvc_v4l2_release,
.unlocked_ioctl = uvc_v4l2_ioctl,
.read = uvc_v4l2_read,
.mmap = uvc_v4l2_mmap,
.poll = uvc_v4l2_poll,
};