v4l2的架構圖在深色模式時會看不到內容,是否可以換張圖呢?
Yinghua Yeh
已解決
研究 vcam 並改進。
Reference:
探討 V4L2 和 Linux framebuffer
v4l2 (Video4Linux2)
v4l2 是 Linux 下關於影像收集相關裝置的驅動框架,也是一個統一的介面規範。其架構如下圖所示:
用 Graphviz 重新繪製圖片,避免品質低劣的圖表。
使用向量繪圖,不是點陣圖!
Reference: 第一章 v4l2 簡介
從架構圖中,我們可以將 v4l2 分為三個層級,分別為 User Space、Kernel Space 和 Hardware space。在 User Space 的包含 /dev/videoX
模組;在 Kernel Space 的包含 cdev
、video_device
、v4l2 device
及 v4l2_subdev
模組;在 Hardware space 的包含 Camera sensor hardware
模組。而當中的參數設置主要是透過 ioctl
進行設定的。
/dev/videoX
User space 是使用者可以存取到的 device 節點,在圖中為 /dev
底下的 videoX,從 Kernel space 來看,這個 device 的主裝置編號及次裝置編號分別為 81 和 X。
注意用語:
其連接到的 cdev
是個由字元驅動的核心模組,功能為連接 video_device
和 /dev
底下的 device 節點(此圖中 /dev/videoX
),這個模組讓 User space 的使用者可以透過一些操作(像是 open、 read、 write 等)來存取 video_device
。
與 cdev
連接的 video device
主要是負責影像裝置的模組,他將影像裝置的各種功能(像是 open、read、write 等)提供給上層應用的介面,從 v4l2_subdev
中獲得影像的資料和他的控制訊息。
v4l2_subdev
代表的是一個 V4L2 的裝置,透過這個模組,應用程式就可以配置及控制影像裝置。而 video_device
可以藉由 v4l2_subdev
和具體的子裝置 (如攝影機感測器) 進行溝通。
注意用語:
Camera Sensor Hardware
是實際的攝影機感測器硬體,可以實際的捕捉影像的資料。cdev
模組為字元裝置提供了基本的操作介面,並將字元裝置與內核中的設備號關聯起來。字元裝置是一種按字元為單位進行存取的裝置,使用者可以透過 read 和 write 等指令與裝置進行交互。常見的字元裝置包括鍵盤、滑鼠等,這些裝置的共同特徵就是每次操作時,電腦都會直接進行處理(像是按下滑鼠的按鍵、鍵盤輸入一個字元等)。
從使用者的角度來對 V4L2 控制流程做觀察:
/dev/videoX
存取 影像裝置。並可以使用 ioctl 來查詢裝置相關的參數設定,並進行裝置的配置。open()
打開 /dev/video0
裝置文件,透過 read 就可以呼叫裝置讀取資料,透過 write 就可以呼叫裝置寫入資料。Negotiate 的作用是什麼?
如果使用編寫 driver的角度對 V4L2 做觀察,編寫者只需要實作硬體相關的程式碼,並且註冊相關裝置即可,其餘部分 V4L2 已經完成配置 。除了編寫具體裝置的程式碼之外,另一個主要的操作是將程式碼與 V4L2 的框架進行綁定,包含將我們的結構與 V4L2 框架中相關結構綁定在一起,以及將 V4L2 所定義的空函式指標與自己的函式 v4l2_device
綁定。V4L2 中主要的結構體如下圖中橘色長方體所示:
注意用語,務必詳閱 https://hackmd.io/@sysprog/it-vocabulary
使用本課程教材的術語,區分「函式」和「函數」。
避免使用「設置」一詞,其意思不清晰。
如果要將我們的結構與 V4L2 框架中相關結構綁定在一起,我們需要先了解以下幾個重要的結構:
更新上方超連結
注意用語:
struct video_device
:這是主要的裝置結構體,用來表示一個像 /dev/videoX
的 V4L2 裝置,負責向 kernel 註冊字元裝置以讓 User Space 的的應用程式可以透過標準的檔案操作(如 open
、read
、write
、ioctl
)來與裝置交互。他包含指向裝置操作的指針( v4l2_file_operations
及 v4l2_ioctl_ops
)、控制處理器的 pointer ( v4l2_ctrl_handler
)之外,還有一個親代裝置指針指向 v4l2_device
。在 vcam 程式碼中配置 video_deive
空間的方式如下所示:減少非必要的縮排
而開發者實際註冊一個 video_device
可以使用 video_register_device 巨集;解除註冊則是使用 video_unregister_device。註冊時的程式碼如下,這個步驟會在初始化裝置之後進行:
c err = video_register_device(vdev, VFL_TYPE_GRABBER, -1); if (err) { video_device_release(vdev); /* or kfree(my_vdev); */ return err; }
從程式碼中我們可以看到需要傳入的參數包含 vdev
,也就是我們幫 video_device
配置的空間;VFL_TYPE_GRABBER
,也就是這一個 video_device
的種類;第三個參數是這個 video_device
可以控制的節點編號,-1
代表讓 v4l2 framework 自行選擇第一個閒置的節點編號。
video_device
的種類包含:
當要對 video_device
進行初始化時,需要將其 v4l2_dev
的成員指向已經初始化的 v4l2_deice
結構體,以定義這個 video_deive
是被哪一個 v4l2_devive
所管理。
struct 是「結構體」,而非「結構」
高維度?你在說線性代數嗎?
用清晰的漢語書寫!
struct v4l2_device
:一個更高層級的裝置結構體,負責管理多個子裝置。其中包含一個指向 v4l2_ctrl_handler
的指標,也有一個 list_head
用來鏈接所有的子裝置。避免用低劣的機器翻譯,用你的意思去書寫,搭配指定的詞彙規範。
在 vcam 專案中的 device.c
中可以找到初始化一個 v4l2_deice
的程式碼,使用的是 v4l2_deice_register
來進行初始化的動作:
c /* Register V4L2 device */ snprintf(vcam->v4l2_dev.name, sizeof(vcam->v4l2_dev.name), "%s-%d", vcam_dev_name, (int) idx); ret = v4l2_device_register(NULL, &vcam->v4l2_dev); if (ret) { pr_err("v4l2 registration failure\n"); goto v4l2_registration_failure;
struct v4l2_subdev
:具體的子裝置結構體,包含多種操作 ( v4l2_subdev_ops
),而這些操作會再被進一步劃分為不同的類別。上面三個結構體均包含 v4l2_ctrl_handler 的指標,這是一個負責處理裝置的控制命令,負責持續追蹤控制操作,其包含一個他擁有的 v4l2_ctrl
物件清單,代表具體的控制項,像是裝置的類型、名稱、步長等訊息。透過 v4l2_strl_handler
,裝置驅動程式可以統一管理裝置的控制項,處理來自 User Space 的控制命令,並將這些轉換成對於硬體的具體操作。
vcam
可以符合預期的註冊為 V4L2 裝置驅動程式的原因是因為他遵守了 V4L2 API 的設計規範,以下包含幾個具體的操作步驟:
video_register_device
將 video device 裝置註冊到 v4l2_device。ioctl
操作,像是 VIDIOC_QUERYCAP
、VIDIOC_ENUM_FMT
、VIDIOC_G_FMT
、VIDIOC_S_FMT
等。Framebuffer 是 RAM 中的一部分
Framebuffer 是系統 RAM 中的一個專門區域,用來儲存和更新顯示裝置上呈現的影像。
使用 Linux 核心內附的文件 (Documentations 目錄) 和程式碼來解說,不要淪落為「搬運工」。注意用語!
,可以視為一個 V4L2 driver 和 User Space 的連接,專門儲存裝置驅動影像播放的 bitmap,是一個包含影像中所有 pixel 的 memory buffer。當螢幕的顯示需要更新時,應用程式可以透過 I / O 技術 將新的圖像資料寫入 framebuffer 的顯示卡記憶體區域,顯示器則會從 framebuffer 中讀取資料並輸出到裝置上。
注意用語。
memory 要看場景來翻譯,不要偷懶標示「(主)」
而並不是所有的裝置都使用相同的的 buffer,buffer 有三種常見的種類,我們可以依照需求選擇適當的 buffer 類型作為 framebuffer:
何謂 scatter/gather DMA?
使用以下表格統整三種 buffer 的使用時機,並概述 framebuffer 在甚麼時機應該選用哪種 buffer 類型:
Buffer type | 適用情形 |
---|---|
Both scattered | 通常適用於高性能的 video captured 裝置,這些裝置可以處理分散的(主) 記憶體區塊。這種方式在(主) 記憶體碎片較多的系統中特別有用,因為它不需要大塊連續的(主) 記憶體。 |
Physically scattered, virtual contigous | 這類 buffer 在 kernel space 中的操作方便,適合那些需要 kernel 層面進行大量處理的影像應用,例如影片解碼或圖像處理等。但由於硬體位址上不連續,不適合簡單的 DMA 操作。 |
Physically contigous | 這是傳統 video captured 和顯示裝置最常用的 buffer 類型,特別是在 DMA 操作必須非常高效的情況下。這些 buffer 確保資料在硬體和(主) 記憶體之間的快速傳輸,但在(主) 記憶體管理上需要更加小心以避免碎片問題。 |
參考資料:
2.15 Defining Colorspaces in V4L2
Video4Linux2 part 5a: colors and formats
Colorspace 的意思是用來描述顏色的座標空間,V4L2 中定義了不同種類的 colorspace,但只有兩種較常被使用,分別是
V4L2_COLORSPACE_SRGB
:使用 [red, green, blue] tuple 來儲存顏色,這個 colorspace 同時也可與 YUV 和 YCbCr colorspace 做轉換。V4L2_COLORSPACE_SMPTE170M
:用來表示類比訊號,常用於 NTSC 或 PAL 電視中。在 vcam 裝置驅動程式中需要使用不同的 colorspace 的原因是因為要確保虛擬相機能夠更正確的模擬真實裝置的顏色和顯示。根據具體的應用需求和顯示裝置選擇適當的 colorspace,可以提高顏色準確性和一致性,滿足不同應用場景的需求。
注意用語。
在 V4L2 的裝置驅動程式中,I / O 負責處理應用程式與驅動程式間的資料傳輸。在很多情況下,一個 video adapter 不會提供多種 I / O 的選項,比如說相機控制器可以提供相機介面,但幾乎沒有提供其他選項。有一些較複雜的 video adapter,像是電視卡,會有多個輸入介面來連接到不同的連接器,這些不同的介面與對應的調節器 (tuner) 可以讓輸入的介面有不同的特性。
務必尊重台灣資訊科技前輩的篳路藍縷,使用存在數十年的經典用語,避免濫用「共製漢字」詞彙。
以下對輸入命令進行說明:
使用 VIDIOC_ENUMINPUT
將所有的輸入源列出
file
對應的是要開啟的影像裝置,private_data
指的是 driver 設定的 private field,input
則是真實資訊通過的區域,其中列出幾點:
__u32 index
: 唯一一個可以在 User Space 設置的參數,指的是使用者有興趣的 index number inputs。__u8 name
: input 的名稱。__u32 type
: input 的類別。__u32 audioset
: 描述哪一個音訊的 input 可以與影像的 input 結合。__u32 status
: 印出 input 的狀態。測試環境:
在執行 make
時遇到以下問題:
從第 35 行中,我們可以看到 class_create
這個函式只接受一個參數,且該參數為 const char *
類別,但我們從第 31 行的錯誤訊息中可以看到 too many arguments to function 'class_create'
且在 control.c
中的 ctldev->dev_class = class_create(MY_MODULE, dev_name)
中傳入了兩個參數,其中 MY_MODULE
在錯誤訊息第 20 行中是 struct module *
,dev_name
是呼叫 create_control_device
時傳入的 const char *
類別,因此推測不應該出現的參數為 MY_MODULE
。將 control.c
檔案中的 ctldev->dev_class = class_create(MY_MODULE, dev_name);
中的 MY_MODULE
刪除。
將心比心,你希望被人叫做「有人」嗎?明確標注 GitHub 帳號名稱。
在 vcam 的專案上 hungyuhang 也已經提交變更:vcam commit a7e6f0e
重新執行 make
命令,會發生以下錯誤:
在 extensions: replace `PDE_DATA` 中提到:
而在 proc: remove PDE_DATA() completely 也有寫到以下的更動:
因此將 fb.c
中的 PDE_DATA
改成 pde_data
即可解決此問題。
接著執行 $ sudo ./vcam-util -l
可以得到以下結果:
查看 vcam-util.c
檔案,-l
命令會執行 list_devices()
函式,先使用 ioctl 函式來獲得現在有的裝置訊息,並將結果存在 dev
中,隨後將 dev.idx
, dev.fb_node
, dev.width
, dev.pix_fmt
以及 dev.video_node
的結果印出。dev
的架構如下所示:
內容包含索引值、framebuffer 的節點、虛擬攝影機裝備的寬度與高度(此虛擬裝置的解析度)、像素的格式 (rgb24 或 yuyv) 以及影片的節點。
透過 ls /dev/video*
來確認具體的裝置號為 /dev/video0
以進行後續對 driver 的測試:
v4l2-compliance 命令是用來測試 video4linux 裝置(包含輸入及輸出)的命令,且幾乎覆蓋到全部的 v4l2 ioctl。這個命令測試的不僅是裝飾是否遵守 v4l2 API,也測試了 driver 是否使用正確的框架。如果使用者想要提交一個新的 v4l2 driver,那該 driver 必須通過 c4l2-compliance
的命令且不出錯。以下針對幾個常用的命令參數進行解說:
以下為執行此命令的結果,旨在測試 video0 所有可使用的格式是否可以被串流:
v4l2-ctl 則是控制 video4linux 裝置的命令,以下為幾個常用參數:
執行 $ sudo v4l2-ctl -d /dev/video0 --all
命令的結果:
在掛載 vcam.ko
的前後可以使用 ls /dev
命令來觀察掛載前後模組的差異。在我的裝置中,掛載後多了 video0
和 fb1
等,因此再使用 $ sudo fbset -fb /dev/fb1 --info
命令來觀察 framebuffer fb1
的資訊。
在完全沒有寫入 vcam 的 framebuffer 時,用 vlc v4l2:///dev/video0
顯示的預設畫面如下所示,為一個漸層的畫面:
如果我們想要對 vcam 所呈現的畫面做更動時,我們要將程式碼寫入其對應的 framebuffer 中。根據 2020 開發紀錄,我們可以先建立一個檔案 test.c
,當中是我們想要對 framebuffer 所呈現畫面進行的操作,對該檔案進行編譯之後,使用 ./test > /proc/vcamfb
將檔案寫入 vcam 對應的 framebuffer 中。然而會出現關於沒有 /proc/vcamfb
的報錯,當我們將 /proc/
底下的檔案列出後,發現當中有一個 fb
檔案,對其執行 cat /proc/fb
的命令來查看內容時發現 vcamfb
其實存在:
這個結果表示系統中有兩個 framebuffer 的裝置,包含 i915drmfb
和 vcamfb
。
更仔細的去探究兩個檔案之間的差異,發現 /proc 是一個虛擬的檔案系統,其提供一個界面,讓使用者獲得系統相關的訊息,當中大部分的檔案是唯讀的。而其中的 /proc/fb
檔提供當 kernel 被編譯時所定義的 framebuffer 的相關資訊。/dev 則是各種 device 儲存的位置,我們可以透過以下命令查看 /dev
下的 fb0
和 fb1
對應到 /proc/fb
中的哪一個裝置名稱:
因此我們可以確認 fb1
在 /proc/fb
底下對應到的是 vcamfb
這個名稱。我們重新改寫命令為 $ sudo sh -c './test > /dev/fb1'
,將想要對 vcam 畫面做的操作寫入 fb1
中,就可以成功的將想進行的操作呈現:
注意用語!
已更改
Reference: https://hackmd.io/@yinghuaxia/linux2024-final
將上方超連結指向的內容,展開在本頁面。
針對 Linux v6.8 進行調整
解釋 Linux 核心原始程式碼的相關異動,探討開發者的考量
在 make 的時候會遇到以下問題:
會發生這個錯誤的原因在於在 Linux v6.8 中,min_buffers_needed
被重新命名為 min_queued_buffers
,因此修改 vcam/videobuf.c 中的 min_buffers_needed
參數即可解決此問題。
Reference: [v2,36/36] videobuf2: core: Rename min_buffers_needed field to vb2_queue
第二個會遇到的問題是:
原因是因為 FBINFO_FLAG_DEFAULT
這個參數已經沒有在 Linux v6.8 中被使用,因此將該參數刪除或改為 0 即可解決此問題:
你如何確認?只靠肉眼觀察嗎?是否有更科學的方式檢驗裝置驅動程式運作的機制?
根據 commit b4f470a 中所提到的:
我們可以知道這個改變是因為 kzalloc() 在初始化的步驟已經把 fbinfo.flags
設為零了,因此原本被拿作為初始化的 FBINFO_FLAG_DEFAULT = 0
就可以在移除所有相關的參數後直接被移除。
Reference: [v4,17/18] fbdev: Remove FBINFO_DEFAULT and FBINFO_FLAG_DEFAULT
解讀 Linux 核心相關變革,善用 git log / git blame 追蹤核心開發者的考量因素。
此時編譯可以成功通過,但會有另外一個問題產生:
這個錯誤可能與 Kernel 或編譯器是否支援 BTF 有關,仍需進行相關測試。
待確認
從 https://github.com/sysprog21/vcam/issues 挑選
#35
並嘗試排除,適度更新你的觀察和推論,若需要程式碼修正,則提交 pull request