一、什么是v4l2
vl42是video for Linux 2的縮寫,是一套Linux內(nèi)核視頻設(shè)備的驅(qū)動(dòng)框架,該驅(qū)動(dòng)框架為應(yīng)用層提供一套統(tǒng)一的操作接口(一系列的ioctl)
V4L2在設(shè)計(jì)時(shí),是要支持很多廣泛的設(shè)備的,它們之中只有一部分在本質(zhì)上是真正的視頻設(shè)備,可以支持多種設(shè)備,它可以有以下幾種接口
video capture interface:視頻采集接口,這種接口應(yīng)用于攝像頭,v4l2在最初設(shè)計(jì)的時(shí)候就是應(yīng)用于這種功能
video output interface:視頻輸出接口,將靜止圖像或圖像序列編碼為模擬視頻信號,通過此接口,應(yīng)用程序可以控制編碼過程并將圖像從用戶空間移動(dòng)到驅(qū)動(dòng)程序
video overlay interface:視頻直接傳輸接口,可以將采集到的視頻數(shù)據(jù)直接傳輸?shù)斤@示設(shè)備,不需要cpu參與,這種方式的顯示圖像的效率比其他方式高得多
其他接口這里就不介紹了,下面來看一下v4l2的API
二、v4l2 API介紹
對V4L2設(shè)備進(jìn)行編程包括以下步驟
打開設(shè)備
更改設(shè)備屬性,選擇視頻和音頻輸入,視頻標(biāo)準(zhǔn),圖片亮度等
設(shè)置數(shù)據(jù)格式
設(shè)置輸入/輸出方法
輸入/輸出緩存隊(duì)列循環(huán)
關(guān)閉設(shè)備
其中大多數(shù)操作都是通過應(yīng)用層調(diào)用ioctl實(shí)現(xiàn)的,可以將這些ioctl分為下面幾類
2.1 查詢設(shè)備的功能
由于V4L2涵蓋了各種各樣的設(shè)備,因此并非API的所有方面都適用于所有類型的設(shè)備,在使用v4l2設(shè)備時(shí),必須調(diào)用此API,獲得設(shè)備支持的功能(capture、output、overlay…)
注:可以點(diǎn)擊名稱查看API講解
2.2 應(yīng)用優(yōu)先級
當(dāng)多個(gè)應(yīng)用程序共享設(shè)備時(shí),可能需要為它們分配不同的優(yōu)先級。視頻錄制應(yīng)用程序可以例如阻止其他應(yīng)用程序改變視頻控制或切換當(dāng)前的電視頻道。另一個(gè)目標(biāo)是允許在后臺工作的低優(yōu)先級應(yīng)用程序,這些應(yīng)用程序可以被用戶控制的應(yīng)用程序搶占,并在以后自動(dòng)重新獲得對設(shè)備的控制
2.3 輸入和輸出設(shè)備
2.4 視頻標(biāo)準(zhǔn)
2.5 控制屬性
2.6 圖像格式
圖像由多種格式Y(jié)UV和RGB還有壓縮格式等等,其中每種格式又分有多種格式,比如RGB:RGB565、RGB888…所以在使用設(shè)備時(shí),需要對格式進(jìn)行設(shè)置
2.7 圖像裁剪、插入與縮放
2.8 數(shù)據(jù)的輸入和輸出
內(nèi)核中使用緩存隊(duì)列對圖像數(shù)據(jù)進(jìn)行管理,用戶空間獲取圖像數(shù)據(jù)有兩種方式,一種是通過read、write方式讀取內(nèi)核空間的緩存,一種是將內(nèi)核空間的緩存映射到用戶空間。在操作v4l2設(shè)備時(shí),通過VIDIOC_QUERYCAP獲取設(shè)備支持哪種方式
ioctl API就先介紹到這里,還有非常多的接口這里就不一一介紹了,具體可以查看V4L2 Function Reference;下面來講一講如何使用這些接口
三、v4l2設(shè)備操作流程
V4L2支持多種接口:capture(捕獲)、output(輸出)、overlay(預(yù)覽)等等 這里講解如何使用capture功能,下面講解操作流程
step1:打開設(shè)備 在Linux中,視頻設(shè)備節(jié)點(diǎn)為/dev/videox,使用open函數(shù)將其打開
intfd=open(name,flag); if(fd0) { ????printf("ERR(%s):failed?to?open?%s ",?__func__,?name); ????return?-1; } return?fd;
step 2:查詢設(shè)備功能
if(ioctl(fd,VIDIOC_QUERYCAP,cap)0) { ????printf("ERR(%s):VIDIOC_QUERYCAP?failed ",?__func__); ????return?-1; }
看一看v4l2_capability:
structv4l2_capability{ __u8driver[16];/*i.e."bttv"*/ __u8card[32];/*i.e."HauppaugeWinTV"*/ __u8bus_info[32];/*"PCI:"+pci_name(pci_dev)*/ __u32version;/*shoulduseKERNEL_VERSION()*/ __u32capabilities;/*Devicecapabilities*/ __u32reserved[4]; };
其中最重要的是capabilities字段,這個(gè)字段標(biāo)記著v4l2設(shè)備的功能,capabilities有以下部分標(biāo)記位:
我們可以通過這樣子去判斷設(shè)備的功能:
step 3:設(shè)置輸入設(shè)備 一個(gè)設(shè)備可能有多個(gè)輸入,比如:在芯片上,攝像頭控制器和攝像頭接口是分離的,需要選擇哪一個(gè)攝像頭接口作為攝像頭控制器的輸入源
當(dāng)然,并不是所有的設(shè)備都需要設(shè)置輸入,比如:uvc攝像頭,一般只有一個(gè)輸入,默認(rèn)就會(huì)選擇,不需要設(shè)置
下面介紹如何設(shè)置輸入設(shè)備
1.枚舉輸入設(shè)備 下面這段程序枚舉了該設(shè)備所有的輸入源,并打印輸入源的名稱:
structv4l2_inputinput; input.index=0; while(!ioctl(fd,VIDIOC_ENUMINPUT,&input)) { printf("input:%s ",input.name); ++input.index; }
2.設(shè)置輸入設(shè)備
structv4l2_inputinput; input.index=index;//指定輸入設(shè)備 if(ioctl(fd,VIDIOC_S_INPUT,&input)0) { ????printf("ERR(%s):VIDIOC_S_INPUT?failed ",?__func__); ????return?-1; }
step 4:設(shè)置圖像格式 有的攝像頭支持多種像素格式,有的攝像頭只支持一種像素格式,在設(shè)置格式之前,要先枚舉出所有的格式,看一看是否支持要設(shè)置的格式,然后再進(jìn)一步設(shè)置
1.枚舉支持的像素格式
structv4l2_fmtdescfmtdesc; fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; fmtdesc.index=0; while(!ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)) { printf("fmt:%s ",fmtdesc.description); fmtdesc.index++; }
2.設(shè)置像素格式
structv4l2_formatv4l2_fmt; memset(&v4l2_fmt,0,sizeof(structv4l2_format)); v4l2_fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; v4l2_fmt.fmt.pix.width=width;//寬度 v4l2_fmt.fmt.pix.height=height;//高度 v4l2_fmt.fmt.pix.pixelformat=V4L2_PIX_FMT_YUYV;//像素格式 v4l2_fmt.fmt.pix.field=V4L2_FIELD_ANY; if(ioctl(fd,VIDIOC_S_FMT,&v4l2_fmt)0) { ????printf("ERR(%s):VIDIOC_S_FMT?failed ",?__func__); ????return?-1; }
step 5:設(shè)置緩存 v4l2設(shè)備讀取數(shù)據(jù)的方式有兩種,一種是read方式,一種是streaming方式,具體需要看step 2的返回結(jié)果是支持V4L2_CAP_READWRITE還是V4L2_CAP_STREAMING
read方式很容易理解,就是通過read函數(shù)讀取,那么streaming是什么意思呢?
streaming就是在內(nèi)核空間中維護(hù)一個(gè)緩存隊(duì)列,然后將內(nèi)存映射到用戶空間,應(yīng)用讀取圖像數(shù)據(jù)就是一個(gè)不斷地出隊(duì)列和入隊(duì)列的過程,如下圖所示:
下面講解如何去申請和映射緩存:
1.申請緩存
structv4l2_requestbuffersreq; req.count=nr_bufs;//緩存數(shù)量 req.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory=V4L2_MEMORY_MMAP; if(ioctl(fd,VIDIOC_REQBUFS,&req)0) { ????printf("ERR(%s):VIDIOC_REQBUFS?failed ",?__func__); ????return?-1; }
2.映射緩存 為什么要映射緩存?
因?yàn)槿绻褂胷ead方式讀取的話,圖像數(shù)據(jù)是從內(nèi)核空間拷貝會(huì)應(yīng)用空間,而一副圖像的數(shù)據(jù)一般來講是比較大的,所以效率會(huì)比較低。而如果使用映射的方式,講內(nèi)核空間的內(nèi)存應(yīng)用到用戶空間,那么用戶空間讀取數(shù)據(jù)就想在操作內(nèi)存一樣,不需要經(jīng)過內(nèi)核空間到用戶空間的拷貝,大大提高效率
映射緩存需要先查詢緩存信息,然后再使用緩存信息進(jìn)行映射,下面是一個(gè)例子:
structv4l2_bufferv4l2_buffer; void*addr; memset(&v4l2_buffer,0,sizeof(structv4l2_buffer)); v4l2_buffer.index=i;//想要查詢的緩存 v4l2_buffer.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; v4l2_buffer.memory=V4L2_MEMORY_MMAP; /*查詢緩存信息*/ ret=ioctl(fd,VIDIOC_QUERYBUF,&v4l2_buffer); if(ret0) { ????printf("Unable?to?query?buffer. "); ????return?-1; } /*?映射?*/ addr?=?mmap(NULL?/*?start?anywhere?*/?, ????????????v4l2_buffer.length,?PROT_READ?|?PROT_WRITE,?MAP_SHARED, ????????????fd,?v4l2_buffer.m.offset);
注:需要將所有申請的緩存使用上述方法進(jìn)行映射
3.將所有的緩存放入隊(duì)列
structv4l2_bufferv4l2_buffer; for(i=0;i
step 6:打開設(shè)備
enumv4l2_buf_typetype=V4L2_BUF_TYPE_VIDEO_CAPTURE; if(ioctl(fd,VIDIOC_STREAMON,&type)0) { ????printf("ERR(%s):VIDIOC_STREAMON?failed ",?__func__); ????return?-1; }
step 7:讀取數(shù)據(jù) 獲取圖像數(shù)據(jù)其實(shí)就是一個(gè)不斷地入隊(duì)列和出隊(duì)列地過程,在出隊(duì)列前要調(diào)用poll等待數(shù)據(jù)準(zhǔn)備完成
1.poll
structpollfdpoll_fds[1]; poll_fds[0].fd=fd; poll_fds[0].events=POLLIN;//等待可讀 poll(poll_fds,1,10000);
2.出隊(duì)列
structv4l2_bufferbuffer; buffer.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; buffer.memory=V4L2_MEMORY_MMAP; if(ioctl(fd,VIDIOC_DQBUF,&buffer)0) { ????printf("ERR(%s):VIDIOC_DQBUF?failed,?dropped?frame ",?__func__); ????return?-1; }
出隊(duì)列后得到了緩存的下標(biāo)buffer.index,然后找到對飲的緩存,通過映射過后的地址進(jìn)行數(shù)據(jù)的讀取
3.入隊(duì)列 再數(shù)據(jù)讀取完成后,要將buf重新放入隊(duì)列中:
structv4l2_bufferv4l2_buf; v4l2_buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; v4l2_buf.memory=V4L2_MEMORY_MMAP; v4l2_buf.index=i;//指定buf if(ioctl(fd,VIDIOC_QBUF,&v4l2_buf)0) { ????printf("ERR(%s):VIDIOC_QBUF?failed ",?__func__); ????return?-1; }
讀取數(shù)據(jù)就是在上面這三步一直不斷地循環(huán)
step 8:關(guān)閉設(shè)備
1.關(guān)閉設(shè)備
enumv4l2_buf_typetype=V4L2_BUF_TYPE_VIDEO_CAPTURE; if(ioctl(fd,VIDIOC_STREAMOFF,&type)0) { ????printf("ERR(%s):VIDIOC_STREAMOFF?failed ",?__func__); ????return?-1; }
2.取消映射
for(i=0;ilength);
關(guān)閉文件描述符
close(fd);
libv4l2 v4l2設(shè)備操作起來還是比較繁瑣的,為此我對其進(jìn)行了封裝,寫了一套庫,使用起來更加方便,可以從這里libv4l2獲取
其中附帶一個(gè)實(shí)例example_cature,通過capture /dev/video0運(yùn)行程序采集一張YUYV格式的圖片,采集后得到了pic.yuv,可以通過ffplay查看ffplay -pixel_format yuyv422 -f rawvideo -video_size 640x480 pic.yuv,效果圖如下
四、v4l2采集圖像在frame buffer顯示
如何將采集圖像在frame buff上顯示?
1.轉(zhuǎn)換圖像格式,將yuv格式轉(zhuǎn)換成frame buff可以接收的rgb格式
2.操作frame buff,通過映射frame buff的顯存到用戶空間,直接寫顯存就可以顯示圖像
具體的實(shí)現(xiàn)過程這里就不詳細(xì)說了,下面給出一個(gè)例子。
執(zhí)行make編譯后可以得到video2lcd,執(zhí)行video2lcd /dev/video0
運(yùn)行效果如下:
五、v4l2采集圖像使用Qt顯示
如何使用qt顯示,道理跟在frame buff上顯示是一樣的,都是采集,轉(zhuǎn)化格式,顯示,只是在顯示部分不同而已,這里給出一個(gè)例子。
審核編輯:湯梓紅
-
內(nèi)核
+關(guān)注
關(guān)注
3文章
1366瀏覽量
40236 -
接口
+關(guān)注
關(guān)注
33文章
8526瀏覽量
150862 -
Linux
+關(guān)注
關(guān)注
87文章
11232瀏覽量
208952 -
攝像頭
+關(guān)注
關(guān)注
59文章
4814瀏覽量
95474
原文標(biāo)題:深入學(xué)習(xí)Linux攝像頭v4l2應(yīng)用編程
文章出處:【微信號:嵌入式應(yīng)用研究院,微信公眾號:嵌入式應(yīng)用研究院】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論