linux系統中,網路攝影機驅動程式安裝好後,為了進行視頻採集必須加入Video4Linux模組,從而可以通過Video4Linux模組提供的編程介面(API)從網路攝影機裝置中擷取映像幀。下面具體研究基於V4L的視頻採集程式設計。
1 Video4Linux概述
Video4Linux是Linux中關於視頻裝置的核心驅動,為針對視頻裝置的應用程式編程提供一系列介面函數,在Linux下,視頻採集裝置的正常使用依賴Video4-Linux標準的支援,在編譯核心時要選中Video4Linux項,對應的裝置檔案是/dev/video。對於USB口網路攝影機,其驅動程式中需要提供基本的I/O操作介面函數open、read、write、close的實現。對中斷的處理實現,記憶體映射功能以及對I/O通道的控制介面函數ioctl的實現等,並把它們定義在structfile
operations中。這樣當應用程式對裝置檔案進行諸如open、read、write、close等系統調用時,Linux核心將通過file operations結構訪問驅動程式提供的函數。
2.視頻編程的資料結構及函數
Linux下V4L視頻採集,主要是調用V4L模組參數進行視頻未經處理資料採集。使用的一些主要參數和函數定義在系統/include/linux/videodev.h檔案中。下面是videodev.h中定義的幾個重要的資料結構如下:
typedef struct v4l_struct{ int fd; struct video_capability capability; struct video_channel channel[4]; struct video_picture picture; struct video_window window; struct video_capture capture; struct video_buffer buffer; struct video_mmap mmap; struct video_mbuf mbuf; unsigned char *map; int framestat[2];}v4l_device;
◆video_capability結構包含裝置的基本資料,如裝置名稱、支援的最大最小解析度、訊號源資訊等。它包含以下分量:name[32]指定裝置名稱,maxwidth,maxheight,minwidth,minheight指定網路攝影機能捕捉的最大和最小的映像尺寸。Channels指定訊號源個數,type表示所截取的映像是否能被捕捉,是彩色還是黑白,是否能裁剪,值如VID
TYPE CAPTURE等。
◆video_channel結構指定各個訊號源的屬性,它包含以下分量:channel指定訊號源的編號,name為每個訊號源的名稱,Type指定所輸入的訊號類型,包含兩種類型:VIDEO_TYPE_TV表示為電視訊號;VIDEO_TYPE_CAMERA表示為網路攝影機訊號。Norm表示電視訊號所使用的制式,有VIDEO_MODE_PAL、VIDEO_MODE_NTSC、VIDEO_MODE_SECAM、VIDEO_MODE_AUTO等幾
種選項。
◆video_picture結構用來設定採集映像的各種屬性。它包括以下幾個分量:brightness、hue、colour、contrast來為採集的映像提供類似電視訊號的控制,whiteness來為灰階映像提供額外控制。
◆video_window結構設定採集的映像的顯示方式。它包括以下幾個分量:X用來描述視窗左上方的X座標,Y用來描述左上方的Y座標,width用來描述採集映像的寬度,height用來描述採集映像的高度,當採用色度鍵時chromakey用來表示顏色,用RGB32格式表示的。
◆video_mbuf結構用來描述利用mmap進行映射的幀的資訊,實際上是輸入到網路攝影機儲存空間緩衝區中的幀資訊。它包含以下幾個分量:size表示每幀大小,frames用來表示最多支援的幀數,offsets表示每幀相對基址的位移。
◆video_buffer結構用來對最底層的buffer進行描述,它包含以下幾個含量:void *baseBase描述buffer的物理地址,height描述frame buffer的高度,width描述frame buffer的寬度,depth描述frame buffer的深度,bytesperline描述每兩個相鄰的線之間的儲存空間位元組數。
◆video mmap用於記憶體映射。
在V4L編程中,與映像裝置互動主要是依靠系統調用ioctl函數來實現的。Ioctl(input/output control)函數是通過開啟的檔案描述符對各種檔案尤其是字元裝置檔案進行控制,完成特定的I/O操作。V4L支援的ioctl命令通常需要幾個參數,一般情況下,第一個參數fd代表開啟的檔案/裝置,第二個參數cmd為驅動程式的特殊命令,有的還有第3個或者更多的參數,根據不同的驅動程式不同。
在V4L中定義了一些重要的宏,用來作為ioctl函數的第二個參數,用來操縱裝置,一些主要的宏定義說明如下:
1)VIDIOCGCAP:Ioctl操作得到映像裝置的基本資料,儲存在video_capability結構體中。
2)VIDIOCSCAP:Ioctl操作根據video_capability結構體中的值來設定映像裝置初始參數。
3)VIDIOCGPICT:Ioctl操作得到映像的基本資料,儲存在video_picture中,包括映像的亮度、色度、對比等。
4)VIDIOCSPICT:Iocfl操作根據video_picture結構體中的值來設定採集映像的初始參數。
5)VIDIOCGMBUF:Ioctl操作擷取網路攝影機儲存緩衝區的幀資訊。
6)VIDIOCAMCAPTURE:擷取視頻映像。
7)VIDIOCGWIN:loctl操作得到採集映像地區的基本資料,儲存在video_window結構體中。設定目標視窗的屬性。
8)VIDIOCSWIN:Ioctl操作根據video_window結構體中的值來設定採集映像地區大小等參數。
9)VIDIOSYNC:Ioctl操作判斷視頻映像是否截取成功。
視頻採集函數採用自頂向下的操作方式,首先規劃並定義這些函數。
1)v41_open() :開啟視頻採集裝置函數
首先定義了一個預設的裝置檔案路徑,如果可以開啟“/dev/video0"則將取回的資訊放到vd中。
#define DEFAULT_DEVICE "dev/video0"int v4l_open(char *dev,v4l_device *vd){ if(!dev) dev=DEFAULT_DEVICE; if((vd->fd=open(dev,O_RDWR))<0) { perror("camera open:"); return-1; } if(v4l_get_capability(vd)) return -1; if(v41_get_picture(vd)) return -1; return 0;}
當應用程式輸入的dev裝置檔案參數不存在時,就使用“/dev/video0”這個值。Linux下對裝置檔案的開啟是通過open()函數完成的。
if((vd->fd=open(dev,O_RDWR))<0){ perror("camera_open:"); return-1;}
裝置檔案開啟後,將回傳的檔案描述符放到vd->fd裡。在應用程式中使用v4l_open()函數時,需要先聲明一個video_device類型的裝置變數,然後通過調用v4l_open()函數將裝置開啟。如果可以正常開啟裝置檔案,裝置變數也就獲得了相應的回傳資訊。v41_open()函數被調用的程式如下:
video_device vd;if(camera_open('‘/dev/video0”,&vd)){ return-1;}
2)v4l_get_capability():擷取裝置資訊
成功開啟裝置後,首先取得裝置資訊,v4l_get_capability()函數調用ioctl()取得裝置檔案相關資訊,並且將取得的資訊放到
video_capability結構體中。int v4l_get_capability(v4l_device *vd){ if(ioctl(vd->fd,VIDEOCGCAP,&(vd->capability))<0) { perror("camera_get_capability:"); return-1; } return 0;}
這個函數的主要內容是ioctl(vd->fd,VIDEOCGCAP,&(vd->capability))。vd->fd是由v4l_open()傳回來的檔案描述符,而傳遞VIDIOCGCAP給ioctl()則會傳回裝置相關資訊,存放與vd->capability。
3)v4l_get_picture():映像初始化
取得裝置資訊後,還要取得映像資訊。所謂映像資訊是指輸入到視頻捕捉裝置的映像格式。
int v4l_get_picture(v4l_device *vd){ if(ioctl(vd->fd,VIDIOCGPICT,&(vd->picture))<0) { perror(“camera_get_picture:"); return-1; } return 0;}
4)改變video_picture中的分量的值,先為分量賦新值,再傳遞VIDIOCSPICT給ioctl()函數進行設定。
vd->picture.colour=65535;if(ioctl(vd->fd,VIDIOCSPICT,&(vd->picture))<0){ perror("VIDIOCSPICT"); return-1;}
5)v4l_get_channels():初始化channel
int v4l_get_channels(camera_device *vd){int i;for(i=0;i < vd->capability.channels;i++){vd->channel[i].channel=i;if(ioctl(vd->fd,VIDIOCGCHAN,&(vd->channel[i]))<0){perror("camera_get_channel:");return -1;}}return 0;}
6)v4l_close():關閉裝置
int v4l_close(camera_device *vd){ close(vd->fd); return 0;}
3.映像採集流程
從V4L的資料結構看出,完成基於V4L的USB視頻資料擷取,先要獲得相關的視頻採集裝置的資訊和映像資訊,並對採集視窗、顏色模式、幀狀態初始化,然後才能進行視頻映像的採集。
具體操作流程如下:
1)開啟裝置進行設定,通過使用標準的檔案開啟函數操作;
2)查詢映像緩衝區資訊並設定;VIDIOCSFBUF主要是設定映像緩衝區的基地址和緩衝區大小;
3)查詢映像截取視窗資訊並設定:VIDIOCGWIN和VIDIOCSWIN主要設定截取映像尺寸和位置;
4)查詢通道資訊並設定,可以從一個或者多個通道捕獲資料,來進行通道的查詢設定函數VIDEOCGCHAN和VIDEOCSCHAN;
5)擷取映像並存放在緩衝區。
在V4L中映像截取方式有兩種:
1)通過調用read具有阻塞功能的函數,將輸出映像資料複製到預先設定好的資料緩衝區中,就可以實現對每幀映像資料的讀取;
2)用mmap方式,直接將裝置檔案/dev/video0映射到記憶體中,不需要額外的對資料緩衝區進行複製工作,加快了映像資訊的捕捉速度。另外,mmap()系統調用使得進程之間通過映射同一檔案實現共用記憶體,各個進程可以像訪問普通記憶體一樣對檔案進行訪問,訪問時只需要使用指標而不用調用檔案操作函數。
視頻採集的流程如所示。
鑒於mmap()以上優點,本文採用的第2種方式,利用mmap()方式對視頻進行採集與裁剪的操作如下:
1)先使用ioctl(vd->fd,VIDIOCGMBUF,&vd->mbuf)數獲得網路攝影機儲存緩衝區的幀資訊,之後修改video_map中的設定,例如重新設定映像幀的垂直及水平解析度、彩色顯示格式以及當前幀的狀態。可利用如下語句
vd->mmap.height=240;vd->mmap.width=320;vd->mmap.format=VIDEO_PALETTE_RGB24;vd->framestat[0]=vd->framestat[1]=0;vd->flame=0;
2)接著把網路攝影機對應的裝置檔案對應到記憶體區,命令為
vd->map=(unsignedchar *)mmap(0,vd->mbuf.size,PROT_READ|PROT_WRITE,MAP_SHARED,vd->fd,0)
這樣裝置檔案的內容就映射到記憶體區,映射內容區可讀可寫並且不同進程間可共用。該函數成功時返回映像到記憶體區的指標,失敗時返回1。mmap()函數中第一個參數表示共用記憶體的起始地址,在此外設為0表示由系統分配。第二個參數表示映射到調用進程地址空間的位元組數,此值為vd->mbuf.size。第三個參數指定共用記憶體的存取權限,它有以下幾個值PORT_READ(可讀),PROT_WRITE(可寫),PROT_EXEC(可執行),此處設為可讀和可寫。第四個參數指定共用記憶體的屬性,有MAP_SHARED、MAP
_PRIVATE、MAP_FIXED三種屬性,一般情況下從中選一個使用。
3)視頻截取。把視頻映射到記憶體後就可進行視頻的截取。命令為
ioctl(vd->fd,VIDIOCMCAPTRUE,&(vd->mmap));
若調用成功,開始一幀的截取,該操作是非阻塞的,是否截取完畢留給VIDIOCSYNC來判斷。
4)調用VIDEOCSYNC等待一幀截取結束。
if(ioctl(vd->fd,VIDIOCSYNC,&frame)<0){ perror("VIDIOCSYNC ERRORI!!”); return -1;}
函數調用成功,表明一幀映像截取完成,可以開始進行下一次視頻截取。其中frame是當前截取的幀的序號。
5)由於採集是採用雙緩衝的方式,這樣在處理一幀時可以採集另一幀。frame表示當前採集的是哪一幀映像,framestat[2]表示幀的狀態(如未開始採集和等待採集結束)。幀在記憶體中的地址由vd->map+vd->mbuf.offsets[vd->frame]確定。採集結束後調用munmap命令取消映射:munmap(vd->map,vd->mbuf.size)。
以上詳細分析了映像採集流程,下面給出映像採集的具體實現。
4.網路攝影機採集模組的具體實現
視頻採集的具體實現過程如下:
第一部分,裝置初始化。
(1)首先:必須聲明包含2個標頭檔:
#include<sys/types.h>#include<linux/videodev.h>
(2)然後,進行裝置初始化,開啟視頻裝置,網路攝影機在系統中對應的裝置檔案為/dev/video0,通過調用驅動程式中定義的open操作來完成。如下:
int fd=open("/dev/video”,O_RDW-R);
其中fd是裝置開啟後返回的檔案描述符,系統調用函數可使用它對裝置檔案進行操作。
(3)接著通過調用ioctl VIDIOCGCAP操作讀取struct video_capability中有關網路攝影機資訊。如下:
struct video_capability grab_capability;ioctl(fd,VIDIOCGCAP,&grab_capability);/*獲得struct video_capability中的網路攝影機的資訊*/
(4) 然後通過調用ioctl VIDIOCSFBUF設定記憶體緩衝區的相關資訊,緩衝區的資訊可以通過printf函數輸出;
(5)通過調用Ioctl VIDIOCSWIN來完成對視頻視窗的設定,如視窗寬度,高度等;
(6)接著通過調用loctl VIDIOCGPICT操作讀取struct video_capability中有關映像資訊。如下:
struct video_picture grab_picture;ioctl(fd,VIDIOCGPICT,&grab_picture);/*擷取映像資訊*/
(7)在擷取映像資訊後,還可以根據需要改變這些資訊,例如對比、亮度、調色盤等,具體做法是先給video_picture中的相應變數賦新值,再利用Ioctl VIDIOCSPICT函數進行設定。如下:
grab_picture.colour=65535;if(ioctl(fd,VIDIOCSPICT,&grab_picture))<0){ perror("VIDIOCSPICT”); return-1;}
(8)接著初始化channel,必須先完成vd->capability中的資訊調用,使用下列函數:
int v4l_get_channels(v4l_device *vd)
第二部分,使用mmap方式截取視頻。
(1)首先調用ioctl(fd,VIDIOCGMBUF,&grab_vm)函數擷取網路攝影機儲存緩衝區的幀資訊,之後初始化video_mbuf修改video_mmap中的設定,重新設定映像資訊如幀的垂直及水平解析度、彩色顯示格式等。如下:
structvideo mmap grab_buf ; //以下為設定映像框架緩衝區資訊grab_buf.frame=0; //一次只採集一幀grab_buf.height= 240; //映像高度grab_buf.width=320; //映像寬度grab_buf.format=VIDEO_PALETTE_RGB24; //映像的調色盤格式,24位真彩色unsigned char *data=mmap(0,240*320*3,PROT_READ| PROT_WRITE,MAP_SHARD,fd,0); //記憶體映射ioctl(grab fd,VIDIOCMCAPTURE,&grab_buf); //採集映像
(2)然後調用ioctl(grab fd,VIDIOCSYNC,&frame)函數,該函數成功返回則表示採集完畢,採集到的映像資料放到以data為起始地址,長度為240*320*3的記憶體地區中,讀取該記憶體中的資料便可得到映像資料。
(3)在此基礎上同樣可實現連續幀的採集,即一次採集連續多幀映像的資料。此時首先要設定grab_bur.frame為要採集的幀數。在迴圈語句中,也是使用ioctl VIOCMCAPTURE和ioctl VIDIOCSYNC操作完成每幀讀取,但是要給採集到的每幀映像賦地址為data+grab_vm.offsets[frame],然後儲存檔案格式。其中grab_vm為video_mbuf結構體變數的一個聲明,利用ioctl(fd,VIDIOCGMBUF,
&grab_vm)便可獲得grab 的資訊。
(4)若要繼續採集可再加一個外迴圈,在外迴圈語句中只要給原來的內迴圈再賦伊grab_buf.frame=0即可。