執行人: padaray
專題解說影片
ollieni
現代使用 Framebuffer 流程:
CPU 將定義好的圖形算繪指令傳送給 GPU
GPU 解析渲染指令,渲染後將生成的影像資料寫入 Framebuffer 中,通常以 bitmap 格式
video card 讀取 Framebuffer,生成影像訊號,傳送給顯示器
(螢幕)
疑問 : 請問如何生成影像訊號?
投入 vcam 開發,指出其他學員成果的不足和謬誤,予以建議,開發紀錄務必依據資訊科技詞彙翻譯和詞彙對照表。
確保在 Linux v6.8+ 運作。
根據 2020 年到 2023 年的開發紀錄,將有提到的概念彙整,並且加入自己的理解。
主要參考自:
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
為 Linux Kernel 提供一個統一的 API ,不同的 video capture interface
(輸入) 會有不同的硬體驅動需求,我們透過 V4L2 這個 API 則可以直接和不同的硬體裝置互動。V4L2 也提供多種影像格式和解析度 (可調參數),滿足各個應用程式的需求。
在使用 V4L2 函式前,必須要了解 video_device、v4l2_device、v4l2_subdev 他們之間的關係,如果直接從程式碼下去看,很容易造成觀念混亂,例如我搞混了 v4l2_device_register 和 video_device_register,以為它們是兩個相同的函式,只是版本不同
/dev/videoX
節點來使用 video device,例 : mknod /dev/video0 c 81 0
/dev/videoX
和 video_devicecharacter device 簡稱 chardev,但不代表這是「由 char 組成的裝置」,其本質就是 character (字元) 為基本操作單位,可對應到 CS:APP 的第 8 章。
註1 : Linux 將裝置分為 character device、block device、network device,character device 顧名思義是一種由 char 組成的裝置,這種裝置就像 文件 一樣,透過依序讀取來實作資料流的概念,大部分都須實作 open、close、read、write 四種函式
注意用語。
對 user 來說 Kernel Space 實際上怎麼操作並不重要,他們只需要知道要使用 vcam,就存取 /dev/videoX
進行 open、close、ioctl…等操作即可
注意書寫規範:
device 譯作「裝置」,不是「設備」。既然這是行之有年的詞彙,避免過多的中英交雜。
流程:
使用 open
函式開啟 device
使用 ioctl
函式,設定 device 的屬性,選擇影像格式,設定資料格式
open
回傳值)設定 I/O 方式 (暫存區的管理)
a. Read/Write : 默認方式,直接讀取和寫入文件
b. Stream I/O : 不 copy 資料,而是用流的方式傳輸
c. overlay : 將資料從 RAM copy 到 VRAM
使用 close
函式關閉 device
若想撰寫 driver 對 vcam 擴充功能,就需了解整個 Kernel Space 的運作,大致流程如下:
將實際上運行的裝置連接到 V4L2
v4l2_device
v4l2_subdev
,綁定到 v4l2_device
讓使用者可以透過 V4L2 操作 device
video_device
實作 v4l2_file_operations
並用 video_register_device
註冊,完成就能連接 video_device
和 v4l2_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 之外,其他步驟沒有一定順序:
使用者對裝置檔案的操作,核心會做 file_operations
的呼叫,例:呼叫 file_operations 的 open 函式
v4l2_file_operations
是 file_operations
的子集,目的是為了方便開發 V4L2,只取 file_operations
其中比較重要的操作,讓開發者可以專注在影像裝置的功能,並且達到開發的一致性
V4L2 通常會將 file_operations
指向 v4l2_file_operations
,
善用 Ftrace 追蹤執行流程。
1. 初始化 v4l2_device
宣告 v4l2_device
,此函式定義在 /include/media/v4l2-device.h
2. 初始化 v4l2_subdev
,綁定 v4l2_device
宣告 v4l2_subdev
,此函式定義在 /include/media/v4l2-subdev.h
,使用 v4l2_device_register_subdev
綁定 v4l2_device
3. 宣告 video_device
實作 v4l2_file_operations
4. 呼叫 video_register_device
註冊 video_device
使用在 /include/media/v4l2-dev.h
的 video_register_device
函式
5. 通過將 video_device
指向 v4l2_device
完成連接
6. 驅動退出的時候註銷裝置
vcam 會運用到 Framebuffer 的技術,以下資料整理自 Wikipedia 、 過去開發紀錄 、 Linux Kernel 文件。
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、驅動程式、電路板
依據中華民國教育部《重編國語辭典修訂本》對於「渲染」一詞的解釋如下:
而英語的 render 一詞是「如實」地「展現」,無論是藉由色彩和光影著色表現,使其結果看來如實且具體,或者採用音樂一類的「表現方式」來詮釋。上述的《湯姆歷險記》中 render 唯一出現的句子,也是這樣的用法。反過來看「渲染」,這個具備多重意義的現代漢語詞彙,若要跟 render 的寓意映射,實在不恰當,喪失其「如實」的原則。我們要留意到,無論經由 GPU 抑或 CPU 進行 rendering 時,總是「輸入成分是什麼,輸出就該是什麼」,中間可沒「渲染」(無論是誇大或多方面描寫) 的成分!
於是 render 若用於電腦圖學領域,翻譯為「算繪」將是最適切的詞彙 —— 該領域的 render 就是「計算」加上「繪製」,忠實且精準的呈現。
現代使用 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_yuyv
、yuyv_to_rgb24
、yuyv_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 的操作,架構如下:
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
請求,一個一個取得所有裝置資訊
1. struct vb2_queue
中的 min_buffers_needed
不存在
解決方式:
修改 /vcam/videobuf.c
,原因是在 Linux 6.8 中把 min_buffers_needed
重新命名成 min_queued_buffers
2. 修改問題 1 重新 make 後出現以下問題,沒宣告的 identifier
解決方式:
注意用語。
超連結通常對應到有明確標題的網頁,你該避免用「此篇」這種沒有資訊量可研的標示方式。
參考 此篇 處理方式,文章中提到 commit-b4f470aef449 將 FBINFO_FLAG_DEFAULT
移除,原因是 FBINFO_FLAG_DEFAULT
的值為 0 相當於不影響,而 kzalloc
已經做了初始化為 0。代表若用 kzalloc
分配 空間後,就可以不需要再設置 info->flags
為 FBINFO_FLAG_DEFAULT
,但在 fb.c
中原始碼下:
fb_data
是用 vmalloc
進行初始化,vmalloc
不會把記憶體位置初始化為 0,最簡單的方式是直接將 info->flags
值設為 0:
另外我選擇使用 memset
函式將此空間清除為 0,完成後即可 make,但非必要
以上兩個問題已經由 hungyuhang 發出了 pull request 解決
探討核心開發者變更程式碼的考量,並確認有無潛在的技術議題。
依照 vcam README 步驟安裝
1. 安裝相關套件,方便之後驗證
2. 安裝依賴檔 videobuf2_vmalloc
和 videobuf2_v4l2
沒有加載會出現以下錯誤:
3. 掛載 vcam.ko
模組
掛載後會新增三個裝置節點,videoX
、vcamctl
、fbX
vcam-util
控制 vcamlinux kernel 的掛載方式是,呼叫 module.c
的 module_init
函式,在這裡呼叫的是 vcam_init
vcam_init
內部又呼叫了 control.c
的 create_control_device
函式,此函式的作用是用 cdev_add
函式註冊 character device,先分配記憶體位置給 ctldev
[註1],ctldev->dev_class
設置 /dev
目錄下要顯示的檔案名,將 ctldev->cdev
加入 character device,透過 device_create
[註2]建立 ctldev->device
註1 : control device
結構負責管理整個裝置,結構如下:
註2 : device_create
是 linux/drivers/base/core.c
下的函式 註解說明建立設備並且註冊到 sysfs,函式主要做的事是,分配記憶體位置給裝置,設置裝置名稱,把傳入參數設置給 dev ,最後將裝置註冊到 kernel
注意書寫規範:
本頁面張貼的原始程式碼,應該用 4 個空白字元進行縮排。
4. 掛載成功後,會在 /dev 下新增裝置節點
使用 ./vcam-util -l
命令列出所有 V4L2 device
查看 vcam-util.c
可以知道 -l 會呼叫 list_devices
list_devices
透過 VCAM_IOCTL_GET_DEVICE
request 取得裝置資訊,當 ioctl 不回復 0 代表裝置存在,則繼續顯示該裝置資訊,直到找不到裝置為止
5. 測試驅動程式是否正常運作
使用 v4l2-compliance
進行測試,正常情況下應該要返回 Failed: 0, Warnings: 0
v4l2-compliance
會測試裝置可以支持的所有格式,透過 Memory Mapping 進行測試,Stride 1920 代表 每列有 640 個像素,每個像素有 RGB 三個資訊,共 1920 bytes,Field None: OK 代表測試結果沒問題
6. 查看 framebuffer 資訊
可以看到 framebuffer 儲存的是 1280x800 畫質,但我們的裝置是 640x480,目前不確定會不會發生衝突
1. 安裝 vlc,使用命令開啟 vcam
會顯示出以下畫面,代表 vcam 成功運行
在課程期末專題找出同樣從事 vcam 專案開發的學員,在其開發紀錄指出其他學員成果的不足和謬誤,提出你的疑惑和建議。
在此彙整你的認知和對比你的產出。
要能提出關鍵問題,例如「vcam 為何使用到 framebuffer 和 V4L2?這是基於什麼考量?」「如何消除 vcam 的記憶體洩漏的問題?」「如何降低 vcam 使用時期的 CPU 開銷?」「V4L2 內部有哪些關鍵的核心執行緒搭配處理 vcam 所需的功能?」
除了在其他學員的開發紀錄提問,也該嘗試從 Linux 核心原始碼找出對應解答和素材。