V4L2子系統


1       V4L2架構簡述

V4L2Linux社區定義的Linux內核的多媒體框架, 本質上來說它就是一個字符設備, 然后社區定義了一系列標准的ioctl來與內核交互.

1.1    框圖

 

首先注意框圖的實線部分, 對應的是只需要驅動片上外設的情景, 例如mtkvdec, 或者atmellcd 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_devicev4l2_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交互了.

 

核心層在注冊字符設備時, 給定的opsv4l2_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, 代表數組中的哪些itemskip.

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 mtkvdec.

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個接口: registeredunregisteredopenclose. 4個接口是給v4l2核心層代碼回調用的. 當向核心層注冊/注銷subdev, 核心層會回調這里的registered/unregistered. 當用戶空間打開/關閉設備節點時, 核心層會回調這里的open/close.

3.1.3      v4l2 async related

頭文件 : include/media/v4l2-async.h

 

async機制有點類似於設備模型中devicedriver的匹配過程.

 

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)subdevv4l2_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的注冊/注銷APIv4l2_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_eventv4l2_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操作外接硬件.

 

除此之外, 這個外接硬件也可能直接接在I2CSPI這些簡單的片上外設上, 比如radio, 這種情況下就沒有必要針對這些簡單的外設創建設備節點, 而是直接針對外接硬件創建設備節點.

當然, 在片上外設+外接硬件的設計中, 也可以針對外接硬件創建設備節點. 這樣既可以通過控制器代碼間接操作外接硬件, 也可以通過subdev設備節點之間操作外接硬件.

 

API的作用就是針對v4l2_dev下掛載的所有v4l2_subdev, 如果subdev.flags設置了V4L2_SUBDEV_FL_HAS_DEVNODE標志, 則為該subdev創建一個設備節點(設備節點的創建過程其實就是注冊video_device.

 

subdev_do_ioctlv4l2規定的ioctl轉換成v4l2_subdev定義了v4l2_subdev_ops (subdev定義了幾組(core/tuner/audio/video/…, 每組都有各自的function ).

5       內存管理與訪問

5.1    概述

前面幾小節介紹了很多關於控制方面的結構體和API, 本節主要介紹數據方面的內容.

 

所謂數據, 其實就是與內存相關的問題. 最終的目的是使得內核與用戶空間可方便的交換數據. V4L2支持三種不同的數據交換方式:

         readwrite 借助字符設備的read/write接口, 這種方式數據需要在內核和用戶之間拷貝, 訪問速度會比較慢. 涉及到大量的數據交換時, 不要用這種方式, 會影響性能.

         內存映射緩沖區(V4L2_MEMORY_MMAP) : 是在內核空間開辟緩沖區,應用通過mmap()系統調用映射到用戶地址空間。這些緩沖區可以是大而連續DMA緩沖區、通過vmalloc()創建的虛擬緩沖區,或者直接在設備的IO內存中開辟的緩沖區(如果硬件支持).

         用戶空間緩沖區(V4L2_MEMORY_USERPTR) : 用戶空間中開辟緩沖區,用戶與內核空間之間交換緩沖區指針。很明顯,在這種情況下是不需要mmap()調用的,但驅動需要額外的設計, 以便使之可訪問用戶空間內存.

 

Readwrite方式屬於幀IO訪問方式, 每一幀都需要用戶和內核之間數據拷貝; 而后兩種是流IO訪問方式, 不需要內存拷貝, 訪問速度比較快.

內存映射緩沖區訪問方式是比較常用的方式.

 

Videobuf管理: videobuf2-core.cvideobuf2-dma-contig.cvideobuf2-dma-sg.cvideobuf2-memops.cvideobuf2-vmalloc.cv4l2-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的方式:mmapuserptretc. 詳見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.cvideobuf2-dma-sg.cvideobuf-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)

VBv4l2核心層傳遞給驅動

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的情況在來細究.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM