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