1. 程式人生 > >Linux下的V4L2的程式設計總結

Linux下的V4L2的程式設計總結

V4L2介紹的部落格:
部落格一:
http://blog.csdn.net/eastmoon502136/article/details/8190262
部落格二:
http://blog.chinaunix.net/uid-26833883-id-3249346.html

    下面我就直接貼了,在根據這兩篇部落格的基礎上,加以自己的一些註釋,希望和我一樣的初學者在看有關V4L2的API程式設計的時候或多或少有些思路吧!

一:V4L2的應用流程

       Video for Linux two(Video4Linux2)簡稱V4L2,是V4L的改進版。V4L2是linux作業系統下用於採集圖片、視訊和音訊資料的API介面,配合適當的視訊採集裝置和相應的驅動程式,可以實現圖片、視訊、音訊等的採集。在遠端會議、可視電話、視訊監控系統和

嵌入式多媒體終端中都有廣泛的應用。

    在Linux下,所有外設都被看成一種特殊的檔案,成為“裝置檔案”,可以象訪問普通檔案一樣對其進行讀寫。一般來說,採用V4L2驅動的攝像頭裝置檔案是/dev/video0。V4L2支援兩種方式來採集影象:記憶體對映方式(mmap)和直接讀取方式(read)。V4L2在include/linux/videodev.h檔案中定義了一些重要的資料結構,在採集影象的過程中,就是通過對這些資料的操作來獲得最終的影象資料。Linux系統V4L2的能力可在Linux核心編譯階段配置,預設情況下都有此開發介面。

       而攝像頭所用的主要是capature了,視訊的捕捉,具體linux的呼叫可以參考下圖。



應用程式通過V4L2進行視訊採集的原理

    V4L2支援記憶體對映方式(mmap)和直接讀取方式(read)來採集資料,前者一般用於連續視訊資料的採集,後者常用於靜態圖片資料的採集,本文重點討論記憶體對映方式的視訊採集。

應用程式通過V4L2介面採集視訊資料分為五個步驟:

首先,開啟視訊裝置檔案,進行視訊採集的引數初始化,通過V4L2介面設定視訊影象的採集視窗、採集的點陣大小和格式;

其次,申請若干視訊採集的幀緩衝區,並將這些幀緩衝區從核心空間對映到使用者空間,便於應用程式讀取/處理視訊資料;

第三,將申請到的幀緩衝區在視訊採集輸入佇列排隊,並啟動視訊採集;

第四,驅動開始視訊資料的採集,應用程式從視訊採集輸出佇列取出幀緩衝區,處理完後,將幀緩衝區重新放入視訊採集輸入佇列,迴圈往復採集連續的視訊資料;

第五,停止視訊採集。

具體的程式實現流程可以參考下面的流程圖:


    其實其他的都比較簡單,就是通過ioctl這個介面去設定一些引數。最主要的就是buf管理。他有一個或者多個輸入佇列和輸出佇列。

啟動視訊採集後,驅動程式開始採集一幀資料,把採集的資料放入視訊採集輸入佇列的第一個幀緩衝區,一幀資料採集完成,也就是第一個幀緩衝區存滿一幀資料後,驅動程式將該幀緩衝區移至視訊採集輸出佇列,等待應用程式從輸出佇列取出。驅動程式接下來採集下一幀資料,放入第二個幀緩衝區,同樣幀緩衝區存滿下一幀資料後,被放入視訊採集輸出佇列。

    應用程式從視訊採集輸出佇列中取出含有視訊資料的幀緩衝區,處理幀緩衝區中的視訊資料,如儲存或壓縮。

最後,應用程式將處理完資料的幀緩衝區重新放入視訊採集輸入佇列,這樣可以迴圈採集,如圖所示。

 

每一個幀緩衝區都有一個對應的狀態標誌變數,其中每一個位元代表一個狀態

  V4L2_BUF_FLAG_UNMAPPED 0B0000

  V4L2_BUF_FLAG_MAPPED 0B0001

  V4L2_BUF_FLAG_ENQUEUED 0B0010

  V4L2_BUF_FLAG_DONE 0B0100

緩衝區的狀態轉化如圖所示。

 

1. 定義

V4L2(Video For Linux Two) 是核心提供給應用程式訪問音、視訊驅動的統一介面。

2. 工作流程:

開啟裝置-> 檢查和設定裝置屬性-> 設定幀格式-> 設定一種輸入輸出方法(緩衝區管理)-> 迴圈獲取資料-> 關閉裝置。

3. 裝置的開啟和關閉:

#include <fcntl.h>
int open(const char *device_name, int flags);
#include <unistd.h>
int close(int fd);

例如你插上你的usb介面的攝像頭,核心將會列印相關的資訊,如下:

int fd=open(“/dev/video0”,O_RDWR); // 開啟裝置
close(fd); // 關閉裝置

注意:V4L2 的相關定義包含在標頭檔案<linux/videodev2.h> 中.

4. 查詢裝置屬性: VIDIOC_QUERYCAP

相關函式:

int ioctl(int fd, int request, struct v4l2_capability *argp);

相關結構體:

struct v4l2_capability
{
u8 driver[16]; // 驅動名字
u8 card[32]; // 裝置名字
u8 bus_info[32]; // 裝置在系統中的位置
u32 version; // 驅動版本號
u32 capabilities; // 裝置支援的操作
u32 reserved[4]; // 保留欄位
};

capabilities 常用值:

V4L2_CAP_VIDEO_CAPTURE // 是否支援影象獲取

例:顯示裝置資訊

struct v4l2_capability cap;
ioctl(fd,VIDIOC_QUERYCAP,&cap);
printf(“Driver Name:%s\nCard Name:%s\nBus info:%s\nDriver Version:%u.%u.%u\n”,cap.driver,cap.card,cap.bus_info,(cap.version>>16)&0XFF, (cap.version>>8)&0XFF,cap.version&0XFF);

5. 設定視訊的制式和幀格式

制式包括PAL,NTSC,幀的格式個包括寬度和高度等。

相關函式:

int ioctl(int fd, int request, struct v4l2_fmtdesc *argp);
int ioctl(int fd, int request, struct v4l2_format *argp);

相關結構體:

v4l2_cropcap 結構體用來設定攝像頭的捕捉能力,在捕捉上視訊時應先先設定

v4l2_cropcap 的 type 域,再通過 VIDIO_CROPCAP 操作命令獲取裝置捕捉能力的引數,保存於 v4l2_cropcap 結構體中,包括 bounds(最大捕捉方框的左上角座標和寬高),defrect  (筆記中沒有使用)。

(預設捕捉方框的左上角座標和寬高)等。

v4l2_format 結構體用來設定攝像頭的視訊制式、幀格式等,在設定這個引數時應先填好 v4l2_format 的各個域,如 type(傳輸流型別),fmt.pix.width(寬),

fmt.pix.heigth(高),fmt.pix.field(取樣區域,如隔行取樣),fmt.pix.pixelformat(採

樣型別,如 YUV4:2:2),然後通過 VIDIO_S_FMT 操作命令設定視訊捕捉格式。如下圖所示:


5.1 查詢並顯示所有支援的格式:VIDIOC_ENUM_FMT

相關函式:

int ioctl(int fd, int request, struct v4l2_fmtdesc *argp);

相關結構體:

struct v4l2_fmtdesc
{
u32 index; // 要查詢的格式序號,應用程式設定
enum v4l2_buf_type type; // 幀型別,應用程式設定
u32 flags; // 是否為壓縮格式
u8 description[32]; // 格式名稱
u32 pixelformat; // 格式
u32 reserved[4]; // 保留
};

例:顯示所有支援的格式

struct v4l2_fmtdesc fmtdesc; fmtdesc.index=0; fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; printf("Support format:\n");
while(ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) != -1)
{
printf("\t%d.%s\n",fmtdesc.index+1,fmtdesc.description);
fmtdesc.index++;
}

5.2 檢視或設定當前格式: VIDIOC_G_FMT, VIDIOC_S_FMT

檢查是否支援某種格式:VIDIOC_TRY_FMT

相關函式:

int ioctl(int fd, int request, struct v4l2_format *argp);

相關結構體:

struct v4l2_format
{
enum v4l2_buf_type type; // 幀型別,應用程式設定
union fmt
{
struct v4l2_pix_format pix; // 視訊裝置使用
struct v4l2_window win;
struct v4l2_vbi_format vbi;
struct v4l2_sliced_vbi_format sliced;
u8 raw_data[200];
};
};
struct v4l2_pix_format
{
u32 width; // 幀寬,單位畫素
u32 height; // 幀高,單位畫素
u32 pixelformat; // 幀格式
enum v4l2_field field;
u32 bytesperline;
u32 sizeimage;
enum v4l2_colorspace colorspace;
u32 priv;
};

例:顯示當前幀的相關資訊

struct v4l2_format fmt; fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; ioctl(fd, VIDIOC_G_FMT, &fmt);
printf(“Current data format information:\n\twidth:%d\n\theight:%d\n”,
fmt.fmt.pix.width,fmt.fmt.pix.height);
struct v4l2_fmtdesc fmtdesc; fmtdesc.index=0; fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1)
{
if(fmtdesc.pixelformat & fmt.fmt.pix.pixelformat)
{
printf(“\tformat:%s\n”,fmtdesc.description);
break;
}
fmtdesc.index++;
}

例:檢查是否支援某種幀格式

struct v4l2_format fmt; fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.pixelformat=V4L2_PIX_FMT_RGB32; if(ioctl(fd,VIDIOC_TRY_FMT,&fmt)==-1) if(errno==EINVAL)
printf(“not support format RGB32!\n”);

6. 影象的縮放 VIDIOC_CROPCAP(沒用)

相關函式:

int ioctl(int fd, int request, struct v4l2_cropcap *argp);
int ioctl(int fd, int request, struct v4l2_crop *argp);
int ioctl(int fd, int request, const struct v4l2_crop *argp);

相關結構體:

Cropping 和 scaling 主要指的是影象的取景範圍及圖片的比例縮放的支援。Crop 就 是把得到的資料作一定的裁剪和伸縮,裁剪可以只取樣我們可以得到的影象大小的一部分,剪裁的主要引數是位置、長度、寬度。而 scale 的設定是通過 VIDIOC_G_FMT 和 VIDIOC_S_FMT 來獲得和設定當前的 image 的長度,寬度來實現的。看下圖


我們可以假設 bounds 是 sensor 最大能捕捉到的影象範圍,而 defrect 是裝置預設 的最大取樣範圍,這個可以通過 VIDIOC_CROPCAP 的 ioctl 來獲得裝置的 crap 相關的屬 性 v4l2_cropcap,其中的 bounds 就是這個bounds,其實就是上限。每個裝置都有個默認的取樣範圍,就是 defrect,就是 default rect 的意思,它比 bounds 要小一些。這個範圍也是通過 VIDIOC_CROPCAP  ioctl 來獲得的 v4l2_cropcap 結構中的 defrect 來表示的,我們可以通過 VIDIOC_G_CROP  VIDIOC_S_CROP 來獲取和設定裝置當前的 crop 設定。

6.1 設定裝置捕捉能力的引數

相關函式:

int ioctl(int fd, int request, struct v4l2_cropcap *argp);

相關結構體:

struct v4l2_cropcap
{
enum v4l2_buf_type type; // 資料流的型別,應用程式設定
struct v4l2_rect bounds; // 這是 camera 的鏡頭能捕捉到的視窗大小的侷限
struct v4l2_rect defrect; // 定義預設視窗大小,包括起點位置及長,寬的大小,大小以畫素為單位
struct v4l2_fract pixelaspect; // 定義了圖片的寬高比
};

6.2 設定視窗取景引數 VIDIOC_G_CROP 和 VIDIOC_S_CROP

相關函式:

int ioctl(int fd, int request, struct v4l2_crop *argp);
int ioctl(int fd, int request, const struct v4l2_crop *argp);

相關結構體:

struct v4l2_crop
{
enum v4l2_buf_type type;// 應用程式設定
struct v4l2_rect c;
}

7.video Inputs and Outputs(沒用)

VIDIOC_G_INPUT 和 VIDIOC_S_INPUT 用來查詢和選則當前的 input,一個 video 裝置 節點可能對應多個視訊源,比如 saf7113 可以最多支援四路 cvbs 輸入,如果上層想在四個cvbs視訊輸入間切換,那麼就要呼叫ioctl(fd, VIDIOC_S_INPUT, &input) 來切換。

VIDIOC_G_INPUT and VIDIOC_G_OUTPUT 返回當前的 video input和output的index.

相關函式:

int ioctl(int fd, int request, struct v4l2_input *argp);

相關結構體:

struct v4l2_input {
__u32 index; /* Which input */
__u8 name[32]; /* Label */
__u32 type; /* Type of input */
__u32 audioset; /* Associated audios (bitfield) */
__u32 tuner; /* Associated tuner */
v4l2_std_id std;
__u32 status;
__u32 reserved[4];
};

我們可以通過VIDIOC_ENUMINPUT and VIDIOC_ENUMOUTPUT 分別列舉一個input或者 output的資訊,我們使用一個v4l2_input結構體來存放查詢結果,這個結構體中有一個 index域用來指定你索要查詢的是第幾個input/ouput,如果你所查詢的這個input是當前正 在使用的,那麼在v4l2_input還會包含一些當前的狀態資訊,如果所 查詢的input/output 不存在,那麼回返回EINVAL錯誤,所以,我們通過迴圈查詢,直到返回錯誤來遍歷所有的 input/output. VIDIOC_G_INPUT and VIDIOC_G_OUTPUT 返回當前的video input和output 的index.

例: 列舉當前輸入視訊所支援的視訊格式

struct v4l2_input input;
struct v4l2_standard standard;
memset (&input, 0, sizeof (input));
//首先獲得當前輸入的 index,注意只是 index,要獲得具體的資訊,就的呼叫列舉操作
if (-1 == ioctl (fd, VIDIOC_G_INPUT, &input.index)) {
perror (”VIDIOC_G_INPUT”);
exit (EXIT_FAILURE);
}
//呼叫列舉操作,獲得 input.index 對應的輸入的具體資訊
if (-1 == ioctl (fd, VIDIOC_ENUMINPUT, &input)) {
perror (”VIDIOC_ENUM_INPUT”);
exit (EXIT_FAILURE);
}
printf (”Current input %s supports:\n”, input.name); memset (&standard, 0, sizeof (standard)); standard.index = 0;
//列舉所有的所支援的 standard,如果 standard.id 與當前 input 的 input.std 有共同的
bit flag,意味著當前的輸入支援這個 standard,這樣將所有驅動所支援的 standard 列舉一個
遍,就可以找到該輸入所支援的所有 standard 了。
while (0 == ioctl (fd, VIDIOC_ENUMSTD, &standard)) {
if (standard.id & input.std)
printf (%s\n”, standard.name);
standard.index++;
}
/* EINVAL indicates the end of the enumeration, which cannot be empty unless this device falls under the USB exception. */
if (errno != EINVAL || standard.index == 0) {
perror (”VIDIOC_ENUMSTD”);
exit (EXIT_FAILURE);
}

8. Video standards(沒用)

相關函式:

v4l2_std_id std_id; //這個就是個64bit得數
int ioctl(int fd, int request, struct v4l2_standard *argp);

相關結構體:

typedef u64 v4l2_std_id;
struct v4l2_standard {
u32 index;
v4l2_std_id id;
u8 name[24];
struct v4l2_fract frameperiod; /* Frames, not fields */
u32 framelines;
u32 reserved[4];
};

當然世界上現在有多個視訊標準,如NTSC和PAL,他們又細分為好多種,那麼我們的設 備輸入/輸出究竟支援什麼樣的標準呢?我們的當前在使用的輸入和輸出正在使用的是哪 個標準呢?我們怎麼設定我們的某個輸入輸出使用的標準呢?這都是有方法的。

查詢我們的輸入支援什麼標準,首先就得找到當前的這個輸入的index,然後查出它的 屬性,在其屬性裡面可以得到該輸入所支援的標準,將它所支援的各個標準與所有的標準的資訊進行比較,就可以獲知所支援的各個標準的屬性。一個輸入所支援的標準應該是一 個集合,而這個集合是用bit與的方式用一個64位數字表示。因此我們所查到的是一個數字。

Example: Information about the current video standard v4l2_std_id std_id; //這個就是個64bit得數

struct v4l2_standard standard;
// VIDIOC_G_STD就是獲得當前輸入使用的standard,不過這裡只是得到了該標準的id
// 即flag,還沒有得到其具體的屬性資訊,具體的屬性資訊要通過列舉操作來得到。
if (-1 == ioctl (fd, VIDIOC_G_STD, &std_id)) { //獲得了當前輸入使用的standard
// Note when VIDIOC_ENUMSTD always returns EINVAL this is no video device
// or it falls under the USB exception, and VIDIOC_G_STD returning EINVAL
// is no error.
perror (”VIDIOC_G_STD”);
exit (EXIT_FAILURE);
}
memset (&standard, 0, sizeof (standard));
standard.index = 0; //從第一個開始列舉
// VIDIOC_ENUMSTD用來列舉所支援的所有的video標準的資訊,不過要先給standard
// 結構的index域制定一個數值,所列舉的標 準的資訊屬性包含在standard裡面,
// 如果我們所列舉的標準和std_id有共同的bit,那麼就意味著這個標準就是當前輸
// 入所使用的標準,這樣我們就得到了當前輸入使用的標準的屬性資訊
while (0 == ioctl (fd, VIDIOC_ENUMSTD, &standard)) {
if (standard.id & std_id) {
printf (”Current video standard: %s\n”, standard.name);
exit (EXIT_SUCCESS);
}
standard.index++;
}
/* EINVAL indicates the end of the enumeration, which cannot be empty unless this device falls under the USB exception. */
if (errno == EINVAL || standard.index == 0) {
perror (”VIDIOC_ENUMSTD”);
exit (EXIT_FAILURE);
}

9. 申請和管理緩衝區(使用 重點)

應用程式和裝置有三種交換資料的方法,直接 read/write、記憶體對映(memory mapping)

和使用者指標。這裡只討論記憶體對映(memory mapping)。

9.1 向裝置申請緩衝區 VIDIOC_REQBUFS

相關函式:

int ioctl(int fd, int request, struct v4l2_requestbuffers *argp);

相關結構體:

struct v4l2_requestbuffers
{
u32 count; // 緩衝區內緩衝幀的數目
enum v4l2_buf_type type; // 緩衝幀資料格式
enum v4l2_memory memory; // 區別是記憶體對映還是使用者指標方式
u32 reserved[2];
};

注:enum v4l2_memoy

{

V4L2_MEMORY_MMAP, V4L2_MEMORY_USERPTR

};

//count,type,memory 都要應用程式設定

例:申請一個擁有四個緩衝幀的緩衝區

struct v4l2_requestbuffers req; 
req.count=4; req.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; 
req.memory=V4L2_MEMORY_MMAP; 
ioctl(fd,VIDIOC_REQBUFS,&req);

9.2 獲取緩衝幀的地址,長度:VIDIOC_QUERYBUF

相關函式:

int ioctl(int fd, int request, struct v4l2_buffer *argp);

相關結構體:

struct v4l2_buffer
{
u32 index; //buffer 序號
enum v4l2_buf_type type; //buffer 型別
u32 byteused; //buffer 中已使用的位元組數
u32 flags; // 區分是MMAP 還是USERPTR
enum v4l2_field field;
struct timeval timestamp; // 獲取第一個位元組時的系統時間
struct v4l2_timecode timecode;
u32 sequence; // 佇列中的序號
enum v4l2_memory memory; //IO 方式,被應用程式設定
union m
{
u32 offset; // 緩衝幀地址,只對MMAP 有效
unsigned long userptr;
};
u32 length; // 緩衝幀長度
u32 input;
u32 reserved;
};

9.3 記憶體對映MMAP 及定義一個結構體來對映每個緩衝幀。 相關結構體:

struct buffer
{
void* start;
unsigned int length;
}*buffers;

相關函式:

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)

//addr 對映起始地址,一般為NULL ,讓核心自動選擇

//length 被對映記憶體塊的長度

//prot 標誌對映後能否被讀寫,其值為PROT_EXEC,PROT_READ,PROT_WRITE, PROT_NONE

//flags 確定此記憶體對映能否被其他程序共享,MAP_SHARED,MAP_PRIVATE

//fd,offset, 確定被對映的記憶體地址返回成功對映後的地址,不成功返回MAP_FAILED ((void*)-1)

相關函式:

int munmap(void *addr, size_t length);// 斷開對映

//addr 為對映後的地址,length 為對映後的記憶體長度

例:將四個已申請到的緩衝幀對映到應用程式,用buffers 指標記錄。

buffers = (buffer*)calloc (req.count, sizeof (*buffers));
if (!buffers) {
// 對映
fprintf (stderr, "Out of memory/n");
exit (EXIT_FAILURE);
}
for (unsigned int n_buffers = 0; n_buffers < req.count; ++n_buffers)
{
struct v4l2_buffer buf;
memset(&buf,0,sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = n_buffers;
// 查詢序號為n_buffers 的緩衝區,得到其起始實體地址和大小
if (-1 == ioctl (fd, VIDIOC_QUERYBUF, &buf))
exit(-1);
buffers[n_buffers].length = buf.length;
// 對映記憶體
buffers[n_buffers].start =mmap (NULL,buf.length,PROT_READ | PROT_WRITE ,MAP_SHARED,fd, buf.m.offset);
if (MAP_FAILED == buffers[n_buffers].start)
exit(-1);
}

10. 緩衝區處理好之後,就可以開始獲取資料了

10.1 啟動 或 停止資料流 VIDIOC_STREAMON, VIDIOC_STREAMOFF

int ioctl(int fd, int request, const int *argp);

//argp 為流型別指標,如V4L2_BUF_TYPE_VIDEO_CAPTURE.

10.2 在開始之前,還應當把緩衝幀放入緩衝佇列:

VIDIOC_QBUF// 把幀放入佇列

VIDIOC_DQBUF// 從佇列中取出幀

int ioctl(int fd, int request, struct v4l2_buffer *argp);

例:把四個緩衝幀放入佇列,並啟動資料流

unsigned int i;
enum v4l2_buf_type type;
for (i = 0; i < 4; ++i) // 將緩衝幀放入佇列
{
struct v4l2_buffer buf;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
ioctl (fd, VIDIOC_QBUF, &buf);
}
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl (fd, VIDIOC_STREAMON, &type);
// 這有個問題,這些buf 看起來和前面申請的buf 沒什麼關係,為什麼呢?

例:獲取一幀並處理 ? 好像這裡有問題

//這裡的buf 確實與上面的沒有關係, 但是下面的捕捉圖片資料可以成功,

            //說明緩衝的資料沒有進入佇列也可以讀出來。 但是我覺得上面操作的是同一個檔案描述

            //符,說明其實還是將緩衝區的資料放進的佇列中

struct v4l2_buffer buf; CLEAR (buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
ioctl (fd, VIDIOC_DQBUF, &buf); // 從緩衝區取出一個緩衝幀
process_image (buffers[buf.index.]start); //
ioctl (fdVIDIOC_QBUF&buf); //

★emouse 思·睿部落格文章★ 原創文章轉載請註明:http://emouse.cnblogs.com

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中的內容):

#ifndef _V4L_H_

#define _V4L_H_

#include <sys/types.h>

#include <linux/videodev.h> //使用v4l必須包含的標頭檔案

這個標頭檔案可以在/usr/include/linux下找到,裡面包含了對v4l各種結構的定義,以及各種ioctl的使用方法,所以在下文中有關v4l的相關結構體並不做詳細的介紹,可以參看此檔案就會得到你想要的內容。

下面是定義的結構體,和相關函式,突然給出這麼多的程式碼很唐突,不過隨著一點點解釋條理就會很清晰了。

struct _v4l_struct

{

int fd;//儲存開啟視訊檔案的裝置描述符

struct video_capability capability;//該結構及下面的結構為v4l所定義可在上述標頭檔案中找到

struct video_picture picture;

struct video_mmap mmap;

struct video_mbuf mbuf;

unsigned char *map;//用於指向影象資料的指標

              int frame_current;

int frame_using[VIDEO_MAXFRAME];//這兩個變數用於雙緩衝在後面介紹。

};

typedef struct _v4l_struct v4l_device;

//上面的定義的結構體,有的文中章有定義channel的變數,但對於攝像頭來說設定這個變數意義不大通常只有一個channel,本文不是為了寫出一個大而全且成熟的函式庫,只是為了介紹如何使用v4l,再加上本人水平也有限,能夠給讀者一個路線我就很知足了,所以並沒有設定這個變數同時與channel相關的函式也沒有給出。

extern int v4l_open(char *, v4l_device *);

extern int v4l_close(v4l_device *);

extern int v4l_get_capability(v4l_device *);

extern int v4l_get_picture(v4l_device *);

extern int v4l_get_mbuf(v4l_device *);

extern int v4l_set_picture(v4l_device *, int, int, int, int, int,);

extern int v4l_grab_picture(v4l_device *, unsigned int);

extern int v4l_mmap_init(v4l_device *);

extern int v4l_grab_init(v4l_device *, int, int);

extern int v4l_grab_frame(v4l_device *, int);

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 *);

具體的函式如下

#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(“v4l_open:”);return -1;}

if(v4l_get_capability(vd))return -1;

if(v4l_get_picture(vd))return -1;//這兩個函式就是即將要完成的獲取裝置資訊的函式

return 0

}

同樣對於第6步也十分簡單,就是int v4l_close(v4l_device *);的作用。

函式如下:

int v4l_close(v4l_device *vd)

{close(vd->fd);return 0;}

現在我們完成第2步中獲得裝置資訊的任務,下面先給出函式在對函式作出相應的說明。

int v4l_get_capability(v4l_device *vd)

{  

   if (ioctl(vd->fd, VIDIOCGCAP, &(vd->capability)) < 0) {  

perror("v4l_get_capability:");  

return -1;  

}  

return 0;  

}

int v4l_get_picture(v4l_device *vd)  

{  

   if (ioctl(vd->fd, VIDIOCGPICT, &(vd->picture)) < 0) {  

perror("v4l_get_picture:");

return -1;  

}  

return 0;  

}

對於以上兩個函式我們不熟悉的地方可有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;

       __u16whiteness;      

__u16     depth;           

__u16   palette;   

}picture結構包括了亮度,對比度,色深,調色盤等等資訊。標頭檔案裡還列出了palette相關的值,這裡並沒有給出。

瞭解了以上也就瞭解了這兩個簡單函式的作用,現在我們已經獲取到了相關視訊裝置的capabilty和picture屬性。

這裡直接給出另外一個函式

int v4l_get_mbuf(v4l_device *vd)  

{  

if (ioctl(vd->fd, VIDIOCGMBUF ,&(vd->mbuf)) < 0) {  

perror("v4l_get_mbuf:");

return -1;  

}  

return 0;  

}

int v4l_get_mbuf(v4l_device *vd)  

{  

if (ioctl(vd->fd, VIDIOCGMBUF ,&(vd->mbuf)) < 0) {  

      perror("v4l_get_mbuf:");  

return -1;  

}  

return 0;  

}

對於結構體video_mbuf在v4l中的定義如下,video_mbuf結構體是為了服務使用mmap記憶體對映來獲取影象的方法而設定的結構體,通過這個結構體可以獲得攝像頭裝置儲存影象的記憶體大小。具體的定義如下,各變數的使用也會在下文詳細說明。

struct video_mbuf

{

int   size;        可對映的攝像頭記憶體大小

       intframes;    攝像頭可同時儲存的幀數

int   offsets[VIDEO_MAX_FRAME];每一幀影象的偏移量

};

下面完成第3步按照需要更改裝置的相應設定,事實上可以更改的設定很多,本文以更改picture屬性為例說明更改屬性的一般方法。

那麼我們就完成extern int v4l_set_picture(v4l_device *, int, int, int, int, int,);這個函式吧

int v4l_set_picture(v4l_device *vd,int br,int hue,int col,int cont,int white)

{

if(br) vd->picture.brightnesss=br;

if(hue) vd->picture.hue=hue;

if(col) vd->picture.color=col;

if(cont) vd->picture.contrast=cont;

if(white) vd->picture.whiteness=white;

if(ioctl(vd->fd,VIDIOCSPICT,&(vd->picture))<0)

{perror("v4l_set_picture: ");return -1;}  

return 0;

}

int v4l_get_mbuf(v4l_device *vd)  

{  

if (ioctl(vd->fd, VIDIOCGMBUF ,&(vd->mbuf)) < 0) {  

perror("v4l_get_mbuf:");

return -1;  

}  

return 0;  

}

對於結構體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,);這個函式吧

int v4l_set_picture(v4l_device *vd,int br,int hue,int col,int cont,int white)

{

if(br) vd->picture.brightnesss=br;

if(hue) vd->picture.hue=hue;

if(col) vd->picture.color=col;

if(cont) vd->picture.contrast=cont;

if(white) vd->picture.whiteness=white;

if(ioctl(vd->fd,VIDIOCSPICT,&(vd->picture))<0)

{perror("v4l_set_picture: ");return -1;}  

return 0;

}

上述函式就是更改picture相關屬性的例子,其核心還是v4l給我們提供的ioctl的相關呼叫,通過這個函式可以修改如亮度,對比度等相關的值。