Linux 驱动开发之USB设备分析6

Linux 驱动开发之USB设备分析6(基于Linux6.6)---USB摄像头驱动介绍

一、概述

USB 摄像头通常通过 UVC (USB Video Class) 协议与操作系统进行通信。UVC 是一个标准协议,允许各种 USB 摄像头设备与操作系统之间进行视频数据传输和控制。UVC 协议最初由 USB-IF(USB Implementers Forum)定义,并且被广泛应用于 USB 摄像头、视频采集设备、以及其他多媒体设备。

1.1、UVC(USB Video Class)协议概述

UVC 协议定义了 USB 设备与操作系统之间的接口,主要用于传输视频数据流。它的目标是使得 USB 摄像头能够与不同的操作系统兼容,而不需要专用的驱动程序。UVC 驱动程序在大多数 Linux 发行版中默认包含,因此通常即插即用,无需额外配置。

UVC 协议的关键特点:

  • 标准化接口:通过标准化的接口,UVC 协议允许操作系统与各种 USB 视频设备兼容。
  • 即插即用:大多数支持 UVC 协议的 USB 摄像头在 Linux 上支持即插即用,不需要额外的驱动程序。
  • 设备控制:UVC 协议支持各种视频设备控制功能,例如分辨率、帧率、对比度、亮度等设置。
  • 多平台支持:除了 Linux,UVC 协议还支持 Windows 和 macOS,使得 UVC 设备具有广泛的兼容性。

1.2、Linux 下的 USB 摄像头支持

在 Linux 中,UVC 驱动uvcvideo)是默认的 USB 摄像头驱动。该驱动程序支持 UVC 协议标准,并允许操作系统与 USB 摄像头进行通信,捕获视频流,控制摄像头的设置等。通常,Linux 系统在插入 UVC 兼容设备时会自动加载 uvcvideo 驱动程序。

主要组件:

  • UVC 驱动程序(uvcvideo):这是 Linux 内核模块,提供 UVC 摄像头的基本支持。它提供了对摄像头视频流的捕获、处理和配置的支持。

  • V4L2(Video for Linux 2):V4L2 是 Linux 下的视频捕获接口标准。UVC 驱动程序与 V4L2 接口集成,使得摄像头可以被其他视频应用(如 mplayervlccheese)使用。

  • UVC 控制:UVC 协议不仅支持视频流传输,还支持多种设备控制功能,如调整亮度、对比度、色调等设置。这些控制功能通过 V4L2 提供的接口进行管理。

如何使用 USB 摄像头

  1. 即插即用:大多数现代 Linux 发行版都会自动识别 UVC 设备并加载相关的 uvcvideo 驱动。插入 USB 摄像头后,系统通常会自动创建 /dev/video0 设备节点。

  2. 查看设备信息: 你可以使用 lsusb 命令查看已连接的 USB 设备,确认是否识别到 USB 摄像头:

lsusb

输出示例:

  • Bus 001 Device 004: ID 046d:0825 Logitech, Inc. Webcam C270
    
  • 查看设备节点: 使用以下命令查看系统中是否创建了 /dev/video0

  • ls /dev/video*
    

    如果系统识别了摄像头,通常会看到 /dev/video0 这样的设备节点。

  • 测试视频设备: 你可以使用如 cheesemplayervlc 等工具来测试和捕获视频流:

  • cheese
    

    这会启动一个简单的应用,显示 USB 摄像头的视频流。

  • V4L2 控制: V4L2 提供了命令行工具,如 v4l2-ctl,用于控制摄像头的参数设置,例如分辨率、亮度、对比度等:

  1. v4l2-ctl --list-formats
    

    这将列出摄像头支持的视频格式。

1.3、如何编写 USB 摄像头驱动

尽管大多数 USB 摄像头在 Linux 上可以通过 UVC 驱动程序自动工作,但如果你需要编写一个自定义的 USB 摄像头驱动程序,可以参考以下内容:

驱动程序设计要素

  • 设备识别与初始化:你的驱动程序首先需要识别 USB 摄像头设备,并根据设备的 ID 加载适当的驱动。

  • 数据流捕获:驱动程序需要处理 USB 摄像头的图像数据流。你需要实现 V4L2 接口,用于处理视频流捕获和帧缓冲。

  • 设备控制:驱动程序需要实现设备控制接口,支持调节如亮度、对比度等摄像头设置。通常,V4L2 提供了一套标准接口用于控制这些功能。

示例代码

如果要创建一个简单的 USB 摄像头驱动程序,可以参考 uvcvideo 驱动程序的实现。UVC 驱动程序本身已经包含在 Linux 内核中,并且是开源的,可以在 /drivers/media/usb/uvc 目录下找到相关代码。

二、初始化设备模块

Spac5xx的实现是按照标准的USB VIDEO设备的驱动框架编写(其具体的驱动框架可参照drivers/usb/usbvideo.c文件),整个源程序由四个主体部分组成:

设备模块的初始化模块和卸载模块,上层软件接口模块,数据传输模块

具体的模块分析如下:

该驱动采用了显式的模块初始化和消除函数,即调用module_init来初始化一个模块,并在卸载时调用moduel-exit函数

其具体实现如下: 

1、模块初始化
module_init (usb_spca5xx_init); 
 
static int __init  usb_spca5xx_init (void) 
{  
#ifdef CONFIG_PROC_FS                     
	proc_spca50x_create ();//建立PROC设备文件 
#endif /* CONFIG_PROC_FS */    
	if (usb_register (&spca5xx_driver) < 0) //注册USB设备驱动    
		return -1;    
	info ("spca5xx driver %s registered", version);   
	return 0; 
}
2、模块卸载
module_exit (usb_spca5xx_exit); 
 
static void __exit  usb_spca5xx_exit (void) 
{    
	usb_deregister (&spca5xx_driver); //注销USB设备驱动   
	info ("driver spca5xx deregistered"); 
#ifdef CONFIG_PROC_FS    
	proc_spca50x_destroy ();//撤消PROC设备文件 
#endif /* CONFIG_PROC_FS */ 
} 

关键数据结构 USB驱动结构,即插即用功能的实现 

static struct usb_driver spca5xx_driver = {        
	"spca5xx",         
	spca5xx_probe, //注册设备自我侦测功能        
	spca5xx_disconnect,//注册设备自我断开功能        
	{NULL,NULL} 
};

用两个函数调用spca5xx_probe 和spca5xx_disconnect来支持USB设备的即插即用功能:

a -- spca5xx_probe具体实现如下:

static void * spca5xx_probe (struct usb_device *dev, unsigned int ifnum, const struct usb_device_id *id) 
{    
	struct usb_interface_descriptor *interface;          //USB设备接口描述符 
	struct usb_spca50x *spca50x;                    //物理设备数据结构   
	int err_probe;   
	int i;    
	if (dev->descriptor.bNumConfigurations != 1)        //探测设备是不是可配置     
		goto nodevice;   
	if (ifnum > 0)     
		goto nodevice;    
	interface = &dev->actconfig->interface[ifnum].altsetting[0];  
	MOD_INC_USE_COUNT;    
	interface = &intf->altsetting[0].desc;   
	if (interface->bInterfaceNumber > 0)     
		goto nodevice;    
	if ((spca50x = kmalloc (sizeof (struct usb_spca50x), GFP_KERNEL)) == NULL) //分配物理地址空间     
	{        
		err ("couldn't kmalloc spca50x struct");       
		goto error;     
	}    
 
	memset (spca50x, 0, sizeof (struct usb_spca50x));   
	spca50x->dev = dev;    
	spca50x->iface = interface->bInterfaceNumber;    
	if ((err_probe = spcaDetectCamera (spca50x)) < 0)       //具体物理设备查找,匹配厂商号,设备号(在子程序中)       
	{        
		err (" Devices not found !! ");       
		goto error;     
	}    
	PDEBUG (0, "Camera type %s ", Plist[spca50x->cameratype].name)
	for (i = 0; i < SPCA50X_NUMFRAMES; i++)      
		init_waitqueue_head (&spca50x->frame[i].wq);     //初始化帧等待队列     
		init_waitqueue_head (&spca50x->wq);            //初始化驱动等待队列   
 
	if (!spca50x_configure (spca50x))                  //物理设备配置(主要完成传感器侦测和图形参数配置),主要思想是给控制寄存器写值,读回其返回值,以此判断具体的传感器型号     
	{        
		spca50x->user = 0;        
		init_MUTEX (&spca50x->lock);                  //信号量初始化       
		init_MUTEX (&spca50x->buf_lock);        
		spca50x->v4l_lock = SPIN_LOCK_UNLOCKED;       
		spca50x->buf_state = BUF_NOT_ALLOCATED;     
	}                                    
	else     
	{
		err ("Failed to configure camera");       
			goto error;     
	} 
  
 
	/* Init video stuff */ 
  
	spca50x->vdev = video_device_alloc ();           //设备控制块内存分配   
	if (!spca50x->vdev)     
		goto error;
	memcpy (spca50x->vdev, &spca50x_template, sizeof (spca50x_template));  
	//系统调用的挂接,在此将驱动实现的系统调用,挂到内核中   
	video_set_drvdata (spca50x->vdev, spca50x); 
  
		if (video_register_device (spca50x->vdev, VFL_TYPE_GRABBER, video_nr) < 0)     
	{                                            
	//video设备注册       
		err ("video_register_device failed");       
		goto error;     
	} 
  
	spca50x->present = 1;   
	if (spca50x->force_rgb) 
    
		info ("data format set to RGB");   
	spca50x->task.sync = 0; 
  
	spca50x->task.routine = auto_bh;   
	spca50x->task.data = spca50x;   
	spca50x->bh_requested = 0; 
			
	MOD_DEC_USE_COUNT; //增加模块使用数   
	return spca50x; //返回数剧结构 
error://错误处理   
	if (spca50x->vdev)     
	{ 
      
		if (spca50x->vdev->minor == -1) 
       
			video_device_release (spca50x->vdev);       
		else 
       
			video_unregister_device (spca50x->vdev);       
		spca50x->vdev = NULL;     
	} 
  
	if (spca50x)     
	{ 
      
		kfree (spca50x);       
		spca50x = NULL;     
	} 
  
	MOD_DEC_USE_COUNT;   
	return NULL; 
nodevice: 
  
	return NULL; 
}

b -- Spca5xx_disconnect 的具体实现如下:

static void  spca5xx_disconnect (struct usb_device *dev, void *ptr) 
{    
	struct usb_spca50x *spca50x = (struct usb_spca50x *) ptr;   
	int n;    
	MOD_INC_USE_COUNT; //增加模块使用数   
	if (!spca50x)     
		return;    
	down (&spca50x->lock); //减少信号量   
	spca50x->present = 0;  //驱动卸载置0    
	for (n = 0; n < SPCA50X_NUMFRAMES; n++)       //标示所有帧ABORTING状态     
	{
		spca50x->frame[n].grabstate = FRAME_ABORTING;     
		spca50x->curframe = -1;   
	
	}
	 for (n = 0; n < SPCA50X_NUMFRAMES; n++)       //唤醒所有等待进程     
	{
		if (waitqueue_active (&spca50x->frame[n].wq))       
			wake_up_interruptible (&spca50x->frame[n].wq);   
		
		if (waitqueue_active (&spca50x->wq))        
			wake_up_interruptible (&spca50x->wq);    
	}
	spca5xx_kill_isoc(spca50x);  //子函数终止URB包的传输   
	PDEBUG (3,"Disconnect Kill isoc done");    
	up (&spca50x->lock);  //增加信号量    
	while(spca50x->user) /如果还有进程在使用,进程切换       
		schedule();      
	down (&spca50x->lock);     
	if (spca50x->vdev)  
	{       
		video_unregister_device (spca50x->vdev);   //注销video设备     
		usb_driver_release_interface (&spca5xx_driver,&spca50x->dev->actconfig->interface[spca50x->iface]); //端口释放       
		spca50x->dev = NULL;       
	}
	up (&spca50x->lock); 
#ifdef CONFIG_PROC_FS        
	destroy_proc_spca50x_cam (spca50x); //注销PROC文件 
#endif /* CONFIG_PROC_FS */ 
       
	if (spca50x && !spca50x->user)                      //释放内存空间        
	{           
		spca5xx_dealloc (spca50x);          
		kfree (spca50x);          
		spca50x = NULL;        
	}    
	MOD_DEC_USE_COUNT;                              //减少模块记数   
	PDEBUG (3, "Disconnect complete"); 
}

三、上层软件接口模块

该模块通过file_operations数据结构,依据V4L协议规范,实现设备的关键系统调用,实现设备文件化的UNIX系统设计特点。作为摄相头驱动,其功能在于数据采集,而没有向摄相头输出的功能,因此在源码中没有实现write系统调用。

其关键的数据结构如下:

static struct video_device spca50x_template = {   
	.owner = THIS_MODULE,  
	.name = "SPCA5XX USB Camera",   
	.type = VID_TYPE_CAPTURE,  
	.hardware = VID_HARDWARE_SPCA5XX,   
	.fops = &spca5xx_fops,         
}; 
 
 
static struct file_operations spca5xx_fops = {   
	.owner = THIS_MODULE,  
	.open = spca5xx_open, //open功能   
	.release = spca5xx_close,//close功能   
	.read = spca5xx_read, //read功能   
	.mmap = spca5xx_mmap, //内存映射功能   
	.ioctl = spca5xx_ioctl, //文件信息获取  
	.llseek = no_llseek,//文件定位功能未实现 
}; 
1. Open功能

完成设备的打开和初始化,并初始化解码器模块。其具体实现如下:

static int  spca5xx_open(struct video_device *vdev, int flags) 
{    
	struct usb_spca50x *spca50x = video_get_drvdata (vdev);   
	int err;    
	MOD_INC_USE_COUNT;                         //增加模块记数   
	down (&spca50x->lock);                              
	err = -ENODEV;    
 
	if (!spca50x->present)                 //检查设备是不是存在,有不有驱动,是不是忙     
		goto out;  
	err = -EBUSY;   
	if (spca50x->user)     
		goto out;    
	err = -ENOMEM;    
	if (spca50x_alloc (spca50x))  
		goto out;                    
	
	err = spca50x_init_source (spca50x);           //初始化传感器和解码模块,在此函数的实现中,对每一款DSP芯片的初始化都不一样,对中星微301P的DSP芯片的初始化在子函数zc3xx_init,其实现方法为寄存器填值。   
	if (err != 0)
	{        
		PDEBUG (0, "DEALLOC error on spca50x_init_source\n");       
		up (&spca50x->lock);        
		spca5xx_dealloc (spca50x);       
		goto out2;     
	} 
 
	spca5xx_initDecoder(spca50x);                  //解码模块初始化,其模块的具体实现采用的是huffman算法   	spca5xx_setFrameDecoder(spca50x);   
	spca50x->user++;    
	
	err = spca50x_init_isoc (spca50x);              //初始化URB(usb request block) 包,启动摄相头,采用同步传输的方式传送数据   
	if (err)     
	{        
		PDEBUG (0, " DEALLOC error on init_Isoc\n");       
		spca50x->user--;        
		spca5xx_kill_isoc (spca50x);       
		up (&spca50x->lock);        
		spca5xx_dealloc (spca50x);       
		goto out2;
	}    
 
	spca50x->brightness = spca50x_get_brghtness (spca50x) << 8;   
	spca50x->whiteness = 0; 
 
out:    
	up (&spca50x->lock); 
out2:   
	if (err)     
		MOD_DEC_USE_COUNT;   
	if (err)    	
	{        
		PDEBUG (2, "Open failed");     
	}   
	else     
	{        
		PDEBUG (2, "Open done");    
 	}    
	return err; 
}
2.Close功能

完成设备的关闭,其具体过程是:

static void spca5xx_close(struct video_device *vdev) 
{   
	struct usb_spca50x *spca50x =vdev->priv;   
	int i;   
	PDEBUG (2, "spca50x_close");   
	down (&spca50x->lock);  //参数设置   
	spca50x->user--;   
	spca50x->curframe = -1; 
  
	if(spca50x->present)//present:是或有驱动加载     
	{      
		spca50x_stop_isoc (spca50x);//停止摄相头工作和数据包送       
		spcaCameraShutDown (spca50x);//关闭摄相头,由子函数spca50x_stop_iso完成   
		for (i = 0; i < SPCA50X_NUMFRAMES; i++)    //唤醒所有等待进程     
		{      
			if(waitqueue_active(&spca50x->frame[i].wq)) 
         		wake_up_interruptible (&spca50x->frame[i].wq);     
		} 
       	if(waitqueue_active (&spca50x->wq))        
			wake_up_interruptible (&spca50x->wq);   
	}  
   	
	up(&spca50x->lock); 
   	spca5xx_dealloc (spca50x);//回收内存空间   
	
	PDEBUG(2,"Release ressources done");   
	MOD_DEC_USE_COUNT; 
} 
3、 Read功能

 完成数据的读取,其主要的工作就是将数据由内核空间传送到进程用户空间

static long spca5xx_rea(struct video_device *dev, char * buf, unsigned long count,int noblock) 
{   
	struct usb_spca50x *spca50x = video_get_drvdata (dev);   
	int i;   
	int frmx = -1;   
	int rc;   
	volatile struct spca50x_frame *frame; 
	
        if(down_interruptible(&spca50x->lock))  //获取信号量       
		return -EINTR; 
	
        if(!dev || !buf){//判断设备情况     
		up(&spca50x->lock);     
		return -EFAULT; 
	}  
  
	if(!spca50x->dev){     
		up(&spca50x->lock);     
		return -EIO; 
	} 
  	
	if (!spca50x->streaming){     
		up(&spca50x->lock);     
		return -EIO; 
	} 
	
        if((rc = wait_event_interruptible(spca50x->wq,     //在指定的队列上睡眠,直到参数2的条件为真 
              spca50x->frame[0].grabstate == FRAME_DONE || 
              spca50x->frame[1].grabstate == FRAME_DONE ||               
	      spca50x->frame[2].grabstate == FRAME_DONE ||               
              spca50x->frame[3].grabstate == FRAME_DONE )))	
        {               
		up(&spca50x->lock);               
		return rc;        
	}  
  
 
	for (i = 0; i < SPCA50X_NUMFRAMES; i++)         //当数据到来     
		if (spca50x->frame[i].grabstate == FRAME_DONE)   //标识数已到       
			frmx = i;   
	if (frmx < 0)     
	{ 
      
		PDEBUG(2, "Couldnt find a frame ready to be read.");       
		up(&spca50x->lock);       
		return -EFAULT;     
	} 
  
	frame = &spca50x->frame[frmx]; 
	PDEBUG (2, "count asked: %d available: %d", (int) count,(int) frame->scanlength);   
	if (count > frame->scanlength)     
		count = frame->scanlength; 
  
	if ((i = copy_to_user (buf, frame->data, count)))   //实现用户空间和内核空间的数据贝     
	{ 
      
		PDEBUG (2, "Copy failed! %d bytes not copied", i);           
		up(&spca50x->lock);       
		return -EFAULT;     
	} 
  
	/* Release the frame */  
	frame->grabstate = FRAME_READY; //标识数据已空 
	up(&spca50x->lock);                           
	return count;//返回拷贝的数据数 
}
4、Mmap功能

实现将设备内存映射到用户进程的地址空间的功能,其关键函数是remap_page_range,其具体实现如下:

static int spca5xx_mmap(struct video_device *dev,const char *adr, unsigned long size) 
{ 
  
	unsigned long start = (unsigned long) adr;   
	struct usb_spca50x *spca50x = dev->priv;   
	unsigned long page, pos;   
	if (spca50x->dev == NULL)     
		return -EIO; 
	
	PDEBUG (4, "mmap: %ld (%lX) bytes", size, size);   
	if(size > (((SPCA50X_NUMFRAMES * MAX_DATA_SIZE) + PAGE_SIZE - 1) & ~(PAGE_SIZE -1)))     
		return -EINVAL; 
       
	if (down_interruptible(&spca50x->lock))  //获取信号量               
		return -EINTR; 
  
	pos = (unsigned long) spca50x->fbuf;
	while (size > 0)  //循环实现内存映射     
	{      			
		page = kvirt_to_pa (pos); 
		if (remap_page_range (start, page, PAGE_SIZE, PAGE_SHARED)){  //实现内存映射 
			up(&spca50x->lock); 
			return -EAGAIN;    
		}       
	
		start += PAGE_SIZE;       
		pos += PAGE_SIZE;       
		if (size > PAGE_SIZE)        
			size -= PAGE_SIZE;       
		else     
			size = 0;     
	} 
 
	up(&spca50x->lock); //释放信号量   
	return 0; 
}
5、Ioctl功能:  

实现文件信息的获取功能

static int spca5xx_ioctl (struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) 
{       
	struct video_device *vdev = file->private_data;        
	struct usb_spca50x *spca50x = vdev->priv;        
	int rc; 
       
	if(down_interruptible(&spca50x->lock))       //获取信号量               
		return -EINTR; 
  	
	rc = video_usercopy (inode, file, cmd, arg, spca5xx_do_ioctl);  //将信息传送到用户进程,其关键函数实现spca5xx_do_ioctl 
	up(&spca50x->lock);   
	return rc;
} 

spca5xx_do_ioctl函数的实现依赖于不同的硬件,本驱动为了支持多种芯片,实现程序过于烦琐,其主要思想是通过copy_to_user(arg,b,sizeof(struct video_capability)函数将设备信息传递给用户进程。

四、数据传输模块

源程序采用tasklet来实现同步快速传递数据,并通过spcadecode.c上的软件解码模块实现图形信息的解码。此模块的入口点挂节在spca_open函数中,其具体的函数为spca50x_init_isoc。当设备被打开时,同步传输数据也已经开始,并通过spca50x_move_data函数将数据传递给驱动程序,驱动程序通过轮询的办法实现对数据的访问。

Void outpict_do_tasklet (unsigned long ptr) 
{   
	int err;   
	struct spca50x_frame *taskletframe = (struct spca50x_frame *) ptr;   
	taskletframe->scanlength = taskletframe->highwater - taskletframe->data; 
  	PDEBUG (2, "Tasklet ask spcadecoder hdrwidth %d hdrheight %d method %d",         
		taskletframe->hdrwidth, taskletframe->hdrheight,          
		taskletframe->method); 
  
	err = spca50x_outpicture (taskletframe);  //输出处理过的图片数据   
	if (err != 0)     
	{      
		PDEBUG (0, "frame decoder failed (%d)", err);       
		taskletframe->grabstate = FRAME_ERROR;    
	}   
	else     
	{      
		taskletframe->grabstate = FRAME_DONE;     
	} 
  
	if (waitqueue_active (&taskletframe->wq))//如果有进程等待,唤醒等待进程     
		wake_up_interruptible (&taskletframe->wq); 
}

值得一提的是spcadecode.c上解码模块将原始压缩图形数据流yyuyv,yuvy, jpeg411,jpeg422解码为RGB图形,但此部分解压缩算法的实现也依赖于压缩的格式,归根结底依赖于DSP(数字处理芯片)中的硬件压缩算法。



五、USB CORE的支持

LINUX下的USB设备对下层硬件的操作依靠系统实现的USB CORE层,USB CORE对上层驱动提供了众多函数接口如:usb_control_msg,usb_sndctrlpipe等,其中最典型的使用为源码中对USB端点寄存器的读写函数spca50x_reg_write和spca50x_reg_read等,具体实现如下:(举spca50x_reg_write的实现,其他类似)

static int spca50x_reg_write(struct usb_device *dev,__u16 reg,__u16 index,__u16 value) 
{       
	int rc;        
	rc = usb_control_msg(dev,          //通过USB CORE提供的接口函数设置寄存器的值 
				usb_sndctrlpipe(dev, 0), 
		                reg, 
		                USB_TYPE_VENDOR | USB_RECIP_DEVICE, 
		                value, index, NULL, 0, TimeOut); 
       
	PDEBUG(5, "reg write: 0x%02X,0x%02X:0x%02X, 0x%x", reg, index, value, rc);        
	if (rc < 0)              
		err("reg write: error %d", rc);        
	return rc; 
} 

 六、总结

整体上,Linux 下的 USB 摄像头驱动可分为四个主要模块:初始化模块卸载模块上层软件接口模块数据传输模块。下面我将分别总结这四个模块的功能,并给出一个简化的流程图,展示其协同工作。

1. 初始化模块

初始化模块主要负责在 USB 摄像头插入时加载驱动程序、注册设备、分配资源,并初始化相关硬件。

功能:

  • 设备识别:当一个 USB 摄像头插入时,usbcore 模块负责识别设备并调用对应的驱动程序(如 uvcvideo)。
  • 设备初始化:驱动程序加载后,初始化 USB 摄像头设备,包括读取摄像头的能力(如分辨率、支持的格式等),设置基本参数。
  • 设备注册:注册摄像头设备,使得内核能够通过 /dev/videoX 设备节点与摄像头交互。

关键流程:

  1. USB 设备枚举:当 USB 设备连接到系统时,内核通过 usbcore 识别设备,并调用相应的驱动程序(例如 uvcvideo)。
  2. 设备初始化:驱动程序通过 UVC 协议与设备通信,获取设备能力、分辨率、视频格式等信息,并将设备注册为视频设备(通过 v4l2)。
  3. 资源分配:为设备分配内存缓冲区、初始化传输队列等。

2. 卸载模块

卸载模块主要负责在 USB 摄像头拔出时清理资源并卸载设备。

功能:

  • 设备卸载:当 USB 摄像头被拔出时,驱动程序需要解除与设备的绑定,注销视频设备。
  • 资源释放:清理分配的内存、缓冲区,释放设备资源。
  • 模块卸载:如果没有其他设备使用该模块,驱动程序会被卸载。

关键流程:

  1. 设备移除检测:当 USB 设备被拔出时,内核通过 usbcore 检测到设备移除并触发相应的卸载操作。
  2. 资源释放:驱动程序会停止所有的传输操作,释放所有申请的内存、缓冲区等资源。
  3. 设备注销:注销视频设备并移除摄像头的相关设备节点(如 /dev/videoX)。
  4. 驱动卸载:如果没有其他摄像头设备使用该驱动模块,驱动模块会被从内核中卸载。

3. 上层软件接口模块

上层软件接口模块允许应用程序与驱动程序进行交互,获取视频数据,设置摄像头参数等。通常通过 V4L2(Video for Linux 2)接口与驱动进行交互。

功能:

  • 视频流控制:应用程序可以通过 V4L2 接口启动、停止视频捕获,获取摄像头视频流。
  • 设备控制:应用程序可以通过 V4L2 控制摄像头参数,如亮度、对比度、分辨率等。
  • 视频格式转换:如果应用需要不同的视频格式,驱动程序会负责将摄像头的视频数据转换为合适的格式。

关键流程:

  1. 应用请求设备:应用程序通过 V4L2 打开 /dev/videoX 设备节点,初始化视频流设置。
  2. 配置设备:应用通过 V4L2 控制接口设置视频捕获参数,如分辨率、帧率等。
  3. 视频流捕获:应用通过 mmapread 等接口从摄像头设备获取视频帧数据。
  4. 停止视频流:当视频捕获结束时,应用通过 V4L2 停止捕获并释放资源。

4. 数据传输模块

数据传输模块负责从 USB 摄像头采集视频数据,并将数据传输到用户空间进行处理。该模块负责 USB 设备的数据流管理和缓冲区管理。

功能:

  • 数据采集:从摄像头采集视频数据流,使用异步传输或中断传输。
  • 数据传输:通过 USB 总线传输视频帧数据。
  • 缓冲区管理:为视频流数据分配和管理缓冲区,确保数据流的顺畅。

关键流程:

  1. 数据请求:驱动程序向 USB 摄像头请求视频数据,并通过 USB 传输接口将数据传输到内存缓冲区。
  2. 数据接收:数据传输完成后,摄像头将视频数据存储到内存缓冲区中。
  3. 数据转发:驱动程序通过 mmapread 将缓冲区中的数据传递给上层应用程序。
  4. 循环传输:数据传输循环进行,直到应用程序停止视频捕获。

5. 流程图

下面是一个简化的流程图,展示了 Linux 系统中 USB 摄像头驱动的工作流程,包括设备初始化、数据传输、上层接口和卸载:

           +----------------------------+
           |      USB 摄像头插入         |
           +----------------------------+
                      |
                      v
           +----------------------------+
           |  USB 设备识别与驱动加载    |----+
           +----------------------------+    |
                      |                       |
                      v                       |
           +----------------------------+    |
           |  驱动初始化与设备注册     |    |
           +----------------------------+    |
                      |                       |
                      v                       |
           +----------------------------+    |
           | 上层应用请求设备与配置    |<---+
           +----------------------------+
                      |
                      v
           +----------------------------+
           | 数据传输(从摄像头到缓冲区)|
           +----------------------------+
                      |
                      v
           +----------------------------+
           | 数据传递给上层应用程序     |
           +----------------------------+
                      |
                      v
           +----------------------------+
           | 摄像头拔出(设备移除)     |
           +----------------------------+
                      |
                      v
           +----------------------------+
           | 驱动卸载与资源释放         |
           +----------------------------+

总结

在 Linux 中,USB 摄像头驱动系统通常分为四个主要模块:初始化模块、卸载模块、上层软件接口模块和数据传输模块。每个模块负责摄像头驱动的不同阶段:

  • 初始化模块 负责加载驱动、注册设备并初始化硬件。
  • 卸载模块 负责在设备移除时卸载驱动并释放资源。
  • 上层软件接口模块 提供给应用程序与驱动程序交互的接口。
  • 数据传输模块 管理视频数据的采集、传输和缓冲。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值