基于AT91RM9200的圖像采集系統(tǒng)設(shè)計(jì)
(3) V4L簡(jiǎn)介與攝像頭驅(qū)動(dòng)程序開發(fā)
Video for Linux(簡(jiǎn)V4L)是Linux中關(guān)于視頻設(shè)備的內(nèi)核驅(qū)動(dòng),它為編寫視頻應(yīng)用程序提供一系列接口函數(shù),內(nèi)核、驅(qū)動(dòng)程序和應(yīng)用程序以它為標(biāo)準(zhǔn)進(jìn)行交流,因此視頻類驅(qū)動(dòng)程序的開發(fā)必須遵循此標(biāo)準(zhǔn),應(yīng)用V4L API函數(shù)進(jìn)行設(shè)計(jì)[4]。
設(shè)備驅(qū)動(dòng)程序是Linux內(nèi)核與應(yīng)用程序之間的接口,通過USB客戶驅(qū)動(dòng)程序提供的USBD接口和應(yīng)用程序接口,屏蔽了硬件實(shí)現(xiàn)的細(xì)節(jié)。應(yīng)用程序?qū)⑼獠吭O(shè)備看成是一類特殊文件__設(shè)備文件,可以使用像操作普通文件一樣的系統(tǒng)調(diào)用接口函數(shù)來完成對(duì)外部設(shè)備的打開、關(guān)閉、讀寫和I/O控制操作。陷于篇幅原因只對(duì)驅(qū)動(dòng)程序的重要部分進(jìn)行闡述。
驅(qū)動(dòng)程序的注冊(cè)、注銷:所有的USB設(shè)備類驅(qū)動(dòng)程序都要在USBD中進(jìn)行注冊(cè)和注銷,Linux中的驅(qū)動(dòng)程序通常采用模塊方式編寫,使用函數(shù)module_init注冊(cè)設(shè)備,使用函數(shù)module_ exit注銷設(shè)備。
module_init(usb_gfkd_init); /*加載模塊入口,調(diào)用函數(shù)usb_register()注冊(cè)設(shè)備*/
module_exit(usb_gfkd_exit); /*注銷模塊入口,調(diào)用函數(shù)usb_deregister()注銷設(shè)備*/
驅(qū)動(dòng)程序與USBD的接口:USBD為每個(gè)設(shè)備驅(qū)動(dòng)程序維護(hù)一個(gè)相關(guān)的 usb_driver的數(shù)據(jù)結(jié)構(gòu),負(fù)責(zé)設(shè)備的初始化和卸載。當(dāng)總線上有設(shè)備連接操作時(shí),USBD通過該結(jié)構(gòu)來查找相關(guān)的驅(qū)動(dòng)程序,并調(diào)用初始化函數(shù) probe()對(duì)設(shè)備初始化;當(dāng)設(shè)備斷開時(shí),USBD也通過該結(jié)構(gòu)來查找相關(guān)的驅(qū)動(dòng)程序,并調(diào)用設(shè)備卸載函數(shù)disconnect ()對(duì)設(shè)備卸載。USBD接口的數(shù)據(jù)結(jié)構(gòu)定義為:
static struct usb_driver gfkd_driver = { "gfkd",gfkd_probe,gfkd_disconnect};
初始化函數(shù)static void * gfkd_probe(…)首先讀取設(shè)備的Usb dev結(jié)構(gòu),根據(jù)設(shè)備的配置描述符判斷該設(shè)備是否被驅(qū)動(dòng)程序所支持, 判斷使用接口是否正確,然后為驅(qū)動(dòng)申請(qǐng)一塊內(nèi)存,再探測(cè)使用的攝像頭,完成對(duì)攝像頭的初始化,最后創(chuàng)建攝像頭的設(shè)備文件結(jié)點(diǎn)[5]。
卸載函數(shù)static void gfkd_disconnect (struct usb_device *dev, void *ptr)的作用是終止數(shù)據(jù)傳輸、刪除攝像頭的設(shè)備文件結(jié)點(diǎn)、釋放接口、將驅(qū)動(dòng)占用的內(nèi)存釋放。連接
驅(qū)動(dòng)程序與應(yīng)用程序接口:攝像頭驅(qū)動(dòng)程序在static struct file_operations gfkd_fops中給應(yīng)用程序提供了統(tǒng)一的外設(shè)操作函數(shù)接口,當(dāng)應(yīng)用程序?qū)z像頭進(jìn)行open 、release、read、內(nèi)存映射mmap以及IO控制等系統(tǒng)調(diào)用操作時(shí)將通過該結(jié)構(gòu)訪問驅(qū)動(dòng)程序提供的函數(shù)。
static struct file_operations gfkd_fops = {
.owner = THIS_MODULE, .open = gfkd_open,
.release = gfkd_close, .read = gfkd_read,
.mmap = gfkd_mmap, .ioctl = gfkd_ioctl,
.llseek = no_llseek, };
打開攝像頭函數(shù)static int gfkd_open(struct inode *inode, struct file *file)作用是打開攝像頭的設(shè)備文件結(jié)點(diǎn),并為數(shù)據(jù)傳輸做好必要的準(zhǔn)備工作。它先調(diào)用函數(shù)gfkd _alloc()分配用于視頻解碼的臨時(shí)數(shù)據(jù)緩沖區(qū)、幀緩沖區(qū)和數(shù)據(jù)緩沖區(qū);然后初始化攝像頭,用函數(shù)gfkd _setMode()設(shè)置輸出的視頻格式和分辨率;再用函數(shù)gfkd _setFrameDecoder()設(shè)置幀緩沖區(qū)接收的視頻幀的格式和分辨率;最后調(diào)用函數(shù)gfkd _init_isoc()初始化等時(shí)數(shù)據(jù)傳輸設(shè)置、打開攝像頭和分配提交URB。
關(guān)閉攝像頭函數(shù)static int gfkd_close(struct inode *inode, struct file *file)作用是關(guān)閉攝像頭的設(shè)備文件結(jié)點(diǎn)。它先調(diào)用函數(shù)gfkd _stop_isoc()終止等時(shí)數(shù)據(jù)傳輸;再調(diào)用函數(shù)CameraShutDown()關(guān)閉攝像頭;最后使用函數(shù)gfkd _dealloc()釋放分配的各種緩沖區(qū)。
內(nèi)存映射函數(shù)static int gfkd_mmap(struct file *file, struct vm_area_struct *vma)實(shí)現(xiàn)內(nèi)核空間與用戶空間的內(nèi)存映射。先通過函數(shù)vmalloc()申請(qǐng)分配足夠大的內(nèi)核態(tài)內(nèi)存作為圖像幀緩沖區(qū),并能存儲(chǔ)兩個(gè)URB采集的圖像;然后用函數(shù)remap_page_range()將其映射到用戶空間中。這樣提高了用戶程序獲取內(nèi)核態(tài)圖像幀緩沖區(qū)數(shù)據(jù)的速度。
讀函數(shù)static long gfkd_read(struct video_device *dev, char *buf, unsigned long count, int noblock)通過調(diào)用函數(shù)copy_to_user()將圖像數(shù)據(jù)從內(nèi)核態(tài)的幀緩沖區(qū)拷貝到用戶態(tài)的數(shù)據(jù)緩沖區(qū)。
IO控制函數(shù)static int gfkd_ioctl(struct video_device *vdev, unsigned int cmd, void *arg)的功能是接收應(yīng)用程序的各種命令,實(shí)現(xiàn)對(duì)攝像頭的控制操作,如獲得攝像頭的參數(shù)、設(shè)置攝像頭的分辨率、開始采集圖圖像和設(shè)置幀同步。
由于Linux中任何USB傳輸都是通過URB實(shí)現(xiàn)的,每次URB傳輸都包括URB的建立、發(fā)出、回收、數(shù)據(jù)整理等階段不產(chǎn)生有效數(shù)據(jù),因此在具體實(shí)現(xiàn)中采用等時(shí)傳輸方式,通過建立兩個(gè)URB,使用雙URB輪流通信的方法來提高圖像的采集速度。
本驅(qū)動(dòng)程序開發(fā)是基于ATMEL最新版Linux-2.4.27-vrs1-Atmel,在驅(qū)動(dòng)程序開發(fā)完后需重新配置內(nèi)核,讓內(nèi)核支持usb- ohci 和video for linux,再把驅(qū)動(dòng)程序配置成module,然后重新編譯內(nèi)核生成.o文件。將編譯好的驅(qū)動(dòng)放入文件系統(tǒng),建立設(shè)備文件,然后將文件系統(tǒng)燒入 flash,再連接USB攝像頭(如內(nèi)置中芯微Zc301P DSP),把模塊加載進(jìn)內(nèi)核并注冊(cè)就可以找到該攝像頭并顯示:
gfkd _core.c: USB gfkd camera found. Type Vimicro Zc301P 0x301b
gfkd _core.c: gfkd driver 00.57.06LE registered
(4) 圖像采集的實(shí)現(xiàn)與性能分析
服務(wù)端應(yīng)用程序的實(shí)現(xiàn)是基于C/S模式,使用了3個(gè)線程,其中一個(gè)主線程,一個(gè)圖像采集線程負(fù)責(zé)從驅(qū)動(dòng)程序獲取圖像,可根據(jù)變量grabMethod選擇采用read方式或內(nèi)存映射方式獲取圖像;另有一個(gè)圖像發(fā)送線程負(fù)責(zé)圖像發(fā)送,程序通過建立帶共享鎖的4幀圖像循環(huán)隊(duì)列做為圖像采集線程和圖像發(fā)送線程進(jìn)行數(shù)據(jù)交換的公共緩沖區(qū)。服務(wù)端還使用了兩個(gè)socket,一個(gè)用于和服務(wù)端口綁定后偵聽是否有服務(wù)請(qǐng)求,另外一個(gè)用于發(fā)送圖像數(shù)據(jù),主線程流程如圖3所示。
程序首先設(shè)置采集圖像的相關(guān)參數(shù)(如設(shè)備號(hào)、圖像大小、初始化圖像幀緩沖區(qū)等),然后通過函數(shù) int init_videoIn()獲取攝像頭參數(shù),設(shè)置采集圖像寬度、高度、格式、采集方式等參數(shù),并分配4幀采集圖像緩存 vd->ptframe[i] =(unsigned char *) realloc (vd->ptframe[i], sizeof(struct frame_t) + (size_t) vd->framesizeIn ),再啟動(dòng)圖像采集線程 pthread_create (w1, NULL, (void *) grab, NULL)進(jìn)行圖像采集;創(chuàng)建服務(wù)端socket,與服務(wù)端口綁定后偵聽服務(wù)請(qǐng)求;如果有新連接進(jìn)來,函數(shù)accept()返回一個(gè)新的發(fā)送 socket,并啟動(dòng)新的圖像發(fā)送線程,pthread_create(server_th, NULL, (void *)service, new_sock); 如果采集結(jié)束或連接產(chǎn)生錯(cuò)誤,調(diào)用pthread_join (w1, NULL)和close(serv_sock)關(guān)閉圖像采集線程和圖像發(fā)送線程,釋放有關(guān)資源后退出。
圖3.主程序流程
評(píng)論