V4L2 攝像頭應用程序編程

1. 打開設備文件

int  fd;
fd = open("/dev/video0",O_RDWR | O_NOBLOCK,0);

通常來說,驅動應該支持read/write和mmap兩種傳遞幀數據的方式,但是較多情況下,採用的是mmap方式,如果應用通過read/write來和驅動進行數據交互,則在打開設備的時候設置爲阻塞模式或者非阻塞模式,都可以,但是如果是應用是使用mmap來與驅動進行數據交互的話,則在打開設備時設置爲阻塞模式,是沒有意義的,因爲驅動在數據交互的過程中,根本就不會檢查這個flag是否被設置了。

2. 獲取設備的capablity參數

通過系統調用VIDIOC_QUERYCAP來獲取設備的capablity參數,如是設備支持視頻採集還是視頻輸出,判斷驅動是否支持read/write和mmap等等。

struct v4l2_capability cap;

int ret = ioctl(fd,VIDIOC_QUERYCAP,&cap);

3. 查詢設備所支持的格式

通過VIDIOC_ENUM_FMT命令來枚舉設備所支持的不同格式,實例代碼如下。

struct v4l2_fmtdesc fmt;

int ret;
bzero(&fmt,sizeof(fmt));
fmt.index = 0;//從0 開始枚舉
fmt.type = v4l2_BUF_TYPE_VIDEO_CAPTURE;//視頻捕獲設備
while(!ioctl(fd,VIDIOC_ENUM_FMT,&fmt))
{
	......
	fmt.index++; //枚舉所有的格式
}

4.獲取設備所支持的制式,通過命令VIDIOC_ENUMSTD來獲取,實例代碼如下:

v4l2_std_id std;
ioctl(fd,VIDIOC_ENUMSTD,&std);
......

5.設置視頻格式

  這一步是相當重要的一個步驟了,用於設置視頻的捕獲格式,如設置視頻圖像數據的長,寬,圖像格式等等

struct v4l2_format fmt;

fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = IMG_WIDTH;
fmt.fmt.pix.height = IMG_HEIGHT;
fmt.fmt.pix.pixeformat = V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.filed = V4L2_FIELD_INTERLACED;

int ret;
ret = ioctl(fd,VIDIOC_S_FMT,&fmt);

bzero(&fmt,sizeof(fmt));
//設置之後,讀取實際的視頻格式
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

if(!ioctl(fd,VIDIOC_G_GMT,&fmt) ){
	printf(......);
	......
}else{
	......
}

注意: 如果視頻設備驅動不支持應用設定的圖像格式,則視頻驅動會根據具體的設備來重新修改參數fmt的值,所以,一般在應用程序中,在設置視頻格式之後,要獲取實際的視頻格式。

6 .幀緩衝區申請與初始化

在前面也提到過,應用程序與驅動進行數據交互,主要有常用的read/write方式和流I/O方式,而流I/O方式有兩種緩衝區:分別爲內核空間緩存區,和用戶空間緩存區,內核空間緩衝區需要使用mmap進行映射,而用戶空間緩存區不需要進行mmap映射,但是會增加驅動的複雜性。這裏就以常用的mmap方式爲例進行介紹。實例代碼如下。

struct v4l2_requestbuffers reqbuf;
bzero(&req,sizeof(reqbuf));

reqbuf.count = 3;// 設定申請緩存區的個數。

reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

reqbuf.memory = V4L2_MEMORY_MMAP;//流I/O方式

int ret;
ret = ioctl(fd,VIDIOC_REQBUFS,&reqbuf);
......

注意:申請緩存區的實際數目是由內核驅動層根據內存的使用情況來決定的,實際的數目可能會小於應用程序欲分配的數目。

 

7 查詢緩存區參數,並且通過mmpap系統調用映射到進程的用戶空間。

實例代碼如下:


struct buffer{
	void  *start;
	size_t length;
};

struct buffer  *buffers;

buffers = (struct buffer *)calloc(reqbuf.count,sizeof(*buffers));

int i ;

for(i = 0; i < reqbuf.count;i++){
	struct v4l2_buffer buf;
	bzero(&buf,sizeof(buf));
	buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	buf.memroy = V4L2_MEMORY_MMAP;
	buf.index = i;
	if(!ioctl(fd,VIDIOC_QUERYBUF,&buf)){
		buffers[i].length = buf.length;
		buffers[i].start = mmap(NULL,buf.length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,buf.m.offset);
......
	}
}

8. 將一個空的緩存區放到視頻緩衝區輸入隊列中

其實質就是告訴內核驅動空閒緩衝區的序列號,以便內核驅動來維護內核空間的視頻緩衝區,實例如下:

int i;
for(i  = 0;i< reqbuf.count;i++){
	struct v4l2_buffer buf;
	memset(&buf,0,sizeof(struct v4l2_buffer));
	buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	buf.memory = V4L2_MEMORY_MMAP;
	buf.index = i;
	if(!ioctl(fd,VIDIOC_QBUF,&buf)){
		.....
	}else{
			......
	}
}

9.開始採集

啓動視頻採集,應用程序通過VIDIOC_STREAMON啓動視頻採集過程。實例如下:

enum v4l2_buf_type type;

type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd,VIDIOC_STREAMON,&type);

10. 從視頻輸出隊列中取出視頻數據

在開始採集視頻之後,通過select來監測數據的到來,當數據可讀時,從驅動中的視頻輸出隊列中取出視頻數據。其實質就是通過ioctl來獲取就緒緩存區的序列號,然後應用就可以操作對應buffers數組。

struct v4l2_buffer buf;

buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if(!ioctl(fd,VIDIOC_DQBUF,&buf)){
	......
}

以上就是針對V4L2攝像頭應用程序大體流程的一些概括,如有錯誤,敬請指正!









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