Linux下USB網路攝影機驅動開發

來源:互聯網
上載者:User

今年上半年我對市場常見的vimicro 網路攝影機移植了Linux下的驅動至在研的一款嵌入式開發板,開發環境為:

            OS:Linux2.6.9

Compile:cross 3.3.2

CPU:Intel PXA270

Camera Type :Vimicro ZC301P +HV7131R

使用的驅動源碼基於自由軟體spca5xx。基於源碼作了些小改動。記錄一下。

Linux下的USB驅動架構 
Linux下完整的USB驅動程式必須包括3個模組:USB core,USB 主控制器驅動,USB裝置驅動。三者的關係如下:

 

其中:

Linux Core封裝了支援USB主控制器和USB裝置驅動的API,通過一系列的資料結構、宏、函數來抽象化裝置硬體並掩蔽硬體具體細節,使得Linux系統對USB裝置的訪問採用如同一般檔案訪問類似的介面函數,如此實現了硬體無關。“裝置即是檔案”。

USB HC(Host Controller)全面負責主機端對USB的資訊傳輸分工,對下註冊功能和相關資料結構;對上提供了裝置驅動與USB Core資料轉送的橋樑。USB上的即時資料流資料轉送通常通過URB(USB Request Block)的資料結構實現,每一次資料轉送中,裝置驅動建立URB,提交給USB HC,USB HC負責完成此次與裝置端的URB互動。

USB驅動程式的結構 
struct usb_driver spca5xx_driver

struct video_device spca50x_template

struct file_operations spca5xx_fops

模組初始化與卸載 
spca5xx_init()模組初始化;spca5xx_disconnect()模組卸載;spca5xx_probe() 物理裝置尋找、匹配、配置,並向核心註冊video裝置。

系統調用介面 
spca5xx_open()完成裝置的開啟和初始化,並初始化解碼器模組,spca5xx_close()完成裝置的關閉;

spca5xx_read()完成資料的讀取,其主要的工作就是將資料由核心空間傳送到進程使用者空間;

spca5xx_mmap()實現將裝置記憶體映射到使用者進程的地址空間的功能;

spca5xx_ioctl()實現檔案資訊的擷取功能。

資料轉送 
spca50x_reg_write

網路攝影機資料轉送過程 
USB匯流排上資訊傳輸類型可分成控制、中斷、等時、成塊四種:其中控制型主要用於裝置的“配置”與控制;中斷型主要用於主機對USB裝置的周期性查詢;即時性使用與即時的音視頻訊號傳輸;成塊型用於資訊量大,沒有很強的時間要求,但要求可靠傳遞的應用。對網路攝影機而言,通常採用的是即時傳輸。USB裝置端提供若干通訊連接埠(port),主機與連接埠間建立起邏輯上的通訊管道,進行資料轉送。在網路攝影機的裝置初始化階段,主機與0號連接埠通訊,驅動程式檢測網路攝影機型號和所有連接埠資訊;進入資料轉送階段,主機與探測到的即時輸入連接埠建立通訊管道,即時回傳網路攝影機拍攝到的映像資料。

USB主機控制器把每秒匯流排時間分成1024個固定大小的frame,每個frame佔據1ms的時間頻寬並以此為時間單位遞增,同時對應一個等時互動隊列。主機負責把USB匯流排上回傳的等時映像資料流量分配給各frame,USB主機控制器每一秒鐘掃描一遍所有frame,依次讀入各frame攜帶的等時資料包至核心緩衝區中。應用程式通過記憶體映射,在使用者空間直接讀取核心緩衝區內容,存入Linux顯存幀緩衝(framebuffer)中,從而在顯示屏中出現映像資料。

筆者的實驗網路攝影機為Vimicro ZC301P,sensor是HV7131R,驅動程式中每秒鐘進行2次URB傳輸,每個URB攜帶16個1023位元組的資料包,這樣每秒鐘所能得到的映像資料大小為32Kbyte。對RGB565、大小320×240、16位色彩深度的映像格式,在PXA270處理器的嵌入式開發板上測試,幀速達到每秒17幀,完全滿足嵌入式網路即時視頻會議的需要。

 

提高網路攝影機工作品質的幾種方法 
雙URB 
雙幀緩衝 
V4L協議改進

 

 

註:最近重新分析SPCA5XX的源碼,網路上偶得一師兄的文章,對SPCA5XX分析得很是詳盡,看後頗有心得,特轉貼於此,以示謝意。

Spac5xx的實現是按照標準的USB VIDEO裝置的驅動架構編寫(其具體的驅動架構可參照/usr/src/linux/drivers/usb/usbvideo.c檔案),整個來源程式由四個主體部分組成:裝置模組的初始化模組和卸載模組,上層軟體介面模組,資料轉送模組。具體的模組分析如下:

初始化裝置模組:

該驅動採用了顯式的模組初始化和消除函數,即調用module_init來初始化一個模組,並在卸載時調用moduel-exit函數(此二函數在2.3.13核心開始支援)。其具體實現如下:

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

tatic 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裝置的隨插即用功能:

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;

}

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, //檔案定位功能未實現

};

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_isoc完成

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;

}

Read功能:

完成資料的讀取,其主要的工作就是將資料由核心空間傳送到進程使用者空間。

static long

spca5xx_read(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; //返回拷貝的資料數

}

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;

}

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;

}

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.