v4l2 操做實際上就是 open() 設備, close() 設備,以及中間過程的 ioctl() 操做。對於 ioctl 的調用,要注意對 errno 的判斷,若是調用被其餘信號中斷,即 errno 等於 EINTR 的時候,要從新調用。 數組
Video capture device 的實際功能就是採集視頻信號,並將數字化的圖像保存在 memory 中,如今幾乎上全部的相關設備都能採集 25/30 幀 /s 。在下面的討論中,我只列舉出一些和 camera 密切相關的一些屬性和方法。 數據結構
打開設備通常都是使用 open() 打開 /dev 下的 video 設備文件 ,好比說 /dev/video1, 打開以前首先要對相應的設備文件進行檢查 ,好比說使用 stat() 得到文件屬性,並判斷是否爲字符設備文件。 app
驅動經過主設備號 81 和 0 ~ 255 之間的次設備號來註冊 device note ,系統 管理 員經過設備的主次設備號在 /dev 目錄下建立相應的字符設備文件。應用 程序不能經過設備的主次設備號來打開設備,而必須經過適當的 device name ,即 /dev 目錄下的設備文件來打開設備。 ide
v4l2 支持一個設備文件能夠被屢次打開,卻只容許其中一個應用程序與設備進行數據 交換 ,其餘應用程序只能用來設定一些設備參數,對設備進行一些控制。 函數
//====== 相關 spec : http://v4l2spec.bytesex.org/spec/c174.htm#OPEN oop
對設備進行初始化是一個很複雜的過程,其中要進行一系列參數的協商,其中重要的包括 v4l2_capability, v4l2_cropcap, v4l2_format 等等。 性能
首先使用 VIDIOC_QUERYCAP 命令 來得到當前設備的各個屬性,查看設備對各項功能的支持程度: 優化
int ioctl(int fd, int request, struct v4l2_capability *argp); spa
全部的 v4l2 驅動都必須支持 VIDIOC_QUERYCAP ,並且在打開設備之後,這個 ioctl 必須是被首先調用的。 指針
v4l2_capability 的各項參數能夠查 API ,其中比較重要的是下面的成員變量:
_u32 capabilities
這個 32 位無符號整型定義了當前設備對一些關鍵屬性的支持:
V4L2_CAP_VIDEO_CAPTURE 0x00000001 // 這個設備支持 video capture 的接口,即這個設備具有 video capture 的功能 V4L2_CAP_VIDEO_OUTPUT 0x00000002 // 這個設備支持 video output 的接口,即這個設備具有 video output 的功能 V4L2_CAP_VIDEO_OVERLAY 0x00000004 // 這個設備支持 video overlay 的接口,即這個設備具有 video overlay 的功能,在這個功能下會將採集到的 imag 方在視頻設備的 meomory 中保存,並直接在屏幕上顯示,而不須要通過其餘的處理。 V4L2_CAP_VIDEO_OUTPUT_OVERLAY 0x00000200 // 這個設備支持 video output overlay( 又名 On-Screen Display) ,這是一個實驗性的功能, spec 說明他在未來可能會改變,若是打開這個功能必須將 video overlay 功能給關閉,反之亦然 V4L2_CAP_READWRITE 0x01000000 // 這個設備是否支持 read() 和 write() I/O 操做函數 V4L2_CAP_STREAMING 0x04000000 // 這個設備是否支持 streaming I/O 操做函數
在實際操做過程當中,能夠將取得的 capabilites 與這些宏進行與遠算來判斷設備是否支持相應的功能。
除了 VIDIOC_QUERYCAP 以外,設備其餘屬性的得到能夠經過其餘的命令,好比說 VIDIOC_ENUMINPUT 和 VIDIOC_ENUMOUTPUT 能夠枚舉出設備的輸入輸出物理鏈接。
//============== 相關 spec : http://v4l2spec.bytesex.org/spec/x282.htm
得到 device 的 capability 之後,能夠根據應用程序的功能要求對設備參數進行一系列的設置 ,這些參數又分爲兩部分,一個是 user contrl ,還一個是 extended control
User control 參數包含一個 ID ,以及相應的 Type ,下面對各個 type 進行簡單的列舉:
ID Type V4L2_CID_BASE // 第一個預約義的 ID ,實際等於 V4L2_CID_BRIGHTNESS ,由於 V4L2_CID_BRIGHTNESS 是第一個預約義的 ID V4L2_CID_USER_BASE // 實際上等同於 V4L2_CID_BASE V4L2_CID_BRIGHTNESS integer // 圖片的亮度,或者說黑色位準 V4L2_CID_AUTO_WHITE_BALANCE boolean //camera 的自動白平衡 V4L2_CID_EXPOSURE integer //camera 的爆光時間 V4L2_CID_LASTP1 // 最後一個預約義的 ID ,實際等於上一個 ID + 1 V4L2_CID_PRIVATE_BASE // 第一個 driver 定義的通常 control ID
能夠經過 VIDIOC_QUERYCTRL 和 VIDIOC_QUERYMENU ioctls 來枚舉出有效的 control ID ,及其屬性,好比說 ID 值,類型,是否有效,是否可修改,最大值,最小值,步長等等 ,主要的數據結構是 v4l2_queryctrl 和 v4l2_querymenu ,他們的結構能夠參考 spec 。另外能夠經過 V4L2_CID_BASE 和 V4L2_CID_LASTP1 能夠枚舉出全部的預約義 control ID ,能夠經過 V4L2_CID_PRIVATE_BASE 來枚舉出全部的驅動定義的 control ID 。 Menu 其實是同一個 ID 可能具備多個選項的目錄。
int ioctl(int fd, int request,struct v4l2_queryctrl *argp);
int ioctl(int fd, int request, struct v4l2_querymenu *argp);
得到 user control ID 之後,能夠對其中能夠修改的 ID 按照應用程序的要求進行修改 VIDIOC_G_CTRL, VIDIOC_S_CTRL :
int ioctl(int fd, int request, struct v4l2_control *argp);
v4l2_control 的結構比較簡單,就是相應的 ID 及其 value 。
//========== 相關 spec : http://v4l2spec.bytesex.org/spec/x542.htm
除了 user control 以外還有一個就是擴展控制,擴展控制能夠同時原子的對多個 ID 進行 control ,相關命令是三個: VIDIOC_G_EXT_CTRLS, VIDIOC_S_EXT_CTRLS 和 VIDIOC_TRY_EXT_CTRLS :
int ioctl(int fd, int request, struct v4l2_ext_controls *argp);
其中最重要的是 v4l2_ext_controls 這個數據結構,它包含幾個內容:
__u32 ctrl_class
// 如今 spec 中只定義了兩種類型的 class : V4L2_CTRL_CLASS_USER 和 V4L2_CTRL_CLASS_MPEG
__u32 count
//ctrl 數組中的 control ,即 v4l2_ext_control 的個數
struct v4l2_ext_control * controls
//control 數組, v4l2_ext_control 包含要設定的 ID ,以及 value
應用程序可使用 V4L2_CTRL_FLAG_NEXT_CTRL 來對擴展 control 進行枚舉, V4L2_CTRL_FLAG_NEXT_CTRL 返回下一個 ID 更高的 control ID :
struct v4l2_queryctrl qctrl;
qctrl.id = V4L2_CTRL_FLAG_NEXT_CTRL;
while (0 == ioctl (fd, VIDIOC_QUERYCTRL, &qctrl)) {
/* ... */
qctrl.id |= V4L2_CTRL_FLAG_NEXT_CTRL;
}
要枚舉指定的 control class 中的 control 可使用下面的方法:
qctrl.id = V4L2_CTRL_CLASS_MPEG | V4L2_CTRL_FLAG_NEXT_CTRL;
while (0 == ioctl (fd, VIDIOC_QUERYCTRL, &qctrl)) {
if (V4L2_CTRL_ID2CLASS (qctrl.id) != V4L2_CTRL_CLASS_MPEG)
break;
/* ... */
qctrl.id |= V4L2_CTRL_FLAG_NEXT_CTRL;
}
固然前提是驅動必須提供對 V4L2_CTRL_FLAG_NEXT_CTRL 的支持。
應用程序能夠爲建立一個控制面板,其中包含一系列控制,每一個 control class 用一個 V4L2_CTRL_TYPE_CTRL_CLASS 類型開始,當使用 VIDIOC_QUERYCTRL 的時候將返回這個 control class 的 name ,下面咱們來看 camera control class 的一些 control :
ID Type
V4L2_CID_CAMERA_CLASS class
//camera class 的描述符,當調用 VIDIOC_QUERYCTRL 的時候將返回一個對這個 class 的描述
V4L2_CID_EXPOSURE_AUTO integer
// 自動爆光
V4L2_CID_FOCUS_AUTO boolean
// 自動對焦
//============== 相關 spec : http://v4l2spec.bytesex.org/spec/x802.htm
對各類控制參數進行設置之後,下面要進行的就是要得到設備對 Image Cropping 和 Scaling 的支持,即對圖像的取景範圍以及圖片的比例縮放的支持。 Image Crop 的功能通俗一點講就是他對 camera 鏡頭能捕捉的圖像,截取一個範圍來保存,而這個要截取的範圍就是最終保存下來的圖像。
對於一個視頻捕捉或者視頻直接播放的設備來講,源是視頻信號,而 cropping ioctl 決定視頻信號的哪部分被採樣,目標則是應用程序或者屏幕上讀到的圖片。對於視頻輸出設備來講,輸入是應用程序傳入的圖片,而輸出則是視頻流, cropping ioctl 此時則決定圖片的哪部分會被插入視頻信號,全部的視頻捕捉和視頻輸出設備都必須支持 VIDIOC_CROPCAP ioctl:
Int ioctl(int fd, int request, struct v4l2_cropcap *argp)
其中數據結構 v4l2_cropcap 的幾個要重要的成員變量是下面這些:
enum v4l2_buf_type type // 數據流的類型,在 VIDIOC_CROPCAP 這個控制中只有 V4L2_BUF_TYPE_CAPTURE, V4L2_BUF_TYPE_OUTPUT, V4L2_BUF_TYPE_OVERLAY 以及驅動定義的一些通常類型 V4L2_BUF_TYPE_PRIVATE 是有用的 struct v4l2_rect bounds // 這是 camera 的鏡頭能捕捉到的窗口大小的侷限,在應用程序設置窗口參數的時候要注意,不能超過這個長寬限制 struct v4l2_rect defrect // 定義了默認的窗口大小,包括起點的位置以及長寬的大小,大小以像素爲單位 struct v4l2_fract pixelaspect // 定義了圖片的寬高比
應用程序可使用 VIDIOC_G_CROP 和 VIDIOC_S_CROP 來得到對這些窗口參數並對其進行設置,也就是所謂的 Scaling Adjustments ,由於硬件可能在這些窗口參數設置上具備不少限制,當須要對窗口參數進行設置的時候,驅動會按照自身的規律在應用程序要求和設備限制上決定一個平衡值,一 般應用程序應該先使用 VIDIOC_CROPCAP 來得到硬件限制,並使設定的參數在 bound 範圍之內:
int ioctl(int fd, int request, struct v4l2_crop *argp);
int ioctl(int fd, int request, const struct v4l2_crop *argp);
//==== 相關 spec : http://v4l2spec.bytesex.org/spec/x1904.htm
設置好取景窗口參數之後,下面要進行的設置就是對圖形格式的協商,這個 Data Format 的協商經過 VIDIOC_G_FMT 和 VIDIOC_S_FMT 來實現。另外 VIDIOC_TRY_FMT 的功能等同與 VIDIOC_S_FMT ,惟一的不一樣就是他不會改變驅動的狀態,它在任什麼時候候均可以被調用,主要用來得到硬件的限制,從而對參數進行協商。若是驅動須要與應用程序交換數據,則必 須支持 VIDIOC_G_FMT 和 VIDIOC_S_FMT , VIDIOC_TRY_FMT 是可選的,可是是強烈推薦實現的。
Int ioctl(int fd, int requeset, struct v4l2_format *argp) ;
前面講過,雖然一個設備文件能夠支持多打開,可是隻容許一個能與驅動進行數據交換,所以在設備的初始化過程當中對 VIDIOC_S_FMT ioctl 的調用是一個轉折點,第一個調用 VIDIOC_S_FMT ioctl 的文件描述符會打開一個邏輯的流 ,若是此時其餘的文件描述符對設備進行的操做有可能破壞這個流的時候是會被禁止的,好比說若是另一個應用程序想修改 video standard ,只有對流擁有全部權的文件描述符才能修改這方面的屬性。再好比當 overlay 已經開始的時候, video capture 就會被限制在和 overlay 相同的 cropping 和 image size 。
通常來講只容許同一個文件描述符擁有一個邏輯流,惟一的例外是 video capture 和 video overlay 可使用同一個文件描述符。
下面來看看 v4l2_format 這個數據結構,它包含幾個重要內容:
enum v4l2_buf_type type //buf 的類型,好比說 V4L2_BUF_TYPE_VIDEO_CAPTURE union fmt struct v4l2_pix_format //used for video capture and output struct v4l2_window //used for video overlay ………… 其中最重要的是 union 中的兩個結構體, v4l2_window 是 overlay interface 的內容,將在 overlay 中再討論,先看一下 v4l2_pix_format 的結構: __u32 width __u32 height // 分別是 image 的寬度和高度,以像素爲單位,應用程序能夠設置這些參數,驅動會返回一個最靠近這些參數的值,爲何是最靠近的值呢,由於圖像格式以及硬件限制的緣由,可 能應用程序要求的值沒法獲得知足。[在這裏普及一個基礎 知識 , YUV 格式有兩種存儲方式,一種就是將其 3 個份量存在同一個數組中,而後幾個像素組成一個宏塊,這種方式叫 packed ;另一種就是 3 個份量分別存放在不一樣的數組中,這種方式叫作 planar 。] __u32 pixelformat // 這就是圖像格式了,能夠是 RGB ,也能夠是 YUV ,還能夠是壓縮格式 MPEG 或者 JPEG ,這個值是經過一個 4 字母宏來計算出來的: #define v4l2_fourcc(a,b,c,d)(((__u32)(a)<<0) | ((__u32)(b)<<8)| ((__u32)(c)<<16) | ((__u32)(a)<<24)) ,具體格式的標準宏能夠參照 spec 。 enum v4l2_field field // 這個定義了視頻信號的場的順序,好比視頻信號多是順序掃描的,也多是隔行掃描的。通常將場分爲 top 場和 bottom 場,一個 video camera 不會在一個時間內暴光一個整幀,而是將其分紅場分別傳輸。全部的 video capture 和 output 裝置都必須指定其場的傳輸順序,便是 top 場在前仍是 bottom 場在時間上和空間 上的順序 。具體的能夠從參考 spec 關於 Field Order 的描述,通常採用的是 V4L2_FIILED_INTERLACED ,在這個模式下 image 包含交叉存取的幀,場的順序由當前的視頻標準來決定。 __u32 bytesperline // 即每行像素所佔的 byte 數,應用程序和驅動均可以設置這個參數,但驅動能夠忽略應用程序的參數,而返回一個硬件要求的參數,應用程序能夠設置這個參數爲 0 來讓驅動返回一個默認值。 Image 在內存中仍是按照每行像素這樣來存儲的,每一行像素後面都有一個襯墊來表明該行像素的結束。 __u32 sizeimage // 要保存一個完整的 Image 須要的 buffer 空間,單位是 byte ,由驅動來設定,是保存一個圖像所須要的最大 byte 數,而不是圖像被壓縮的 byte 數。
若是驅動須要與應用程序交換 image data 則必須支持 VIDIOC_ENUM_FMT 來列出全部驅動支持的 FMT 格式 :
[實際上, crop 是對取景進行限制,而 fmt 則是對最終保存下來的圖片屬性進行設置 ,若是取景後的圖片和要求的圖像屬性有衝突,就要將取景後的圖片進行相應的調整,好比放大,縮小等等 ]
看下面的例子:
Resetting the cropping parameters (A video capture device is assumed; change V4L2_BUF_TYPE_VIDEO_CAPTURE for other devices.) struct v4l2_cropcap cropcap; struct v4l2_crop crop; memset (&cropcap, 0, sizeof (cropcap)); cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (-1 == ioctl (fd, VIDIOC_CROPCAP, &cropcap)) { perror ("VIDIOC_CROPCAP"); exit (EXIT_FAILURE); } memset (&crop, 0, sizeof (crop)); crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; crop.c = cropcap.defrect; /* Ignore if cropping is not supported (EINVAL). */ if (-1 == ioctl (fd, VIDIOC_S_CROP, &crop) && errno != EINVAL) { perror ("VIDIOC_S_CROP"); exit (EXIT_FAILURE); } Simple downscaling (A video capture device is assumed.) struct v4l2_cropcap cropcap; struct v4l2_format format; reset_cropping_parameters (); /* Scale down to 1/4 size of full picture. */ memset (&format, 0, sizeof (format)); /* defaults */ format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; format.fmt.pix.width = cropcap.defrect.width >> 1; format.fmt.pix.height = cropcap.defrect.height >> 1; format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; if (-1 == ioctl (fd, VIDIOC_S_FMT, &format)) { perror ("VIDIOC_S_FORMAT"); exit (EXIT_FAILURE); } /* We could check the actual image size now, the actual scaling factor or if the driver can scale at all. */
另外還有一個可選的選項,就是若是採用 read/write 模式,還能夠經過設置流參數屬性來優化 capture 的性能,在這裏就不討論了,具體的能夠去參照 spec 。
完成這一系列參數的初始化之後,最後一個要協商的就是 I/0 模式的選擇:主要分爲兩種,一種是 Read/Write ,這也是打開 video device 以後默認選擇的 I/O 方法,其餘的方法若是使用必須通過協商;還有一個就是 stream ,其中 stream 中根據實現方式的不一樣又能夠分爲 Memory Mapping 和 User Pointers 。 Driver 能夠決定是否支持對 I/O 的 switch ,這不是必須的,若是 driver 不支持,則只有經過 open/close device 來實現 I/0 的切換。
首先來看 Read/Write ,若是 VIDIOC_QUERYCAP 調用返回的 v4l2_capability 參數中, V4L2_CAP_READWRITE 被設置成真了的話,就說明支持 Read/Write I/O 。這是最簡單最原始的方法,它須要進行數據 的拷貝 ( 而不是像 memory map 那樣只須要進行指針的交換 ) ,並且不會交換元數據 ( 好比說幀計數器和時間戳之類的可用於識別幀丟失和進行幀同步 ) ,雖然它是最原始的方法,但由於其簡單,因此對於簡單的應用 程序好比只須要 capture 靜態圖像是頗有用的 。
若是使用 Read/Write 方法支持的話,必須同時支持另外兩個函數 select() 和 poll() ,這兩個函數用來進行 I/0 的多路複用。
對於 streaming 它有兩種方式, driver 對兩種方式的支持要使用 VIDIOC_REQBUFS 來肯定:
int ioctl(int fd, int request, struct v4l2_requestbuffers *argp);
對於 memory mapped 方式, Memory mapped buffers 是經過 VIDIOC_REQBUFS 在 device memory 中申請的,並且必須在 map 進應用程序虛擬地址空間 以前就申請好。而對於 User pointers , User buffers 是在應用程序本身開闢的,只是經過 VIDIOC_REQBUFS 將驅動轉化到 user pointer 的 I/O 模式下。這兩種方式都不會拷貝數據,而只是 buffer 指針的交互。
首先來看一下 v4l2_requestbuffers 這個數據結構:
__u32 count // 要申請的 buffer 的數量,只有當 memory 被設置成 V4L2_MEMORY_MMAP 的時候纔會設置這個參數 enum v4l2_buf_type type enum v4l2_memory memory // 要麼是 V4L2_MEMORY_MMAP ,要麼是 V4L2_MEMORY_USERPTR
對於 memory mapped 模式,要在 device memory 下申請 buffer ,應用程序必須初始化上面的 3 個參數,驅動最後返回的 buffer 的個數可能等於 count ,也可能少於或者多於 count ,少於多是由於內存不足,多於則多是驅動爲更好地完成相應功能增長的 buffer 。若是 driver 不支持 memory mapped 調用這個 ioctl 就會返回 EINVAL 。
由於 memory map 模式下分配的是實實在在的物理內存,不是虛擬內存,因此使用完之後必定要使用 munmap() 釋放。
應用程序能夠從新調用 VIDICO_REQBUFS 來改變 buffer 的個數,但前提是必須先釋放已經 mapped 的 buffer ,能夠先 munmap ,而後設置參數 count 爲 0 來釋放全部的 buffer 。
對於 User pointer I/O ,應用程序只需設置上面的 type 和 memory 類型就能夠了。
申請好 buffer 後在進行 memory mapped 以前,首先要使用 VIDIOC_QUERYBUF 來得到分配的 buffer 信息,以傳給函數 mmap() 來進行 map :
int ioctl(int fd, int request, struct v4l2_buffer *argp);
VIDIOC_QUERYBUF 是 memory mapped 這種模式下使用的方法,在 User pointer 模式下不須要使用這個函數,在調用以前應用程序須要設定 v4l2_buffer 中的兩個參數,一個是 buffer 類型,另一個是 index number( 有效值從 0 到申請的 buffer 數目減 1) ,調用這個 ioctl 會將相應 buffer 中的 flag : V4L2_BUF_FLAG_MAPPED, V4L2_BUF_FLAG_QUEUED 和 V4L2_BUF_FLAG_DONE 設置爲有效。下面咱們來仔細看看 v4l2_buffer 這個數據結構:
__u32 index
// 應用程序來設定,僅僅用來申明是哪一個 buffer
enum v4l2_buf_type type
__u32 bytesused
//buffer 中已經使用的 byte 數,若是是 input stream 由 driver 來設定,相反則由應用程序來設定
__u32 flags
// 定義了 buffer 的一些標誌位,來代表這個 buffer 處在哪一個隊列,好比輸入隊列或者輸出隊列 (V4L2_BUF_FLAG_QUEUED V4L2_BUF_FLAG_DONE) ,是否關鍵幀等等,具體能夠參照 spec
enum v4l2_memory memory
//V4L2_MEOMORY_MMAP / V4L2_MEMORY_USERPTR / V4L2_MEMORY_OVERLAY
union m
__u32 offset
// 當 memory 類型是 V4L2_MEOMORY_MMAP 的時候,主要用來代表 buffer 在 device momory 中相對起始位置的偏移,主要用在 mmap() 參數中,對應用程序沒有左右
unsigned long userptr
// 當 memory 類型是 V4L2_MEMORY_USERPTR 的時候,這是一個指向虛擬內存中 buffer 的指針,由應用程序來設定。
__u32 length
//buffer 的 size
在 driver 內部管理 着兩個 buffer queues ,一個輸入隊列,一個輸出隊列。對於 capture device 來講,當輸入隊列中的 buffer 被塞滿數據之後會自動變爲輸出隊列,等待調用 VIDIOC_DQBUF 將數據進行處理之後從新調用 VIDIOC_QBUF 將 buffer 從新放進輸入隊列;對於 output device 來講 buffer 被顯示之後自動變爲輸出隊列。
剛初始化的全部 map 過的 buffer 開始都處於 dequeced 的狀態,由 driver 來管理對應用程序是不可訪問的。對於 capture 應用程序來講,首先是經過 VIDIOC_QBUF 將全部 map 過的 buffer 加入隊列,而後經過 VIDIOC_STREAMON 開始 capture ,並進入 read loop ,在這裏應用程序會等待直到有一個 buffer 被填滿能夠從隊列中 dequeued ,當數據使用完後再 enqueue 進輸入隊列;對於 output 應用程序來講,首先應用程序會 buffer 裝滿數據而後 enqueued ,當足夠的 buffer 進入隊列之後就調用 VIDIOC_STREAMON 將數據輸出。
有兩種方法來阻塞應用程序的執行,直到有 buffer 能被 dequeued ,默認的是當調用 VIDIOC_DQBUF 的時候會被阻塞,直到有數據在 outgoing queue ,可是若是打開設備文件 的時候使用了 O_NONBLOCK ,則當調用 VIDIOC_DQBUF 而又沒有數據可讀的時候就會當即返回。另一種方法是調用 select 和 poll 來對文件描述符進行監聽是否有數據可讀。
VIDIOC_STREAMON 和 VIDIOC_STREAMOFF 兩個 ioctl 用來開始和中止 capturing 或者 output ,並且 VIDIOC_STREAMOFF 會刪除輸入和輸出隊列中的全部 buffer 。
所以 drvier 若是要實現 memory mapping I/O 必須支持 VIDIOC_REQBUFS, VIDIOC_QUERYBUF, VIDIOC_QBUF, VIDIOC_DQBUF, VIDIOC_STREAMON 和 VIDIOC_STREAMOFF ioctl, the mmap(), munmap(), select() 和 poll() 函數 。
User Pointers 是一種綜合了 Read/Write 和 memory mappded 優點的 I/O 方法, buffer 是由應用程序本身申請的,能夠是在虛擬內存或者共享內存中。在 capture 和 output 方面基原本說和 memory mapped 方式是相同的,在這裏只提一下它申請內存的方式。
User pointer 方式下,申請的內存也 memory page size 爲單位對齊,並且 buffersize 也有必定限制,例示代碼中是這樣計算 buffer size 的,暫時還不知道這樣分配 buffer size 的依據是什麼,先簡單地這樣用就行了:
page_size = getpagesize ();
buffer_size = (buffer_size + page_size - 1) & ~(page_size – 1);
buffers[n_buffers].start = memalign (/* boundary */ page_size,
buffer_size);
通過上面的一系列的數據協商已經 buffer 的分配之後就能夠調用 VIDIOC_QBUF 將 buffer 所有加入輸入隊列中,並調用 VIDIOC_STREAM0N 開始捕獲數據了:
int ioctl(int fd, int request, struct v4l2_buffer *argp);
//VIDIOC_QBUF VIDIOC_DQBUF
int ioctl(int fd, int request, const int *argp);
//VIDIOC_STREAM0N VIDIOC_STREAMOFF ( int 參數是 buffer 類型)
開始捕獲數據之後就會進入一個主循環,可使用 select 或者 poll 來監聽文件描述符的狀態,一旦有數據可讀,就調用函數來讀取數據。
讀取數據根據 I/O 方式的不一樣而不一樣:
Read/Write 方式直接從文件描述符中讀一個幀大小的數據;
Memory mapped 方式下先從輸出隊列中 dequeued 一個 buffer ,而後對幀數據進行處理,處理完成之後再放入輸入隊列。
User pointer 方式下也是首先從輸出隊列中 dequeued 一個 buffer ,而後對這個 buffer 進行判斷,看是不是應用程序開始申請的 buffer ,而後再對這個 buffer 進行處理,最後放入輸入隊列。
最後就是捕捉以及資源釋放並關閉 device 。