摘要:通過對(duì)基于中星微ZC0301芯片的USB攝像頭符合Video for Linux和Video for Linux Version Two標(biāo)準(zhǔn)的兩個(gè)驅(qū)動(dòng)程序的分析研究,給出了符合這兩種標(biāo)準(zhǔn)的USB攝像頭驅(qū)動(dòng)程序在圖像數(shù)據(jù)多幀緩沖和圖像采集設(shè)計(jì)中的不同點(diǎn),并實(shí)現(xiàn)了相應(yīng)的圖像采集程序。
關(guān)鍵詞:Linux;攝像頭;驅(qū)動(dòng)程序;Video for Linux;Video for Linux Two;多緩沖
中圖分類號(hào):TP316文獻(xiàn)標(biāo)識(shí)碼:A文章編號(hào):1009-3044(2008)33-1485-04
The Investigation of Multi-Buffer in USB Camera Driving Program and Image Capture in Linux System
ZHANG Wen-ya
(School of Information Science Technology, Southwest Jiaotong University, Chengdu 610031, China)
Abstract: This paper analyzes two USB camera drivingprograms of Vimicro company's ZC0301 chip.One is in conformity with the Video for Linux principle and the other is in conformity with the Video for Linux Two principle.The difference between the two programs about multi-buffer and image capture is given,and the image capture program is implemented.
Key words: Linux; camera; device driver; Video for Linux; Video for Linux Two; multi-buffer
1 引言
USB攝像頭目前已得到廣泛應(yīng)用。因其靈活、方便的特性,易于集成到嵌入式系統(tǒng)中。比如視頻聊天、網(wǎng)絡(luò)監(jiān)控和可視電話等。在我們開發(fā)的無線網(wǎng)絡(luò)攝像機(jī)系統(tǒng)中,USB攝像頭直接連接到嵌入式開發(fā)版,先用攝像頭進(jìn)行圖像采集,再通過嵌入式模塊進(jìn)行進(jìn)一步的處理。攝像頭驅(qū)動(dòng)程序一般在設(shè)計(jì)中采用符合V4L(Video for Linux)標(biāo)準(zhǔn)的驅(qū)動(dòng)程序配合相應(yīng)的應(yīng)用程序,用的比較多的是開源的spca5xx驅(qū)動(dòng)程序,但隨著Video for Linux Version Two標(biāo)準(zhǔn)的出現(xiàn),在一些驅(qū)動(dòng)中也采用符合V4L2(Video for Linux Version Two)標(biāo)準(zhǔn)的驅(qū)動(dòng)程序配合相應(yīng)的應(yīng)用程序。作者在開發(fā)過程中使用的是基于中星微ZC0301芯片的攝像頭。通過在設(shè)計(jì)過程中對(duì)這款攝像頭分別基于這兩種標(biāo)準(zhǔn)的驅(qū)動(dòng)程序的研究,給出了這兩種驅(qū)動(dòng)程序在多緩沖和圖像采集方面處理的不同點(diǎn),并分別給出相應(yīng)的圖像采集程序框架,以期加深讀者對(duì)驅(qū)動(dòng)程序的理解,根據(jù)實(shí)際開發(fā)需要更好的進(jìn)行應(yīng)用程序方面的設(shè)計(jì)。符合V4L的驅(qū)動(dòng)程序參考開源的gspcav1驅(qū)動(dòng)程序,符合V4L2的驅(qū)動(dòng)程序參考linux2.6內(nèi)核內(nèi)的zc0301驅(qū)動(dòng)程序。
2 使用多緩沖提高效果
在Linux系統(tǒng)中,文件操作通常是由read、write等系統(tǒng)調(diào)用來完成。通過在驅(qū)動(dòng)中用copy_to_user,copy_from_user等函數(shù)在內(nèi)核態(tài)、用戶態(tài)內(nèi)存空間中互相拷貝數(shù)據(jù)。但我們主要處理的是大批量的圖像數(shù)據(jù),采用上面的方法,頻繁的進(jìn)行讀寫,會(huì)增加時(shí)間開銷。因此一般采用內(nèi)存映射的方法來解決。mmap 方法是 file_operation 結(jié)構(gòu)的一部分, 當(dāng)發(fā)出 mmap系統(tǒng)調(diào)用時(shí)被引用。首先申請(qǐng)足夠大的內(nèi)核態(tài)內(nèi)存,將其作為圖像數(shù)據(jù)緩沖空間,URB帶回的圖像數(shù)據(jù)在這里暫存;然后使用函數(shù)將其逐頁映射到用戶空間中。用戶態(tài)的圖像處理程序使用mmap() 函數(shù),直接讀寫內(nèi)核態(tài)圖像緩沖內(nèi)存,這樣可以大大減少額外開銷,提高效率。
2.1 符合V4L標(biāo)準(zhǔn)的驅(qū)動(dòng)中的多幀緩沖
在gspcav1驅(qū)動(dòng)程序中,采用雙幀緩沖,即在核態(tài)內(nèi)存申請(qǐng)兩幀圖像緩沖內(nèi)存。
gspcav1的設(shè)備數(shù)據(jù)結(jié)構(gòu)
struct usb_spca50x {
struct video_device *vdev;
struct usb_device *dev;
char *fbuf;
struct spca50x_frame frame[SPCA50X_NUMFRAMES];
struct spca50x_sbuf sbuf[SPCA50X_NUMSBUF];
……}
其中struct spca50x_frame {
unsigned char *data;/* 幀緩沖區(qū) */
unsigned char *tmpbuffer;
……};
用來保存捕獲的每一幀圖像數(shù)據(jù)及狀態(tài)等等多個(gè)重要信息。SPCA50X_NUMFRAMES宏定義為2,在驅(qū)動(dòng)中明確定義采用雙緩沖來進(jìn)行圖像捕捉。用戶不可以在應(yīng)用程序中進(jìn)行修改。在驅(qū)動(dòng)中通過spca50x_alloc()函數(shù)分配兩幀的核態(tài)內(nèi)存。部分代碼如下:
static int spca50x_alloc(struct usb_spca50x *spca50x)
{……
spca50x->fbuf = rvmalloc(SPCA50X_NUMFRAMES * MAX_DATA_SIZE);
……
for (i = 0; i < SPCA50X_NUMFRAMES; i++) {
……
spca50x->frame[i].grabstate = FRAME_UNUSED;
spca50x->frame[i].data = spca50x->fbuf + i * MAX_DATA_SIZE;
spca50x->frame[i].highwater=spca50x->frame[i].data;
}……}
spca50x->fbuf一次被分配兩幀空間,再將它對(duì)應(yīng)的地址按幀分別賦給spca50x->frame[i].data。使用spca50x_move_data()函數(shù)將urb中的數(shù)據(jù)傳到設(shè)備幀緩沖。驅(qū)動(dòng)提供mmap文件操作方式。部分代碼如下:
static int spca5xx_mmap(struct file *file, struct vm_area_struct *vma)
{……
pos = (unsigned long) spca50x->fbuf;
while (size > 0) {
page = kvirt_to_pa(pos);
if(remap_pfn_range(vma, start, page >> PAGE_SHIFT, PAGE_SIZE, PAGE_SHARED)) {up(spca50x->lock);
return -EAGAIN;}
start += PAGE_SIZE;
pos += PAGE_SIZE;
if (size > PAGE_SIZE)
size -= PAGE_SIZE;
elsesize = 0;
……}
大量工作由內(nèi)核完成,為實(shí)現(xiàn) mmap, 驅(qū)動(dòng)只要建立合適的頁表給用來存取設(shè)備的虛擬地址范圍。如果需要, 用新的操作集合替換vma->vm_ops。在這里建立頁表的方法是調(diào)用 remap_pfn_range 一次完成全部。
2.2 符合V4L2標(biāo)準(zhǔn)的驅(qū)動(dòng)中的多幀緩沖
在zc0301驅(qū)動(dòng)中使用多幀緩沖提高效果。申請(qǐng)的緩沖幀數(shù)可以由用戶定義,但不能超過該驅(qū)動(dòng)定義的最大值32,不能小于2。 zc0301的設(shè)備數(shù)據(jù)結(jié)構(gòu)
struct zc0301_device {
struct video_device* v4ldev;
struct usb_device* usbdev;
struct zc0301_frame_t *frame_current, frame[ZC0301_MAX_FRAMES];
struct list_head inqueue, outqueue;
……}
struct zc0301_frame_t定義如下:
struct zc0301_frame_t {
void* bufmem;
struct v4l2_buffer buf;
enum zc0301_frame_state state;
struct list_head frame;
unsigned long vma_use_count;};
該結(jié)構(gòu)體用來保存在驅(qū)動(dòng)中每幀圖像數(shù)據(jù)的存儲(chǔ)地址及該幀的狀態(tài)等多個(gè)重要信息。用戶可根據(jù)需要在應(yīng)用程序中用ioctl VIDIOC_REQBUFS 來初始化要映射的內(nèi)存空間。在驅(qū)動(dòng)中 zc0301_request_buffers函數(shù)來申請(qǐng)幀緩沖空間,部分代碼如下:
static u32 zc0301_request_buffers(
struct zc0301_device* cam, u32count,enum
zc0301_io_method io)
{……
cam->nbuffers = count;
while (cam->nbuffers > 0) {
if ((buff = vmalloc_32_user(cam->nbuffers *PAGE_ALIGN(imagesize))))
break;
cam->nbuffers--;}
for (i = 0; i < cam->nbuffers; i++) {
cam->frame[i].bufmem = buff + i*
PAGE_ALIGN(imagesize);
cam->frame[i].buf.index = i;
cam->frame[i].buf.m.offset = i*
PAGE_ALIGN(imagesize);
……
cam->frame[i].buf.memory = V4L2_MEMORY_MMAP;
}
return cam->nbuffers;}
使用vmalloc分配count個(gè)幀空間,再通過循環(huán)將每幀的起始地址和偏移地址存入cam->frame[i]中。可通過zc0301_release_buffers()函數(shù)釋放空間。該驅(qū)動(dòng)提供的mmap文件操作方式部分代碼如下:
static int zc0301_mmap(struct file* filp, struct vm_area_struct *vma)
{……
void *pos;
pos = cam->frame[i].bufmem;
while (size > 0) { /* size is page- aligned */
if(vm_insert_page(vma,start,vmalloc_to_page
(pos))) {
mutex_unlock(cam->fileop_mutex);
return -EAGAIN;}
start += PAGE_SIZE;
pos += PAGE_SIZE;
size -= PAGE_SIZE;
}……}
為實(shí)現(xiàn)該mmap文件操作,驅(qū)動(dòng)在建立頁表給用來存取設(shè)備的虛擬地址范圍時(shí)使用vm_insert_page()函數(shù),將分配好的每一頁依次插入的用戶虛擬空間中。
3 圖像采集
3.1 符合V4L標(biāo)準(zhǔn)的圖像采集
gspcav1驅(qū)動(dòng)中用ioctl命令VIDIOCMCAPTURE進(jìn)行圖像采集。執(zhí)行該命令后該幀狀態(tài)為spca50x->
frame[vm->frame].grabstate=FRAME_READY。該命令中判斷用戶所給圖像參數(shù)是否滿足要求,設(shè)置采集圖像數(shù)據(jù),寫寄存器參數(shù),設(shè)置該幀狀態(tài)為FRAME_READY,開始采集圖像數(shù)據(jù)。但并不表示已經(jīng)完成整個(gè)圖像數(shù)據(jù)的捕捉,需要通過VIDIOCSYNC進(jìn)行同步。這個(gè)命令表示該幀圖像已經(jīng)完成。驅(qū)動(dòng)將在此等待幀狀態(tài)由FRAME_GRABBING變?yōu)镕RAME_DONE,在等待隊(duì)列中阻塞等待圖像采集完成wait_event_interruptible(spca50x->frame[
frame].wq,(spca50x->frame[frame].grabstate==FRAME_DONE)),當(dāng)該幀狀態(tài)為FRAME_DONE時(shí)釋放當(dāng)前幀,將其狀態(tài)改為FRAME_UNUSED,當(dāng)另一幀被填滿時(shí)可以再次使用這一幀。
3.2 符合V4L2標(biāo)準(zhǔn)的圖像采集
zc0301驅(qū)動(dòng)中用兩個(gè)FIFO的緩沖區(qū)隊(duì)列來完成圖像的獲取和輸出。struct list_head inqueue, outqueue;當(dāng)前幀狀態(tài)cam->frame[i].state == F_UNUSED加入到inqueue中,用來捕捉圖像數(shù)據(jù),而一旦該幀數(shù)據(jù)捕捉完畢就從inqueue隊(duì)列中移出,加入到outqueue隊(duì)列中,ioctl命令 VIDIOC_QBUF用于將空閑的幀加入到輸入隊(duì)列inqueue中,接收下一幀圖像, VIDIOC_DQBUF則從輸出隊(duì)列中獲得已捕捉到的一幀圖像。zc0301_vidioc_qbuf()函數(shù)用來實(shí)現(xiàn)將一幀加入inqueue隊(duì)列,部分代碼如下:
static int zc0301_vidioc_qbuf(
struct zc0301_device* cam, void __user * arg)
{……
if (copy_from_user(b, arg, sizeof(b)))
return -EFAULT;
……
cam->frame[b.index].state = F_QUEUED;
spin_lock_irqsave(cam->queue_lock, lock_flags);
list_add_tail(cam->frame[b.index].frame, cam->inqueue);
spin_unlock_irqrestore(cam->queue_lock, lock_flags);
……}
驅(qū)動(dòng)中用zc0301_vidioc_dqbuf()函數(shù),從outqueue隊(duì)列中得到一幀圖像,部分代碼如下:
static int zc0301_vidioc_dqbuf(
struct zc0301_device* cam, struct file* filp,
void __user * arg)
{ ……
if (copy_from_user(b, arg, sizeof(b)))
return -EFAULT;
……
timeout=wait_event_interruptible_timeout( cam->wait_frame,(!list_empty(cam->outqueue)) ||
(cam->state DEV_DISCONNECTED) ||(cam->state
DEV_MISCONFIGURED),cam->module_param.frame_timeout*1000* msecs_to_jiffies(1) );
spin_lock_irqsave(cam->queue_lock, lock_flags);
f = list_entry(cam->outqueue.next, struct zc0301_frame_t, frame);
list_del(cam->outqueue.next);
spin_unlock_irqrestore(cam->queue_lock, lock_flags);
f->state = F_UNUSED;
memcpy(b, f->buf, sizeof(b));
……
if (copy_to_user(arg, b, sizeof(b)))
return -EFAULT;
……}
V4l2中通過select判斷圖像是否已經(jīng)捕捉完畢,與VIDIOC_DQBUF配合使用。通過select()函數(shù)判斷該驅(qū)動(dòng)是否有數(shù)據(jù)可以讀出。當(dāng)設(shè)備已捕獲到數(shù)據(jù)時(shí)通過VIDIOC_DQBUF輸出。
4 USB攝像頭圖像采集
在嵌入式Linux系統(tǒng)中,USB攝像頭被注冊(cè)為一個(gè)標(biāo)準(zhǔn)的視頻設(shè)備/dev/video,在這里我們采用mmap方式獲取視頻圖像,給出現(xiàn)有的Video for Linux兩個(gè)版本下通過API接口獲取視頻圖像數(shù)據(jù)的主要步驟。
4.1 基于V4L API接口主要操作步驟
1) 打開視頻設(shè)備
Linux系統(tǒng)中,攝像頭的設(shè)備文件為/dev/video0,調(diào)用系統(tǒng)函數(shù)open打開該設(shè)備。
fd=open (dev_name, O_RDWR);
2) 通過ioctl函數(shù)VIDIOCGCAP控制命令讀取設(shè)備
struct video_capability包括攝像頭的屬性,攝像頭所能獲取的最大圖像大小,用像素作單位。通過ioctl發(fā)出VIDIOCGPICT控制命令,struct video_picture主要定義了圖像的屬性,諸如亮度,對(duì)比度,等等。
3) 設(shè)置視頻捕獲的圖像格式
struct video_picture campic;
ampic.palette =VIDEO_PALETTE_RGB24;
campic.brightness = 44464;
campic.hue = 36000;
campic.colour = 0;
campic.contrast = 43312;
campic.whiteness = 13312;
campic.depth = 24;
ret = ioctl( cam_fd, VIDIOCSPICT,cam_pic ); //*設(shè)置攝像頭緩沖中voideo_picture信息*/
4) 視頻數(shù)據(jù)幀捕獲。
使用ioctl命令VIDIOCGMBUF獲得幀緩沖空間,使用mmap()內(nèi)存映射到用戶空間,然后調(diào)用VIDIOCMCAPTURE開始采集圖像數(shù)據(jù),VIDIOCSYNC同步等待一幀完成。獲取圖像數(shù)據(jù)后就可以進(jìn)行圖片輸出或其它編解碼處理。
4.2 基于V412 API接口主要操作步驟
1) 打開視頻設(shè)備
操作與V4L基本相同,調(diào)用open()打開該設(shè)備。
fd=open (dev_name, O_RDWR);
2) 獲取該視頻設(shè)備所支持的V4L2特性
所有的V4L2設(shè)備驅(qū)動(dòng)都需要支持VIDIOC_QUERYCAP ioctl的系統(tǒng)調(diào)用。通過該調(diào)用,確定該驅(qū)動(dòng)程序是否與V4L2規(guī)范相兼容,同時(shí)獲取該設(shè)備所支持的V4L2特性。
ret=ioctl(fd, VIDIOC_QUERYCAP, cap);
在攝像頭應(yīng)用程序的開發(fā)過程中需要判定該設(shè)備是否支持視頻捕獲
if(!(cap.capabilitiesV4L2_CAP_VIDEO_CAPTURE))
{fprintf (stderr, \"%s is no video capture device\\", dev_name);
exit (EXIT_FAILURE);}
如果采用mmap方式還要判斷該設(shè)備是否支持流采集
if (!(cap.capabilities V4L2_CAP_STREAMING)) {
fprintf (stderr, \"%s does not support streaming i/o\\",dev_name);
exit (EXIT_FAILURE);}
3) 設(shè)置視頻捕獲的圖像格式
memset(fmt, 0, sizeof(struct v412 format));
fint.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
fint.fint.pix.width=640;
fint.fint.pix.height=480;
fint.fint.pix.pixelformat= V4L2_PIX_FMT_JPEG;
ret=ioctl(fd, VIDIOC_S_FMT, fmt );
4) 建立到用戶區(qū)的內(nèi)存映射
通過ioctl VIDIOC_REQBUF 申請(qǐng)內(nèi)核緩沖區(qū),根據(jù)需要分配若干個(gè)幀,再通過ioctl VIDIOC_QUERYBUF 得到一幀數(shù)據(jù),通過 mmap映射到用戶空間中。
mmap (NULL /* start anywhere */,
buf.length,PROT_READ | PROT_WRITE
/* required */, MAP_SHARED /* recommended */,
fd, buf.m.offset);
5) 視頻數(shù)據(jù)幀采集
將緩沖幀加入inqueue輸入隊(duì)列,準(zhǔn)備緩沖圖像數(shù)據(jù)空間,通過VIDIOC_STREAMON開始捕獲圖像數(shù)據(jù)。用select()確定一幀數(shù)據(jù)是否捕捉完成,由VIDIOC_ DQBUF得到輸出的幀圖像數(shù)據(jù)。
5結(jié)束語
具體分析了V4L與V4L2兩個(gè)不同標(biāo)準(zhǔn)下USB攝像頭驅(qū)動(dòng)中多幀緩沖的實(shí)現(xiàn)以及兩種標(biāo)準(zhǔn)下采集圖像數(shù)據(jù)的不同方式,并分別給出了相應(yīng)的圖像采集主要步驟。在這兩種驅(qū)動(dòng)下實(shí)現(xiàn)了圖像采集應(yīng)用程序的開發(fā)。
參考文獻(xiàn):
[1] Jonathan Corbet. Linux設(shè)備驅(qū)動(dòng)程序 [M]. 3版. 北京:中國電力出版社,2006.
[2] 劉春成.基于嵌入式Linux的USB攝像頭驅(qū)動(dòng)開發(fā)[J].計(jì)算機(jī)工程與設(shè)計(jì),2007,28(8):1885-1888.
[3] 王滔,季曉勇.在嵌入式Linux平臺(tái)上使用USB攝像頭[J].微計(jì)算機(jī)應(yīng)用,2006,27(1):52-54.
[4] Video for Linux Two API Specification Revision 0.24[S], 2008
[5] 毛德操,胡希明.Linux內(nèi)核源代碼情景分析(上、下)[M].杭州:浙江大學(xué)出版社,2001.