Try   HackMD

Linux 核心專題: vcam 研究

執行人: padaray
專題解說影片

Reviewed by ollieni

現代使用 Framebuffer 流程:
CPU 將定義好的圖形算繪指令傳送給 GPU
GPU 解析渲染指令,渲染後將生成的影像資料寫入 Framebuffer 中,通常以 bitmap 格式
video card 讀取 Framebuffer,生成影像訊號,傳送給顯示器
(螢幕)
疑問 : 請問如何生成影像訊號?

任務簡介

投入 vcam 開發,指出其他學員成果的不足和謬誤,予以建議,開發紀錄務必依據資訊科技詞彙翻譯詞彙對照表

TODO: vcam 開發

確保在 Linux v6.8+ 運作。

整理過去報告

根據 2020 年到 2023 年的開發紀錄,將有提到的概念彙整,並且加入自己的理解。

Video for Linux 2 (V4L2)

主要參考自:
2022 年開發紀錄
The Video4Linux2 API: an introduction - 此文件版本較舊,函式或變數名稱很多都不同,但可以用來了解整個流程
Linux Kernel’s documentation: V4L2 - Linux Kernel 的官方文件,完整介紹函式如何使用
Linux V4L2 學習

V4L2 涵蓋的範圍已不只「影音管理」,請依循 Linux 核心原始程式碼裡頭對應的 Kconfig 來更新其描述。

V4L2 是 Linux 核心的影音管理 API ,主要功能是負責支援各種裝置 (device),只有一些是關於影像

V4L2 is designed to support a wide variety of devices, only some of which are truly "video" in nature. - The Video4Linux2 API: an introduction

為甚麼需要 V4L2 的存在 ?

為 Linux Kernel 提供一個統一的 API ,不同的 video capture interface (輸入) 會有不同的硬體驅動需求,我們透過 V4L2 這個 API 則可以直接和不同的硬體裝置互動。V4L2 也提供多種影像格式和解析度 (可調參數),滿足各個應用程式的需求。

V4L2 整體框架

在使用 V4L2 函式前,必須要了解 video_device、v4l2_device、v4l2_subdev 他們之間的關係,如果直接從程式碼下去看,很容易造成觀念混亂,例如我搞混了 v4l2_device_register 和 video_device_register,以為它們是兩個相同的函式,只是版本不同

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 device number(81, X) : V4L2 的 major device number 是 81,透過存取 /dev/videoX 節點來使用 video device,例 : mknod /dev/video0 c 81 0
  • character device driver : V4L2 本身是一個 character device[註1],因此需要透過此驅動程式運作,連接 /dev/videoX 和 video_device
  • video_device : 裝置的具體操作方法,和 v4l2_subdev 進行互動
  • v4l2_device : 可以看做是一個介面,管理很多個子裝置,將 video_device 和 v4l2_device 做連結
  • v4l2_subdev : 實際上運作的各個裝置,實作具體功能,例如:sensor、camera

character device 簡稱 chardev,但不代表這是「由 char 組成的裝置」,其本質就是 character (字元) 為基本操作單位,可對應到 CS:APP 的第 8 章。

註1 : Linux 將裝置分為 character device、block device、network device,character device 顧名思義是一種由 char 組成的裝置,這種裝置就像 文件 一樣,透過依序讀取來實作資料流的概念,大部分都須實作 open、close、read、write 四種函式

注意用語。

以使用者角度,如何使用 V4L2

對 user 來說 Kernel Space 實際上怎麼操作並不重要,他們只需要知道要使用 vcam,就存取 /dev/videoX 進行 open、close、ioctl等操作即可

注意書寫規範:

  • 程式碼註解不該出現中文,總是用美式英語書寫
  • 用流暢的漢語書寫

device 譯作「裝置」,不是「設備」。既然這是行之有年的詞彙,避免過多的中英交雜。

流程:

  1. 使用 open 函式開啟 device

    ​​​​int open(const char *device_name, int flags)
    ​​​​// 實際應用
    ​​​​fd = open("/dev/video0", O_RDWR)
    
    • device_name : 要開啟的 device
    • flags : open flags,通常使用 O_RDWR 只進行讀寫模式
    • return : file description,負數代表錯誤發生
  2. 使用 ioctl 函式,設定 device 的屬性,選擇影像格式,設定資料格式

    ​​​​int ioctl(int fd, int request, void *argp)
    ​​​​// VIDIOC_QUERYCAP request: 此裝置是甚麼以及此裝置能做甚麼
    ​​​​struct capability cap;
    ​​​​ioctl(fd, VIDIOC_QUERYCAP, &cap)
    
    • file:描述此裝置的文件,也就是 fd(open回傳值)
    • request:依據不同的操作填入 request
    • argp:不同的操作填入不同的參數
    ​​​​cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
    
  3. 設定 I/O 方式 (暫存區的管理)
    a. Read/Write : 默認方式,直接讀取和寫入文件
    b. Stream I/O : 不 copy 資料,而是用流的方式傳輸
    c. overlay : 將資料從 RAM copy 到 VRAM

  4. 使用 close 函式關閉 device

    ​​​​int close(int fd)
    

以開發者角度,如何使用 V4L2

若想撰寫 driver 對 vcam 擴充功能,就需了解整個 Kernel Space 的運作,大致流程如下:

將實際上運行的裝置連接到 V4L2

  • 宣告一個 v4l2_device
  • 宣告並初始化一個以上的 v4l2_subdev ,綁定到 v4l2_device

讓使用者可以透過 V4L2 操作 device

  • 宣告一個 video_device 實作 v4l2_file_operations 並用 video_register_device 註冊,完成就能連接 video_devicev4l2_device
  • 通過將 video_device 指向 v4l2_device 完成連接, cdev_add 函式註冊 character device (讓 kernel 知道他是一個 character device),並且指定 file_operations,讓 user 可以使用 open/read/write/ioctl 方法
  • v4l2_device_register 也會通過 device_register 讓其在 /sys 文件系統下可見,讓使用者可以看見並存取

結合上面的架構圖流程步驟如下,除了步驟 1 之外,其他步驟沒有一定順序:

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 →

使用者對裝置檔案的操作,核心會做 file_operations 的呼叫,例:呼叫 file_operations 的 open 函式

v4l2_file_operationsfile_operations 的子集,目的是為了方便開發 V4L2,只取 file_operations 其中比較重要的操作,讓開發者可以專注在影像裝置的功能,並且達到開發的一致性

V4L2 通常會將 file_operations 指向 v4l2_file_operations

善用 Ftrace 追蹤執行流程。

1. 初始化 v4l2_device
宣告 v4l2_device,此函式定義在 /include/media/v4l2-device.h

static struct v4l2_device my_v4l2_device;
ret = v4l2_device_register(NULL, &my_v4l2_device); //透過此方式初始化
void v4l2_device_get(struct v4l2_device *my_v4l2_device) //獲得該裝置的參數
struct v4l2_device {
	struct device *dev;
	struct media_device *mdev;
	struct list_head subdevs;
	spinlock_t lock;
	char name[36];
	void (*notify)(struct v4l2_subdev *sd,
			unsigned int notification, void *arg);
	struct v4l2_ctrl_handler *ctrl_handler;
	struct v4l2_prio_state prio;
	struct kref ref;
	void (*release)(struct v4l2_device *v4l2_dev);
};

2. 初始化 v4l2_subdev,綁定 v4l2_device

宣告 v4l2_subdev,此函式定義在 /include/media/v4l2-subdev.h,使用 v4l2_device_register_subdev 綁定 v4l2_device

static struct v4l2_subdev my_subdev;
v4l2_subdev_init(&subdev, &my_subdev_ops);
ret = v4l2_device_register_subdev(&my_v4l2_device, &my_subdev)

3. 宣告 video_device 實作 v4l2_file_operations

static struct video_device my_video_device;
static const struct v4l2_file_operations my_v4l2_fops = {
    .owner = THIS_MODULE,
    .open = open_func,
    .release = release_func,
    .read = read_func,
    .write = write_func,
    .unlocked_ioctl = video_ioctl2, // 通过 V4L2 框架处理 ioctl
};

4. 呼叫 video_register_device 註冊 video_device
使用在 /include/media/v4l2-dev.hvideo_register_device 函式

video_register_device(struct video_device *vdev, int type, int nr);
video_unregister_device(struct video_device * vdev)
ret = video_register_device(&my_video_device, VFL_TYPE_GRABBER, -1);
  • vdev : 要註冊的 video_device
  • type : 要註冊的裝置類型
  • nr : 所需裝置的序號,0 = /dev/video0,-1 = first free
  • return : 負數代表錯誤發生

5. 通過將 video_device 指向 v4l2_device 完成連接

my_video_device.v4l2_dev = &my_v4l2_device;

6. 驅動退出的時候註銷裝置

video_unregister_device(&my_video_device);
v4l2_device_unregister_subdev(&my_subdev);
v4l2_device_unregister(&my_v4l2_device);

Framebuffer

vcam 會運用到 Framebuffer 的技術,以下資料整理自 Wikipedia 、 過去開發紀錄 、 Linux Kernel 文件

」是量詞,而 frame 是名詞,應依據場景給予合適的譯詞。此處可譯作「影格」。

詞彙對照表

Frame : 指的就是每一個影格,可以理解成一張圖片。我們常提到的 FPS (frame per second),就是每一秒鐘可以顯示多少張圖片
buffer : 一段記憶體空間,所擔任的角色是個暫存區,將待會要處理的資料先暫放在這
Framebuffer : 是 RAM (random-access memory) 的一部分[註1],將要處理的影像資料暫放的地方,之後 vcam 就是從這裡取得 input

[註1] : 這邊的 RAM 指的是 video card[註2] 中的 VRAM (Video RAM),用來儲存圖像資料
[註2] : video card 也稱作 graphics card,GPU 是其中的核心部分,其他部分有 VRAM、驅動程式、電路板

依據中華民國教育部《重編國語辭典修訂本》對於「渲染」一詞的解釋如下:

  1. 國畫的一種用色技巧。以水墨或顏料,襯托物像,使分出陰陽向背的效果。
  2. 言詞、文字過度吹噓誇大。如:「新聞媒體常有渲染的報導。」
  3. 一種電影創作的表現手法。它透過對景物、人物、環境的心理、行為,做多方面描寫形容,突出形象,加強藝術效果。

而英語的 render 一詞是「如實」地「展現」,無論是藉由色彩和光影著色表現,使其結果看來如實且具體,或者採用音樂一類的「表現方式」來詮釋。上述的《湯姆歷險記》中 render 唯一出現的句子,也是這樣的用法。反過來看「渲染」,這個具備多重意義的現代漢語詞彙,若要跟 render 的寓意映射,實在不恰當,喪失其「如實」的原則。我們要留意到,無論經由 GPU 抑或 CPU 進行 rendering 時,總是「輸入成分是什麼,輸出就該是什麼」,中間可沒「渲染」(無論是誇大或多方面描寫) 的成分!

於是 render 若用於電腦圖學領域,翻譯為「算繪」將是最適切的詞彙 —— 該領域的 render 就是「計算」加上「繪製」,忠實且精準的呈現。

參見: 論 Render 翻譯 (算繪/演繹)

現代使用 Framebuffer 流程

  1. CPU 將定義好的圖形算繪指令傳送給 GPU
  2. GPU 解析渲染指令,渲染後將生成的影像資料寫入 Framebuffer 中,通常以 bitmap 格式
  3. video card 讀取 Framebuffer,生成影像訊號,傳送給顯示器 (螢幕)

為何 vcam 用到 Linux framebuffer?

程式碼理解

提供高階的描述,不是急著列出每個檔案、每個函式,避免「舉燭」。

  • module.c
    主要透過 vcam_init 函式在掛載時初始化 vcam,在卸載時用 vcam_exit 清除記憶體。
    devices_max 參數設定最多同時管理幾個 vcam
    create_devices 設定初始化時建立的 vcam 數量
    allow_pix_conversion 是否允許像素格式轉換
    allow_scaling 是否可以縮放圖像
    allow_cropping 是否可以裁減圖像

  • control.c
    此檔案主要負責建立和管理裝置,也實作了 charactor device 的介面,透過這個介面使用者只需要透過 User Space 的應用程式就可以和裝置進行互動,其中的函式都是和 control_device 結構互動,會在後面進行詳細介紹

  • device.c
    此檔案負責 ioctl 的操作實作、各種影像資料的處理、v4l2_file_operations 的實作,以下列舉:
    vcam_ioctl_ops : 實作 iotcl 操作
    vcam_querycap : 查詢裝置能做的事
    vcam_sizes[] : 設定攝影機的解析度
    vcam_fops : 定義了 open、release、read 等操作
    rgb24_to_yuyvyuyv_to_rgb24yuyv_to_rgb24_one_pix : 實作各種格式的轉換

  • videobuf.c
    此檔案使用 videobuf2 框架來設置並管理 framebuffer,主要目的是將 framebuffer 的資料輸出,透過以下函式進行管理:
    vcam_out_queue_setup : 根據影像大小,設置 buffer
    vcam_out_buffer_prepare : 確定 buffer 足夠容納影像
    vcam_out_buffer_queue : 將準備好的 buffer 添加到 queue
    vcam_start_streaming : 啟動一個 thread 進行資料流傳輸
    vcam_stop_streaming : 取消 thread 並停止資料流傳輸
    vcam_out_videobuf2_setup : 初始化 videobuf2
    vcam_vb2_ops : 定義操作 framebuffer 的操作,架構如下:

    ​​​​static const struct vb2_ops vcam_vb2_ops = {
    ​​​​.queue_setup = vcam_out_queue_setup,
    ​​​​.buf_prepare = vcam_out_buffer_prepare,
    ​​​​.buf_queue = vcam_out_buffer_queue,
    ​​​​.start_streaming = vcam_start_streaming,
    ​​​​.stop_streaming = vcam_stop_streaming,
    ​​​​.wait_prepare = vcam_outbuf_unlock,
    ​​​​.wait_finish = vcam_outbuf_lock,
    ​​​​};
    
  • fb.c
    此檔案實作將裝置的資料輸入進 framebuffer,透過函式進行管理,以下為較為重要的函式:
    vcamfb_ops : 將對 framebuffer 的操作綁定函式
    vcam_fb_open : 打開 framebuffer 裝置
    vcam_fb_write : 寫入 framebuffer
    vcam_fb_release : 釋放 framebuffer 的記憶體(設為0),並且關閉 framebuffer 裝置
    vcam_fb_check_var : 檢查調整 framebuffer 裝置的螢幕顯示資料
    vcam_fb_mmap : 把 framebuffer 的記憶體空間 mapping 到 User space
    vcamfb_init : 初始化 framebuffer 裝置
    vcamfb_destroy : 釋放 framebuffer 的記憶體,註銷此裝置

  • vcam-util.c
    透過此檔案的函式,可以直接與 vcam 進行互動
    device_template : 設定好預設的模板,沒有調整參數就用此模板
    create_device : ioctl 函式傳送 VCAM_IOCTL_CREATE_DEVICE 請求,建立新的 vcam 裝置
    remove_device : ioctl 函式傳送 VCAM_IOCTL_DESTROY_DEVICE 請求,移除特定索引值的 vcam 裝置
    modify_device : ioctl 函式傳送 VCAM_IOCTL_MODIFY_SETTING 請求,調整 vcam 參數
    list_devices : ioctl 函式傳送 VCAM_IOCTL_GET_DEVICE 請求,一個一個取得所有裝置資訊

VCAM 實作

版本

ray@ray:~$ uname -r
6.8.0-35-generic

make 遇到的問題

1. struct vb2_queue 中的 min_buffers_needed 不存在

ray@ray:~/vcam$ make
make -C /lib/modules/6.8.0-35-generic/build M=/home/ray/vcam modules
make[1]: Entering directory '/usr/src/linux-headers-6.8.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-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/ray/vcam/module.o
  CC [M]  /home/ray/vcam/control.o
  CC [M]  /home/ray/vcam/device.o
  CC [M]  /home/ray/vcam/videobuf.o
/home/ray/vcam/videobuf.c: In function ‘vcam_out_videobuf2_setup’:
/home/ray/vcam/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/ray/vcam/videobuf.o] Error 1
make[2]: *** [/usr/src/linux-headers-6.8.0-35-generic/Makefile:1926: /home/ray/vcam] Error 2
make[1]: *** [Makefile:240: __sub-make] Error 2
make[1]: Leaving directory '/usr/src/linux-headers-6.8.0-35-generic'
make: *** [Makefile:14: kmod] Error 2

解決方式:

修改 /vcam/videobuf.c,原因是在 Linux 6.8 中把 min_buffers_needed 重新命名成 min_queued_buffers

- q->min_buffers_needed = 2;
+ q->min_queued_buffers = 2;

2. 修改問題 1 重新 make 後出現以下問題,沒宣告的 identifier

ray@ray:~/vcam$ make
make -C /lib/modules/6.8.0-35-generic/build M=/home/ray/vcam modules
make[1]: Entering directory '/usr/src/linux-headers-6.8.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-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/ray/vcam/videobuf.o
  CC [M]  /home/ray/vcam/fb.o
/home/ray/vcam/fb.c: In function ‘vcamfb_init’:
/home/ray/vcam/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/ray/vcam/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/ray/vcam/fb.o] Error 1
make[2]: *** [/usr/src/linux-headers-6.8.0-35-generic/Makefile:1926: /home/ray/vcam] Error 2
make[1]: *** [Makefile:240: __sub-make] Error 2
make[1]: Leaving directory '/usr/src/linux-headers-6.8.0-35-generic'
make: *** [Makefile:14: kmod] Error 2

解決方式:

注意用語。

超連結通常對應到有明確標題的網頁,你該避免用「此篇」這種沒有資訊量可研的標示方式。

參考 此篇 處理方式,文章中提到 commit-b4f470aef449FBINFO_FLAG_DEFAULT 移除,原因是 FBINFO_FLAG_DEFAULT 的值為 0 相當於不影響,而 kzalloc 已經做了初始化為 0。代表若用 kzalloc 分配 空間後,就可以不需要再設置 info->flagsFBINFO_FLAG_DEFAULT,但在 fb.c 中原始碼下:

fb_data = vmalloc(sizeof(struct vcamfb_info));
dev->fb_priv = (void *) fb_data;
info = &fb_data->info;

fb_data 是用 vmalloc 進行初始化,vmalloc 不會把記憶體位置初始化為 0,最簡單的方式是直接將 info->flags 值設為 0:

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

另外我選擇使用 memset 函式將此空間清除為 0,完成後即可 make,但非必要

fb_data = vmalloc(sizeof(struct vcamfb_info));
+ memset(fb_data, 0, sizeof(struct vcamfb_info));

以上兩個問題已經由 hungyuhang 發出了 pull request 解決

探討核心開發者變更程式碼的考量,並確認有無潛在的技術議題。

安裝 vcam

依照 vcam README 步驟安裝
1. 安裝相關套件,方便之後驗證

$ sudo apt install v4l-utils

2. 安裝依賴檔 videobuf2_vmallocvideobuf2_v4l2

$ sudo modprobe -a videobuf2_vmalloc videobuf2_v4l2

沒有加載會出現以下錯誤:

ray@ray:~/vcam$ sudo insmod vcam.ko
insmod: ERROR: could not insert module vcam.ko: Unknown symbol in module
ray@ray:~/vcam$ sudo dmesg | tail -n 100
[10827.688772] vcam: Unknown symbol v4l2_device_unregister (err -2)
[10827.688775] vcam: Unknown symbol video_device_release (err -2)
[10827.688782] vcam: Unknown symbol video_device_release_empty (err -2)
[10827.688788] vcam: Unknown symbol vb2_ioctl_reqbufs (err -2)
[11497.071746] vcam: Unknown symbol vb2_queue_init (err -2)
...

3. 掛載 vcam.ko 模組
掛載後會新增三個裝置節點,videoXvcamctlfbX

  • videoX : V4L2 device
  • vcamctl : 透過操作 vcam-util 控制 vcam
  • fbX : 控制 framebuffer
$ sudo insmod vcam.ko

linux kernel 的掛載方式是,呼叫 module.cmodule_init 函式,在這裡呼叫的是 vcam_init

static int __init vcam_init(void)
{
    int i;
    int ret = create_control_device(CONTROL_DEV_NAME);
    if (ret)
        goto failure;

    for (i = 0; i < create_devices; i++)
        request_vcam_device(NULL);

failure:
    return ret;
}

vcam_init 內部又呼叫了 control.ccreate_control_device 函式,此函式的作用是用 cdev_add 函式註冊 character device,先分配記憶體位置給 ctldev[註1],ctldev->dev_class 設置 /dev 目錄下要顯示的檔案名,將 ctldev->cdev 加入 character device,透過 device_create [註2]建立 ctldev->device

int __init create_control_device(const char *dev_name)
{
    int ret = 0;

    ctldev = alloc_control_device;
    ...
        
    #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
    ctldev->dev_class = class_create(dev_name);
    #else
    ctldev->dev_class = class_create(THIS_MODULE, dev_name);
    ...
    
    cdev_init(&ctldev->cdev, &control_fops);
    ctldev->cdev.owner = THIS_MODULE;
    ...

    ret = cdev_add(&ctldev->cdev, ctldev->dev_number, 1);
    ...

    ctldev->device = device_create(ctldev->dev_class, NULL, ctldev->dev_number,
                                   NULL, dev_name, MINOR(ctldev->dev_number));
    ...

    spin_lock_init(&ctldev->vcam_devices_lock);

    ...
}

註1 : control device 結構負責管理整個裝置,結構如下:

struct control_device {
    int major;    // major device number,同個 module 下的 major 是一樣的,v4l2_device 就是 81
    dev_t dev_number;    // 第幾個裝置
    struct class *dev_class;    //建立 /dev 的節點
    struct device *device;
    struct cdev cdev;
    struct vcam_device **vcam_devices;    // 我們的 vcam
    size_t vcam_device_count;    // vcam 的裝置數量
    spinlock_t vcam_devices_lock;
};

註2 : device_createlinux/drivers/base/core.c 下的函式 註解說明建立設備並且註冊到 sysfs,函式主要做的事是,分配記憶體位置給裝置,設置裝置名稱,把傳入參數設置給 dev ,最後將裝置註冊到 kernel

/**
* device_create - creates a device and registers it with sysfs
...
*/
struct device *device_create(const struct class *class, struct device *parent,
			     dev_t devt, void *drvdata, const char *fmt, ...)
{
	va_list vargs;
	struct device *dev;

	va_start(vargs, fmt);
	dev = device_create_groups_vargs(class, parent, devt, drvdata, NULL,
					  fmt, vargs);
	va_end(vargs);
	return dev;
}

struct device *device_create_groups_vargs(const struct class *class, struct device *parent,
			   dev_t devt, void *drvdata,
			   const struct attribute_group **groups,
			   const char *fmt, va_list args)
{
    ...

	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
	...
        
    device_initialize(dev);
	dev->devt = devt;
	dev->class = class;
	dev->parent = parent;
	dev->groups = groups;
	dev->release = device_create_release;
	dev_set_drvdata(dev, drvdata);

	retval = kobject_set_name_vargs(&dev->kobj, fmt, args);
	if (retval)
		goto error;

	retval = device_add(dev);
	if (retval)
		goto error;

	return dev;

    ...
}

注意書寫規範:

  • 使用 lab0 規範的程式碼書寫風格,務必用 clang-format 確認一致

本頁面張貼的原始程式碼,應該用 4 個空白字元進行縮排。

4. 掛載成功後,會在 /dev 下新增裝置節點
使用 ./vcam-util -l 命令列出所有 V4L2 device

ray@ray:~/vcam$ sudo ./vcam-util -l
Available virtual V4L2 compatible devices:
1. fb1(640,480,1/1,rgb24) -> /dev/video0

查看 vcam-util.c 可以知道 -l 會呼叫 list_devices

    /* Process command line options */
    do {
        next_option =
            getopt_long(argc, argv, short_options, long_options, NULL);
        switch (next_option) {
        ...
        case 'l':
            list_devices();
            break;
        ...

list_devices 透過 VCAM_IOCTL_GET_DEVICE request 取得裝置資訊,當 ioctl 不回復 0 代表裝置存在,則繼續顯示該裝置資訊,直到找不到裝置為止

int list_devices()
{
    struct vcam_device_spec dev = {.idx = 0};

    int fd = open(ctl_path, O_RDWR);
    if (fd == -1) {
        fprintf(stderr, "Failed to open %s device.\n", ctl_path);
        return -1;
    }

    printf("Available virtual V4L2 compatible devices:\n");
    while (!ioctl(fd, VCAM_IOCTL_GET_DEVICE, &dev)) {
        dev.idx++;
        printf("%d. %s(%d,%d,%d/%d,%s) -> %s\n", dev.idx, dev.fb_node,
               dev.width, dev.height, dev.cropratio.numerator,
               dev.cropratio.denominator,
               dev.pix_fmt == VCAM_PIXFMT_RGB24 ? "rgb24" : "yuyv",
               dev.video_node);
    }
    close(fd);
    return 0;
}

5. 測試驅動程式是否正常運作
使用 v4l2-compliance 進行測試,正常情況下應該要返回 Failed: 0, Warnings: 0

ray@ray:~/vcam$ sudo v4l2-compliance -d /dev/video0 -f
...
Stream using all formats:
	test MMAP for Format RGB3, Frame Size 640x480@59.94 Hz:
		Stride 1920, Field None: OK                                 
	test MMAP for Format RGB3, Frame Size 640x480@1.00 Hz:
		Stride 1920, Field None: OK                                 
Total for vcam device /dev/video0: 48, Succeeded: 48, Failed: 0, Warnings: 0

v4l2-compliance 會測試裝置可以支持的所有格式,透過 Memory Mapping 進行測試,Stride 1920 代表 每列有 640 個像素,每個像素有 RGB 三個資訊,共 1920 bytes,Field None: OK 代表測試結果沒問題


6. 查看 framebuffer 資訊
可以看到 framebuffer 儲存的是 1280x800 畫質,但我們的裝置是 640x480,目前不確定會不會發生衝突

ray@ray:~/vcam$ sudo fbset -fb /dev/fb0 --info

mode "1280x800"
    geometry 1280 800 1280 800 32
    timings 0 0 0 0 0 0 0
    rgba 8/16,8/8,8/0,0/0
endmode

Frame buffer device information:
    Name        : vmwgfxdrmfb
    Address     : 0
    Size        : 4096000
    Type        : PACKED PIXELS
    Visual      : TRUECOLOR
    XPanStep    : 1
    YPanStep    : 1
    YWrapStep   : 0
    LineLength  : 5120
    Accelerator : No


開啟 vcam

1. 安裝 vlc,使用命令開啟 vcam

$ sudo apt install vlc
$ vlc v4l2:///dev/video0

會顯示出以下畫面,代表 vcam 成功運行

image

  1. 參考 eecheng 可開啟指定圖片 的程式碼進行測試

TODO: 檢視其他學員在 vcam 的投入狀況,提出疑惑和建議

課程期末專題找出同樣從事 vcam 專案開發的學員,在其開發紀錄指出其他學員成果的不足和謬誤,提出你的疑惑和建議。
在此彙整你的認知和對比你的產出。
要能提出關鍵問題,例如「vcam 為何使用到 framebuffer 和 V4L2?這是基於什麼考量?」「如何消除 vcam 的記憶體洩漏的問題?」「如何降低 vcam 使用時期的 CPU 開銷?」「V4L2 內部有哪些關鍵的核心執行緒搭配處理 vcam 所需的功能?」
除了在其他學員的開發紀錄提問,也該嘗試從 Linux 核心原始碼找出對應解答和素材。