1 V4L2架構簡述
V4L2是Linux社區定義的Linux內核的多媒體框架, 本質上來說它就是一個字符設備, 然后社區定義了一系列標准的ioctl來與內核交互.
1.1 框圖
首先注意框圖的實線部分, 對應的是只需要驅動片上外設的情景, 例如mtk的vdec, 或者atmel的lcd overlay. 這種情況下struct video_device做為字符設備驅動響應用戶空間的ioctl. 在注冊struct video_device時, 必須創建一個struct v4l2_device(雖然這種情況下它沒什么卵用).
這種情形在《video_device》一節中細述.
其次注意框圖的實線+虛線部分, 對應的是需要驅動片上外設+外接硬件的情景, 典型的如Camera, 片上外設是Camera控制器(例如atmel-isc.c), 外接硬件是sensor(例如ov2640.c). 這種情況下還是用struct video_device來抽象片上外設, 但同時會新增struct v4l2_subdev來抽象外接硬件. 用戶空間的ioctl先傳遞至片上外設對應的驅動代碼, 然后片上外設代碼通過v4l2_subdev_call調用v4l2_subdev提供的相關接口操作外接硬件.
這種情形下,一個片上外設可能有多個外接硬件, 每個外接硬件都對應一個v4l2_subdev, 所有的subdev都鏈接在同一個v4l2_device下. 通過v4l2_device, 我們可以遍歷所有的sbudev. v4l2_device此時相當於所以subdev的父設備.
《v4l2_subdev》一節主要描述這種情形.
還有一種不太常見的情形, 例如收音機, 這種情形下片上外設很簡單(就是I2C), 我們主要是想驅動外接硬件radio. 此時就不需要針對片上外設創建字符設備了(也就是不需要注冊針對片上外設的video_device), 我們可以直接把subdev以字符設備的形式暴露給用戶空間, 用戶空間在ioctl時直接發給subdev處理.
《v4l2_subdev作為字符設備》一節描述這種情形.
1.2 關系圖
2 video_device
2.1 核心數據結構
2.1.1 struct video_device
頭文件 : include/media/v4l2-dev.h
向V4L2子系統注冊一個video_device意味着用戶空間在/dev/下會多出一個字符設備節點. 在注冊前, 需要准備好如下數據結構, 這樣一旦注冊成功, 底層驅動就可以響應用戶空間請求了.
需要准備的數據結構包括:
struct v4l2_file_operations *fops
頭文件 : include/media/v4l2-dev.h
這個數據結構之於video_device的作用與struct file_operations之於cdev的作用一模一樣. 主要目的是響應用戶空間的open/read/write/ioctl/close等請求.
struct v4l2_ioctl_ops *ioctl_ops
頭文件 : include/media/v4l2-ioctl.h
用戶空間的ioctl請求最終會轉發到這里來處理.
用戶空間能發送哪些ioctl是由V4L2標准規定的, 因此這里的v4l2_ioctl_ops需要實現的接口函數也是與v4l2標准一一對應的. 具體可參考頭文件定義.
struct list_headfh_list
設備節點每被打開一次, V4L2核心層就會生成一個struct v4l2_fh(詳見后文介紹)與之對應. 因此一個video_device下可能會有多個v4l2_fh. 這里的鏈表頭fh_list用於掛載所有隸屬於本video_device的v4l2_fh.
struct v4l2_ctrl_handler *ctrl_handler
頭文件 : include/media/v4l2-ctrls.h
詳見《v4l2 control related》
struct v4l2_prio_state *prio
頭文件 : include/media/v4l2-dev.h
作用暫不知.
struct vb2_queue *queue
v4l2_ioctl_ops主要提供控制接口, 而這里的vb2_queue則主要提供數據接口. 它與memory的管理密切相關, memory管理在V4L2中是一個比較龐大的框架, 因此在《1.5 內存管理與訪問》中專門介紹.
2.1.2 struct v4l2_fh
頭文件 : include/media/v4l2-fh.h
設備節點每被打開一次, V4L2核心層就會生成一個struct v4l2_fh.
這個數據結構的意義:
一方面是存儲”runtime”相關的一些信息, 例如它可以存儲當前被打開的這個”實例”有多少event需要被dequeued, 當前這個”實例”的優先級.
另一方面, 它有點類似C++中的派生類, 可以override ‘struct video_device’ 中的一些信息, 例如兩者都定義了struct v4l2_ctrl_handler, 系統優先使用v4l2_fh -> v4l2_ctrl_handler.
2.1.3 v4l2 control related
在V4L2架構中, 用戶空間與內核主要是通過ioctl交互的. V4L2標准定義了交互的’協議’.
針對下表所列的幾種CTRL類型的ioctl (這些ioctl主要是與硬件相關的一些設置, 例如亮度、飽和度、對比度和清晰度等), V4L2內核代碼實現了一套框架, 稱之為”v4l2 control”.
IOCTL |
Func |
VIDIOC_QUERYCTRL |
v4l_queryctrl |
VIDIOC_QUERY_EXT_CTRL |
v4l_query_ext_ctrl |
VIDIOC_QUERYMENU |
v4l_querymenu |
VIDIOC_G_CTRL |
v4l_g_ctrl |
VIDIOC_S_CTRL |
v4l_s_ctrl |
VIDIOC_G_EXT_CTRLS |
v4l_g_ext_ctrls |
VIDIOC_S_EXT_CTRLS |
v4l_s_ext_ctrls |
VIDIOC_TRY_EXT_CTRLS |
v4l_try_ext_ctrls |
當然你也可以選擇不使用這套框架, 這樣使用的就是原來的v4l2_ioctl_ops框架. 這塊的處理代碼典型的如下:
如果使用”v4l2 control”框架, 則優先調用v4l2_fh ->ctrl_handler; 其次是video_device ->ctrl_handler.
否則, 使用v4l2_ioctl_ops框架, 調用底層驅動實現的vidioc_xxx函數.
在”v4l2 control”框架中, 內核系統為我們處理了很多公共的邏輯, 這樣需要我們自己編寫的代碼就很簡單了. 因此推薦優先使用此框架.
”v4l2 control”框架相關的幾個核心數據結構如下:
頭文件 : include/media/v4l2-ctrls.h
struct v4l2_ctrl
struct v4l2_ctrl_ops
v4l2_ctrl代表一個屬性(屬性名 / 取值范圍、默認值、當前值).
v4l2_ctrl_ops代表此屬性的操作方法(獲取屬性值/嘗試設置屬性值/真正設置屬性值).
struct v4l2_ctrl_handler
相當於一個鏈表, 掛載所有的v4l2_ctrls.
struct video_device中有一個指針指向這個鏈表, 它的意義是指在向系統注冊video_device時准備好這樣一條鏈表, 注冊完畢后用戶空間就可以直接使用v4l2 control相關功能了.
struct v4l2_fh中也有一個指針指向這個鏈表, 當用戶空間open設備節點時, 這個指針會默認指向video_device ->v4l2_ctrl_handler. 不過內核驅動可以隨后准備一條新的鏈表, 然后讓v4l2_fh ->v4l2_ctrl_handler指向這條鏈表. 此即所謂override.
那到底該如何構建這樣一條鏈表呢? 詳見《v4l2 control APIs》.
2.2 APIs
2.2.1 video_device APIs
struct video_device *video_device_alloc(void)
分配video_device存儲空間.
static inline int __must_check video_register_device(struct video_device *vdev, …)
向核心層注冊一個video_device, 此時核心層會創建字符設備驅動. 后面用戶空間就可以通過字符設備節點與video_device交互了.
核心層在注冊字符設備時, 給定的ops是v4l2_fops, 它會把來至用戶空間的請求轉發給video_device->v4l2_file_operations.
2.2.2 v4l2 control APIs
頭文件 : include/media/v4l2-ctrls.h
實現文件 : drivers/media/v4l2-core/v4l2-ctrls.c
v4l2_ctrl_handler_init(struct v4l2_ctrl_handler *hdl, unsigned int nr_of_controls_hint)
初始化v4l2_ctrl_handler鏈表, @nr_of_controls_hint代表鏈表的最大容量
void v4l2_ctrl_handler_free(struct v4l2_ctrl_handler *hdl)
清除v4l2_ctrl_handler, 釋放相關資源
struct v4l2_ctrl *v4l2_ctrl_new_std(struct v4l2_ctrl_handler *hdl, const struct v4l2_ctrl_ops *ops, u32 id, s64 min, s64 max, u64 step, s64 def)
向鏈表中新增一個非菜單式的控制變量. @min是最小值, @max是最大值, @step是步進長度, @def是默認值.
這個函數適用於在某一范圍內均勻變化的控制變量, 例如聲音.
struct v4l2_ctrl *v4l2_ctrl_new_std_menu(struct v4l2_ctrl_handler *hdl, const struct v4l2_ctrl_ops *ops, u32 id, u8 max, u64 mask, u8 def)
向鏈表中新增一個菜單式控制變量. 與上一個API類似, @max是最大值, @def是默認值. 區別在於@min is set to 0 and the @mask value determines which menu items are to be skipped.
這個函數適用於從0開始, 步進為1的控制變量.
struct v4l2_ctrl *v4l2_ctrl_new_int_menu(struct v4l2_ctrl_handler *hdl, const struct v4l2_ctrl_ops *ops, u32 id, u8 max, u8 def, const s64 *qmenu_int)
與上一個API類似, 不過有顯著區別, 首先@qmenu_int是一個數組, 里面存儲着這個菜單所有的可選值; 其次@max不是代表可取的最大值, 而是代表數組索引的最大值. @def則是代表默認的索引值.
這個函數適用於變量值是不連續的無規則的整數的控制變量.
struct v4l2_ctrl *v4l2_ctrl_new_std_menu_items(struct v4l2_ctrl_handler *hdl, const struct v4l2_ctrl_ops *ops, u32 id, u8 max, u64 mask, u8 def, const char * const *qmenu)
與上一個API很類似, 區別在於@qmenu不是整形數組, 而是字符串數組. 另外它還多了一個@mask, 代表數組中的哪些item被skip.
int v4l2_ctrl_add_handler(struct v4l2_ctrl_handler *hdl, struct v4l2_ctrl_handler *add, v4l2_ctrl_filter filter)
把@add中所有的控制變量都添加到@hdl中.
int v4l2_ctrl_handler_setup(struct v4l2_ctrl_handler *hdl)
此API的調用時可選的. 每個控制變量都是自己的默認值, 此API的目的是把所有控制變量的默認值設置到硬件里面.
了解了這些API, 在來看如何構造一個v4l2_ctrl_handler鏈表就很簡單了. 這里不贅述了.
2.3 ioctl
用戶空間對V4L2設備的操作基本都是ioctl來實現的, ioctl框架是由v4l2_ioctl.c文件實現, 文件中定義結構體數組v4l2_ioctls, 可以看做是ioctl指令和回調函數的關系表. 用戶空間調用系統調用ioctl, 傳遞下來ioctl指令, 然后通過查找此關系表找到對應回調函數.
2.4 Demo
Ref mtk的vdec.
3 v4l2_subdev
subdev其實不算V4L2框架的一部分, 可能是因為它是在V4L2出現后才新增的. 它定義了一套自己的接口函數struct v4l2_subdev_ops, 所有的subdev驅動都要實現這套函數.
不過這套函數與V4L2定義的ioctl標准是基本兼容的, 我們可以把用戶空間的標准v4l2 ioctl調用映射到v4l2_subdev_ops, 例如subdev_do_ioctl就做了這種映射.
3.1 核心數據結構
3.1.1 struct v4l2_device
頭文件 : include/media/v4l2-device.h
v4l2_device中有一個元素是’struct kref ref ’, 因此它的第一個功能是可作為引用計數.
另外, 如果系統中存在v4l2_subdev, 那v4l2_device就充當所有v4l2_subdev的父設備, 管理着注冊在其下的子設備.
3.1.2 struct v4l2_subdev
頭文件 : include/media/v4l2-subdev.h
struct v4l2_subdev用於抽象一個子設備(外接硬件). 它會掛載到v4l2_device鏈表下.
除了這個結構體外, 我們還需要一些ops用於描述如何操作硬件, 它們是:
struct v4l2_subdev_ops
struct v4l2_subdev_core_ops
struct v4l2_subdev_tuner_ops
struct v4l2_subdev_audio_ops
struct v4l2_subdev_video_ops
struct v4l2_subdev_vbi_ops
struct v4l2_subdev_ir_ops
struct v4l2_subdev_sensor_ops
struct v4l2_subdev_pad_ops
v4l2_subdev_ops及相關的幾個ops, 主要是用來描述如何操作硬件的, 比如設置寄存器等.
struct v4l2_subdev_internal_ops
v4l2_subdev_internal_ops提供了4個接口: registered、unregistered、open、close. 這4個接口是給v4l2核心層代碼回調用的. 當向核心層注冊/注銷subdev時, 核心層會回調這里的registered/unregistered. 當用戶空間打開/關閉設備節點時, 核心層會回調這里的open/close.
3.1.3 v4l2 async related
頭文件 : include/media/v4l2-async.h
async機制有點類似於設備模型中device與driver的匹配過程.
以camera為例, 硬件組成為片上camera控制器和外接sensor. 在控制器相關的代碼里面可以通過async機制等待外接sensor初始化, 當sensor初始化代碼被調用時, 會通過async通知控制器代碼, 然后控制器代碼就可以開始創建設備節點(也就是注冊video_device設備)等.
從代碼細節上看, 控制器代碼首先要定義一個或多個v4l2_async_subdev, 每個代表控制器想與哪個sensor進行匹配, 多個就組成了一個匹配列表. 然后控制器代碼需要實現v4l2_async_notifier_operations中定義回調函數. 最后控制器代碼把這兩者一起封裝成一個v4l2_async_notifier.
struct v4l2_async_subdev
struct v4l2_async_notifier_operations
struct v4l2_async_notifier
數據結構准備好后, 控制器代碼就可調用v4l2_async_notifier_register把這個notifier注冊到系統.
當subdev初始化時, 它會調用v4l2_async_notifier_register, 例如ov2640.c, 該函數會掃描notifier鏈表, 並進行匹配(匹配規則詳見v4l2_async_find_match). 當匹配成功后, 會調用v4l2_device_register_subdev(v4l2_dev, sd)把subdev與v4l2_dev關聯起來. 最后會調用notifier中定義的complete回調函數, 主控器代碼在該函數中會注冊video_device設備. 最終, 用戶空間就會出現設備節點了.
之后, 當用戶空間通過ioctl操作設備節點時, 主控器代碼會調用v4l2_subdev_call(sd, o, f, args...)來調用subdev中定義的ops函數, 從而實現對subdev的控制.
3.2 APIs
3.2.1 v4l2_device APIs
頭文件 : include/media/ v4l2-device.h
實現文件 : drivers/media/v4l2-core/v4l2-device.c
int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
調用者自行分配一個v4l2_device空間, 然后調用此API對新分配的空間進行初始化.
void v4l2_device_unregister(struct v4l2_device *v4l2_dev)
注銷v4l2_device以及它下面掛載的所有v4l2_subdev.
int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev, struct v4l2_subdev *sd)
向v4l2_device注冊一個subdev.
void v4l2_device_unregister_subdev(struct v4l2_subdev *sd)
注銷一個subdev: 將其從v4l2_device下移除, 如果此subdev已經創建了設備節點, 則調用video_unregister_device注銷節點.
static inline void v4l2_device_get(struct v4l2_device *v4l2_dev)
增加引用計數.
int v4l2_device_put(struct v4l2_device *v4l2_dev)
減少引用計數.
static inline void v4l2_subdev_notify(struct v4l2_subdev *sd, unsigned int notification, void *arg)
subdev可通過此API通知v4l2_device有事件產生了.
3.2.2 v4l2_subdev APIs
subdev的注冊/注銷API為v4l2_device_register_subdev/ v4l2_device_unregister_subdev. 詳見《v4l2_devices APIs》一節的描述.
另外, v4l2_subdev自身還定義了如下API:
頭文件 : include/media/v4l2-subdev.h
實現文件 : drivers/media/v4l2-core/v4l2-subdev.c
void v4l2_subdev_init(struct v4l2_subdev *sd, const struct v4l2_subdev_ops *ops)
初始化v4l2_subdev空間.
#define v4l2_subdev_call(sd, o, f, args...)
調用v4l2_subdev_xxx_ops中定義的函數.
#define v4l2_subdev_has_op(sd, o, f)
檢查v4l2_subdev_xxx_ops中是否定義了某個函數.
void v4l2_subdev_notify_event(struct v4l2_subdev *sd, const struct v4l2_event *ev)
用戶空間可以通過ioctl VIDIOC_DQEVENT來等待某個v4l2_event, 此時v4l2核心層代碼會調用v4l2_event_dequeue, block並等待事件產生.
當subdev檢測到event產生時(此時一般會有中斷發生), subdev中斷處理函數會調用v4l2_subdev_notify_event. v4l2_subdev_notify_event里面會做兩件事:
一是調用v4l2_event_queue, 此時會喚醒用戶空間的block, 並把事件傳送給用戶空間.
二是調用v4l2_subdev_notify(詳見《v4l2 device APIs》), 通知v4l2_device有事件產生了.
3.2.3 v4l2 async APIs
頭文件 : include/media/v4l2-async.h
實現文件 : drivers/media/v4l2-core/v4l2-async.c
int v4l2_async_notifier_register(struct v4l2_device *v4l2_dev, struct v4l2_async_notifier *notifier)
片上控制器相關代碼調用此API注冊一個notifier.
void v4l2_async_notifier_unregister(struct v4l2_async_notifier *notifier)
片上控制器相關代碼調用此API注銷一個notifier.
int v4l2_async_register_subdev(struct v4l2_subdev *sd)
外接硬件相關代碼調用此API向系統注冊一個v4l2_subdev, 同時也會觸發一次與notifier的匹配過程, 如果匹配上了則調用v4l2_async_notifier_operations中定義的回調函數.
void v4l2_async_unregister_subdev(struct v4l2_subdev *sd)
外接硬件相關代碼調用此API注銷一個v4l2_subdev.
3.3 Demo
Ref Atmel Camera controller code : atmel-isc.c.
4 v4l2_subdev 作為字符設備
int v4l2_device_register_subdev_nodes(struct v4l2_device *v4l2_dev)
前文說過, 用戶空間是通過設備節點與內核交互的.
每個subdev都代表一個外接硬件, 這外接硬件可能是接在某個片上控制器上, 比如camera, 這種情況下我們需要針對這個控制器創建一個設備節點, 然后在控制器的代碼中通過v4l2_subdev_call來操作外接硬件.
除此之外, 這個外接硬件也可能直接接在I2C、SPI這些簡單的片上外設上, 比如radio, 這種情況下就沒有必要針對這些簡單的外設創建設備節點, 而是直接針對外接硬件創建設備節點.
當然, 在片上外設+外接硬件的設計中, 也可以針對外接硬件創建設備節點. 這樣既可以通過控制器代碼間接操作外接硬件, 也可以通過subdev設備節點之間操作外接硬件.
此API的作用就是針對v4l2_dev下掛載的所有v4l2_subdev, 如果subdev.flags設置了V4L2_SUBDEV_FL_HAS_DEVNODE標志, 則為該subdev創建一個設備節點(設備節點的創建過程其實就是注冊video_device).
subdev_do_ioctl會把v4l2規定的ioctl轉換成v4l2_subdev定義了v4l2_subdev_ops (subdev定義了幾組(core/tuner/audio/video/…), 每組都有各自的function ).
5 內存管理與訪問
5.1 概述
前面幾小節介紹了很多關於控制方面的結構體和API, 本節主要介紹數據方面的內容.
所謂數據, 其實就是與內存相關的問題. 最終的目的是使得內核與用戶空間可方便的交換數據. V4L2支持三種不同的數據交換方式:
read和write : 借助字符設備的read/write接口, 這種方式數據需要在內核和用戶之間拷貝, 訪問速度會比較慢. 涉及到大量的數據交換時, 不要用這種方式, 會影響性能.
內存映射緩沖區(V4L2_MEMORY_MMAP) : 是在內核空間開辟緩沖區,應用通過mmap()系統調用映射到用戶地址空間。這些緩沖區可以是大而連續DMA緩沖區、通過vmalloc()創建的虛擬緩沖區,或者直接在設備的IO內存中開辟的緩沖區(如果硬件支持).
用戶空間緩沖區(V4L2_MEMORY_USERPTR) : 是在用戶空間中開辟緩沖區,用戶與內核空間之間交換緩沖區指針。很明顯,在這種情況下是不需要mmap()調用的,但驅動需要額外的設計, 以便使之可訪問用戶空間內存.
Read和write方式屬於幀IO訪問方式, 每一幀都需要用戶和內核之間數據拷貝; 而后兩種是流IO訪問方式, 不需要內存拷貝, 訪問速度比較快.
內存映射緩沖區訪問方式是比較常用的方式.
Videobuf管理: 由videobuf2-core.c、videobuf2-dma-contig.c、videobuf2-dma-sg.c、videobuf2-memops.c、videobuf2-vmalloc.c、v4l2-mem2mem.c等文件實現, 完成videobuffer的分配、管理和注銷.
5.2 核心數據結構
struct vb2_queue
為了使設備支持流IO這種方式, 驅動需要實現struct vb2_queue, 結構體細節如下:
頭文件 : include/media/videobuf2-core.h
Struct vb2_queue |
Comment |
unsigned int type |
private buffer type whose content is defined by the vb2-core caller. For example, for V4L2, it should match the types defined on @enum v4l2_buf_type |
unsigned int io_modes |
訪問IO的方式:mmap、userptr、etc. 詳見enum vb2_io_modes |
… |
|
const struct vb2_ops*ops |
針對隊列的操作函數集合, 例如入隊列、出隊列等. |
const struct vb2_mem_ops*mem_ops |
針對mem的操作函數集合, 例如內存分配與釋放等. |
const struct vb2_buf_ops*buf_ops |
callbacks to deliver buffer information between user-space and kernel-space. |
… |
|
struct vb2_buffer *bufs[VB2_MAX_FRAME] |
每個vb2_buffer代表一塊內存, 這是存儲池 |
unsigned int num_buffers |
池子里面實際有多少個buf |
… |
|
vb2_queue代表一個videobuffer隊列, vb2_buffer是這個隊列中的基本單位, vb2_mem_ops是內存的操作函數集, vb2_ops用來管理隊列.
struct vb2_buffer
一個vb2_buffer代表內核空間的一塊videobuffer. 一個vb2_queue中會有多個這樣的buffer.
頭文件 : include/media/videobuf2-core.h
Struct vb2_buffer |
Comment |
struct vb2_queue *vb2_queue |
指向該buf所隸屬的vb2_queue |
unsigned int memory |
the method, in which the actual data is passed |
… |
|
enum vb2_buffer_state state |
一個buffer的多種狀態 |
一塊buffer可能有多種狀態, 常見的情形如下:
在驅動的傳入隊列中(VB2_BUF_STATE_QUEUED): 驅動程序將會對此隊列中的緩沖區進行處理, 用戶空間通過IOCTL:VIDIOC_QBUF把緩沖區放入到隊列. 對於一個視頻捕獲設備, 傳入隊列中的緩沖區是空的, 驅動會往其中填充數據.
在驅動的傳出隊列中(VB2_BUF_STATE_DONE): 這些緩沖區已由驅動處理過,對於一個視頻捕獲設備,緩存區已經填充了視頻數據,正等用戶空間來認領
用戶空間狀態的隊列(VB2_BUF_STATE_DEQUEUED) : 已經通過IOCTL:VIDIOC_DQBUF傳出到用戶空間的緩沖區, 此時緩沖區由用戶空間擁有, 驅動無法訪問.
這三種狀態的切換如下圖所示:
struct v4l2_buffer
一個v4l2_buffer在用戶空間代表一塊videobuf, 它與vb2_buffer一一對應. 不過需要注意的是, vb2_buffer里面包含了實際的存儲空間; 但v4l2_buffer只是包含一些buffer info, 通過這些info, 用戶空間可以用mmap或其它方式拿到實際的存儲地址, 從而避免在用戶空間與內核之間做數據拷貝.
頭文件 : include/uapi/linux/videodev2.h
Struct v4l2_buffer |
Comment |
__u32index |
buffer序號 |
__u32type |
buffer類型, 見@enum v4l2_buf_type |
__u32bytesused |
緩沖區已使用byte數 |
__u32 flags |
|
__u32 field |
|
struct timeval timestamp |
時間戳,代表幀捕獲的時間 |
struct v4l2_timecode timecode |
|
__u32 sequence |
|
/*memory location */ __u32 memory |
表示緩沖區是內存映射緩沖區還是用戶空間緩沖區 |
union { __u32 offset; unsigned long userptr; struct v4l2_plane *planes; __s32 fd; } m; |
//offset代表內核緩沖區的位置 //userptr代表緩沖區的用戶空間地址 |
__u32 length |
緩沖區大小, 單位byte |
當用戶空間拿到v4l2_buffer, 可以獲取到緩沖區的相關信息. Byteused是圖像數據所占的字節數, 如果是V4L2_MEMORY_MMAP方式, m.offset是內核空間圖像數據存放的開始地址, 會傳遞給mmap函數作為一個偏移, 通過mmap映射返回一個緩沖區指針p, p+byteused是圖像數據在進程的虛擬地址空間所占區域; 如果是用戶指針緩沖區的方式, 可以獲取的圖像數據開始地址的指針m.userptr, userptr是一個用戶空間的指針, userptr+byteused便是所占的虛擬地址空間, 應用可以直接訪問.
struct vb2_mem_ops
vb2_mem_ops包含了內存映射緩沖區、用戶空間緩沖區的內存操作方法:
頭文件 : include/media/videobuf2-core.h
Struct vb2_mem_ops |
Comment |
void *(*alloc)(……) |
分配視頻緩存 |
void (*put)(void *buf_priv) |
釋放視頻緩存 |
void *(*get_userptr)(……) |
獲取用戶空間視頻緩沖區指針 |
void (*put_userptr)(void *buf_priv) |
釋放用戶空間視頻緩沖區指針 |
void (*prepare)(void *buf_priv) void (*finish)(void *buf_priv) void *(*vaddr)(void *buf_priv) void *(*cookie)(void *buf_priv) |
用於緩存同步 |
unsigned int (*num_users)(void *buf_priv) |
返回當期在用戶空間的buffer數 |
int (*mmap)(void *buf_priv, struct vm_area_struct *vma) |
把緩沖區映射到用戶空間 |
這是一個相當龐大的結構體, 這么多的結構體需要實現還不得累死, 幸運的是內核都已經幫我們實現了. 提供了三種類型的視頻緩存區操作方法: 連續的DMA緩沖區、集散的DMA緩沖區以及vmalloc創建的緩沖區, 分別由videobuf2-dma-contig.c、videobuf2-dma-sg.c和videobuf-vmalloc.c文件實現, 可以根據實際情況來使用.
struct vb2_ops
vb2_ops是用來管理buffer隊列的函數集合, 包括隊列和緩沖區初始化:
頭文件 : include/media/videobuf2-core.h
Struct vb2_ops |
Comment |
int(*queue_setup)(struct vb2_queue *q, const struct v4l2_format *fmt, unsigned int *num_buffers, unsigned int*num_planes, unsigned int sizes[], void *alloc_ctxs[]); |
隊列初始化 |
void (*wait_prepare)(struct vb2_queue *q) void (*wait_finish)(struct vb2_queue *q) |
釋放和獲取設備操作鎖 |
int (*buf_init)(struct vb2_buffer *vb) int (*buf_prepare)(struct vb2_buffer *vb) void (*buf_finish)(struct vb2_buffer *vb) void (*buf_cleanup)(struct vb2_buffer *vb) |
對buffer的操作 |
int (*start_streaming)(struct vb2_queue *q, unsigned int count) |
開始視頻流 |
int (*stop_streaming)(struct vb2_queue *q) |
停止視頻流 |
void(*buf_queue)(struct vb2_buffer *vb) |
把VB從v4l2核心層傳遞給驅動 |
6 Demo : 用戶空間訪問設備
下面通過內核映射緩沖區方式訪問視頻設備(CaptureDevice)的流程.
1> 打開設備文件
fd = open(dev_name, O_RDWR /* required */ | O_NONBLOCK, 0);
dev_name[/dev/videoX]
2> 查詢設備支持的能力
struct v4l2_capability cap;
ioctl(fd, VIDIOC_QUERYCAP, &cap)
3> 設置視頻捕獲格式
fmt.type= V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 640;
fmt.fmt.pix.height = 480;
fmt.fmt.pix.pixelformat= V4L2_PIX_FMT_YUYV; //像素格式
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
ioctl(fd,VIDIOC_S_FMT, & fmt)
4> 向驅動申請緩沖區
struct v4l2_requestbuffers req;
req.count= 4; //緩沖個數
req.type= V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory= V4L2_MEMORY_MMAP;
if(-1 == ioctl(fd, VIDIOC_REQBUFS, &req))
5> 獲取每個緩沖區的信息,映射到用戶空間
struct buffer {
void *start;
size_t length;
} *buffers;
buffers = calloc(req.count, sizeof(*buffers));
for (n_buffers= 0; n_buffers < req.count; ++n_buffers) {
struct v4l2_buffer buf;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = n_buffers;
if (-1 ==ioctl(fd, VIDIOC_QUERYBUF, & buf))
errno_exit("VIDIOC_QUERYBUF");
buffers[n_buffers].length= buf.length;
buffers[n_buffers].start= mmap(NULL /* start anywhere */,
buf.length,
PROT_READ | PROT_WRITE /* required */,
MAP_SHARED /* recommended */,
fd, buf.m.offset);
}
6> 把緩沖區放入到傳入隊列上, 打開流IO, 開始視頻采集
for (i =0; i < n_buffers; ++i) {
struct v4l2_buffer buf;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (-1 == ioctl(fd, VIDIOC_QBUF, &buf))
errno_exit("VIDIOC_QBUF");
}
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (-1 == ioctl(fd, VIDIOC_STREAMON, & type))
7> 調用select監測文件描述符, 緩沖區的數據是否填充好, 然后對視頻數據
for (;;) {
fd_set fds;
struct timeval tv;
int r;
FD_ZERO(&fds);
FD_SET(fd,&fds);
/* Timeout. */
tv.tv_sec = 2;
tv.tv_usec = 0;
//監測文件描述是否變化
r = select(fd + 1,& fds, NULL, NULL, & tv);
if (-1 == r) {
if (EINTR == errno)
continue;
errno_exit("select");
}
if (0 == r) {
fprintf(stderr,"select timeout\n");
exit(EXIT_FAILURE);
}
//對視頻數據進行處理
if (read_frame())
break;
/* EAGAIN - continueselect loop. */
}
8> 出已經填充好的緩沖, 獲取到視頻數據的大小, 然后對數據進行處理. 這里取出的緩沖只包含緩沖區的信息, 並沒有進行視頻數據拷貝
buf.type= V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory= V4L2_MEMORY_MMAP;
if (-1 == ioctl(fd, VIDIOC_DQBUF, & buf)) //取出緩沖
errno_exit("VIDIOC_QBUF");
process_image(buffers[buf.index].start, buf.bytesused); //視頻數據處理
if (-1 == ioctl(fd, VIDIOC_QBUF, & buf)) //然后又放入到傳入隊列
errno_exit("VIDIOC_QBUF");
9> 停止視頻采集
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd, VIDIOC_STREAMOff, & type);
10> 關閉設備
close(fd);
7 Soc Camera
當前的代碼中, soc_camera貌似沒什么代碼在使用了, 好像kernel正在逐步移除它. Camera相關的場景現在都是之間使用的v4l2框架.
后續項目中如果遇到使用soc_camera的情況在來細究.