Try   HackMD

Linux 核心專題: 虛擬攝影機裝置驅動程式

執行人: yinghuaxia
GitHub
專題解說錄影

reviewed by 'dcciou'

v4l2的架構圖在深色模式時會看不到內容,是否可以換張圖呢?

Yinghua Yeh
已解決

任務簡介

研究 vcam 並改進。

Reference:

TODO: vcam 所需的背景知識回顧

探討 V4L2 和 Linux framebuffer

vcam 運作原理

v4l2 (Video4Linux2)
v4l2 是 Linux 下關於影像收集相關裝置的驅動框架,也是一個統一的介面規範。其架構如下圖所示:

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

用 Graphviz 重新繪製圖片,避免品質低劣的圖表。
使用向量繪圖,不是點陣圖!

Reference: 第一章 v4l2 簡介

從架構圖中,我們可以將 v4l2 分為三個層級,分別為 User Space、Kernel Space 和 Hardware space。在 User Space 的包含 /dev/videoX 模組;在 Kernel Space 的包含 cdevvideo_devicev4l2 devicev4l2_subdev 模組;在 Hardware space 的包含 Camera sensor hardware 模組。而當中的參數設置主要是透過 ioctl 進行設定的。

  • /dev/videoX

    User space 是使用者可以存取到的 device 節點,在圖中為 /dev 底下的 videoX,從 Kernel space 來看,這個 device 的主裝置編號及次裝置編號分別為 81 和 X。

注意用語:

  • character 是「字元」
  • driver 在本文是 device driver 的簡稱,應翻譯為「裝置驅動程式」,避免過度簡化的「驅動」
  • 其連接到的 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 和具體的子裝置 (如攝影機感測器) 進行溝通。

注意用語:

  • via, through 不該翻譯為「通過」,否則你無法區隔 "pass"
  • Camera Sensor Hardware 是實際的攝影機感測器硬體,可以實際的捕捉影像的資料。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
在 Linux Kernel 中,裝置驅動程式分為三種類型:字元裝置(Character Devices)、區塊裝置(Block Devices)和網路裝置(Network Devices)。這些裝置各自有不同的功能和應用場景,而 cdev 模組為字元裝置提供了基本的操作介面,並將字元裝置與內核中的設備號關聯起來。字元裝置是一種按字元為單位進行存取的裝置,使用者可以透過 read 和 write 等指令與裝置進行交互。常見的字元裝置包括鍵盤、滑鼠等,這些裝置的共同特徵就是每次操作時,電腦都會直接進行處理(像是按下滑鼠的按鍵、鍵盤輸入一個字元等)。

使用者的角度來對 V4L2 控制流程做觀察:

  1. 使用者使用 open() 在 US(User Space) 通過 /dev/videoX 存取 影像裝置。並可以使用 ioctl 來查詢裝置相關的參數設定,並進行裝置的配置。
  2. 協商 (Negotiate) 資料格式
  3. 協商 (Negotiate) 輸入 / 輸出方式,像是:
    • read / write: 應用程式透過 read 和 write 的系統來讀取和寫入影像的資料,如第一點所提到的,應用程式會先使用 open() 打開 /dev/video0 裝置文件,透過 read 就可以呼叫裝置讀取資料,透過 write 就可以呼叫裝置寫入資料。
    • Stream I / O: 這個方式允許應用程式自己分配自己的 buffer 區域,並將這些 buffer 區域的位址告訴裝置驅動程式,裝置驅動程式會直接將資料寫到用戶的 buffer 區域。
    • overlay: 是一種 memory 的 mapping,裝置驅動程式會將一組內核的 buffer 區域映射到 User space,這樣就可以讓使用者透過應用程式直接存取這些 buffer 區域進行讀寫操作。而應用程式使用的是 mmap 的巨集來對 buffer 區域進行存取。
  4. 最後需要使用 close() 將資源釋放並將裝置關閉。

Negotiate 的作用是什麼?

如果使用編寫 driver的角度對 V4L2 做觀察,編寫者只需要實作硬體相關的程式碼,並且註冊相關裝置即可,其餘部分 V4L2 已經完成配置 。除了編寫具體裝置的程式碼之外,另一個主要的操作是將程式碼與 V4L2 的框架進行綁定,包含將我們的結構與 V4L2 框架中相關結構綁定在一起,以及將 V4L2 所定義的空函式指標與自己的函式 v4l2_device 綁定。V4L2 中主要的結構體如下圖中橘色長方體所示:

注意用語,務必詳閱 https://hackmd.io/@sysprog/it-vocabulary

使用本課程教材的術語,區分「函式」和「函數」。
避免使用「設置」一詞,其意思不清晰。

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

如果要將我們的結構與 V4L2 框架中相關結構綁定在一起,我們需要先了解以下幾個重要的結構:

參考資料:
1.4 Video device's internal representation

更新上方超連結

注意用語:

  • file 是「檔案」,而非「文件」(document)
  • struct video_device:這是主要的裝置結構體,用來表示一個像 /dev/videoX 的 V4L2 裝置,負責向 kernel 註冊字元裝置以讓 User Space 的的應用程式可以透過標準的檔案操作(如 openreadwriteioctl )來與裝置交互。他包含指向裝置操作的指針( v4l2_file_operationsv4l2_ioctl_ops )、控制處理器的 pointer ( v4l2_ctrl_handler )之外,還有一個親代裝置指針指向 v4l2_device。在 vcam 程式碼中配置 video_deive 空間的方式如下所示:
struct video_device *vdev = video_device_alloc();

if (vdev == NULL)
        return -ENOMEM;

vdev->release = video_device_release;

減少非必要的縮排

而開發者實際註冊一個 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 的種類包含:

  • VFL_TYPE_GRABBER: /dev/videoX for video input/output devices
  • VFL_TYPE_VBI: /dev/vbiX for vertical blank data (i.e. closed captions, teletext)
  • VFL_TYPE_RADIO: /dev/radioX for radio tuners
  • VFL_TYPE_SDR: /dev/swradioX for Software Defined Radio tuners
  • VFL_TYPE_TOUCH: /dev/v4l-touchX for touch sensors

當要對 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 裝置驅動程式?

vcam 可以符合預期的註冊為 V4L2 裝置驅動程式的原因是因為他遵守了 V4L2 API 的設計規範,以下包含幾個具體的操作步驟:

  1. 定義 V4L2 的 video_device,並配置相關的操作。
  2. 進行初始化和資源的分配。
  3. 使用 video_register_device 將 video device 裝置註冊到 v4l2_device。
  4. 實作必要的 ioctl 操作,像是 VIDIOC_QUERYCAPVIDIOC_ENUM_FMTVIDIOC_G_FMTVIDIOC_S_FMT 等。
  5. 進行錯誤處理及回報,以避免系統崩潰。

Framebuffer 簡介

參考資料:
1.13 Video Framework
Wikipedia Framebuffer

Framebuffer 是 RAM 中的一部分

Framebuffer 是系統 RAM 中的一個專門區域,用來儲存和更新顯示裝置上呈現的影像。

使用 Linux 核心內附的文件 (Documentations 目錄) 和程式碼來解說,不要淪落為「搬運工」。注意用語!

,可以視為一個 V4L2 driver 和 User Space 的連接,專門儲存裝置驅動影像播放的 bitmap,是一個包含影像中所有 pixel 的 memory buffer。當螢幕的顯示需要更新時,應用程式可以透過 I / O 技術 將新的圖像資料寫入 framebuffer 的顯示卡記憶體區域,顯示器則會從 framebuffer 中讀取資料並輸出到裝置上。

注意用語。
memory 要看場景來翻譯,不要偷懶標示「(主)」

而並不是所有的裝置都使用相同的的 buffer,buffer 有三種常見的種類,我們可以依照需求選擇適當的 buffer 類型作為 framebuffer:

  • Buffers which are scattered in both the physical and (kernel) virtual address. 幾乎所有的 User Space buffer 是這種類型,這種 buffer 的配置及使用較靈活,但這種類型的 buffer 會需要可以支持 scatter/gather DMA 操作的硬體。

何謂 scatter/gather DMA?

  • Buffers which are physically scattered, but which are virtually contigous. 這種 buffer 可以被用於 DMA 操作不可用的情形,其 virtually contigous 的特性讓其相對較容易操作。
  • Buffers which are physically contigous. 這種 buffer 會被使用在 DMA 沒有支援 scatter / gather 的功能時。

使用以下表格統整三種 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 確保資料在硬體和(主) 記憶體之間的快速傳輸,但在(主) 記憶體管理上需要更加小心以避免碎片問題。

vcam 的 Colorspace

參考資料:
2.15 Defining Colorspaces in V4L2
Video4Linux2 part 5a: colors and formats

Colorspace 的意思是用來描述顏色的座標空間,V4L2 中定義了不同種類的 colorspace,但只有兩種較常被使用,分別是

  1. V4L2_COLORSPACE_SRGB:使用 [red, green, blue] tuple 來儲存顏色,這個 colorspace 同時也可與 YUVYCbCr colorspace 做轉換。
  2. V4L2_COLORSPACE_SMPTE170M:用來表示類比訊號,常用於 NTSC 或 PAL 電視中。

在 vcam 裝置驅動程式中需要使用不同的 colorspace 的原因是因為要確保虛擬相機能夠更正確的模擬真實裝置的顏色和顯示。根據具體的應用需求和顯示裝置選擇適當的 colorspace,可以提高顏色準確性和一致性,滿足不同應用場景的需求。

vcam 的 I / O

參考資料:Video4Linux2 part 4: inputs and outputs

注意用語。

在 V4L2 的裝置驅動程式中,I / O 負責處理應用程式與驅動程式間的資料傳輸。在很多情況下,一個 video adapter 不會提供多種 I / O 的選項,比如說相機控制器可以提供相機介面,但幾乎沒有提供其他選項。有一些較複雜的 video adapter,像是電視卡,會有多個輸入介面來連接到不同的連接器,這些不同的介面與對應的調節器 (tuner) 可以讓輸入的介面有不同的特性。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
Video adapter 指的是在電腦中的圖形卡,用來連接顯示器與電腦,讓電腦可以輸出文字或是影像到顯示器上,通常由 GPU (圖形處理單元)、顯示卡記憶體 (VRAM) 、輸出介面 (像是 HDMI、VGA 等) 和散熱系統組成。他的工作流程為:

  1. CPU 將要算繪的圖形資料傳送到 GPU,GPU 負責執行算繪的動作。
  2. GPU 將這些算繪過的圖像資料儲存在記憶體當中,這些圖像相關的資料組成最終顯示的圖像。
  3. 最後再將顯示卡記憶體中儲存的圖像資料通過輸出介面轉換成影像的訊號,傳送到顯示裝置上進行顯示。

務必尊重台灣資訊科技前輩的篳路藍縷,使用存在數十年的經典用語,避免濫用「共製漢字」詞彙。

以下對輸入命令進行說明:

  • 使用 VIDIOC_ENUMINPUT 將所有的輸入源列出

    ​​​​int (*vidioc_enum_input)(struct file *file, void *private_data,struct v4l2_input *input);
    

    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 的狀態。

vcam 測試紀錄

測試環境:

$ uname -a
Linux yinghuaxia-Altos-P30-F6 6.5.0-35-generic #35~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Tue May  7 09:00:52 UTC 2 x86_64 x86_64 x86_64 GNU/Linux

在執行 make 時遇到以下問題:

yinghuaxia@yinghuaxia-Altos-P30-F6:~/vcam$ make make -C /lib/modules/6.5.0-35-generic/build M=/home/yinghuaxia/vcam modules make[1]: Entering directory '/usr/src/linux-headers-6.5.0-35-generic' warning: the compiler differs from the one used to build the kernel The kernel was built by: x86_64-linux-gnu-gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0 You are using: gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0 CC [M] /home/yinghuaxia/vcam/module.o CC [M] /home/yinghuaxia/vcam/control.o In file included from ./include/linux/linkage.h:7, from ./arch/x86/include/asm/cache.h:5, from ./include/linux/cache.h:6, from ./arch/x86/include/asm/current.h:9, from ./include/linux/sched.h:12, from ./include/linux/ratelimit.h:6, from ./include/linux/dev_printk.h:16, from ./include/linux/device.h:15, from /home/yinghuaxia/vcam/control.c:3: /home/yinghuaxia/vcam/control.c: In function ‘create_control_device’: ./include/linux/export.h:29:22: error: passing argument 1 of ‘class_create’ from incompatible pointer type [-Werror=incompatible-pointer-types] 29 | #define THIS_MODULE (&__this_module) | ~^~~~~~~~~~~~~~~ | | | struct module * /home/yinghuaxia/vcam/control.c:303:38: note: in expansion of macro ‘THIS_MODULE’ 303 | ctldev->dev_class = class_create(THIS_MODULE, dev_name); | ^~~~~~~~~~~ In file included from ./include/linux/device.h:31: ./include/linux/device/class.h:230:54: note: expected ‘const char *’ but argument is of type ‘struct module *’ 230 | struct class * __must_check class_create(const char *name); | ~~~~~~~~~~~~^~~~ /home/yinghuaxia/vcam/control.c:303:25: error: too many arguments to function ‘class_create’ 303 | ctldev->dev_class = class_create(THIS_MODULE, dev_name); | ^~~~~~~~~~~~ ./include/linux/device/class.h:230:29: note: declared here 230 | struct class * __must_check class_create(const char *name); | ^~~~~~~~~~~~ cc1: some warnings being treated as errors make[3]: *** [scripts/Makefile.build:251: /home/yinghuaxia/vcam/control.o] Error 1 make[2]: *** [/usr/src/linux-headers-6.5.0-35-generic/Makefile:2039: /home/yinghuaxia/vcam] Error 2 make[1]: *** [Makefile:234: __sub-make] Error 2 make[1]: Leaving directory '/usr/src/linux-headers-6.5.0-35-generic' make: *** [Makefile:14: kmod] Error 2

從第 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 刪除。

-  ctldev->dev_class = class_create(THIS_MODULE, dev_name);
+  ctldev->dev_class = class_create(dev_name);

將心比心,你希望被人叫做「有人」嗎?明確標注 GitHub 帳號名稱。

vcam 的專案上 hungyuhang 也已經提交變更:vcam commit a7e6f0e

重新執行 make 命令,會發生以下錯誤:

make -C /lib/modules/6.5.0-35-generic/build M=/home/yinghuaxia/vcam modules
make[1]: Entering directory '/usr/src/linux-headers-6.5.0-35-generic'
warning: the compiler differs from the one used to build the kernel
  The kernel was built by: x86_64-linux-gnu-gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0
  You are using:           gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0
  CC [M]  /home/yinghuaxia/vcam/control.o
  CC [M]  /home/yinghuaxia/vcam/device.o
  CC [M]  /home/yinghuaxia/vcam/videobuf.o
  CC [M]  /home/yinghuaxia/vcam/fb.o
/home/yinghuaxia/vcam/fb.c: In function ‘vcamfb_open’:
/home/yinghuaxia/vcam/fb.c:22:31: error: implicit declaration of function ‘PDE_DATA’; did you mean ‘NODE_DATA’? [-Werror=implicit-function-declaration]
   22 |     struct vcam_device *dev = PDE_DATA(ind);
      |                               ^~~~~~~~
      |                               NODE_DATA
/home/yinghuaxia/vcam/fb.c:22:31: warning: initialization of ‘struct vcam_device *’ from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
/home/yinghuaxia/vcam/fb.c: In function ‘vcamfb_release’:
/home/yinghuaxia/vcam/fb.c:44:31: warning: initialization of ‘struct vcam_device *’ from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
   44 |     struct vcam_device *dev = PDE_DATA(ind);
      |                               ^~~~~~~~
cc1: some warnings being treated as errors
make[3]: *** [scripts/Makefile.build:251: /home/yinghuaxia/vcam/fb.o] Error 1
make[2]: *** [/usr/src/linux-headers-6.5.0-35-generic/Makefile:2039: /home/yinghuaxia/vcam] Error 2
make[1]: *** [Makefile:234: __sub-make] Error 2
make[1]: Leaving directory '/usr/src/linux-headers-6.5.0-35-generic'
make: *** [Makefile:14: kmod] Error 2

extensions: replace `PDE_DATA` 中提到:

The `PDE_DATA` function for retrieving private data from a procfs inode
has been replaced by `pde_data` in 5.17.  Replace all instances of the
former with the latter, but add a macro to xtables_compat.h in order to
preserve compatibility with older kernels.

而在 proc: remove PDE_DATA() completely 也有寫到以下的更動:

Remove PDE_DATA() completely and replace it with pde_data().

因此將 fb.c 中的 PDE_DATA 改成 pde_data 即可解決此問題。

接著執行 $ sudo ./vcam-util -l 可以得到以下結果:

Available virtual V4L2 compatible devices:
1. fb1(640,480,rgb24) -> /dev/video0

查看 vcam-util.c 檔案,-l 命令會執行 list_devices() 函式,先使用 ioctl 函式來獲得現在有的裝置訊息,並將結果存在 dev 中,隨後將 dev.idx, dev.fb_node, dev.width, dev.pix_fmt 以及 dev.video_node 的結果印出。dev 的架構如下所示:

struct vcam_device_spec {
    unsigned int idx;
    unsigned short width, height;
    pixfmt_t pix_fmt;
    char video_node[64];
    char fb_node[64];
};

內容包含索引值、framebuffer 的節點、虛擬攝影機裝備的寬度與高度(此虛擬裝置的解析度)、像素的格式 (rgb24 或 yuyv) 以及影片的節點。

透過 ls /dev/video* 來確認具體的裝置號為 /dev/video0 以進行後續對 driver 的測試:

yinghuaxia@yinghuaxia-Altos-P30-F6:~/vcam$ ls /dev/video*
/dev/video0

v4l2-compliance 命令是用來測試 video4linux 裝置(包含輸入及輸出)的命令,且幾乎覆蓋到全部的 v4l2 ioctl。這個命令測試的不僅是裝飾是否遵守 v4l2 API,也測試了 driver 是否使用正確的框架。如果使用者想要提交一個新的 v4l2 driver,那該 driver 必須通過 c4l2-compliance 的命令且不出錯。以下針對幾個常用的命令參數進行解說:

-d, --device=<dev>
    Use device <dev> as the video device. If <dev> is a number, then /dev/video<dev> is used.

-f, --stream-all-formats
    Test  whether  all available formats can be streamed. This attempts to stream using MMAP mode or read/write (if V4L2_MEMORY_MMAP is not available) for one  second  for all formats, at all sizes, at all intervals and with all field values. In addition, if the driver supports scaling, cropping or composing it will test that as well in various  combinations.  If the driver supports a lot of combinations then this test can take a long time. 
    The configuration of the driver at the time v4l2-compliance was called will be used for the streaming tests.

-s, --streaming=<count>
    Enable  the streaming tests. Set <count> to the number of frames to stream (default 60).  This requires that before v4l2-compliance  is  called  the  device  has  been configured  with  a  valid  input  (or output) and frequency (when the device has a tuner). For DMABUF testing --expbuf-device needs to be set as well. 
    The configuration of the driver at the time v4l2-compliance was called will be used for the streaming tests.

以下為執行此命令的結果,旨在測試 video0 所有可使用的格式是否可以被串流:

$ sudo v4l2-compliance -d /dev/video0 -f

...
Total for vcam device /dev/video0: 47, Succeeded: 47, Failed: 0, Warnings: 0

v4l2-ctl 則是控制 video4linux 裝置的命令,以下為幾個常用參數:

-d, --device=<dev> 
    Use  device <dev> as the V4L2 device. If <dev> is a number, then /dev/video<dev> is used.

-w, --wrapper 
    Use the libv4l2 wrapper library for all V4L2 device accesses. By  default  v4l2-ctl will  directly  access the V4L2 device, but with this option all access will go via this wrapper library.

--all  Display all information available.

執行 $ sudo v4l2-ctl -d /dev/video0 --all 命令的結果:

v4l2-compliance 1.22.1, 64 bits, 64-bit time_t

Compliance test for vcam device /dev/video0:

Driver Info:
	Driver name      : vcam
	Card type        : vcam
	Bus info         : platform: virtual
	Driver version   : 6.5.13
	Capabilities     : 0x85200001
		Video Capture
		Read/Write
		Streaming
		Extended Pix Format
		Device Capabilities

在掛載 vcam.ko 的前後可以使用 ls /dev 命令來觀察掛載前後模組的差異。在我的裝置中,掛載後多了 video0fb1 等,因此再使用 $ sudo fbset -fb /dev/fb1 --info 命令來觀察 framebuffer fb1 的資訊。

mode "640x480"
    geometry 640 480 640 480 24
    timings 0 0 0 0 0 0 0
    rgba 8/0,8/8,8/16,0/0
endmode

Frame buffer device information:
    Name        : vcamfb
    Address     : 0xffff9f1346979000
    Size        : 921600
    Type        : PACKED PIXELS
    Visual      : TRUECOLOR
    XPanStep    : 1
    YPanStep    : 1
    YWrapStep   : 1
    LineLength  : 1920
    Accelerator : No

使用 vcam driver

在完全沒有寫入 vcam 的 framebuffer 時,用 vlc v4l2:///dev/video0 顯示的預設畫面如下所示,為一個漸層的畫面:

image

如果我們想要對 vcam 所呈現的畫面做更動時,我們要將程式碼寫入其對應的 framebuffer 中。根據 2020 開發紀錄,我們可以先建立一個檔案 test.c ,當中是我們想要對 framebuffer 所呈現畫面進行的操作,對該檔案進行編譯之後,使用 ./test > /proc/vcamfb 將檔案寫入 vcam 對應的 framebuffer 中。然而會出現關於沒有 /proc/vcamfb 的報錯,當我們將 /proc/ 底下的檔案列出後,發現當中有一個 fb 檔案,對其執行 cat /proc/fb 的命令來查看內容時發現 vcamfb 其實存在:

$ cat /proc/fb

0 i915drmfb
1 vcamfb

這個結果表示系統中有兩個 framebuffer 的裝置,包含 i915drmfbvcamfb

更仔細的去探究兩個檔案之間的差異,發現 /proc 是一個虛擬的檔案系統,其提供一個界面,讓使用者獲得系統相關的訊息,當中大部分的檔案是唯讀的。而其中的 /proc/fb 檔提供當 kernel 被編譯時所定義的 framebuffer 的相關資訊。/dev 則是各種 device 儲存的位置,我們可以透過以下命令查看 /dev 下的 fb0fb1 對應到 /proc/fb 中的哪一個裝置名稱:

$ cat /sys/class/graphics/fb0/name

i915drmfb

$ cat /sys/class/graphics/fb1/name

vcamfb

因此我們可以確認 fb1/proc/fb 底下對應到的是 vcamfb 這個名稱。我們重新改寫命令為 $ sudo sh -c './test > /dev/fb1',將想要對 vcam 畫面做的操作寫入 fb1 中,就可以成功的將想進行的操作呈現:

rgb

注意用語!

  • memory 是「(主) 記憶體」,而非「內存」
  • implement 是「實作」
  • device 是「裝置」,而非「設備」
  • interface 是「介面」,而非「接口」
  • data 是「資料」,而非「數據」
  • create 是「建立」,而非「創建」
  • process 是「行程」,而非「進程」
  • access 是「存取」,而非「訪問」(visit)
  • call 是「呼叫」,而非「調用」
  • character device 是「字元裝置」,而非「字符設備」
  • command 是「命令」,而非「指令」(instruction)

參見 詞彙對照表資訊科技詞彙翻譯

已更改

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
不!你沒做到 (例如「字符」的誤用),務必詳細閱讀詞彙對照表資訊科技詞彙翻譯,隨後進行用語調整,尊重台灣資訊科技前輩的篳路藍縷。

Reference: https://hackmd.io/@yinghuaxia/linux2024-final

將上方超連結指向的內容,展開在本頁面。

針對 Linux v6.8 進行調整

TODO: 修正 vcam 並使其得以運作在 Linux v6.8

解釋 Linux 核心原始程式碼的相關異動,探討開發者的考量

Linux yinghuaxia-Altos-P30-F6 6.8.0-36-generic #35~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Mon Jun 10 10:49:14 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux

在 make 的時候會遇到以下問題:

make -C /lib/modules/6.8.0-36-generic/build M=/home/yinghuaxia/Downloads/vcam-master modules
make[1]: Entering directory '/usr/src/linux-headers-6.8.0-36-generic'
warning: the compiler differs from the one used to build the kernel
  The kernel was built by: x86_64-linux-gnu-gcc-13 (Ubuntu 13.2.0-23ubuntu4) 13.2.0
  You are using:           gcc-13 (Ubuntu 13.2.0-23ubuntu4) 13.2.0
  CC [M]  /home/yinghuaxia/Downloads/vcam-master/module.o
  CC [M]  /home/yinghuaxia/Downloads/vcam-master/control.o
  CC [M]  /home/yinghuaxia/Downloads/vcam-master/device.o
  CC [M]  /home/yinghuaxia/Downloads/vcam-master/videobuf.o
/home/yinghuaxia/Downloads/vcam-master/videobuf.c: In function ‘vcam_out_videobuf2_setup’:
/home/yinghuaxia/Downloads/vcam-master/videobuf.c:138:6: error: ‘struct vb2_queue’ has no member named ‘min_buffers_needed’
  138 |     q->min_buffers_needed = 2;
      |      ^~
make[3]: *** [scripts/Makefile.build:243: /home/yinghuaxia/Downloads/vcam-master/videobuf.o] Error 1
make[2]: *** [/usr/src/linux-headers-6.8.0-36-generic/Makefile:1926: /home/yinghuaxia/Downloads/vcam-master] Error 2
make[1]: *** [Makefile:240: __sub-make] Error 2
make[1]: Leaving directory '/usr/src/linux-headers-6.8.0-36-generic'
make: *** [Makefile:14: kmod] Error 2

會發生這個錯誤的原因在於在 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

第二個會遇到的問題是:

make -C /lib/modules/6.8.0-36-generic/build M=/home/yinghuaxia/Downloads/vcam-master modules
make[1]: Entering directory '/usr/src/linux-headers-6.8.0-36-generic'
warning: the compiler differs from the one used to build the kernel
  The kernel was built by: x86_64-linux-gnu-gcc-13 (Ubuntu 13.2.0-23ubuntu4) 13.2.0
  You are using:           gcc-13 (Ubuntu 13.2.0-23ubuntu4) 13.2.0
  CC [M]  /home/yinghuaxia/Downloads/vcam-master/videobuf.o
  CC [M]  /home/yinghuaxia/Downloads/vcam-master/fb.o
/home/yinghuaxia/Downloads/vcam-master/fb.c: In function ‘vcamfb_init’:
/home/yinghuaxia/Downloads/vcam-master/fb.c:405:19: error: ‘FBINFO_FLAG_DEFAULT’ undeclared (first use in this function); did you mean ‘FAULT_FLAG_DEFAULT’?
  405 |     info->flags = FBINFO_FLAG_DEFAULT;
      |                   ^~~~~~~~~~~~~~~~~~~
      |                   FAULT_FLAG_DEFAULT
/home/yinghuaxia/Downloads/vcam-master/fb.c:405:19: note: each undeclared identifier is reported only once for each function it appears in
make[3]: *** [scripts/Makefile.build:243: /home/yinghuaxia/Downloads/vcam-master/fb.o] Error 1
make[2]: *** [/usr/src/linux-headers-6.8.0-36-generic/Makefile:1926: /home/yinghuaxia/Downloads/vcam-master] Error 2
make[1]: *** [Makefile:240: __sub-make] Error 2
make[1]: Leaving directory '/usr/src/linux-headers-6.8.0-36-generic'
make: *** [Makefile:14: kmod] Error 2

原因是因為 FBINFO_FLAG_DEFAULT 這個參數已經沒有在 Linux v6.8 中被使用,因此將該參數刪除或改為 0 即可解決此問題:

你如何確認?只靠肉眼觀察嗎?是否有更科學的方式檢驗裝置驅動程式運作的機制?

根據 commit b4f470a 中所提到的:

The flag FBINFO_FLAG_DEFAULT is 0 and has no effect, as struct
fbinfo.flags has been allocated to zero by kzalloc(). So do not
set it.

Flags should signal differences from the default values. After cleaning
up all occurrences of FBINFO_DEFAULT, the token will be removed.

我們可以知道這個改變是因為 kzalloc() 在初始化的步驟已經把 fbinfo.flags 設為零了,因此原本被拿作為初始化的 FBINFO_FLAG_DEFAULT = 0 就可以在移除所有相關的參數後直接被移除。

- info->flags = FBINFO_FLAG_DEFAULT;
+ info->flags = 0;

Reference: [v4,17/18] fbdev: Remove FBINFO_DEFAULT and FBINFO_FLAG_DEFAULT

解讀 Linux 核心相關變革,善用 git log / git blame 追蹤核心開發者的考量因素。

此時編譯可以成功通過,但會有另外一個問題產生:

Skipping BTF generation for /home/yinghuaxia/Downloads/vcam-master/vcam.ko due to unavailability of vmlinux

這個錯誤可能與 Kernel 或編譯器是否支援 BTF 有關,仍需進行相關測試。

待確認

TODO: 貢獻 vcam

https://github.com/sysprog21/vcam/issues 挑選 #35 並嘗試排除,適度更新你的觀察和推論,若需要程式碼修正,則提交 pull request