天天看點

嵌入式linux環境下的視訊采集(V4L2)

Video for Linux two(Video4Linux2)簡稱V4L2,是V4L的改進版。V4L2是linux作業系統下用于采集圖檔、視訊和音頻資料的API接口,配合适當的視訊采集裝置和相應的驅動程式,可以實作圖檔、視訊、音頻等的采集。在遠端會議、可視電話、視訊監控系統和嵌入式多媒體終端中都有廣泛的應用。

  一、Video for Linux two

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

  V4L2規範中不僅定義了通用API元素(Common API Elements),圖像的格式(Image Formats),輸入/輸出方法(Input/Output),還定義了Linux核心驅動處理視訊資訊的一系列接口(Interfaces),這些接口主要有:

  視訊采集接口——Video Capture Interface;

  視訊輸出接口—— Video Output Interface;

  視訊覆寫/預覽接口——Video Overlay Interface;

  視訊輸出覆寫接口——Video Output Overlay Interface;

  編解碼接口——Codec Interface。

  二、應用程式通過V4L2進行視訊采集的原理

  V4L2支援記憶體映射方式(mmap)和直接讀取方式(read)來采集資料,前者一般用于連續視訊資料的采集,後者常用于靜态圖檔資料的采集,本文重點讨論記憶體映射方式的視訊采集。

  應用程式通過V4L2接口采集視訊資料分為五個步驟:

  首先,打開視訊裝置檔案,進行視訊采集的參數初始化,通過V4L2接口設定視訊圖像的采集視窗、采集的點陣大小和格式;

  其次,申請若幹視訊采集的幀緩沖區,并将這些幀緩沖區從核心空間映射到使用者空間,便于應用程式讀取/處理視訊資料;

  第三,将申請到的幀緩沖區在視訊采集輸入隊列排隊,并啟動視訊采集;

  第四,驅動開始視訊資料的采集,應用程式從視訊采集輸出隊列取出幀緩沖區,處理完後,将幀緩沖區重新放入視訊采集輸入隊列,循環往複采集連續的視訊資料;

  第五,停止視訊采集。

  1.視訊采集的參數初始化

  在Linux下,攝像頭硬體已經被映射為裝置檔案“/dev/video0”,用open函數打開這個裝置檔案,獲得其檔案描述符fd_v4l2,然後對這個檔案描述符進行參數初始化。

  (1) 設定視訊的采集視窗參數

  設定采集視窗就是在攝像頭裝置的取景範圍之内設定一個視訊采集區域。主要是對結構體v4l2_crop指派,v4l2_crop由一個v4l2_buffer_type枚舉類型的type和v4l2_rect類型的結構體c構成,來描述視訊采集視窗的類型和大小。type設定為視訊采集類型V4L2_BUF_TYPE_VIDEO_CAPTURE。c是表示采集視窗的大小的結構體,它的成員Left和Top分别表示視訊采集區域的起始橫坐标和縱坐标,width和height分别表示采集圖像的寬度和高度。指派後,用ioctl函數通過這個結構體對fd_v4l2進行設定。

  struct v4l2_crop { enum v4l2_buf_type type;

  struct v4l2_rect c;

  };

  (2)設定視訊點陣格式和點陣大小

  主要是對結構體v4l2_format進行指派,它由type和聯合體fmt構成,來描述視訊裝置目前行為和資料的格式。

  把type指派為視訊采集類型V4L2_BUF_TYPE_VIDEO_CAPTURE,表示定義了一個視訊采集流類型的buffer。fmt中,pix為表示圖形格式的v4l2_pix_format型結構體。需要設定pix裡的幾個變量,pixelformat表示采集格式,設定為V4L2_PIX_FMT_YUV420;width、height表示圖像的寬度、高度,以位元組為機關;sizeimage表示圖像所占的存儲空間大小,以位元組為機關;bytesperline表示每一行的位元組數。指派後,用ioctl函數通過這個結構體對fd_ v4l2進行設定。

  struct v4l2_format

  { enum v4l2_buf_type type;

  union

  { struct v4l2_pix_format pix; // V4L2_BUF_TYPE_VIDEO_CAPTURE

  struct v4l2_window win; // V4L2_BUF_TYPE_VIDEO_OVERLAY

  __u8 raw_data[200]; // user-defined

  } fmt;

  };

  (3)設定視訊采集的幀率

  結構體v4l2_streamparm來描述視訊流的屬性,它由type和聯合體parm構成。type同上,由于選的是V4L2_BUF_TYPE_VIDEO_CAPTURE,是以僅需設定parm中的v412_capture型結構體capture即可。在其中, v4l2_fract型結構體timeperframe表示平均每一幀所占的時間,由其元素numerator和denominator共同決定,該時長為numerator/denominator;而capturemode則表示采集模式,采集高品質圖檔值為1,一般設為0。指派之後,用ioctl函數通過這個結構體對fd_ v4l2進行設定。

  struct v4l2_streamparm

  { enum v4l2_buf_type type;

  union

  { struct v4l2_captureparm capture;

  struct v4l2_outputparm output;

  __u8 raw_data[200];

  } parm;

  };

  2.申請并設定視訊采集的幀緩沖區

  前期初始化完成後,隻是解決了一幀視訊資料的格式和大小問題,而連續視訊幀資料的采集需要用幀緩沖區隊列的方式來解決,即要通過驅動程式在記憶體中申請幾個幀緩沖區來存放視訊資料。

  應用程式通過API接口提供的方法(VIDIOC_REQBUFS)申請若幹個視訊資料的幀緩沖區,申請幀緩沖區數量一般不低于3個,每個幀緩沖區存放一幀視訊資料,這些幀緩沖區在核心空間。

  應用程式通過API接口提供的查詢方法(VIDIOC_QUERYBUF)查詢到幀緩沖區在核心空間的長度和偏移量位址。

  應用程式再通過記憶體映射方法(mmap),将申請到的核心空間幀緩沖區的位址映射到使用者空間位址,這樣就可以直接處理幀緩沖區的資料。

  (1)将幀緩沖區在視訊輸入隊列排隊,并啟動視訊采集

  在驅動程式處理視訊的過程中,定義了兩個隊列:視訊采集輸入隊列(incoming queues)和視訊采集輸出隊列(outgoing queues),前者是等待驅動存放視訊資料的隊列,後者是驅動程式已經放入了視訊資料的隊列。如圖2所示。

  應用程式需要将上述幀緩沖區在視訊采集輸入隊列排隊(VIDIOC_QBUF),然後可啟動視訊采集。

  (2)循環往複,采集連續的視訊資料

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

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

  最後,應用程式将處理完資料的幀緩沖區重新放入視訊采集輸入隊列,這樣可以循環采集,如圖1所示。

  

嵌入式linux環境下的視訊采集(V4L2)

  圖1 視訊采集輸入和輸出隊列示意圖

  (3)最終停止采集,釋放記憶體幀緩沖區

  3.用V4L2采集視訊的程式流程和相關API

  V4L2采集視訊操作基本按照打開視訊裝置、設定視訊格式、啟動視訊采集,循環處理視訊資料、停止視訊采集、關閉視訊裝置,具體操作通過ioctl等函數來實作。一般操作流程如下:

  (1)打開視訊裝置檔案。int fd=open("/dev/video0",O_RDWR);

  (2)查詢視訊裝置的能力,比如是否具有視訊輸入,或者音頻輸入輸出等。ioctl(fd_v4l, VIDIOC_QUERYCAP, &cap)

  (3)設定視訊采集的參數

  設定視訊的制式,制式包括PAL/NTSC,使用ioctl(fd_v4l, VIDIOC_S_STD, &std_id)

  設定視訊圖像的采集視窗的大小,使用ioctl(fd_v4l, VIDIOC_S_CROP, &crop)

  設定視訊幀格式,包括幀的點陣格式,寬度和高度等,使用ioctl(fd_v4l, VIDIOC_S_FMT, &fmt)

  設定視訊的幀率,使用ioctl(fd_v4l, VIDIOC_S_PARM, &parm)

  設定視訊的旋轉方式,使用ioctl(fd_v4l, VIDIOC_S_CTRL, &ctrl)

  (4)向驅動申請視訊流資料的幀緩沖區

  請求/申請若幹個幀緩沖區,一般為不少于3個,使用ioctl(fd_v4l, VIDIOC_REQBUFS, &req)

  查詢幀緩沖區在核心空間中的長度和偏移量 ioctl(fd_v4l, VIDIOC_QUERYBUF, &buf)

  (5)應用程式通過記憶體映射,将幀緩沖區的位址映射到使用者空間,這樣就可以直接操作采集到的幀了,而不必去複制。

  buffers[i].start = mmap (NULL, buffers[i].length, PROT_READ | PROT_WRITE, MAP_SHARED, fd_v4l, buffers[i].offset);

  (6)将申請到的幀緩沖全部放入視訊采集輸出隊列,以便存放采集的資料。ioctl (fd_v4l, VIDIOC_QBUF, &buf)

  (7)開始視訊流資料的采集。 ioctl (fd_v4l, VIDIOC_STREAMON, &type)

  (8) 驅動将采集到的一幀視訊資料存入輸入隊列第一個幀緩沖區,存完後将該幀緩沖區移至視訊采集輸出隊列。

  (9)應用程式從視訊采集輸出隊列中取出已含有采集資料的幀緩沖區。ioctl (fd_v4l, VIDIOC_DQBUF, &buf) ,應用程式處理該幀緩沖區的原始視訊資料。

  (10)處理完後,應用程式的将該幀緩沖區重新排入輸入隊列,這樣便可以循環采集資料。ioctl (fd_v4l, VIDIOC_QBUF, &buf)

  重複上述步驟8到10,直到停止采集資料。

  (11)停止視訊的采集。ioctl (fd_v4l, VIDIOC_STREAMOFF, &type)

  (12)釋放申請的視訊幀緩沖區unmap,關閉視訊裝置檔案close(fd_v4l)。

  以上的程式流程,包含了視訊裝置采集連續的視訊資料的邏輯關系。而在實際運用中,往往還要加入對視訊資料進行處理(如壓縮編碼)的工作,否則,視訊流資料量相當大,需要很大的存儲空間和傳輸帶寬。

  上述過程中,每一個幀緩沖區都有一個對應的狀态标志變量,其中每一個比特代表一個狀态

  V4L2_BUF_FLAG_UNMAPPED 0B0000

  V4L2_BUF_FLAG_MAPPED 0B0001

  V4L2_BUF_FLAG_ENQUEUED 0B0010

  V4L2_BUF_FLAG_DONE 0B0100

  緩沖區的狀态轉化如圖2所示。

  

嵌入式linux環境下的視訊采集(V4L2)

  圖2 緩沖區的狀态标志轉化圖

  三、結束語

  V4L2是Linux環境下開發視訊采集裝置驅動程式的一套規範(API),它為驅動程式的編寫提供統一的接口,并将所有的視訊采集裝置的驅動程式都納入其的管理之中。V4L2不僅給驅動程式編寫者帶來極大的友善,同時也友善了應用程式的編寫和移植,具有廣泛的應用價值。

V4L2 程式設計

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

例:

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&OXFF);

5. 幀格式:

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++;

}

// 檢視或設定目前格式

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.width,fmt.fmt.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.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);

struct v4l2_cropcap

{

enum v4l2_buf_type type;// 應用程式設定

struct v4l2_rect bounds;// 最大邊界

struct v4l2_rect defrect;// 預設值

struct v4l2_fract pixelaspect;

};

// 設定縮放

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. 申請和管理緩沖區,應用程式和裝置有三種交換資料的方法,直接 read/write ,記憶體映射(memory mapping) ,使用者指針。這裡隻讨論 memory mapping.

// 向裝置申請緩沖區

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

擷取緩沖幀的位址,長度:

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;

};

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

}

8. 緩沖區處理好之後,就可以開始擷取資料了

// 啟動/ 停止資料流

VIDIOC_STREAMON,VIDIOC_STREAMOFF

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

//argp 為流類型指針,如V4L2_BUF_TYPE_VIDEO_CAPTURE.

在開始之前,還應當把緩沖幀放入緩沖隊列:

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 沒什麼關系,為什麼呢?

例:擷取一幀并處理

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 (fd, VIDIOC_QBUF, &buf);

繼續閱讀