[基於Video4Linux的視頻採集模塊開發


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中定義的幾個重要的數據結構如下:

[cpp] view plain copy
  1. typedef struct v4l_struct  
  2. {  
  3.   int   fd;  
  4.   struct video_capability capability;  
  5.   struct video_channel channel[4];  
  6.   struct video_picture picture;  
  7.   struct video_window window;  
  8.   struct video_capture capture;  
  9.   struct video_buffer buffer;  
  10.   
  11.   struct video_mmap mmap;  
  12.   struct video_mbuf mbuf;  
  13.   unsigned char *map;  
  14.   int framestat[2];  
  15. }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中。

[cpp] view plain copy
  1. #define DEFAULT_DEVICE "dev/video0"  
  2. int v4l_open(char *dev,v4l_device *vd)  
  3. {  
  4.  if(!dev)  
  5.  dev=DEFAULT_DEVICE;  
  6.  if((vd->fd=open(dev,O_RDWR))<0)  
  7.  {  
  8.    perror("camera open:");  
  9.    return-1;  
  10.  }  
  11.    if(v4l_get_capability(vd))  
  12.    return -1;  
  13.    if(v41_get_picture(vd))  
  14.    return -1;  
  15.    return 0;  
  16. }  

當應用程序輸入的dev設備文件參數不存在時,就使用“/dev/video0”這個值。Linux下對設備文件的打開是通過open()函數完成的。

[cpp] view plain copy
  1. if((vd->fd=open(dev,O_RDWR))<0)  
  2. {  
  3.    perror("camera_open:");  
  4.    return-1;  
  5. }  

設備文件開啓後,將回傳的文件描述符放到vd->fd裏。在應用程序中使用v4l_open()函數時,需要先聲明一個video_device類型的設備變量,然後通過調用v4l_open()函數將設備打開。如果可以正常打開設備文件,設備變量也就獲得了相應的回傳信息。v41_open()函數被調用的程序如下:

[cpp] view plain copy
  1. video_device vd;  
  2. if(camera_open('‘/dev/video0”,&vd))  
  3. {  
  4.   return-1;  
  5. }  

2)v4l_get_capability():獲取設備信息
成功開啓設備後,首先取得設備信息,v4l_get_capability()函數調用ioctl()取得設備文件相關信息,並且將取得的信息放到

[cpp] view plain copy
  1. video_capability結構體中。  
  2. int v4l_get_capability(v4l_device *vd)  
  3. {  
  4.  if(ioctl(vd->fd,VIDEOCGCAP,&(vd->capability))<0)  
  5.  {  
  6.    perror("camera_get_capability:");  
  7.    return-1;  
  8.  }  
  9.  return 0;  
  10. }  

這個函數的主要內容是ioctl(vd->fd,VIDEOCGCAP,&(vd->capability))。vd->fd是由v4l_open()傳回來的文件描述符,而傳遞VIDIOCGCAP給ioctl()則會傳回設備相關信息,存放與vd->capability。
3)v4l_get_picture():圖像初始化
       取得設備信息後,還要取得圖像信息。所謂圖像信息是指輸入到視頻捕捉設備的圖像格式。

[cpp] view plain copy
  1. int v4l_get_picture(v4l_device *vd)  
  2. {  
  3.   if(ioctl(vd->fd,VIDIOCGPICT,&(vd->picture))<0)  
  4.   {  
  5.     perror(“camera_get_picture:");  
  6.     return-1;  
  7.    }  
  8.     return 0;  
  9. }  

4)改變video_picture中的分量的值,先爲分量賦新值,再傳遞VIDIOCSPICT給ioctl()函數進行設置。

[cpp] view plain copy
  1. vd->picture.colour=65535;  
  2. if(ioctl(vd->fd,VIDIOCSPICT,&(vd->picture))<0)  
  3. {  
  4.   perror("VIDIOCSPICT");  
  5.   return-1;  
  6. }  

5)v4l_get_channels():初始化channel

[cpp] view plain copy
  1. int v4l_get_channels(camera_device *vd)  
  2. {  
  3. int i;  
  4. for(i=0;i < vd->capability.channels;i++){  
  5. vd->channel[i].channel=i;  
  6. if(ioctl(vd->fd,VIDIOCGCHAN,&(vd->channel[i]))<0)  
  7. {  
  8. perror("camera_get_channel:");  
  9. return -1;  
  10. }}  
  11. return 0;  
  12. }  

6)v4l_close():關閉設備

[cpp] view plain copy
  1. int v4l_close(camera_device *vd)  
  2. {  
  3.   close(vd->fd);  
  4.   return 0;  
  5. }  

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中的設置,例如重新設置圖像幀的垂直及水平分辨率、彩色顯示格式以及當前幀的狀態。可利用如下語句

[cpp] view plain copy
  1. vd->mmap.height=240;  
  2. vd->mmap.width=320;  
  3. vd->mmap.format=VIDEO_PALETTE_RGB24;  
  4. vd->framestat[0]=vd->framestat[1]=0;  
  5. vd->flame=0;  

      2)接着把攝像頭對應的設備文件映射到內存區,命令爲

[cpp] view plain copy
  1. 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)視頻截取。把視頻映射到內存後就可進行視頻的截取。命令爲

[cpp] view plain copy
  1. ioctl(vd->fd,VIDIOCMCAPTRUE,&(vd->mmap));  

若調用成功,開始一幀的截取,該操作是非阻塞的,是否截取完畢留給VIDIOCSYNC來判斷。

      4)調用VIDEOCSYNC等待一幀截取結束。

[cpp] view plain copy
  1. if(ioctl(vd->fd,VIDIOCSYNC,&frame)<0)  
  2. {  
  3.    perror("VIDIOCSYNC ERRORI!!”);  
  4.    return -1;  
  5. }  

函數調用成功,表明一幀圖像截取完成,可以開始進行下一次視頻截取。其中frame是當前截取的幀的序號。

      5)由於採集是採用雙緩衝的方式,這樣在處理一幀時可以採集另一幀。frame表示當前採集的是哪一幀圖像,framestat[2]表示幀的狀態(如未開始採集和等待採集結束)。幀在內存中的地址由vd->map+vd->mbuf.offsets[vd->frame]確定。採集結束後調用munmap命令取消映射:munmap(vd->map,vd->mbuf.size)。

     以上詳細分析了圖像採集流程,下面給出圖像採集的具體實現。

4.攝像頭採集模塊的具體實現 

視頻採集的具體實現過程如下:

第一部分,設備初始化。
(1)首先:必須聲明包含2個頭文件:

[cpp] view plain copy
  1. #include<sys/types.h>  
  2. #include<linux/videodev.h>  

(2)然後,進行設備初始化,打開視頻設備,攝像頭在系統中對應的設備文件爲/dev/video0,通過調用驅動程序中定義的open操作來完成。如下:

[cpp] view plain copy
  1. int fd=open("/dev/video”,O_RDW-R);  

其中fd是設備打開後返回的文件描述符,系統調用函數可使用它對設備文件進行操作。

(3)接着通過調用ioctl VIDIOCGCAP操作讀取struct video_capability中有關攝像頭信息。如下:

[cpp] view plain copy
  1. struct video_capability grab_capability;  
  2. ioctl(fd,VIDIOCGCAP,&grab_capability);/*獲得struct video_capability中的攝像頭的信息*/  

(4) 然後通過調用ioctl VIDIOCSFBUF設置內存緩衝區的相關信息,緩衝區的信息可以通過printf函數輸出;

(5)通過調用Ioctl VIDIOCSWIN來完成對視頻窗口的設置,如窗口寬度,高度等;

(6)接着通過調用loctl VIDIOCGPICT操作讀取struct video_capability中有關圖像信息。如下:

[cpp] view plain copy
  1. struct video_picture grab_picture;  
  2. ioctl(fd,VIDIOCGPICT,&grab_picture);/*獲取圖像信息*/  

(7)在獲取圖像信息後,還可以根據需要改變這些信息,例如對比度、亮度、調色板等,具體做法是先給video_picture中的相應變量賦新值,再利用Ioctl VIDIOCSPICT函數進行設置。如下:

[cpp] view plain copy
  1. grab_picture.colour=65535;  
  2. if(ioctl(fd,VIDIOCSPICT,&grab_picture))<0)  
  3. {  
  4.   perror("VIDIOCSPICT”);  
  5.   return-1;  
  6. }  

(8)接着初始化channel,必須先完成vd->capability中的信息調用,使用下列函數:

[cpp] view plain copy
  1. int v4l_get_channels(v4l_device *vd)  

第二部分,使用mmap方式截取視頻。
(1)首先調用ioctl(fd,VIDIOCGMBUF,&grab_vm)函數獲取攝像頭存儲緩衝區的幀信息,之後初始化video_mbuf修改video_mmap中的設置,重新設置圖像信息如幀的垂直及水平分辨率、彩色顯示格式等。如下:

[cpp] view plain copy
  1. structvideo mmap grab_buf ;  //以下爲設置圖像幀緩衝區信息  
  2. grab_buf.frame=0;   //一次只採集一幀  
  3. grab_buf.height= 240;  //圖像高度  
  4. grab_buf.width=320;   //圖像寬度  
  5. grab_buf.format=VIDEO_PALETTE_RGB24;   //圖像的調色板格式,24位真彩色  
  6. unsigned char *data=mmap(0,240*320*3,PROT_READ| PROT_WRITE,MAP_SHARD,fd,0); //內存映射  
  7. 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即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章