v4l2讀取攝像頭程序流程解析

v4l2 操作實際上就是 open() 設備, close() 設備,以及中間過程的 ioctl() 操作。對於 ioctl 的調用,要注意對 errno 的判斷,如果調用被其他信號中斷,即 errno 等於 EINTR 的時候,要重新調用。

Video capture device 的實際功能就是採集視頻信號,並將數字化的圖像保存在 memory 中,現在幾乎上所有的相關設備都能採集 25/30 幀 /s 。在下面的討論中,我只列舉出一些和 camera 密切相關的一些屬性和方法。

1 、 open_device

打開設備一般都是使用 open() 打開 /dev 下的 video 設備文件 ,比如說 /dev/video1, 打開之前首先要對相應的設備文件進行檢查 ,比如說使用 stat() 獲得文件屬性,並判斷是否爲字符設備文件。

驅動通過主設備號 81 和 0 ~ 255 之間的次設備號來註冊 device note ,系統 管理 員通過設備的主次設備號在 /dev 目錄下創建相應的字符設備文件。應用 程序不能通過設備的主次設備號來打開設備,而必須通過適當的 device name ,即 /dev 目錄下的設備文件來打開設備。

v4l2 支持一個設備文件可以被多次打開,卻只允許其中一個應用程序與設備進行數據 交換 ,其他應用程序只能用來設定一些設備參數,對設備進行一些控制。

//====== 相關 spec : http://v4l2spec.bytesex.org/spec/c174.htm#OPEN

2 、 init_device

對設備進行初始化是一個很複雜的過程,其中要進行一系列參數的協商,其中重要的包括 v4l2_capability, v4l2_cropcap, v4l2_format 等等。

2.1 VIDIOC_QUERYCAP查詢能力

首先使用 VIDIOC_QUERYCAP 命令 來獲得當前設備的各個屬性,查看設備對各項功能的支持程度:

int ioctl(int fd, int request, struct v4l2_capability *argp);

所有的 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

 2.2 設置設備參數

獲得 device 的 capability 以後,可以根據應用程序的功能要求對設備參數進行一系列的設置 ,這些參數又分爲兩部分,一個是 user contrl ,還一個是 extended control

2.2.1 設置user contrl 的參數

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

2.2.2 擴展控制

除了 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

2.3 獲取設備對Image Cropping 和Scaling的支持信息

對各種控制參數進行設置以後,下面要進行的就是要獲得設備對 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

 2.4 設置圖像格式

設置好取景窗口參數以後,下面要進行的設置就是對圖形格式的協商,這個 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 。

 2.5 I/O模式的選擇

完成這一系列參數的初始化以後,最後一個要協商的就是 I/0 模式的選擇:主要分爲兩種,一種是 Read/Write ,這也是打開 video device 之後默認選擇的 I/O 方法,其他的方法如果使用必須經過協商;還有一個就是 stream ,其中 stream 中根據實現方式的不同又可以分爲 Memory Mapping 和 User Pointers 。 Driver 可以決定是否支持對 I/O 的 switch ,這不是必須的,如果 driver 不支持,則只有通過 open/close device 來實現 I/0 的切換。

2.5.1 read/write

首先來看 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




2.5.2 內存映射

對於 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() 函數 。

 2.5.3 用戶空間指針

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

 

3 、 start_capturing

經過上面的一系列的數據協商已經 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 類型)

 

4 、 mainloop

開始捕獲數據以後就會進入一個主循環,可以使用 select 或者 poll 來監聽文件描述符的狀態,一旦有數據可讀,就調用函數來讀取數據。

 

5 、 read_frame

讀取數據根據 I/O 方式的不同而不同:

Read/Write 方式直接從文件描述符中讀一個幀大小的數據;

Memory mapped 方式下先從輸出隊列中 dequeued 一個 buffer ,然後對幀數據進行處理,處理完成以後再放入輸入隊列。

User pointer 方式下也是首先從輸出隊列中 dequeued 一個 buffer ,然後對這個 buffer 進行判斷,看是否是應用程序開始申請的 buffer ,然後再對這個 buffer 進行處理,最後放入輸入隊列。

6 、 stop_capturing / uninit_device / close device

最後就是捕捉以及資源釋放並關閉 device 。

            </div>
        </div>
發佈了14 篇原創文章 · 獲贊 9 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章