1 V4L2應用程式設計基礎
1.1 概述
- V4L2應用程式設計需要使用如下系統調用,
open(): 打開V4L2裝置
close(): 關閉V4L2裝置
ioctl(): 向V4L2裝置驅動程式發送控制指令
mmap(): 将V4L2裝置驅動程式配置設定的緩沖區記憶體映射到使用者空間
read()或write(): 這2個系統調用是否支援取決于流傳輸方法
- V4L2應用程式設計所需的宏定義和資料結構可通過如下頭檔案包含,
#include <linux/videodev2.h>
說明1:videodev2.h頭檔案路徑
應用程式和核心驅動都需要包含videodev2.h頭檔案,才能夠確定使用者态和核心态資料結構一緻,其中,
① 使用者态頭檔案由工具鍊提供
② 核心态頭檔案為include/uapi/linux/videodev2.h,但是相應的驅動程式中隻需要包含<linux/videodev2.h>即可,該頭檔案會包含uapi目錄下的頭檔案
說明2:V4L2 ioctl控制指令按如下方式定義,詳細分析在後文的程式分步驟詳解中進行,這種控制指令定義方式可參考Linux裝置驅動基礎02:Linux核心子產品 chapter 3.6
1.2 UVC Camera圖像采集程式
1.2.1 概述
- 實驗硬體裝置為飛淩imx8mp開發闆 + uvc camera
- 本示例用于說明V4L2應用程式設計的基本架構,以及如何擷取并儲存single-planar格式的圖像資料
1.2.2 程式分步驟詳解
1.2.2.1 打開裝置節點
說明1:列印資訊
說明2:在目前實驗環境中,UVC camera對應的裝置節點為/dev/video4
說明3:對于V4L2裝置,也可以在open函數中指定O_NONBLOCK标志,即以非阻塞模式打開。在非阻塞模式下,如果在進行ioctl + VIDIOC_DQBUF操作時沒有已經就緒的緩沖區,則應用程式不會阻塞,而是立即傳回-1,并且将errno置為EAGAIN
1.2.2.2 擷取裝置能力
說明1:列印資訊
說明2:v4l2_capability結構體
說明3:VIDIOC_QUERYCAP控制指令
① VIDIOC_QUERYCAP指令用于向驅動程式查詢裝置能力
② 指令參數傳遞方向從核心态到使用者态,使用者态需要傳遞v4l2_capability結構體位址,驅動程式會向其中填充裝置能力資訊
說明4:capabilities和device_cap字段的關系
① 一個實體裝置可以導出多個裝置節點供使用者态使用(e.g. 一個實體裝置可以同時導出/dev/videoX、/dev/vbiY和/dev/radioZ),capabilities字段就是描述給實體裝置作為一個整體的能力
② device_cap字段則是描述目前打開裝置的能力(e.g. 上述實體裝置導出的/dev/videoX裝置節點的能力)
說明5:capabilities和device_cap字段的構成
capabilities和device_cap字段由一系列标志位構成,此處簡要說明如下概念的含義,
① single-planar和multi-planar用于描述圖像類型的存儲方式,相關概念可參考視訊技術基礎01:圖像基礎和前處理 chapter 2
② capture和output用于描述裝置資料流向,capture的資料是從裝置到記憶體(e.g. 視訊采集裝置),output的資料是從記憶體到裝置(e.g. 視訊輸出裝置)
③ memory-to-memory也是用于描述裝置的資料流向,即資料從記憶體到裝置再到記憶體,典型的就是圖像codec的資料流向(e.g. 待編碼的圖像來自記憶體,編碼後的碼流也輸出到記憶體)
說明6:實驗硬體裝置能力分析
① capabilities字段值為0x84a00001,對應标志位如下,
V4L2_CAP_DEVICE_CAPS | 裝置支援device_caps字段 隻有capabilities字段設定該标志位,才表示驅動會設定device_caps字段 |
V4L2_CAP_STREAMING | 裝置支援流式IO |
V4L2_CAP_EXT_PIX_FORMAT | 裝置支援格式擴充字段,使用方式詳見chapter 1.2.2.5 |
V4L2_CAP_META_CAPTURE | 裝置支援擷取中繼資料 |
V4L2_CAP_VIDEO_CAPTURE | 裝置支援single-planar格式圖像擷取 |
② device_caps字段值為0x04200001,對應标志位如下,
V4L2_CAP_STREAMING | 裝置支援流式IO |
V4L2_CAP_EXT_PIX_FORMAT | 裝置支援格式擴充字段 |
V4L2_CAP_VIDEO_CAPTURE | 裝置支援single-planar格式圖像擷取 |
說明7:擷取到裝置能力後,常見的操作是對其進行檢查,以確定裝置支援我們需要使用的模式
說明8:使用CLEAR宏清零ioctl參數
① 示例程式在使用cap變量之前,先使用CLEAR宏将其清零,進而避免結構體中原有内容的幹擾
② 始終将提供給V4L2 API的參數清零是一種很好的做法
1.2.2.3 枚舉裝置格式
說明1:列印資訊
可見實驗硬體裝置支援MJPEG和YUYV輸出格式
說明2:v4l2_fmtdesc結構體
說明2:VIDIOC_ENUM_FMT控制指令
① VIDIOC_ENUM_FMT指令用于枚舉驅動程式支援的圖像格式
② 指令參數傳遞方向為雙向傳遞,
- 使用者态在傳遞v4l2_fmtdesc結構體位址時,需要設定其中的type和index字段
- 驅動程式會填充v4l2_fmtdesc結構體的其他字段
③ 枚舉圖像格式時,index字段總是從0開始。當枚舉完成時,ioctl系統調用傳回-1,并将errno設定為EINVAL
說明4:關于緩沖區類型
① 緩沖區類型由v4l2_buf_type枚舉值指定,具體枚舉值如下,
② 應用程式設定的緩沖區類型主要用于在驅動中索引vb2_queue結構體
從實作邏輯上來說,是驅動程式先指定vb2_queue結構體的緩沖區類型,之後應用程式需要指定相應的緩沖區類型才能索引到正确的vb2_queue結構體
說明5:關于格式标志flags
① 格式标志flags由一系列标志位構成,具體如下,
② 實驗硬體裝置支援格式的标志flags字段值如下,
- MJPEG格式的flags字段值為0x1,即MJPEG格式是一種壓縮格式
- YUYV格式的flags字段值為0x0,即YUYV格式既不是壓縮格式,也不是軟體模拟格式
說明6:關于fourcc格式編碼
① 在V4L2中,圖像格式使用V4L2_PIX_FMT_開頭的宏定義辨別,其核心是将描述圖像格式的4個字元編碼為一個4B無符号整數,也就是fourcc編碼
② fourcc編碼的具體方式如下,
1.2.2.4 枚舉指定格式分辨率
說明1:列印資訊
示例程式中分别周遊了裝置支援的MJPEG和YUYV格式分辨率
說明2:v4l2_frmsizeenum結構體
說明3:VIDIOC_ENUM_FRAMESIZES控制指令
① VIDIOC_ENUM_FRAMESIZES指令用于枚舉驅動程式支援的指定格式的分辨率
② 指令參數傳遞方向為雙向傳遞,
- 使用者态在傳遞v4l2_frmsizeenum結構體位址時,需要設定其中的index和pixel_format字段
- 驅動程式會填充v4l2_frmsizeenum結構體的其他字段
③ 枚舉指定格式分辨率時,index字段總是從0開始。當枚舉完成時,ioctl系統調用傳回-1,并将errno設定為EINVAL
說明4:關于分辨率類型
分辨率類型由v4l2_frmsizetypes枚舉值指定,具體枚舉值如下,
① 離散型分辨率:裝置支援的分辨率由一系列離散值構成
② 階梯型分辨率:裝置支援的分辨率在指定的範圍内以步長為機關遞增
③ 連續型分辨率:連續型分辨率可以了解為階梯型分辨率的特例,即步長為1的階梯型分辨率
1.2.2.5 設定圖像格式
在擷取了裝置支援的格式和分辨率之後,就可以在該範圍内根據應用程式的需要設定圖像格式
說明1:列印資訊
可見應用程式中設定的不是裝置支援的分辨率,但是驅動程式會對其進行修改,将其設定為與應用設定值最接近的裝置支援的分辨率
說明2:v4l2_format結構體
根據不同的緩沖區類型,會使用不同的圖像格式結構體,其中v4l2_pix_format結構體用于描述sigle-planar格式
說明3:VIDIOC_S_FMT控制指令
① VIDIOC_S_FMT指令用于設定裝置的圖像格式
② 指令參數傳遞方向為雙向傳遞,
- 使用者态在傳遞v4l2_format結構體位址時,需要設定其中的type字段,并且可以設定與之對應的圖像格式結構體中的所有字段(e.g. 如果type字段設定為V4L2_BUF_TYPE_VIDEO_CAPTURE,則可以設定v4l2_pix_format結構體中的字段)
- 驅動程式會傳回目前實際設定的圖像格式
③ 驅動程式會根據裝置特性對應用程式傳遞的圖像格式進行修改(e.g. 應用程式傳遞了裝置不支援的分辨率),是以應用程式需要檢查VIDIOC_S_FMT指令傳回的實際參數
④ 驅動程式隻有在應用程式設定的type字段無效時才會傳回錯誤,其他情況下都會根據裝置特性進行處理
說明4:為了驗證上述分析,我們将圖像格式設定為裝置不支援的YUV420格式,可見驅動程式會将其修正為裝置支援的MJPEG格式
說明5:圖像存儲方式
① YUYV格式的存儲方式和采樣方式如下圖所示,詳情可參考2.7.12. V4L2_PIX_FMT_YUYV (‘YUYV’) ‒ The Linux Kernel documentation
② 示例程式在設定了圖像格式為YUYV,分辨率為1280 * 720的情況下,驅動程式填充了bytesperline和sizeimage字段,其中,
- bytesperline = 2560(= 1280 * 2,YUYV格式存儲每個像素需要2B)
- sizeimage = 1843200(= 1280 * 720 * 2,YUYV格式存儲每個像素需要2B)
說明6:如果将圖像格式設定為MJPEG,由于碼流沒有跨距的概念,是以驅動程式将bytesperline字段設定為0;而sizeimage字段也按照1280 * 720 * 2的方式計算,這是因為MJPEG壓縮格式的圖像大小肯定不會大于YUYV格式的圖像
說明7:VIDIOC_G_FMT控制指令
① V4L2提供的VIDIOC_G_FMT指令用于擷取裝置目前圖像格式
② VIDIOC_G_FMT指令傳遞的參數結構體與VIDIOC_S_FMT指令相同,但是應用程式隻需要設定type字段,可見VIDIOC_G_FMT指令擷取的圖像格式與VIDIOC_S_FMT指令傳回的圖像格式相同
說明8:VIDIOC_TRY_FMT控制指令
① V4L2提供的VIDIOC_TRY_FMT指令用于嘗試應用程式要設定的圖像格式是否可行,驅動程式也會根據裝置特性對應用程式傳遞的圖像格式進行修改
② VIDIOC_TRY_FMT指令和VIDIOC_S_FMT指令的唯一差別就是他不會修改驅動程式的狀态,也就是說VIDIOC_TRY_FMT指令隻是嘗試設定而不會真正設定圖像格式
1.2.2.6 申請緩沖區
說明1:列印資訊
可見此處實際配置設定的緩沖區個數與應用程式申請的個數相同
說明2:v4l2_requestbuffers結構體
說明3:VIDIOC_REQBUFS控制指令
① VIDIOC_REQBUFS指令用于向驅動程式申請用于存儲圖像資料的緩沖區
② 指令參數傳遞方向為雙向傳遞,
- 使用者态在傳遞v4l2_requestbuffers結構體位址時,需要設定其中的count、type和memory字段
- 驅動程式會根據裝置特性修改使用者設定的count字段,将其設定為實際配置設定的緩沖區數量;同時會根據驅動程式支援的緩沖區記憶體IO模式設定capabilities字段
③ 由于驅動程式實際配置設定的緩沖區數量可能多于或少于應用程式請求的數量,是以應用程式應在系統調用傳回後檢查count字段,以確定該值符合需求
④ 如果執行VIDIOC_REQBUFS指令時将count字段設定為0,表示釋放已配置設定的所有緩沖區
說明4:緩沖區記憶體類型
① 首先需要說明的是,緩沖區記憶體類型是本文中使用的名字,在Linux核心中也被稱作IO mode,例如核心中的vb2_queue結構體中就通過io_modes字段描述相關屬性
緩沖區記憶體類型這個名字主要展現緩沖區記憶體的配置設定方式,IO mode這個名字主要展現對緩沖區記憶體的通路方式
② 需要特别說明的是,在V4L2架構中緩沖區管理結構體的配置設定和緩沖區記憶體的配置設定是可以分離的。在執行VIDIOC_REQBUFS指令時,緩沖區管理結構體總是被配置設定的(對應核心中的vb2_buffer結構體),但是緩沖區記憶體的配置設定時機則是可配置的,這也就引出了緩沖區記憶體類型的概念
③ 緩沖區記憶體類型被設定為v4l2_memory枚舉值,具體含義如下,
其中,隻有mmap類型才會在執行VIDIOC_REQBUFS指令時,既配置設定緩沖區管理結構體,也配置設定緩沖區記憶體
④ 從IO mode的角度,mmap / userptr / dmabuf類型都屬于流式通路,與之相對的是read / write通路方式
說明5:關于capabilities字段
① capabilities字段由驅動根據自身支援的IO mode進行設定
② capabilities字段可由如下标志位位或得到,具體含義如下,
③ 示例程式執行VIDIOC_REQBUFS指令後傳回的capabilities值為0x17,即相應驅動程式支援mmap / userptr / dmabuf / orphaned_bufs
說明6:VIDIOC_REQBUFS指令是應用程式判斷驅動程式支援哪些緩沖區記憶體類型的唯一方法,如果驅動程式不支援應用程式設定的緩沖區記憶體類型,ioctl系統調用會傳回-1,并将errno置為EINVAL
1.2.2.7 映射緩沖區
我們首先以最常用的mmap方式使用記憶體緩沖區,
說明1:列印資訊
① bytesused字段為0,因為目前緩沖區記憶體中還沒有圖像資料
② offset字段每個緩沖區不同,對于示例程式設定的YUYV格式和分辨率,offset字段從0開始,以每幀圖像的大小為步長。該字段供驅動程式在處理mmap映射時,索引到對應的緩沖區
說明2:緩沖區記憶體在使用者态的管理結構體
① 一般在應用程式中需要設計相關資料結構來管理緩沖區記憶體資訊
② 示例程式中定義的user_buf結構體兼顧了不同圖像格式的plane數量以及不同的緩沖區記憶體類型
說明3:v4l2_buffer結構體
v4l2_buffer結構體用于描述緩沖區資訊,操作緩沖區的控制指令均會傳遞v4l2_buffer結構體類型參數,包括VIDIOC_QUERYBUF / VIDIOC_QBUF / VIDIOC_DQBUF / VIDIOC_PREPARE_BUF等
說明4:VIDIOC_QUERYBUF控制指令
① VIDIOC_QUERYBUF指令用于擷取緩沖區的狀态資訊
② 指令參數傳遞方向為雙向傳遞,
- 對于single-planar圖像格式,使用者态在傳遞v4l2_buffer結構體位址時,需要設定index和type字段
- 驅動程式會根據緩沖區目前狀态填充其餘字段
③ 對于multi-planar圖像格式,需要應用程式準備v4l2_plane數組,并且将數組位址設定到planes字段,具體使用方法詳見後文示例
1.2.2.8 将緩沖區加入隊列
對于圖像采集應用程式,習慣上在開始采集并進入讀取循環之前将一定數量的空緩沖區加入隊列(在大多數情況下,就是配置設定的緩沖區數量)。這有助于提高應用程式的流暢性,并防止應用程式因為缺少填充的緩沖區而被阻塞,該操作一般在配置設定緩沖區之後立即進行
說明1:V4L2緩沖區管理概述
① 對于一個核心态的vb2_queue結構體,V4L2架構會維護2個緩沖區隊列,
- queued_list:list of buffers currently queued from userspace
由應用程式送出給驅動程式使用的緩沖區隊列
e.g. 對于圖像采集裝置,應用程式将緩沖區通過VIDIOC_QBUF操作加入queued_list,以便填充資料
- done_list:list of buffers ready to be dequeued to userspace
驅動程式處理完成,送出給應用程式使用的緩沖區隊列
e.g. 對于圖像采集裝置,驅動程式會将填充好資料的緩沖區加入done_list,等待應用程式通過VIDIOC_DQBUF操作取出
② 緩沖區在queued_list和done_list隊列之間的流轉狀态如下,
- 剛配置設定的緩沖區既不在queued_list,也不在done_list
- 應用程式通過VIDIOC_QBUF操作,将緩沖區加入queued_list,以便驅動程式使用
- 驅動程式從queued_list擷取緩沖區使用,在操作完成後(e.g. 填充完圖像後),将緩沖區再加入done_list
- 應用程式通過VIDIOC_DQBUF操作,将緩沖區同時從done_list和queued_list隊列中取出使用
說明2:VIDIOC_QBUF控制指令
① VIDIOC_QBUF指令供應用程式将準備好的緩沖區送出給驅動程式,V4L2架構會将其加入queued_list隊列,
- 對于輸入緩沖區(capture資料流向裝置),送出的是空緩沖區
- 對于輸出緩沖區(output資料流向裝置),送出的是填充好的緩沖區
② 指令參數傳遞方向為雙向傳遞,
- 使用者态在傳遞v4l2_buffer結構體位址時,通用地需要設定其中的index、type和memory字段。根據不同的資料流向和緩沖區記憶體類型,還需要設定相應字段
- 對于輸出緩沖區(output資料流向裝置),需要設定bytesused字段,以辨別有效資料長度
- 如果緩沖區記憶體類型為userptr,需要設定userptr和length字段
- 如果緩沖區記憶體類型為dmabuf,需要設定fd字段,而length字段無需設定
- 如果緩沖區記憶體類型為mmap,并不需要設定offset字段(因為核心态V4L2架構可根據index字段索引到相應的offset),也不需要設定length字段(核心态V4L2架構會維護該字段)
- 對于multi-planar圖像格式,需要應用程式準備v4l2_plane數組,并改為設定v4l2_plane結構體中的相應字段
說明3:将緩沖區加入隊列會将緩沖區的記憶體頁鎖定在實體記憶體中,這樣這些頁面就不會被交換到磁盤中,以便于硬體裝置通路(通常是DMA操作)
1.2.2.9 開啟流傳輸
說明:VIDIOC_STREAMON控制指令
① VIDIOC_STREAMON指令用于打開流傳輸
② 指令參數傳遞方向從使用者态到核心态,使用者态需要傳遞存儲緩沖區類型的整型變量位址,驅動程式将使用該緩沖區類型索引vb2_queue結構體
1.2.2.10 采集圖像
說明1:列印資訊
可見驅動程式設定的前2個sequence字段分别為0和2,後續則按1累加(這可能是程式處理耗時較長導緻的丢幀)
說明2:VIDIOC_DQBUF控制指令
① VIDIOC_DQBUF指令供應用程式将驅動程式準備就緒的緩沖區取出隊列,
- 對于輸入緩沖區(capture資料流向裝置),取出的是驅動程式填充好的緩沖區
- 對于輸出緩沖區(output資料流向裝置),取出的是驅動程式使用完畢的緩沖區
② 指令參數傳遞方向為雙向傳遞,
- 使用者态在傳遞v4l2_buffer結構體位址時,無論緩沖區記憶體類型和資料流向,隻需要設定其中的type和memory字段
- 驅動程式會設定v4l2_buffer結構體的其餘字段
- 對于multi-planar圖像格式,需要應用程式準備v4l2_plane數組,且将數組位址設定到planes字段
③ 在進行VIDIOC_DQBUF操作時,如果沒有準備就緒的緩沖區,預設會将調用程序阻塞。如果在打開裝置節點時設定O_NONBLOCK标志,則ioctl會立即傳回-1,并将errno置為EAGAIN
說明4:儲存圖像
① 在将緩沖區中的圖像寫入檔案時,應該使用bytesused字段,該字段辨別緩沖區中有效資料的長度
② 對于YUV類型圖像格式,由于是非壓縮類型,緩沖區中有效資料長度和緩沖區長度是一緻的。如果将示例程式中的格式改為MJPEG這類壓縮類型,則每幀圖像有效資料的長度是不同的(但是都不會超過緩沖區長度)
說明5:緩沖區重新加入隊列
① 在将緩沖區中的有效資料寫入檔案後,可以将該緩沖區重新加入隊列,用于繼續擷取圖像
② 将緩沖區再次加入隊列時,v4l2_buffer結構體中包含了之前驅動程式設定的byteused等字段。但是這不會導緻問題,因為V4L2架構不會處理使用者态傳遞的這些字段
說明6:使用poll函數等待緩沖區就緒
① poll函數用于實作多路複用的IO模型,可以通過該函數監聽多個檔案描述符的狀态,poll函數原型如下,
/*
* fds: 要監聽的檔案描述符及事件數組(pollfd結構體數組)
* nfds: 監聽的檔案描述符個數,即fds數組的成員個數
* timeout: 監聽逾時時間,以ms為機關
* 傳回值:
* 有事件發生,傳回revents字段不為0的檔案描述符個數
* 等待逾時,傳回0
* 發生錯誤,傳回-1,并設定errno
*/
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
其中pollfd結構體如下,
② 将示例程式修改為使用poll函數等待緩沖區就緒,效果如下,
1.2.2.11 關閉流傳輸
此處在關閉流傳輸之後,也關閉了裝置節點
說明:VIDIOC_STREAMOFF控制指令
① VIDIOC_STREAMOFF指令用于關閉流傳輸,該指令會釋放所有緩沖區
② 指令參數傳遞方向從使用者态到核心态,使用者态需要傳遞存儲緩沖區類型的整型變量位址,驅動程式将使用該緩沖區類型索引vb2_queue結構體
1.2.3 其他緩沖區記憶體類型操作
1.2.3.1 ION記憶體配置設定示例
為了驗證userptr和dmabuf緩沖區記憶體類型,我們通過ION機制配置設定的實體連續記憶體進行驗證。下面給出ION記憶體配置設定的示例程式,并按步驟分析
1.2.3.1.1 打開裝置節點
說明:列印資訊
1.2.3.1.2 查詢ION可用的heap個數
說明1:列印資訊
可見ION可用的heap個數為3
說明2:每個ION可用的heap對應/sys/kernel/debug/ion目錄下的一個子目錄
在每個子目錄中,可擷取如下資訊,
① num_of_buffers:目前通過ION配置設定的buffer個數
② num_of_alloc_bytes:目前通過ION配置設定buffer的總位元組數
③ alloc_bytes_wm:通過ION配置設定記憶體的總位元組數水位線(watermark),即記錄曆史上最高的總位元組數
1.2.3.1.3 擷取heap資訊
此處根據上一步擷取的heap個數配置設定ion_heap_data結構體數組,用于擷取heap資訊。可見此處使用的也是ION_IOC_HEAP_QUERY指令,隻是之前将ion_heap_query.heaps字段設定為NULL,此時驅動程式不會拷貝heap資訊
說明1:列印資訊
可見通過ION_IOC_HEAP_QUERY操作擷取的heap資訊與/sys/kernel/debug/ion目錄下的資訊是比對的
說明2:示例程式中通過heap_id變量計算ION_HEAP_TYPE_DMA類型heap的掩碼,供後續配置設定記憶體時使用
1.2.3.1.4 配置設定實體連續記憶體
說明1:列印資訊
可見ION配置設定的記憶體是以dma_buf的方式提供給應用程式
說明2:通過查詢/sys/kernel/debug/ion/linux,cma目錄下的節點,可見相關資訊與示例程式運作狀态比對
1.2.3.1.5 擷取配置設定記憶體的實體位址
通過DMA_BUF_IOCTL_PHYS操作,可以根據ION配置設定記憶體的dma_buf fd擷取配置設定記憶體的實體位址
說明1:列印資訊
說明2:實驗環境ION linux,cma heap所在區間
① 實驗環境實體記憶體資訊如下,
- 起始位址為0x4000 0000
- 大小為0x1 0000 0000(4GB)
- 實體記憶體範圍為0x4000 0000 ~ 0x1 4000 0000
② 裝置樹中對linux,cma heap記憶體的設定如下,
- 起始位址由作業系統在0x4000 0000 ~ 0xC000 0000之間選擇
- 大小為0x3C00 0000(960MB)
- 根據核心啟動階段的列印資訊,linux,cma heap實體記憶體範圍為0xC400 0000 ~ 0x1 0000 0000,可見示例程式配置設定記憶體的實體位址在該範圍中
1.2.3.1.6 将配置設定記憶體映射到使用者空間
如果想在應用程式中操作ION配置設定的記憶體,需要将其映射到程序的使用者空間
說明1:列印資訊
說明2:檢視ion_test程序的虛拟位址空間布局,可見配置設定了128MB的虛拟記憶體區(vm_area_struct)用于映射dam_buf,示例程式中傳回的正是該虛拟記憶體區的起始位址
說明3:經過驗證,關閉ION檔案節點不會釋放已配置設定的記憶體,相關記憶體将在程序結束時被釋放
說明4:實作alloc_ion_buffer函數
① 在示例程式的基礎上,封裝實作alloc_ion_buffer函數用于後續驗證,該函數原型如下,
/*
* fd[IN]: ion裝置節點檔案描述符
* len[IN]: 要配置設定的記憶體大小
* dmabuf_fd[OUT]: 配置設定記憶體的dma_buf fd
* paddr[OUT]: 配置設定記憶體的實體位址
* vaddr[OUT]: 配置設定記憶體的虛拟位址
* 傳回值: 成功傳回0,錯誤傳回負值錯誤碼
*/
int alloc_ion_buffer(int fd, int len, int *dmabuf_fd,
unsigned long *paddr, unsigned long *vaddr);
② alloc_ion_buffer函數的實作和測試用例如下,
1.2.3.2 userptr緩沖區記憶體操作
說明:本節僅記錄與mmap類型不同的操作步驟
1.2.3.2.1 申請緩沖區
說明:列印資訊
可見此處申請的緩沖區記憶體類型為V4L2_MEMORY_USERPTR
1.2.3.2.2 擷取緩沖區狀态
通過VIDIOC_QUERYBUF操作擷取各緩沖區狀态,此處需要記錄緩沖區記憶體大小,供後續配置設定緩沖區記憶體時使用
說明:列印資訊
可見此時userptr字段尚未設定,字段值為0
1.2.3.2.3 配置設定緩沖區記憶體
說明:列印資訊
1.2.3.2.4 将緩沖區加入隊列
對于userptr類型緩沖區記憶體,需要length和userptr字段
說明:關于length字段的正确設定
① 如果将length字段設定為0,VIDIOC_QBUF操作會傳回EINVAL錯誤
② 如果設定length字段的值超過實際配置設定的記憶體長度,VIDIOC_QBUF操作會傳回EFAULT錯誤
③ 如果設定length字段的值小于實際配置設定的記憶體長度,VIDIOC_QBUF操作會傳回EINVAL錯誤
1.2.3.2.5 采集圖像
說明1:列印資訊
最終采集圖像的效果與使用mmap類型緩沖區記憶體相同
說明2:在将緩沖區重新加入隊列時,由于v4l2_buffer結構體中已經包含了正确的index / type / memory / userptr / length字段,是以無需重新設定
1.2.3.3 dmabuf緩沖區記憶體操作
說明:本節僅記錄與mmap類型不同的操作步驟
1.2.3.3.1 申請緩沖區
說明:列印資訊
1.2.3.2.2 擷取緩沖區狀态
通過VIDIOC_QUERYBUF操作擷取各緩沖區狀态,此處也需要記錄緩沖區記憶體大小,供後續配置設定緩沖區記憶體時使用
說明:列印資訊
可見此時fd字段尚未設定,字段值為0
1.2.3.3.3 配置設定緩沖區記憶體
此處與userptr類型緩沖區記憶體的配置設定是相同的,隻是在使用者态記錄的相關資訊不同
說明:列印資訊
1.2.3.3.4 将緩沖區加入隊列
說明1:列印資訊
經過驗證,對于目前實驗使用的UVC Camera驅動在對dmabuf類型的緩沖區進行VIDIOC_QBUF操作時會報錯
說明2:錯誤分析
① 通過如下2條指令啟用V4L2架構調試,然後重新執行示例程式,即可得到相關報錯資訊
echo 0x3 > /sys/module/videobuf2_v4l2/parameters/debug
echo 0x3 > /sys/module/videobuf2_common/parameters/debug
② 對照核心代碼,錯誤位置如下。可見在進行attach_dmabuf操作時沒有報錯,但是在進行map_dmabuf操作時映射失敗
③ 上述代碼中調用的是vb2_queue.mem_ops操作函數集中的回調函數,根據uvc驅動代碼,此處設定的操作函數集為vb2_vmalloc_memops(同時可見此處設定了mmap / userptr / dmabuf三種io mode)
④ 在相關函數添加列印資訊,可見因為dma_buf.ops->vmap回調函數設定為NULL,是以dma_buf_vmap函數傳回NULL,是以映射失敗
⑤ 分析ion核心代碼,可見确實沒有設定vmap回調函數,是以也就不支援在vb2_vmalloc_memops中實作dmabuf到vaddr的映射
1.2.3.4 補充:VIDIOC_EXPBUF控制指令
介紹VIDIOC_EXPBUF控制指令主要是為了與上文的dmabuf類型的緩沖區記憶體進行辨析
- v4l2_exportbuffer結構體
- VIDIOC_EXPBUF控制指令
① VIDIOC_EXPBUF指令是mmap類型緩沖區記憶體的擴充指令,用于将已經申請的mmap類型緩沖區記憶體導出為dmabuf檔案描述符供其他驅動程式使用,是以VIDIOC_EXPBUF指令隻能用于mmap類型緩沖區記憶體
② 指令參數傳遞方向為雙向傳遞,
- 使用者态在傳遞v4l2_exportbuffer結構體位址時,需要設定其中的type、index和plane字段(如果對flags字段沒有特别要求,可以就設定為0)
- 驅動程式會根據導出的dmabuf檔案描述符設定fd字段
- 示例程式
① 導出single-planar格式緩沖區
② 導出multi-planar格式緩沖區
1.3 MIPI Camera圖像采集程式
1.3.1 概述
- 實驗硬體為飛淩imx8mp開發闆 + ov5645 mipi camera
- 本示例在上一章示例的基礎上,說明如何擷取并儲存multi-planar格式的圖像資料
1.3.2 程式分步驟詳解
1.3.2.1 打開裝置節點
說明1:列印資訊
說明2:在目前實驗環境中,MIPI Camera對應的裝置節點為/dev/video3
1.3.2.2 擷取裝置能力
說明1:列印資訊
說明2:實驗硬體裝置能力分析
① capabilities字段值為0x84201000,對應标志位如下,
V4L2_CAP_DEVICE_CAPS | 裝置支援device_caps字段 隻有capabilities字段設定該标志位,才表示驅動會設定device_caps字段 |
V4L2_CAP_STREAMING | 裝置支援流式IO |
V4L2_CAP_EXT_PIX_FORMAT | 裝置支援格式擴充字段,使用方式詳見chapter 1.2.2.5 |
V4L2_CAP_VIDEO_CAPTURE_MPLANE | 裝置支援multi-planar格式圖像擷取 |
② device_caps字段值為0x4201000,對應标志位如下,
V4L2_CAP_STREAMING | 裝置支援流式IO |
V4L2_CAP_EXT_PIX_FORMAT | 裝置支援格式擴充字段,使用方式詳見chapter 1.2.2.5 |
V4L2_CAP_VIDEO_CAPTURE_MPLANE | 裝置支援multi-planar格式圖像擷取 |
1.3.2.3 枚舉裝置格式
說明:列印資訊
為了驗證multi-planar格式的圖像采集,後續将圖像格式設定為2 plane的NV12格式
1.3.2.4 枚舉指定格式分辨率
說明:列印資訊
1.3.2.5 設定圖像格式
說明1:列印資訊
說明2:v4l2_pix_format_mplane結構體
說明3:圖像存儲方式
① NV12格式的存儲方式和采樣方式如下圖所示,詳情可參考2.7.24. V4L2_PIX_FMT_NV12 (‘NV12’), V4L2_PIX_FMT_NV21 (‘NV21’) ‒ The Linux Kernel documentation
② 示例程式在設定了圖像格式為NV12,分辨率為1920 * 1080的情況下,驅動程式填充了2個plane的sizeimage和bytesperline字段,其中,
- Y分量
bytersperline = 1920 (= 1920 * 1,存儲每個Y分量需要1B)
sizeimage = 2073600(= 1920 * 1080 * 1,存儲每個Y分量需要1B)
- UV分量
bytersperline = 1920 (= 1920 / 2 * 2,UV分量水準采樣率是Y分量的一半,存儲一組UV分量需要2B)
sizeimage = 1036800(= 1920 * 1080 / 2 / 2 * 2,UV分量水準采樣率和垂直采樣率都是是Y分量的一半,存儲一組UV分量需要2B)
1.3.2.6 申請緩沖區
說明1:列印資訊
說明2:示例程式執行VIDIOC_REQBUFS指令後傳回的capabilities值為0x17,即相應驅動程式支援mmap / userptr / dmabuf / orphaned_bufs
1.3.2.7 映射緩沖區
說明:列印資訊
1.3.2.8 将緩沖區加入隊列
1.3.2.9 開啟流傳輸
1.3.2.10 采集圖像
說明1:列印資訊
說明2:将緩沖區重新加入隊列時,v4l2_buffer結構體和v4l2_plane數組中均包含需要設定參數的正确值,是以可以不做處理直接加入
說明3:關于sequence字段
① 示例程式中驅動程式填充的sequence字段不連續,3之後是6,猜測可能是将圖像資料寫入檔案耗時較長導緻丢幀
② 将示例程式中寫入檔案的操作注釋掉,可見sequence字段連續
1.3.2.11 關閉流傳輸
1.3.3 其他緩沖區記憶體類型操作
1.3.3.1 userptr緩沖區記憶體操作
1.3.3.1.1 申請緩沖區
說明:列印資訊
1.3.3.1.2 擷取緩沖區狀态
通過VIDIOC_QUERYBUF操作擷取各緩沖區狀态,此處需要記錄緩沖區記憶體每個plane的大小,供後續配置設定緩沖區記憶體時使用
說明:列印資訊
可見此時userptr字段尚未設定,字段值為0
1.3.3.1.3 配置設定緩沖區記憶體
說明:列印資訊
1.3.3.1.4 将緩沖區加入隊列
1.3.3.1.5 采集圖像
說明:列印資訊
1.3.3.2 dmabuf緩沖區記憶體操作
1.3.3.2.1 申請緩沖區
說明:列印資訊
1.3.3.2.2 擷取緩沖區狀态
通過VIDIOC_QUERYBUF操作擷取各緩沖區狀态,此處也需要記錄緩沖區記憶體每個plane的大小,供後續配置設定緩沖區記憶體時使用
說明:列印資訊
可見此時fd字段尚未設定,字段值為0
1.3.3.2.3 配置設定緩沖區記憶體
說明:列印資訊
1.3.3.2.4 将緩沖區加入隊列
經過驗證,對于dmabuf類型緩沖區記憶體,在進行VIDIOC_QBUF操作時确實可以不設定length字段
1.3.3.2.5 采集圖像
說明:列印資訊
2 V4L2使用者空間工具
2.1 v4l-utils工具包概述
- v4l-utils是由Linux維護的V4L2開發工具包,他提供了一系列用于操作V4L2裝置屬性和媒體架構的工具,同時還提供了相關函數庫(e.g. libv4l2庫)
- v4l-utils工具包中最常用的工具是v4l2-ctl和media-ctl,其中,
① v4l2-ctl用于檢視和配置V4L2裝置
② media-ctl用來檢視和配置media framework中的各entity的資訊
- 安裝v4l-utils工具包
① 在Ubuntu中可以通過如下指令安裝
sudo apt-get install v4l-utils
② 如果想從源碼建構v4l-utils工具包,可參考V4l-utils - LinuxTVWiki
2.2 v4l2-ctl工具使用示例
2.2.1 列出視訊裝置及其功能
- 使用--list-devices選項可以列出系統中所有的V4L2裝置
v4l2-ctl --list-devices
- 如果想要擷取指定裝置的資訊,可以使用-d選項指定裝置檔案名,并使用-D選項擷取其資訊
# 1. 如不使用-d選項指定裝置,則預設操作/dev/video0
# 2. -d /dev/video<x>可以簡化為-d<x>,例如-d4等價于-d /dev/video4
v4l2-ctl -d /dev/video<x> -D
- 使用--all選項替換上面的-D選項,則可以顯示與該裝置相關的所有資訊
v4l2-ctl -d /dev/video<x> --all
說明:擷取v4l2-ctl工具指令行選項
# 擷取幫助資訊
v4l2-ctl --help
# 擷取所有指令行選項
v4l2-ctl --help-all
通過如下指令則可以擷取某個方面的指令行選項,
2.2.2 更改裝置屬性
- 更改裝置屬性之前,需要了解裝置支援哪些屬性,這可以通過-L選項實作
- 通過--get-ctrl選項可以擷取指定屬性的目前值
v4l2-ctl -d /dev/video<x> --get-ctrl <屬性名稱>
- 通過--set-ctrl選項可以設定指定屬性的值
v4l2-ctl -d /dev/video<x> --set-ctrl <屬性名稱>=<屬性值>
2.2.3 設定圖像格式、分辨率和幀率
- 設定圖像格式、分辨率和幀率之前,需要了解裝置支援的參數,這可以通過--list-formats-ext選項實作
v4l2-ctl -d /dev/video<x> --list-formats-ext
可見--list-formats-ext選項會列出輸入裝置(capture資料流向)支援的圖像格式,指定圖像格式支援的分辨率,以及指定格式在指定分辨率之下支援的幀率
說明1:對于輸出裝置(output資料流向),則是使用--list-formats-out-ext選項。需要注意的是,--list-formats-out-ext選項隻有較新版本的v4l-utils工具包才支援
說明2:--list-formats-ext相當于是--list-formats、--list-framesizes和--list-frameintervals選項的集合
① 首先使用--list-formats選項列出輸入裝置支援的圖像格式(輸出裝置則使用--list-formats-out選項)
② 之後使用--list-framesizes選項列出指定圖像格式支援的分辨率
# 圖像格式名是圖像格式的fourcc編碼字元
v4l2-ctl -d /dev/video<x> --list-framesizes <圖像格式名>
③ 最後使用--list-frameintervals選項列出指定圖像格式在指定分辨率之下支援的幀率
v4l2-ctl -d /dev/video<x> --list-frameintervals width=<w>,height=<h>,pixelformat=<f>
- 在設定分辨率和幀率之前,需要先設定圖像格式,這可以通過--set-fmt-video選項實作
v4l2-ctl -d /dev/video<x> --set-fmt-video width=<w>,height=<h>,pixelformat=<f>
說明1:使用--set-fmt-video選項設定的是輸入裝置格式(capture資料流向裝置,也就是該裝置的輸出格式),使用--set-fmt-video-out選項則可以設定輸出裝置格式(output資料流向裝置,也就是該裝置的輸入格式)
說明2:擷取目前的圖像格式和分辨率
① 使用--get-fmt-video選項可以擷取輸入裝置圖像格式和分辨率
② 使用--get-fmt-video-out選項可以擷取輸出裝置圖像格式和分辨率
- 在确定了圖像格式和分辨率之後,就可以設定幀率,這可以通過--set-parm選項實作
說明:使用--get-parm和--set-parm選項是操作輸入裝置,如果要操作輸出裝置,則是使用--get-output-parm和--set-output-parm選項
2.2.4 采集幀和流傳輸
在設定完圖像格式、分辨率和幀率之後,就可以進行圖像采集了。而其中的核心就是緩沖區的配置設定與管理,這可以通過--stream-mmap和--stream-count選項實作
# 如果--stream-mmap選項後不加緩沖區個數,預設會配置設定3個緩沖區
v4l2-ctl -d /dev/video<x> --stream-mmap <緩沖區個數> --stream-count <要采集的圖像個數> --stream-to <輸出檔案名>
說明1:一般可以在設定圖像格式和分辨率的同時啟動流傳輸
v4l2-ctl -d /dev/video<x> --set-fmt-video width=<w>,height=<h>,pixelformat=<f> --stream-mmap <緩沖區個數> --stream-count <要采集的圖像個數>
說明2:--stream-mmap設定的是輸入裝置緩沖區記憶體類型,如果要設定輸出裝置緩沖區記憶體類型,需要使用--stream-out-mmap選項
說明3:關于緩沖區記憶體類型
① --stream-mmap <緩沖區個數>:輸入裝置緩沖區記憶體類型為mmap(對應輸出裝置緩沖區為--stream-out-mmap)
② --stream-user <緩沖區個數>:輸入裝置緩沖區記憶體類型為userptr(對應輸出裝置緩沖區為--stream-out-user)
③ --stream-dmabuf:輸入裝置緩沖區記憶體類型為dmabuf,此時需要配套設定--stream-out-mmap選項。也就是說--stream-dmabuf選擇僅用于支援memory-to-memory裝置,此時v4l2-ctl将mmap類型的輸出裝置緩沖區記憶體導出為dmabuf供輸入裝置使用
同樣地,如果使用--stream-out-dmabuf設定輸入裝置緩沖區記憶體類型為dmabuf,也需要配套設定--stream-mmap選項。也就是将mmap類型的輸入裝置緩沖區導出為dmabuf供輸出裝置使用
說明4:輸出到檔案
如果要将V4L2裝置的輸出内容儲存到檔案(言外之意是可以不儲存到檔案,此時相當于丢棄采集到的資料,這是v4l2-ctl的預設行為),可以使用如下選項,
① --stream-to:将輸出資料寫入指定檔案
② --stream-to-hdr:與--stream-to相同,但是每幀資料前會有一個header,主要用于壓縮格式(e.g. 輸出MJPEG類型的資料)
說明5:從檔案輸入
對于某些V4L2裝置,可以從檔案中擷取輸入資料(e.g. 解碼器從檔案中擷取碼流資料),此時可以使用如下選項,
① --stream-from:從指定檔案擷取輸入資料
② --stream-from-hdr:與--stream-from相同,但是每幀資料前會有一個header,也是用于壓縮格式(e.g. 輸入H264類型的碼流資料)
2.3 V4L2驅動合規性測試
使用v4l-utils工具包中的v4l2-compliance工具可以測試V4L2裝置的所有方面,例如裝置對V4L2 ioctl指令的支援特性
3 在使用者空間調試V4L2
- 使用如下2條指令可以啟用V4L2核心調試架構
echo 0x3 > /sys/module/videobuf2_v4l2/parameters/debug
echo 0x3 > /sys/module/videobuf2_common/parameters/debug
啟用V4L2核心調試架構後再次執行UVC Camera圖像采集程式,此時核心态會列印關鍵步驟的調試資訊
說明:該核心調試架構通過核心子產品參數實作,關于核心子產品參數的相關内容,可參考Linux裝置驅動基礎02:Linux核心子產品 chapter 3
- /sys/module/videobuf2_common/parameters/debug實作
① 在drivers/media/common/videobuf2/videobuf2-core.c檔案中定義核心子產品參數debug
② videobuf2-core.c檔案被編譯在videobuf2-common核心子產品
- /sys/module/videobuf2_v4l2/parameters/debug實作
① 在drivers/media/common/videobuf2/videobuf2-v4l2.c檔案中定義核心子產品參數debug
② videobuf2-v4l2.c檔案被編譯在videobuf2-v4l2核心子產品
說明1:關于核心子產品名中的"-"和"_"
① 在Makefile中指定的子產品名是videobuf2-common和videobuf2-v4l2,但是在sysfs中的子產品名卻是videobuf2_common和videobuf2_v4l2
② 為了驗證該現象,我們将Linux裝置驅動基礎02:Linux核心子產品 chapter 2中的hello_module子產品改為hello-module子產品(源檔案需要修改為hello-module.c)
③ 加載hello-module.ko,可見在sysfs中的子產品名被修改為hello_module,是以這種修改應該是Linux作業系統的行為
說明2:videobuf2_common子產品和videobuf2_v4l2子產品對應的源檔案中并沒有子產品加載和子產品解除安裝函數,也就是說核心子產品可以沒有子產品加載和子產品解除安裝函數
為了驗證該觀點,将Linux裝置驅動基礎02:Linux核心子產品 chapter 2中的hello_module子產品修改如下,取消子產品加載和子產品解除安裝函數,同時導出子產品參數
經過驗證,修改後的核心子產品可以加載,并且可以正确導出核心子產品參數