linux V4L視頻接口API使用與…

V4L API介紹與應用
注:《轉》video4linux(v4l)使用攝像頭的實例基礎教程與體會
看了網上的一些文章,其中比較重要的也是比較知名的吧,有戴小鼠寫的《基於Video4Linux 的USB 攝像頭圖像採集實現》,有陳俊宏寫的《video stream 初探》的一系列共六篇文章,也找了一些英文的資料,看到過《video4linux programming》但是這篇文章偏重於視頻設備在linux中的驅動實現,所以對像我這種低端的只是使用v4l相關係統調用的人來說有些幫助但幫助不大,《Video4Linux Kernel API Reference》詳細介紹了v4l中各個重要的結構體的作用。
1.video4linux基礎相關
1.1 v4l的介紹與一些基礎知識的介紹
I.首先說明一下video4linux(v4l)。
它是一些視頻系統,視頻軟件,音頻軟件的基礎,經常使用在需要採集圖像的場合,如視頻監控,webcam,可視電話,經常應用在embedded linux中是linux嵌入式開發中經常使用的系統接口。它是linux內核提供給用戶空間的編程接口,各種的視頻和音頻設備開發相應的驅動程序後,就可以通過v4l提供的系統API來控制視頻和音頻設備,也就是說v4l分爲兩層,底層爲音視頻設備在內核中的驅動,上層爲系統提供的API,而對於我們來說需要的就是使用這些系統的API。
II.Linux系統中的文件操作
有關Linux系統中的文件操作不屬於本文的內容。但是還是要了解相關係統調用的作用和使用方法。其中包括open(),read(),close(),ioctl(),mmap()。詳細的使用不作說明。在Linux系統中各種設備(當然包括視頻設備)也都是用文件的形式來使用的。他們存在與dev目錄下,所以本質上說,在Linux中各種外設的使用(如果它們已經正確的被驅動),與文件操作本質上是沒有什麼區別的。
1.2 建立一套簡單的v4l函數庫
這一節將一邊介紹v4l的使用方法,一邊建立一套簡單的函數,應該說是一套很基本的函數,它完成很基本的夠能但足夠展示如何使用v4l。這些函數可以用來被其他程序使用,封裝基本的v4l功能。本文只介紹一些和攝像頭相關的編程方法,並且是最基礎和最簡單的,所以一些內容並沒有介紹,一些與其他視頻設備(如視頻採集卡)和音頻設備有關的內容也沒有介紹,本人也不是很理解這方面的內容。
這裏先給出接下來將要開發出來函數的一個總覽。
相關結構體和函數的定義我們就放到一個名爲v4l.h的文件中,相關函數的編寫就放在一個名爲v4l.c的文件中把。
對於這個函數庫共有如下的定義(也就是大體v4l.h中的內容):
1: #ifndef _V4L_H_
2: #define _V4L_H_
3: #include <sys/types.h>
4: #include <linux/videodev.h> //使用v4l必須包含的頭文件
這個頭文件可以在/usr/include/linux下找到,裏面包含了對v4l各種結構的定義,以及各種ioctl的使用方法,所以在下文中有關v4l的相關結構體並不做詳細的介紹,可以參看此文件就會得到你想要的內容。
下面是定義的結構體,和相關函數,突然給出這麼多的代碼很唐突,不過隨着一點點解釋條理就會很清晰了。
1: struct _v4l_struct
2:
3: {
4:
5: int fd;//保存打開視頻文件的設備描述符
6:
7: struct video_capability capability;//該結構及下面的結構爲v4l所定義可在上述頭文件中找到
8:
9: struct video_picture picture;
10:
11: struct video_mmap mmap;
12:
13: struct video_mbuf mbuf;
14:
15: unsigned char *map;//用於指向圖像數據的指針
16:
17: int frame_current;
18:
19: int frame_using[VIDEO_MAXFRAME];//這兩個變量用於雙緩衝在後面介紹。
20:
21: };
22:
23: typedef struct _v4l_struct v4l_device;
//上面的定義的結構體,有的文中章有定義channel的變量,但對於攝像頭來說設置這個變量意義不大通常只有一個channel,本文不是爲了寫出一個大而全且成熟的函數庫,只是爲了介紹如何使用v4l,再加上本人水平也有限,能夠給讀者一個路線我就很知足了,所以並沒有設置這個變量同時與channel相關的函數也沒有給出。
1: extern int v4l_open(char *, v4l_device *);
2:
3: extern int v4l_close(v4l_device *);
4:
5: extern int v4l_get_capability(v4l_device *);
6:
7: extern int v4l_get_picture(v4l_device *);
8:
9: extern int v4l_get_mbuf(v4l_device *);
10:
11: extern int v4l_set_picture(v4l_device *, int, int, int, int, int,);
12:
13: extern int v4l_grab_picture(v4l_device *, unsigned int);
14:
15: extern int v4l_mmap_init(v4l_device *);
16:
17: extern int v4l_grab_init(v4l_device *, int, int);
18:
19: extern int v4l_grab_frame(v4l_device *, int);
20:
21: extern int v4l_grab_sync(v4l_device *);
前面已經說過使用v4l視頻編程的流程和對文件操作並沒有什麼本質的不同,大概的流程如下:
 1.打開視頻設備(通常是/dev/video0)
 2.獲得設備信息。
 3.根據需要更改設備的相關設置。
 4.獲得採集到的圖像數據(在這裏v4l提供了兩種方式,直接通過打開的設備讀取數據,使用mmap內存映射的方式獲取數據)。
 5.對採集到的數據進行操作(如顯示到屏幕,圖像處理,存儲成圖片文件)。
 6.關閉視頻設備。
知道了流程之後,我們就需要根據流程完成相應的函數。
那麼我們首先完成第1步打開視頻設備,需要完成int v4l_open(char *, v4l_device *);
具體的函數如下
1: #define DEFAULT_DEVICE “/dev/video0”
2:
3: int v4l_open(char *dev , v4l_device *vd)
4:
5: {
6:
7: if(!dev)dev= DEFAULT_DEVICE;
8:
9: if((vd-fd=open(dev,O_RDWR))<0){perror(“v4l_open:”);return -1;}
10:
11: if(v4l_get_capability(vd))return -1;
12:
13: if(v4l_get_picture(vd))return -1;//這兩個函數就是即將要完成的獲取設備信息的函數
14:
15: return 0
16:
17: }
同樣對於第6步也十分簡單,就是int v4l_close(v4l_device *);的作用。
函數如下:
1: int v4l_close(v4l_device *vd)
2:
3: {close(vd->fd);return 0;}
現在我們完成第2步中獲得設備信息的任務,下面先給出函數在對函數作出相應的說明。
1: int v4l_get_capability(v4l_device *vd)
2:
3: {
4:
5: if (ioctl(vd->fd, VIDIOCGCAP, &(vd->capability)) < 0) {
6:
7: perror("v4l_get_capability:");
8:
9: return -1;
10:
11: }
13: return 0;
15: }
19: int v4l_get_picture(v4l_device *vd)
20:
21: {
22:
23: if (ioctl(vd->fd, VIDIOCGPICT, &(vd->picture)) < 0) {
24:
25: perror("v4l_get_picture:");
26:
27: return -1;
28:
29: }
30:
31: return 0;
32:
33: }
對於以上兩個函數我們不熟悉的地方可有vd->capability和vd->picture兩個結構體,和這兩個函數中最主要的語句ioctl。對於ioctl的行爲它是由驅動程序提供和定義的,在這裏當然是由v4l所定義的,其中宏VIDIOCGCAP和VIDIOCGPICT的分別表示獲得視頻設備的capability和picture。對於其他的宏功能定義可以在你的Linux系統中的/usr/include/linux/videodev.h中找到,這個頭文件也包含了capability和picture的定義。例如:
struct video_capability
{
 char name[32];
 int type;
 int channels; 
 int audios; 
 int maxwidth; 
 int maxheight; 
 int minwidth; 
 int minheight; 
};capability結構它包括了視頻設備的名稱,頻道數,音頻設備數,支持的最大最小寬度和高度等信息。
struct video_picture
{
 __u16 brightness;
 __u16 hue;
 __u16 colour;
 __u16 contrast;
 __u16 whiteness; 
 __u16 depth;  
 __u16 palette; 
}picture結構包括了亮度,對比度,色深,調色板等等信息。頭文件裏還列出了palette相關的值,這裏並沒有給出。
瞭解了以上也就瞭解了這兩個簡單函數的作用,現在我們已經獲取到了相關視頻設備的capabilty和picture屬性。
這裏直接給出另外一個函數
1: int v4l_get_mbuf(v4l_device *vd)
2:
3: {
4:
5: if (ioctl(vd->fd, VIDIOCGMBUG ,&(vd->mbuf)) < 0) {
6:
7: perror("v4l_get_mbuf:");
8:
9: return -1;
10:
11: }
12:
13: return 0;
14:
15: }
對於結構體video_mbuf在v4l中的定義如下,video_mbuf結構體是爲了服務使用mmap內存映射來獲取圖像的方法而設置的結構體,通過這個結構體可以獲得攝像頭設備存儲圖像的內存大小。具體的定義如下,各變量的使用也會在下文詳細說明。
struct video_mbuf
{
 int size;  可映射的攝像頭內存大小
 int frames; 攝像頭可同時存儲的幀數
 int offsets[VIDEO_MAX_FRAME];每一幀圖像的偏移量
};
下面完成第3步按照需要更改設備的相應設置,事實上可以更改的設置很多,本文以更改picture屬性爲例說明更改屬性的一般方法。
 那麼我們就完成extern int v4l_set_picture(v4l_device *, int, int, int, int, int,);這個函數吧
1: int v4l_set_picture(v4l_device *vd,int br,int hue,int col,int cont,int white)
2:
3: {
4:
5: if(br) vd->picture.brightnesss=br;
6:
7: if(hue) vd->picture.hue=hue;
8:
9: if(col) vd->picture.color=col;
10:
11: if(cont) vd->picture.contrast=cont;
12:
13: if(white) vd->picture.whiteness=white;
14:
15: if(ioctl(vd->fd,VIDIOCSPICT,&(vd->picture))<0)
16:
17: {perror("v4l_set_picture: ");return -1;}
18:
19: return 0;
20:
21: }
上述函數就是更改picture相關屬性的例子,其核心還是v4l給我們提供的ioctl的相關調用,通過這個函數可以修改如亮度,對比度等相關的值。
第4步獲得採集到的圖像數據。
    這一步是使用v4l比較重要的一步,涉及到幾個函數的編寫。當然使用v4l就是爲了要獲得圖像,所以這一步很關鍵,但是當你獲得了圖像數據後,還需要根據你想要達到的目的和具體情況做進一步的處理,也就是第5步所做的事情,這些內容將在後面第三部分提到。這裏講如何獲得採集到的數據。如前所述獲得圖像的方式有兩種,分別是直接讀取設備和使用mmap內存映射,而通常大家使用的方法都是後者。
1).直接讀取設備
直接讀設備的方式就是使用read()函數,我們先前定義的
extern int v4l_grab_picture(v4l_device *, unsigned int);函數就是完成這個工作的,它的實現也很簡單。
1: int v4l_grab_picture(v4l_device *vd, unsighed int size)
2:
3: {
4:
5: if(read(vd-fd,&(vd->map),size)==0)return -1;
6:
7: return 0;
8:
9: }
該函數的使用也很簡單,就是給出圖像數據的大小,vd->map所指向的數據就是圖像數據。而圖像數據的大小你要根據設備的屬性自己計算獲得。
在這部分涉及到下面幾個函數,它們配合來完成最終圖像採集的功能。
 extern int v4l_mmap_init(v4l_device *);該函數把攝像頭圖像數據映射到進程內存中,也就是隻要使用vd->map指針就可以使用採集到的圖像數據(下文詳細說明)
extern int v4l_grab_init(v4l_device *, int, int);該函數完成圖像採集前的初始化工作。
extern int v4l_grab_frame(v4l_device *, int);該函數是真正完成圖像採集的一步,在本文使用了一個通常都會使用的一個小技巧,可以在處理一幀數據時同時採集下一幀的數據,因爲通常我們使用的攝像頭都可以至少存儲兩幀的數據。
extern int v4l_grab_sync(v4l_device *);該函數用來完成截取圖像的同步工作,在截取一幀圖像後調用,返回表明一幀截取結束。
下面分別介紹這幾個函數。
  mmap()系統調用使得進程之間通過映射同一個普通文件實現共享內存。普通文件被映射到進程地址空間後,進程可以像訪問普通內存一樣對文件進行訪問,不必在調用read(),write()等操作。兩個不同進程A、B共享內存的意思是,同一塊物理內存被映射到進程A、B各自的進程地址空間。進程A可以即時訪問進程B對共享內存中數據的更新,反之亦然。
採用共享內存通信的一個顯而易見的好處是減少I/O操作提高讀取效率,因爲使用mmap後進程可以直接讀取內存而不需要任何數據的拷貝。
mmap的函數原型如下
void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset )
addr:共享內存的起始地址,一般設爲0,表示由系統分配。
len:指定映射內存的大小。在我們這裏,該值爲攝像頭mbuf結構體的size值,即圖像數據的總大小。
port:指定共享內存的訪問權限 PROT_READ(可讀),PROT_WRITE(可寫)
flags:一般設置爲MAP_SHARED
fd:同享文件的文件描述符。
介紹完了mmap的使用,就可以介紹上文中定義的函數extern int v4l_mmap_init(v4l_device *)了。先給出這個函數的代碼,再做說明。
1: int v4l_mmap_init(v4l_device *vd)
2:
3: {
4:
5: if (v4l_get_mbuf(vd) < 0)
6:
7: return -1;
8:
9: if ((vd->map = mmap(0, vd->mbuf.size, PROT_READ|PROT_WRITE, MAP_SHARED, vd->fd, 0)) < 0) {
10:
11: perror("v4l_mmap_init:mmap");
12:
13: return -1;
14:
15: }
16:
17: return 0;
18:
19: }
這個函數首先使用v4l_get_mbuf(vd)獲得一個攝像頭重要的參數,就是需要映射內存的大小,即vd->mbuf.size,然後調用mmap,當我們在編程是調用v4l_mmap_init後,vd.map指針所指向的內存空間即爲我們將要採集的圖像數據。
這個函數首先使用v4l_get_mbuf(vd)獲得一個攝像頭重要的參數,就是需要映射內存的大小,即vd->mbuf.size,然後調用mmap,當我們在編程是調用v4l_mmap_init後,vd.map指針所指向的內存空間即爲我們將要採集的圖像數據。
獲得圖像前的初始化工作v4l_grab_init();該函數十分簡單直接粘上去,其中將。vd->frame_using[0]和vd->frame_using[1]都設爲FALSE,表示兩幀的截取都沒有開始。
1:
2:
3: int v4l_grab_init(v4l_device *vd, int width, int height)
4:
5: {
6:
7: vd->mmap.width = width;
8:
9: vd->mmap.height = height;
10:
11: vd->mmap.format = vd->picture.palette;
12:
13: vd->frame_current = 0;
14:
15: vd->frame_using[0] = FALSE;
16:
17: vd->frame_using[1] = FALSE;
18:
19:
20:
21: return v4l_grab_frame(vd, 0);
22:
23: }
24:
25:
真正獲得圖像的函數extern int v4l_grab_frame(v4l_device *, int);
1: int v4l_grab_frame(v4l_device *vd, int frame)
2:
3: {
4:
5: if (vd->frame_using[frame]) {
6:
7: fprintf(stderr, "v4l_grab_frame: frame %d is already used.n", frame);
8:
9: return -1;
10:
11: }
12:
13:
14:
15: vd->mmap.frame = frame;
16:
17: if (ioctl(vd->fd, VIDIOCMCAPTURE, &(vd->mmap)) < 0) {
18:
19: perror("v4l_grab_frame");
20:
21: return -1;
22:
23: }
24:
25: vd->frame_using[frame] = TRUE;
26:
27: vd->frame_current = frame;
28:
29: return 0;
30:
31: }
讀到這裏,應該覺得這個函數也是相當的簡單。最關鍵的一步即爲調用ioctl(vd->fd, VIDIOCMCAPTURE, &(vd->mmap)),調用後相應的圖像就已經獲取完畢。其他的代碼是爲了完成雙緩衝就是截取兩幀圖像用的,可以自己理解下。
 在截取圖像後還要進行同步操作,就是調用extern int v4l_grab_sync(v4l_device *);函數,該函數如下
1: int v4l_grab_sync(v4l_device *vd)
2:
3: {
4:
5: if (ioctl(vd->fd, VIDIOCSYNC, &(vd->frame_current)) < 0) {
6:
7: perror("v4l_grab_sync");
8:
9: }
10:
11: vd->frame_using[vd->frame_current] = FALSE;
12:
13: return 0;
14:
15: }
該函數返回0說明你想要獲取的圖像幀已經獲取完畢。
圖像存在了哪裏?
 最終我們使用v4l的目的是爲了獲取設備中的圖像,那麼圖像存在哪裏?從上面的文章可以知道,vd.map指針所指就是你要獲得的第一幀圖像。圖像的位置,存在vd.map+vd.mbuf.offsets[vd.frame_current]處。其中vd.frame_current=0,即爲第一幀的位置,vd.frame_current=1,爲第二幀的位置。
2 上述v4l庫使用的方法
給出了上述的一些代碼,這裏用一些簡單的代碼表明如何來使用它。上文中已經說過將相關結構體和函數的定義放到一個名爲v4l.h的文件中,相關函數的編寫放在一個名爲v4l.c的文件。現在我們要使用它們。
使用的方法很簡單,你創建一個.c文件,假設叫test.c吧,那麼test.c如下
1: //test.c
2:
3: include “v4l.h”
4:
5: ...
6:
7: v4l_device vd;
8:
9:
10:
11: void main()
12:
13: {
14:
15: v4l_open(DEFAULT_DEVICE,&vd);
16:
17: v4l_mmap_init(&vd);
18:
19: v4l_grab_init(&vd,320,240);
20:
21: v4l_grab_sync(&vd);//此時就已經獲得了一幀的圖像,存在vd.map中
22:
23: while(1)
24:
25: {
26:
27: vd.frame_current ^= 1;
28:
29: v4l_grab_frame(&vd, vd.frame_current);
30:
31: v4l_grab_sync(&vd);
32:
33: 圖像處理函數(vd.map+vd. vd.map+vd.mbuf.offsets[vd.frame_current]);
34:
35: //循環採集,調用你設計的圖像處理函數來處理圖像
36:
37: //其中vd.map+vd. vd.map+vd.mbuf.offsets[vd.frame_current]就是圖像所在位置。
38:
39: }
40:
41: }
42:
43:
3 有關獲取的圖像的一些問題
問:我獲取到的圖像究竟長什麼樣?
答:每個攝像頭獲取的圖像數據的格式可能都不盡相同,可以通過picture. palette獲得。獲得的圖像有黑白的,有yuv格式的,RGB格式的,也有直接爲jpeg格式的。你要根據實際情況,和你的需要對圖像進行處理。比如常見的,如果你要在嵌入式的LCD上顯示假設LCD是RGB24的,但是你獲得圖像是YUV格式的那麼你就將他轉換爲RGB24的。具體的轉換方法可以上網查找,也可參考前面提到過的effectTV中的相關代碼。
問:如何顯示圖像或將圖像保存?
答:假設你採集到的圖像爲RGB24格式的,我接觸過的可以使用SDL庫顯示(網絡上很流行的叫spcaview的軟件就是這樣的,不過它將圖像數據壓縮爲jpeg的格式後顯示,這個軟件也被經常的移植到一些嵌入式平臺使用,如ARM的)。當然也可以使用嵌入式linux的Framebuffer直接寫屏顯示。將圖像保存可以用libjpeg將其保存爲jpeg圖片直接存儲,相關的使用方法可以上網查找。也可以使用一些視頻編碼,將其編碼保存(我希望學習一下相關的技術因爲我對這方面一點不懂,如果你有一些資料可以推薦給我看,我十分想看一看)。
                                                                                            (完)

 

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