一、什么是v4l2
vl42是video for Linux 2的縮寫,是一套Linux內核視頻設備的驅動框架,該驅動框架為應用層提供一套統一的操作接口(一系列的ioctl)
V4L2在設計時,是要支持很多廣泛的設備的,它們之中只有一部分在本質上是真正的視頻設備,可以支持多種設備,它可以有以下幾種接口
video capture interface:視頻采集接口,這種接口應用于攝像頭,v4l2在最初設計的時候就是應用于這種功能
video output interface:視頻輸出接口,將靜止圖像或圖像序列編碼為模擬視頻信號,通過此接口,應用程序可以控制編碼過程并將圖像從用戶空間移動到驅動程序
video overlay interface:視頻直接傳輸接口,可以將采集到的視頻數據直接傳輸到顯示設備,不需要cpu參與,這種方式的顯示圖像的效率比其他方式高得多
其他接口這里就不介紹了,下面來看一下v4l2的API
二、v4l2 API介紹
對V4L2設備進行編程包括以下步驟
打開設備
更改設備屬性,選擇視頻和音頻輸入,視頻標準,圖片亮度等
設置數據格式
設置輸入/輸出方法
輸入/輸出緩存隊列循環
關閉設備
其中大多數操作都是通過應用層調用ioctl實現的,可以將這些ioctl分為下面幾類
2.1 查詢設備的功能
由于V4L2涵蓋了各種各樣的設備,因此并非API的所有方面都適用于所有類型的設備,在使用v4l2設備時,必須調用此API,獲得設備支持的功能(capture、output、overlay…)
注:可以點擊名稱查看API講解
2.2 應用優先級
當多個應用程序共享設備時,可能需要為它們分配不同的優先級。視頻錄制應用程序可以例如阻止其他應用程序改變視頻控制或切換當前的電視頻道。另一個目標是允許在后臺工作的低優先級應用程序,這些應用程序可以被用戶控制的應用程序搶占,并在以后自動重新獲得對設備的控制
2.3 輸入和輸出設備
2.4 視頻標準
2.5 控制屬性
2.6 圖像格式
圖像由多種格式YUV和RGB還有壓縮格式等等,其中每種格式又分有多種格式,比如RGB:RGB565、RGB888…所以在使用設備時,需要對格式進行設置
2.7 圖像裁剪、插入與縮放
2.8 數據的輸入和輸出
內核中使用緩存隊列對圖像數據進行管理,用戶空間獲取圖像數據有兩種方式,一種是通過read、write方式讀取內核空間的緩存,一種是將內核空間的緩存映射到用戶空間。在操作v4l2設備時,通過VIDIOC_QUERYCAP獲取設備支持哪種方式
ioctl API就先介紹到這里,還有非常多的接口這里就不一一介紹了,具體可以查看V4L2 Function Reference;下面來講一講如何使用這些接口
三、v4l2設備操作流程
V4L2支持多種接口:capture(捕獲)、output(輸出)、overlay(預覽)等等 這里講解如何使用capture功能,下面講解操作流程
step1:打開設備 在Linux中,視頻設備節點為/dev/videox,使用open函數將其打開
intfd=open(name,flag); if(fd0) { ????printf("ERR(%s):failed?to?open?%s ",?__func__,?name); ????return?-1; } return?fd;
step 2:查詢設備功能
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字段,這個字段標記著v4l2設備的功能,capabilities有以下部分標記位:
我們可以通過這樣子去判斷設備的功能:
step 3:設置輸入設備 一個設備可能有多個輸入,比如:在芯片上,攝像頭控制器和攝像頭接口是分離的,需要選擇哪一個攝像頭接口作為攝像頭控制器的輸入源
當然,并不是所有的設備都需要設置輸入,比如:uvc攝像頭,一般只有一個輸入,默認就會選擇,不需要設置
下面介紹如何設置輸入設備
1.枚舉輸入設備 下面這段程序枚舉了該設備所有的輸入源,并打印輸入源的名稱:
structv4l2_inputinput; input.index=0; while(!ioctl(fd,VIDIOC_ENUMINPUT,&input)) { printf("input:%s ",input.name); ++input.index; }
2.設置輸入設備
structv4l2_inputinput; input.index=index;//指定輸入設備 if(ioctl(fd,VIDIOC_S_INPUT,&input)0) { ????printf("ERR(%s):VIDIOC_S_INPUT?failed ",?__func__); ????return?-1; }
step 4:設置圖像格式 有的攝像頭支持多種像素格式,有的攝像頭只支持一種像素格式,在設置格式之前,要先枚舉出所有的格式,看一看是否支持要設置的格式,然后再進一步設置
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.設置像素格式
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:設置緩存 v4l2設備讀取數據的方式有兩種,一種是read方式,一種是streaming方式,具體需要看step 2的返回結果是支持V4L2_CAP_READWRITE還是V4L2_CAP_STREAMING
read方式很容易理解,就是通過read函數讀取,那么streaming是什么意思呢?
streaming就是在內核空間中維護一個緩存隊列,然后將內存映射到用戶空間,應用讀取圖像數據就是一個不斷地出隊列和入隊列的過程,如下圖所示:
下面講解如何去申請和映射緩存:
1.申請緩存
structv4l2_requestbuffersreq; req.count=nr_bufs;//緩存數量 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.映射緩存 為什么要映射緩存?
因為如果使用read方式讀取的話,圖像數據是從內核空間拷貝會應用空間,而一副圖像的數據一般來講是比較大的,所以效率會比較低。而如果使用映射的方式,講內核空間的內存應用到用戶空間,那么用戶空間讀取數據就想在操作內存一樣,不需要經過內核空間到用戶空間的拷貝,大大提高效率
映射緩存需要先查詢緩存信息,然后再使用緩存信息進行映射,下面是一個例子:
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);
注:需要將所有申請的緩存使用上述方法進行映射
3.將所有的緩存放入隊列
structv4l2_bufferv4l2_buffer; for(i=0;i
step 6:打開設備
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:讀取數據 獲取圖像數據其實就是一個不斷地入隊列和出隊列地過程,在出隊列前要調用poll等待數據準備完成
1.poll
structpollfdpoll_fds[1]; poll_fds[0].fd=fd; poll_fds[0].events=POLLIN;//等待可讀 poll(poll_fds,1,10000);
2.出隊列
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; }
出隊列后得到了緩存的下標buffer.index,然后找到對飲的緩存,通過映射過后的地址進行數據的讀取
3.入隊列 再數據讀取完成后,要將buf重新放入隊列中:
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; }
讀取數據就是在上面這三步一直不斷地循環
step 8:關閉設備
1.關閉設備
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);
關閉文件描述符
close(fd);
libv4l2 v4l2設備操作起來還是比較繁瑣的,為此我對其進行了封裝,寫了一套庫,使用起來更加方便,可以從這里libv4l2獲取
其中附帶一個實例example_cature,通過capture /dev/video0運行程序采集一張YUYV格式的圖片,采集后得到了pic.yuv,可以通過ffplay查看ffplay -pixel_format yuyv422 -f rawvideo -video_size 640x480 pic.yuv,效果圖如下
四、v4l2采集圖像在frame buffer顯示
如何將采集圖像在frame buff上顯示?
1.轉換圖像格式,將yuv格式轉換成frame buff可以接收的rgb格式
2.操作frame buff,通過映射frame buff的顯存到用戶空間,直接寫顯存就可以顯示圖像
具體的實現過程這里就不詳細說了,下面給出一個例子。
執行make編譯后可以得到video2lcd,執行video2lcd /dev/video0
運行效果如下:
五、v4l2采集圖像使用Qt顯示
如何使用qt顯示,道理跟在frame buff上顯示是一樣的,都是采集,轉化格式,顯示,只是在顯示部分不同而已,這里給出一個例子。
審核編輯:湯梓紅
-
內核
+關注
關注
3文章
1382瀏覽量
40430 -
接口
+關注
關注
33文章
8694瀏覽量
151929 -
Linux
+關注
關注
87文章
11345瀏覽量
210417 -
攝像頭
+關注
關注
60文章
4865瀏覽量
96322
原文標題:深入學習Linux攝像頭v4l2應用編程
文章出處:【微信號:嵌入式應用研究院,微信公眾號:嵌入式應用研究院】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
如何在Raspberry Pi(樹莓派)上調用V4L2來操縱攝像頭拍照
【WRTnode2R試用體驗】使用V4L2獲取攝像頭的信息
運行linuxtv官網的v4l2代碼,capture攝像頭時select超時怎么解決?
運行linuxtv官網的v4l2代碼,capture攝像頭時select超時怎么解決?
需要了解Linux V4L2的驅動架構
![需要了解<b class='flag-5'>Linux</b> <b class='flag-5'>V4L2</b>的驅動架構](https://file.elecfans.com/web1/M00/90/A0/pIYBAFzFcj2AHajyAAF2cjgshbE989.png)
Linux視頻設備驅動編程(v4l2編程)
如何使用v4l2 API讀取攝像頭
![如何使用<b class='flag-5'>v4l2</b> API讀取<b class='flag-5'>攝像頭</b>](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
評論