Try   HackMD

2021q1 Final Project (vcam)

contributed by < WayneLin1992 >

tags: linux2021 vcam

進度規劃

  • 重現 vcam 測試紀錄
  • 理解 framebuffer 原理
  • 理解 V4L2 框架
  • 理解程式運作原理
  • 整合之前的實作

重現vcam測試紀錄

參考資料

vcam github
vcam 測試紀錄

ubuntu 20.04.1

測試環境:

$ uname -a
Linux wayne-ThinkPad-T14s-Gen-1 5.8.0-50-generic #56~20.04.1-Ubuntu SMP Mon Apr 12 21:46:35 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

為了編譯 kernel driver 安裝需要的 linux headers
$ sudo apt install linux-headers-$(uname -r)
為了獲得相關信息,及驗證 安裝 v4l-utils
$ sudo apt install v4l-utils
執行 make 即可

wayne@wayne-ThinkPad-T14s-Gen-1:~/linux2021/vcam$ make
make -C /lib/modules/5.8.0-50-generic/build M=/home/wayne/linux2021/vcam modules
make[1]: 進入目錄「/usr/src/linux-headers-5.8.0-50-generic」
  CC [M]  /home/wayne/linux2021/vcam/module.o
  CC [M]  /home/wayne/linux2021/vcam/control.o
  CC [M]  /home/wayne/linux2021/vcam/device.o
/home/wayne/linux2021/vcam/device.c: In function ‘create_vcam_device’:
/home/wayne/linux2021/vcam/device.c:774:39: error: ‘VFL_TYPE_GRABBER’ undeclared (first use in this function); did you mean ‘VFL_TYPE_SUBDEV’?
  774 |     ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
      |                                       ^~~~~~~~~~~~~~~~
      |                                       VFL_TYPE_SUBDEV
/home/wayne/linux2021/vcam/device.c:774:39: note: each undeclared identifier is reported only once for each function it appears in
make[2]: *** [scripts/Makefile.build:286:/home/wayne/linux2021/vcam/device.o] 錯誤 1
make[1]: *** [Makefile:1783:/home/wayne/linux2021/vcam] 錯誤 2
make[1]: 離開目錄「/usr/src/linux-headers-5.8.0-50-generic」
make: *** [Makefile:14:kmod] 錯誤 2

v4l2-dev.h: remove VFL_TYPE_GRABBERmedia: rename VFL_TYPE_GRABBER to _VIDEO可以知道在 2020 3月 被移除,但相對應的可以使用 VFL_TYPE_VIDEO 來代替。

由此我將 VFL_TYPE_GRABBER 改為 VFL_TYPE_VIDEO 嘗試看看編譯結果

在 kernel 4.15 VFL_TYPE_GRABBER: /dev/videoX for video input/output devices
在 kernel 5.8 VFL_TYPE_VIDEO: /dev/videoX for video input/output devices
參考資料:
linux kernel 4.15v v4l2
linux kernel 5.8v v4l2

device.o 成功編譯但出現其他問題。

make -C /lib/modules/5.8.0-50-generic/build M=/home/wayne/linux2021/vcam modules
make[1]: 進入目錄「/usr/src/linux-headers-5.8.0-50-generic」
  CC [M]  /home/wayne/linux2021/vcam/module.o
  CC [M]  /home/wayne/linux2021/vcam/control.o
  CC [M]  /home/wayne/linux2021/vcam/device.o
  CC [M]  /home/wayne/linux2021/vcam/videobuf.o
  CC [M]  /home/wayne/linux2021/vcam/fb.o
/home/wayne/linux2021/vcam/fb.c: In function ‘init_framebuffer’:
/home/wayne/linux2021/vcam/fb.c:120:54: error: passing argument 4 of ‘proc_create_data’ from incompatible pointer type [-Werror=incompatible-pointer-types]
  120 |     procf = proc_create_data(proc_fname, 0666, NULL, &vcamfb_fops, dev);
      |                                                      ^~~~~~~~~~~~
      |                                                      |
      |                                                      struct file_operations *
In file included from /home/wayne/linux2021/vcam/fb.c:3:
./include/linux/proc_fs.h:103:31: note: expected ‘const struct proc_ops *’ but argument is of type ‘struct file_operations *’
  103 | extern struct proc_dir_entry *proc_create_data(const char *, umode_t,
      |                               ^~~~~~~~~~~~~~~~
cc1: some warnings being treated as errors
make[2]: *** [scripts/Makefile.build:286:/home/wayne/linux2021/vcam/fb.o] 錯誤 1
make[1]: *** [Makefile:1783:/home/wayne/linux2021/vcam] 錯誤 2
make[1]: 離開目錄「/usr/src/linux-headers-5.8.0-50-generic」
make: *** [Makefile:14:kmod] 錯誤 2

proc_create_data 的 arguments 的 type 有因為 linux 更新有所產生改變,
How to fix error: passing argument 4 of 'proc_create' from incompatible pointer type 可以知道這是 kernel version 5.6 之後的更新,查看一下 struct proc_ops
看一下 vcamfb_fops 在 fb.c 中

static struct file_operations vcamfb_fops = {
    .owner = THIS_MODULE,
    .open = vcamfb_open,
    .release = vcamfb_release,
    .write = vcamfb_write,
};

發現到其中 owner 是其中最大的差距 struct module *ownerunsigned int proc_flags 大小也不相等,不能直接透過轉,嘗試直接將 fb.c 中的 file_operations 換成 proc_ops .owner 換成 .proc_flags。

static struct proc_ops  vcamfb_fops = {
+    .proc_open = vcamfb_open,
+    .proc_release = vcamfb_release,
+    .proc_write = vcamfb_write,
};

可以發現到已經成功掛載 vcam
$ sudo ./vcam-util -l

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

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

v4l2-compliance - An application to test video4linux drivers

The v4l2-compliance tool is used to test video4linux devices, either video, vbi, radio or swradio, both input and output. It attempts to test almost all aspects of a V4L2 device and it covers almost all V4L2 ioctls. It has very good support for video capture and output, VBI capture and output and (software) radio tuning and transmitting.
-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 [<count>]

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.

因此可以知道 stream-all-formats 測試將會嘗試不同的格式。

注意到 Buffer ioctls fail
test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: FAIL
可以了解到這裡是對 buffer 進行測試,初始、創立、詢問狀態,失敗
VIDIOC_REQBUFS - Initiate Memory Mapping, User Pointer I/O or DMA buffer I/O
VIDIOC_CREATE_BUFS - Create buffers for Memory Mapped or User Pointer or DMA Buffer I/O
VIDIOC_QUERYBUF - Query the status of a buffer

其中 vcam 中 device.c
.vidioc_reqbufs = vb2_ioctl_reqbufs 這可能是有改變的地方。

參考資料:
v4l2-compliance
VIDIOC_REQBUFS
VIDIOC_CREATE_BUFS
VIDIOC_QUERYBUF

$ sudo v4l2-ctl -d /dev/video2 --all

Driver Info:
	Driver name      : vcam
	Card type        : vcam
	Bus info         : platform: virtual
	Driver version   : 5.8.18
	Capabilities     : 0x85200001
		Video Capture
		Read/Write
		Streaming
		Extended Pix Format
		Device Capabilities
	Device Caps      : 0x05200001
		Video Capture
		Read/Write
		Streaming
		Extended Pix Format
Priority: 2
Video input : 0 (vcam_in 0: ok)
Format Video Capture:
	Width/Height      : 640/480
	Pixel Format      : 'RGB3' (24-bit RGB 8-8-8)
	Field             : None
	Bytes per Line    : 1920
	Size Image        : 921600
	Colorspace        : sRGB
	Transfer Function : Default (maps to sRGB)
	YCbCr/HSV Encoding: Default (maps to ITU-R 601)
	Quantization      : Default (maps to Full Range)
	Flags             : 
Streaming Parameters Video Capture:
	Frames per second: 29.970 (30000/1001)
	Read buffers     : 1

ubuntu 18.04.1

經過老師的建議,我重新安裝 ubuntu 18.04 kernel 4.15 的版本,進行測試。
測試環境:

Linux wayne-MacBookAir 4.15.0-29-generic #31-Ubuntu SMP Tue Jul 17 15:39:52 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

$ sudo insmod vcam.ko

insmod: ERROR: could not insert module vcam.ko: Unknown symbol in module

dmesg

[ 3152.479469] videobuf2_vmalloc: unknown parameter 'videobuf2_v4l2' ignored
[ 3169.618202] vcam: Unknown symbol vb2_queue_init (err 0)
[ 3169.618231] vcam: Unknown symbol video_ioctl2 (err 0)
...
[ 4237.804522] vcam: Unknown symbol video_device_release_empty (err 0)
[ 4237.804541] vcam: Unknown symbol vb2_ioctl_reqbufs (err 0)

$ sudo modprobe -a videobuf2_vmalloc videobuf2_v4l2
這樣就可以成功掛載 vcam
$ sudo insmod vcam.ko

sudo v4l2-ctl -d /dev/video0 --all

Driver Info (not using libv4l2):
	Driver name   : vcam
	Card type     : vcam
	Bus info      : platform: virtual
	Driver version: 4.15.18
	Capabilities  : 0x85200001
		Video Capture
		Read/Write
		Streaming
		Extended Pix Format
		Device Capabilities
	Device Caps   : 0x05200001
		Video Capture
		Read/Write
		Streaming
		Extended Pix Format
Priority: 2
Video input : 0 (vcam_in 0: ok)
Format Video Capture:
	Width/Height      : 640/480
	Pixel Format      : 'RGB3'
	Field             : None
	Bytes per Line    : 1920
	Size Image        : 921600
	Colorspace        : sRGB
	Transfer Function : Default (maps to sRGB)
	YCbCr/HSV Encoding: Default (maps to ITU-R 601)
	Quantization      : Default (maps to Full Range)
	Flags             : 
Streaming Parameters Video Capture:
	Frames per second: 29.970 (30000/1001)
	Read buffers     : 1

能夠成功開啟。

理解 framebuffer 原理

vcam 是一個與 v4l2 兼容,並且由 framebuffer 當輸入,所以了解 vcam 必須先了解 framebuffer。
framebuffer 顧名思義就是就是記憶體的一個區域,裡面存放著圖片或影片的信息(如 RGB YUV),讓 vcam 讀取,所以可以做到使 camera 讀到虛假的畫面。

圖中 DAC(Digital to analog converter) 將數位信號轉成類比訊號。
就像 MMAP(Memory-mapped file) 一樣,將圖像直接寫入記憶體當中,給 I/O 直接讀取,省去執行 I/O 的時間延遲,所以顯示卡才會有對應的獨立的記憶體空間,這也是為了使 I/O 執行較快,再來還可以透過 SIMD 處理,來加快 I/O 的執行速度,對於網路影像,也可以透過 zero-copy 的方式直接將網路圖像直接寫入對應的記憶中,直接讀取。

Double buffering

vcam 主要是使用雙緩衝原理來避免畫面撕裂。
畫面的撕裂如下所示,當顯示的同時有 CPU 或 GPU 對 buffer 做處理就會產生畫面的撕裂。

雙緩衝是透過一個 buffer 為顯示,而另一個 buffer 同時在 填充資料,當 buffer 填充完資料後顯示,同時原本顯示的 buffer 又拿來填充,如下圖

以時間軸表示如下圖 在顯示 buffer 0 時 GPU 和 CPU 對 buffer 1 做處理, VSync(Vertical sync) 代表螢幕同步刷新的時間點,當刷新時間和處理時間必須配合時,否則會產生撕裂畫面。

理解程式運作原理

由於主要執行是透過 framebuffer 的操作,所以要特別觀察其中的操作。

  • module.c
    將 vcam 初始化 vcam_init 實作在 create_control_device 表示,其中使用 kmalloc 進行配置 struct control_device 空間,還可以開啟模組參數如: allow_pix_conversion 可以接受 rgb 轉換成 YUV ,
    static int __init vcam_init(void) 實作 __init create_control_device

include/linux/init.h 看到相關解釋, __init

used to mark some functions or initialized data (doesn't apply to uninitialized data) as `initialization' functions. The kernel can take this as hint that the function is used only during the initialization phase and free up used memory resources after

將會標註為 初始化函數,只會使用一次。
module_param

定義一個模組參數 MODULE_PARM_DESC() 說明模組功能

  • control.c
    當 module 在 kernel 中被建立, class_create 會透過 udev 將會在 /dev 動態的建立一個 device 文件。

__class_create 為 class_create 的內部實作。
struct class * __class_create(struct module * owner, const char * name, struct lock_class_key * key);
__class_create - create a struct class structure

This is used to create a struct class pointer that can then be used in calls to device_create.
Returns struct class pointer on success, or ERR_PTR on error.

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

This function can be used by char device classes. A struct device will be created in sysfs, registered to the specified class. A “dev” file will be created, showing the dev_t for the device.
Returns struct device pointer on success, or ERR_PTR on error.

create_control_device
掛載: device 註冊
透過 cdev_init 將 struct cdev 初始化, cdev 是 The struct cdev is the kernel’s internal structure that represents char devices. This field contains a pointer to that structure when the inode refers to a char device file.
char devices 的操作由 file_operations control_fops 來存取,其中有一項 .unlocked_ioctl = control_ioctl 代表對 char device ioctl 操作。

unlocked_ioctl() 取代 .ioctl 是避免 big kernel lock(BKL)

control_ioctl 包括 VCAM_IOCTL_CREATE_DEVICE 會調用 request_vcam_devicecreate_vcam_device 創立出 vcam_device 並且預設大小為 640 * 480 rgb-24 , 並將創立的 vcam 並連接 ctldev->vcam_devices[idx] = vcam
VCAM_IOCTL_DESTROY_DEVICE 調用 control_iocontrol_destroy_devicevcam destroy 並對應 idx
VCAM_IOCTL_GET_DEVICE 調用 control_iocontrol_get_devicevcam format 提取出來 並寫入 dev_specsnprintf 出。
VCAM_IOCTL_MODIFY_SETTING 調用 control_iocontrol_modify_input_setting dev_spec 修改 input_formatsetting
透過 alloc_chrdev_region 取得動態配置 device 的 major number
透過 cdev_add 向 kernel 登入驅動程式

void cdev_init (struct cdev * cdev, const struct file_operations * fops);
cdev_init — initialize a cdev structure

Initializes cdev, remembering fops, making it ready to add to the system with cdev_add.

int alloc_chrdev_region (dev_t * dev, unsigned baseminor, unsigned count, const char * name);
alloc_chrdev_region — register a range of char device numbers

Allocates a range of char device numbers. The major number will be chosen dynamically, and returned (along with the first minor number) in dev. Returns zero or a negative error code.

int cdev_add (struct cdev * p, dev_t dev, unsigned count);
cdev_add — add a char device to the system

adds the device represented by p to the system, making it live immediately. A negative error code is returned on failure.

destroy_control_device
卸載: device
透過 cdev_del 將其中 cdev 從 kernel 中移除。
透過 unregister_chrdev_region 將其中 major number 移除

cdev 是一種與 kernel 溝通的結構,主要表達 char device,所以需要註冊,struct inode 會在指向 cdevstruct inode 代表 file 在 rootfs 中。

參考資料:
why to register struct cdev in driver code

request_vcam_device
將執行 create_vcam_devicecreate_vcam_device 實作將在 device.c 中,也會在之後說明
spin_lock_irqsavespin_unlock_irqrestore 保證了 vcam_device_count 正確性。

spin_lock_irqsave \ spin_unlock_irqrestore

spin_lock_irqsave(&mLock, flags); // Save the state of interrupt enable in flags and then disable interrupts
// Critical section
spin_unlock_irqrestore(&mLock, flags); // Return to the previous state saved in flags

spin_lock_irqsave會保存當下狀態及中斷的狀態,並把 interrupt 關掉,等到 critical section 處裡完,將會回復到原本狀態,並將 interrupt 開啟。

spin_lock_irq \ spin_unlock_irq

spin_lock_irq(&mLock); // Does not know if interrupts are already disabled
// Critical section
spin_unlock_irq(&mLock); // Could result in an unwanted interrupt re-enable...

只能在中斷時 isr 中斷處理程式中使用
因此大部分都會使用 spin_lock_irqsave 來上鎖。
參考資料:
spin_lock_irqsave vs spin_lock_irq

  • device.c
    create_vcam_device 使用 kzmalloc 配置一個 struct vcam_device 的空間, v4l2_device_register 將會註冊一個 v4l2_device 的空間並初始化, vcam_out_videobuf2_setup 將 video buffer 初始化 ,將在 videobuf.c 實作,將在之後說明,video_set_drvdata 將 device pruvate data 指向 video_device 中, video_register_device 註冊一個 video_device, 其中 VFL_TYPE_GRABBER 對應為 /dev/videoX for video input/output devices, init_framebuffer 初始化 framebuffer 將 fb.c 中實作, vcam_in_queue_setup 初始化 &vcam->in_queue 會在 videobuf.c 實作。

struct vcam_device -> struct video_device -> struct device -> void *driver_data = struct vcam_device *vcam 所以才可以應用 struct vcam_device *dev = (struct vcam_device *) video_drvdata(file); 呼叫出 struct vcam_device 因為 video_devices[iminor(file_inode(file))] 可以得到 struct video_device 又透過 struct device -> driver_data 得到 struct vcam_device

其中 struct v4l2_ioctl_ops vcam_ioctl_ops 也是 compliance 測試的地方,

static const struct v4l2_ioctl_ops vcam_ioctl_ops = {
    .vidioc_querycap = vcam_querycap,        //查詢驅動的功能
    .vidioc_enum_input = vcam_enum_input,    
    .vidioc_g_input = vcam_g_input,            //Query or select the current video input
    .vidioc_s_input = vcam_s_input,
    .vidioc_enum_fmt_vid_cap = vcam_enum_fmt_vid_cap,
    .vidioc_g_fmt_vid_cap = vcam_g_fmt_vid_cap,    //Get or set the data format, try a format
    .vidioc_try_fmt_vid_cap = vcam_try_fmt_vid_cap,
    .vidioc_s_fmt_vid_cap = vcam_s_fmt_vid_cap,
    .vidioc_s_parm = vcam_g_parm,                  //Get or set streaming parameters
    .vidioc_g_parm = vcam_s_parm,
    .vidioc_enum_frameintervals = vcam_enum_frameintervals, //Enumerate frame intervals
    .vidioc_enum_framesizes = vcam_enum_framesizes,    //Enumerate frame sizes
    .vidioc_reqbufs = vb2_ioctl_reqbufs,           //Initiate Memory Mapping
    .vidioc_create_bufs = vb2_ioctl_create_bufs,  //Create buffers for Memory Mapped 
    .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
    .vidioc_querybuf = vb2_ioctl_querybuf,        //Query the status of a buffer
    .vidioc_qbuf = vb2_ioctl_qbuf,                
    .vidioc_dqbuf = vb2_ioctl_dqbuf,
    .vidioc_expbuf = vb2_ioctl_expbuf,
    .vidioc_streamon = vb2_ioctl_streamon,    //Start or stop streaming I/O
    .vidioc_streamoff = vb2_ioctl_streamoff};

其中 vb2_ioctl_xx 被定義在 <media/v4l2-ioctl.h> 中 ,主要對 video buffer 進行操作。
vcam_x_xx 為 vcam 所建立的 function 。
vcam_querycap 設定 vcam 名字及目標功能,可以由<uapi/linux/videodev2.h> 查到相關 capability macro。
vcam_enum_input 設定 input 名稱,可以查到相關<uapi/linux/videodev2.h> intput macro。
vcam_g_inputvivid_g_input_tch 一樣。
vcam_s_inputvivid_set_touch 一樣。
vcam_enum_fmt_vid_cap 為設定 fromt video capability 設定輸入的規格(如: RGB, YUYV)
vivid_enum_fmt_cap 可以互相對照。
vcam_g_parmvcam_s_parm 一樣,只有在 vcam_s_parm 有設定 timeperframe ,這也是為什麼要顛倒,因為在 compliance test g 時有測 timperframe。
vcam_try_fmt_vid_cap vivid 部分是將 multi plane 的資料,合併為 single plane , 而 vcam 是single plane 設定。
vcam_enum_frameintervals
vcam_enum_framesizesvidioc_enum_framesizes 一樣
vb2_ioctl_reqbufs 其中 fill_buf_caps(vdev->queue, &p->capabilities); 是在 Merge tag 'media/v4.20-2' 時才新增的,所以就算 compiler 沒出現 error warning function 還是有可能已經受到更新更改。
video_device

static const struct video_device vcam_video_device_template = {
    .fops = &vcam_fops,
    .ioctl_ops = &vcam_ioctl_ops,
    .release = video_device_release_empty,
};

video_device 定義在 include/media/v4l2-dev.h
struct video_device - Structure used to create and manage the V4L2 device nodes.
fops: pointer to &struct v4l2_file_operations for the video device

  • struct v4l2_file_operations - file system operations used by a V4L2 device

release: video device release() callback
ioctl_ops: pointer to &struct v4l2_ioctl_ops with ioctl callbacks

  • struct v4l2_ioctl_ops - describe operations for each V4L2 ioctl
  • videobuf.c
    vcam_in_queue_setupinit_vcam_in_buffer 對 queue 中初始化,由 q->pending = &q->buffers[0];
    q->ready = &q->buffers[1]; 可以知道,這資料結構為 queue FIFO 的結構。
    swap_in_queue_buffers 就是對上面 queue 的維護,當 ready 和 pending 重疊時,通常為 queue 為 fill 的狀況,才需要 swap ,可以在 fb.c 觀察到。
    前面有提到的
    vcam_out_videobuf2_setup 其中 queue 結構為 struct vb2_queue q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    使用單畫面擷取,適用於畫面捕捉。
    q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ; 代表 queue 的讀取方式

struct vb2_queue
struct vb2_queue — a videobuf queue
type - private buffer type whose content is defined by the vb2-core caller. For example, for V4L2, it should match the V4L2_BUF_TYPE_xx
V4L2_BUF_TYPE_VIDEO_CAPTURE - Buffer of a single-planar video capture stream
io_modes - supported io methods (see vb2_io_modes enum)
vb2_io_modes - queue access methods
參考資料:
vb2_queue
type
io_modes

vcam_vb2_ops 存放對於 vb2_buffer 的操作方式及 queue 的操作,其中要注意 vcam_start_streaming
vcam_start_streaming - 主要是當 streaming 他會將 vcam_in_queue 的 buffer 內容複製到 vcam_out_queue 當中。
kthread_create(submitter_thread, dev, "vcam_submitter"); 目的就是 copy buffer
vcam_out_buffer_queue 會將 vb2_queue 串入 queue 中輸出

  • fb.c
    init_framebuffer 主要執行 proc_create_data 會在 proc 中建立檔案, proc 為一個 virtual file system ,主要提供目前執行的 process 信息,也提供 kernel 和 user space 通訊,0666 為設定檔案權限,參數 file_operations vcamfb_fops 將會把 file write open release 文件操作方式寫入, data 為任何要傳入 proc 的信息。

extern struct proc_dir_entry *proc_create_data(const char *, umode_t, struct proc_dir_entry *, const struct file_operations *, void *);

struct proc_dir_entry *proc_create_data(const char *name, umode_t mode,
		struct proc_dir_entry *parent,
		const struct proc_ops *file_operations, void *data)
{
	struct proc_dir_entry *p;

	p = proc_create_reg(name, mode, &parent, data);
	if (!p)
		return NULL;
	p->proc_ops = proc_ops;
	pde_set_flags(p);
	return proc_register(parent, p);
}

如前面所說,在 kernel 5.7 之後,proc_create_data 參數從 const struct file_operations 改為 const struct proc_ops ,這也是需要注意的地方。
proc

Proc file system (procfs) is virtual file system created on fly when system boots and is dissolved at time of system shut down.
It contains the useful information about the processes that are currently running, it is regarded as control and information centre for kernel.
The proc file system also provides communication medium between kernel space and user space.

$ls -l /proc

-rw-rw-rw-  1 root  root  0  5月  8 18:35 vcamfb0
-r--r--r--  1 root  root  0  5月  8 15:33 version
-r--r--r--  1 root  root  0  5月  8 18:35 version_signature
-r--------  1 root  root  0  5月  8 18:35 vmallocinfo

參考資料:
proc file system

struct vcam_out_buffer 包括了由 head_list 其連接, struct vb2_buffer 的 video buffer。

struct vb2_buffer
struct vb2_buffer - represents a video buffer, that can be read by the driver and relevant entries can be changed by the driver in case of CAPTURE types
參考資料:
vb2_buffer

  • vcam-util.c
    create_device 會設置 dev.width = 640, .height = 480 .pix_fmt = VCAM_PIXFMT_RGB24 ,ioctl(fd, VCAM_IOCTL_CREATE_DEVICE, dev) 將會創建 device 。
    remove_device ioctl(fd, VCAM_IOCTL_DESTROY_DEVICE, dev) 將 device remove 。
    modify_device ioctl(fd, VCAM_IOCTL_GET_DEVICE, &orig_dev) 先得到 device format 在 ioctl(fd, VCAM_IOCTL_MODIFY_SETTING, dev) 修改 foramt 。
    list_devices ioctl(fd, VCAM_IOCTL_GET_DEVICE, &dev) 把所有 device 列出來。

int ioctl(int fd, unsigned long request, ...);
ioctl - control device

The ioctl() system call manipulates the underlying device parameters of special files. In particular, many operating characteristics of character special files (e.g., terminals) may be controlled with ioctl() requests. The argument fd must be an open file descriptor. The second argument is a device-dependent request code. Third argument is an untyped pointer to memory.
Usually, on success zero is returned.
ioctl 將會以第二參數,操作檔案,以下為對應的操作代碼

  • vcam.h
#define VCAM_IOCTL_CREATE_DEVICE 0x111
#define VCAM_IOCTL_DESTROY_DEVICE 0x222
#define VCAM_IOCTL_GET_DEVICE 0x333
#define VCAM_IOCTL_ENUM_DEVICES 0x444
#define VCAM_IOCTL_MODIFY_SETTING 0x555

參考資料:
ioctl

理解 v4l2 框架

v4l2 (video for linux version 2) 框架

v4l2 Structure of the framework

The framework closely resembles the driver structure: it has a v4l2_device struct for the device instance data, a v4l2_subdev struct to refer to sub-device instances, the video_device struct stores V4L2 device node data and the v4l2_fh struct keeps track of filehandle instances. The V4L2 framework also optionally integrates with the media framework. If a driver sets the struct v4l2_device mdev field, sub-devices and video nodes will automatically appear in the media framework as entities.

可以對應下圖進行了解, video_device 將會存取 v4l2_device , v4l2_fh ,而 v4l2_device 將會保存 v4l2_subdev 也就是子系統並使用 head_list 的方式將所有子系統連接起來。


video_register_device

今年的改進

vcam 支援最近的 kernel 版本

更新 vcam 使他能支援從原本 linux kernel v4.15~v5.8
首先了解 vivid

The Virtual Video Test Driver (vivid)

This driver emulates video4linux hardware of various types: video capture, video output, vbi capture and output In addition a simple framebuffer device is available for testing capture and output overlays.

一個由 linux kernel 所維護的 virtual video device ,會隨著 kernel 不斷的更新,可以當作版本更新後驅動程式修改的參考, vcam 專案也參考 vivid 所做的框架, function 等 ,所以可以透過 vivid 對 function 或 API 的修正,vcam 也做出對應的調整,使 module 也可以適應 kernel 更新。
參考資料:
The Virtual Video Test Driver

在 kernel v5.8 vivid 的變化 由 git 可以知道被修正的地方 media: vivid: fix incorrect PA assignment to HDMI outputs 中可以確定 VFL_TYPE_GRABBER 可以改為 VFL_TYPE_VIDEO
在 krenel v5.6 genernic.c 的變化 由 git 可以知道被修正的地方 proc: decouple proc from VFS with "struct proc_ops" 中可以確定由 struct file_operations 換成 struct proc_ops
將上面進行修改後可以成功編譯,所以可以經過 include version.h 來知道你的 kernel 的版本,來進行調整 function 變化,我有嘗試 v5.11 也一樣可以正常執行。

支援其他解析度

720p

了解 vivid 如何調整解析度

if (vivid_is_webcam(dev)) {
	const struct v4l2_frmsize_discrete *sz = 
    v4l2_find_nearest_size(webcam_sizes, 
                           VIVID_WEBCAM_SIZES, width, height, 
                           mp->width, mp->height);
w = sz->width;
h = sz->height;

vivid_try_fmt_vid_cap 可以觀察到裡面使用到 v4l2_find_nearest_size 會找到適合的解析度大小,解析度大小由 webcam_sizes 中定義如(640 * 480 , 1280 * 720 等),其中的期望的解析度大小由 mp->height 及 mp->width 控制。
mp 為 struct v4l2_pix_format_mplane 為 multiplanar format definition ,再來觀察期中 mp->height 及 mp->width 在哪被給予大小,在 vivid_g_fmt_vid_cap 中有對 mp->height = dev->fmt_cap_rect.height;mp->width = dev->fmt_cap_rect.width; 其中 fmt_cap_rect 跟設備有關係。
vivid_s_fmt_vid_cap 裡有使用到 vivid_update_format_cap 這 function 的功用就是由不同的輸入源更新對應的大小及設定,

經過修改後:

透過設定 vcam_sizes array 將存放著 vcam 支援的 frame size , 經過 v4l2_find_nearest_sizevcam_sizes 中找到適合的大小並設定 vcam_update_foramt_cap 設定 buffer 大小,並重新 vmalloc buffer ,當 vcam module.c 中 allow_scaling 將他 enable 時就會將 480p 轉 720p。

static const struct v4l2_frmsize_discrete vcam_sizes[] = {
    {480, 360},
    {VGA_WIDTH, VGA_HEIGHT}, /* 640 * 480 */
    {HD_720_WIDTH, HD_720_HEIGHT}, /* 1280 * 720 */
};
$ v4l2-ctl -d /dev/video0 --list-formats-ext
ioctl: VIDIOC_ENUM_FMT
	Type: Video Capture

	[0]: 'RGB3' (24-bit RGB 8-8-8)
	Size: Discrete 1280x720
	Interval: Stepwise 0.017s - 1001.000s with step 0.017s (0.001-59.940 fps)

也因為這樣設定,當修改 v4l2_find_nearest_size 中的參數時就能支援 360p。

ioctl: VIDIOC_ENUM_FMT
	Type: Video Capture

	[0]: 'RGB3' (24-bit RGB 8-8-8)
	Size: Discrete 480x360
	Interval: Stepwise 0.017s - 1001.000s with step 0.017s (0.001-59.940 fps)

檢查 vcam 是否有 memory leak

一般都瞭解如何在 userspace 檢查是否有 memory leak 可以透過 Address Sanitizer(ASan) 或 valgrind,由 gcc -fsanitize=leak 編譯時開啟 或 valgrind leak-check=full,就可以檢查該程式是否有未釋放的記憶體,但是由於 vcam 將會變成 kernel module ,所以就不能使用這方式,因此 kernel 也有屬於 kernel 的檢查 memory leak 的方式,可以透過 kernel Sanitizer(KASAN) 還有一個 kmemleck 也可以檢查 kernel 的 memory leak

KASAN 適用於 SLUB 及 SLAB 記憶體配置,並支援 x86, arm, arm64 , Kernel memory leak director(kmemleak) 也適用於適用於 SLUB 及 SLAB 記憶體配置,並支援 x86, arm, arm64,兩者都需要透過 kernel hacking 設定 CONFIG_DEBUG_KMEMLEAK=y 及 CONFIG_KASAN=y 之後編譯自己的 kernel ,這裡編譯後需要空間要較大的容量及時間,大約需要 40G 左右,編譯後並取代自己本來的 kernel ,並重新開機完成後可以用 sudo ls /sys/kernel/debug/ 檢查是否多了 kmemleak 也可以由 uname -rms 知道自己 kernel version。
想嘗試可以使用 kmemleak-test 可以知道功能是否能正常使用, kmemleak-test 為 kernel 中嘗試 kmemleak 的 module,也是需要透過 kernel hacking 進行設定。

# modprobe kmemleak-test
# echo scan > /sys/kernel/debug/kmemleak
# cat /sys/kernel/debug/kmemleak
unreferenced object 0xffff88814aa9d640 (size 32):
  comm "modprobe", pid 3004, jiffies 4294943661 (age 528.300s)
  hex dump (first 32 bytes):
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  backtrace:
    [<0000000077ade272>] kmem_cache_alloc_trace+0x101/0x2a0
    [<00000000e9f0731a>] 0xffffffffc0f00047
    [<0000000099bba30a>] do_one_initcall+0x9c/0x2d0
    [<000000005588bf62>] do_init_module+0x10f/0x3c0
    [<00000000ad39151b>] load_module+0x4462/0x48a0
    [<000000001b11a8d1>] __do_sys_finit_module+0x130/0x1c0
    [<000000002a20745d>] __x64_sys_finit_module+0x43/0x50
    [<0000000058fe518f>] do_syscall_64+0x52/0xc0
    [<00000000a17cd98c>] entry_SYSCALL_64_after_hwframe+0x44/0xa9
# echo clear > /sys/kernel/debug/kmemleak

:bell: 要注意剛 insmodmodprobe module 不能馬上 scan 需要一段時間後再 scan 才可以檢查出 leak。

發現到 kmemleak 沒辦法出現報告,但是 KASAN 有報告。

vcam KASAN 報告

先了解一下 KASAN 的報告
The Kernel Address Sanitizer (KASAN) 可以了解報告的內容解讀。

[ 1782.469808] ==================================================================
[ 1782.469828] BUG: KASAN: slab-out-of-bounds in __init_vb2_v4l2_buffer+0x19/0x30 [videobuf2_v4l2]
[ 1782.469831] Write of size 4 at addr ffff88814e99a21c by task vlc/8923

[ 1782.469837] CPU: 3 PID: 8923 Comm: vlc Tainted: G           OE     5.8.6 #1
[ 1782.469839] Hardware name: innotek GmbH VirtualBox/VirtualBox, BIOS VirtualBox 12/01/2006
[ 1782.469840] Call Trace:
[ 1782.469858]  dump_stack+0x9d/0xda
[ 1782.469863]  print_address_description.constprop.0+0x1f/0x210
[ 1782.469867]  ? _raw_spin_lock_irqsave+0x8e/0xf0
[ 1782.469869]  ? _raw_spin_trylock_bh+0x100/0x100
[ 1782.469875]  ? __init_vb2_v4l2_buffer+0x19/0x30 [videobuf2_v4l2]
[ 1782.469877]  kasan_report.cold+0x37/0x7c
[ 1782.469882]  ? __init_vb2_v4l2_buffer+0x19/0x30 [videobuf2_v4l2]
[ 1782.469886]  __asan_store4+0x8b/0xb0
[ 1782.469890]  __init_vb2_v4l2_buffer+0x19/0x30 [videobuf2_v4l2]
[ 1782.469897]  __vb2_queue_alloc+0x1d3/0x660 [videobuf2_common]
[ 1782.469904]  vb2_core_reqbufs+0x45a/0x690 [videobuf2_common]
...
[ 1782.470068]  ? __ia32_compat_sys_ppoll_time64+0x1a0/0x1a0
[ 1782.470071]  do_syscall_64+0x52/0xc0
[ 1782.470074]  entry_SYSCALL_64_after_hwframe+0x44/0xa9
[ 1782.470076] RIP: 0033:0x7f467e89faff
[ 1782.470079] Code: 54 24 1c 48 89 74 24 10 48 89 7c 24 08 e8 79 1c f8 ff 8b 54 24 1c 48 8b 74 24 10 41 89 c0 48 8b 7c 24 08 b8 07 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 2b 44 89 c7 89 44 24 08 e8 ad 1c f8 ff 8b 44
[ 1782.470080] RSP: 002b:00007f465c8f5a90 EFLAGS: 00000297 ORIG_RAX: 0000000000000007
[ 1782.470083] RAX: ffffffffffffffda RBX: 00007f4664000c20 RCX: 00007f467e89faff
[ 1782.470084] RDX: 00000000ffffffff RSI: 0000000000000002 RDI: 00007f465c8f5b90
[ 1782.470085] RBP: 0000000000000001 R08: 0000000000000001 R09: 0000000000000001
[ 1782.470086] R10: 0000000000000001 R11: 0000000000000297 R12: 0000000000000001
[ 1782.470088] R13: 0000000000000001 R14: 0000000001000000 R15: 00007f4656fff010

[ 1782.470092] Allocated by task 8923:
[ 1782.470095]  save_stack+0x23/0x50
[ 1782.470097]  __kasan_kmalloc.constprop.0+0xcf/0xe0
[ 1782.470098]  kasan_kmalloc+0x9/0x10
[ 1782.470100]  __kmalloc+0x146/0x2f0
[ 1782.470105]  __vb2_queue_alloc+0xae/0x660 [videobuf2_common]
[ 1782.470111]  vb2_core_reqbufs+0x45a/0x690 [videobuf2_common]
[ 1782.470116]  __vb2_init_fileio+0x189/0x470 [videobuf2_common]
[ 1782.470121]  vb2_core_poll+0x260/0x320 [videobuf2_common]
[ 1782.470126]  vb2_poll+0x34/0xd0 [videobuf2_v4l2]
[ 1782.470130]  vb2_fop_poll+0x9f/0x1b0 [videobuf2_v4l2]
[ 1782.470149]  v4l2_poll+0xf2/0x110 [videodev]
[ 1782.470151]  do_sys_poll+0x454/0x780
[ 1782.470153]  __x64_sys_poll+0x99/0x230
[ 1782.470155]  do_syscall_64+0x52/0xc0
[ 1782.470157]  entry_SYSCALL_64_after_hwframe+0x44/0xa9

[ 1782.470159] Freed by task 8791:
[ 1782.470161]  save_stack+0x23/0x50
[ 1782.470163]  __kasan_slab_free+0x137/0x180
[ 1782.470164]  kasan_slab_free+0xe/0x10
[ 1782.470166]  kfree+0xa8/0x280
[ 1782.470168]  load_elf_binary+0x13a2/0x244f
[ 1782.470171]  __do_execve_file.isra.0+0x9e6/0x11f0
[ 1782.470173]  __x64_sys_execve+0x59/0x70
[ 1782.470175]  do_syscall_64+0x52/0xc0
[ 1782.470177]  entry_SYSCALL_64_after_hwframe+0x44/0xa9

[ 1782.470180] The buggy address belongs to the object at ffff88814e99a000
                which belongs to the cache kmalloc-1k of size 1024
[ 1782.470183] The buggy address is located 540 bytes inside of
                1024-byte region [ffff88814e99a000, ffff88814e99a400)
[ 1782.470184] The buggy address belongs to the page:
[ 1782.470189] page:ffffea00053a6600 refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 head:ffffea00053a6600 order:3 compound_mapcount:0 compound_pincount:0
[ 1782.470192] flags: 0x17ffffc0010200(slab|head)
[ 1782.470196] raw: 0017ffffc0010200 ffffea00051f7000 0000000200000002 ffff8881c8c0ea00
[ 1782.470199] raw: 0000000000000000 0000000080100010 00000001ffffffff 0000000000000000
[ 1782.470199] page dumped because: kasan: bad access detected

[ 1782.470201] Memory state around the buggy address:
[ 1782.470204]  ffff88814e99a100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[ 1782.470206]  ffff88814e99a180: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[ 1782.470209] >ffff88814e99a200: 00 00 00 fc fc fc fc fc fc fc fc fc fc fc fc fc
[ 1782.470210]                             ^
[ 1782.470212]  ffff88814e99a280: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[ 1782.470215]  ffff88814e99a300: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[ 1782.470216] ==================================================================

一開始為 KASAN 的 summary slab-out-of-bounds in __init_vb2_v4l2_buffer+0x19/0x30 [videobuf2_v4l2] 有越界的情況,在vb2_v4l2_buffer, Call Trace: 為記憶體的堆疊追蹤, Allocated by task 8923: 代表有記憶體配置的位置追蹤, Freed by task 8791: 為釋放記憶體配置的位置追蹤,接下來的部分為描述 slab object 資訊及 memory page 資訊,最後的部分為 Memory state around the buggy address: 這說明 KASAN 追蹤記憶體及地址 0x00 代表可配置、可用的記憶體, 0xFB 代表已釋放的記憶體, 0xFC 代表為 RedZone 區域,假如 KASAN 詢問,就會產生 slab-out-of-bounds 狀況。
__init_vb2_v4l2_buffer vcam 並沒有被使用到,但是在 vcam_out_videobuf2_setup 有使用到 v4l2_buf_ops 其中有個 .init_buffer 的操做,就會使用到 __init_vb2_v4l2_buffer 使用到 to_vb2_v4l2_buffer 就會用到 container_ofvb2_buffer 回到結構體 vb2_v4l2_buffer 這可能是會出現出 out-of-bounds 的原因 但在 vcam 中 vb2_buffer 的結構體為 vcam_out_buffer

struct vb2_v4l2_buffer {
	struct vb2_buffer	vb2_buf;

	__u32			flags;
	__u32			field;
	struct v4l2_timecode	timecode;
	__u32			sequence;
	__s32			request_fd;
	bool			is_held;
	struct vb2_plane	planes[VB2_MAX_PLANES];
};
struct vcam_out_buffer {
    struct vb2_buffer vb;
    struct list_head list;
    size_t filled;
};

當改變 videobuffer 大小後 vcamfb 要註冊失敗,並重新創立

目標:

conv_res_on 轉換 video buffer 大小由 480p -> 720p 時 vcamfb 無法觀察到改變,期待 vcamfb 也可以跟著改變。

研究:

/proc/vcamfb 使 vcam 不在是一般的 v4l2 device driver 他能夠接受主動寫入資料, 其中 vcamfb 的操作 function vcamfb_fops 接受開啟、寫入、釋放。

static struct proc_ops vcamfb_fops = {
    .proc_open = vcamfb_open,
    .proc_release = vcamfb_release,
    .proc_write = vcamfb_write,
};

其中在 vcamfb_write 中有 waiting_bytes = dev->input_format.sizeimage; 代表 waiting 的資料大小會隨著 input 跟著改變,但是 default 的大小為 480p , 但是在執行動作時 input_format 改變成 720p , 所以才使得當你開啟 conv_res_on 也不會產生錯誤。

實際修改

device.c

destroy_framebuffer((const char *) vcam->vcam_fb_fname);
snprintf(vcam->vcam_fb_fname, sizeof(vcam->vcam_fb_fname), "vcamfb%d",
        MINOR(vcam->vdev.dev.devt));
struct proc_dir_entry *pde
pde = init_framebuffer((const char *) vcam->vcam_fb_fname, vcam);
if(!pde)
    destroy_framebuffer(vcam->vcam_fb_fname);
vcam->vcam_fb_procf = pde;
vcam->fb_isopen = 0;

實際結果

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

開啟 conv_res_on video 後

Available virtual V4L2 compatible devices:
1. vcamfb1(1280,720,rgb24) -> /dev/video0

觀察開啟 create device 是否會出現錯誤

Available virtual V4L2 compatible devices:
1. vcamfb1(1280,720,rgb24) -> /dev/video0
2. vcamfb2(1280,720,rgb24) -> /dev/video1

int minor(dev_t dev);

minor - manage a device number
A device ID consists of two parts: a major ID, identifying the class of the device, and a minor ID, identifying a specific instance of a device in that class. A device ID is represented using the type dev_t.

因為隨意的重新修改註冊名子會可能會覆蓋到其他 device 的 vcamfb 而產生錯誤。

經過 pull request 建議我看Linux Virtual Framebuffer Device Driver

virtual_fb.c

virtual_fb.cvfb_initvfb_setup__init 初始化,platform_driver_registervfb_driver 註冊 platform_device
在 probe 中有 register_framebuffer 其中 fb_ops 將會是 framebuffer 的操作

static const struct fb_ops vfb_ops = {
/* For framebuffers with strange non linear layouts or that do not
* work with normal memory mapped access
*/
    .fb_read        = fb_sys_read,    
    .fb_write       = fb_sys_write,
    .fb_check_var	= vfb_check_var,    /* checks var and eventually tweaks it to something supported,
	 * DO NOT MODIFY PAR */
    .fb_set_par	= vfb_set_par,    /* set the video mode according to info->var */
    .fb_setcolreg	= vfb_setcolreg,    /* set color register */
    .fb_pan_display	= vfb_pan_display,    /* pan display */
    .fb_fillrect	= sys_fillrect,    /* Draws a rectangle */
    .fb_copyarea	= sys_copyarea,    /* Copy data from area to another */
    .fb_imageblit	= sys_imageblit,    /* Draws a image to the display */
    .fb_mmap	= vfb_mmap,    /* perform fb specific mmap */
};

詳細資料可以參考 整理 virtual framebuffer 報告

可以透過 fbset 了解到 framebuffer 信息,都會皆露出來。
sudo fbset -fb /dev/fb1 --info

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     : 0xffffa8dfc39e5000
    Size        : 921600
    Type        : PACKED PIXELS
    Visual      : TRUECOLOR
    XPanStep    : 1
    YPanStep    : 1
    YWrapStep   : 1
    LineLength  : 1920
    Accelerator : No

當開啟 scaling 之後

mode "1280x720"
    geometry 1280 720 1280 720 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     : 0xffffa8dfc6549000
    Size        : 2764800
    Type        : PACKED PIXELS
    Visual      : TRUECOLOR
    XPanStep    : 1
    YPanStep    : 1
    YWrapStep   : 1
    LineLength  : 3840
    Accelerator : No

並修改 fb_ops 將原本 proc_ops 取代,使得 /dev/fbX 就能接受 raw data 顯示出圖形。

為了使操作介面更為簡潔, 所以只在 device.h 中只在 struct vcam_device 中存取 void *fb_privfb_priv 的實際定義及操作會在 fb.c 中,這就是一種物件導向封裝的方式,對外面只接露出一個地址。
可以透過 struct vcamfb_info *fb_data = (struct vcamfb_info *) dev->fb_priv; 提取,在進行操作

struct vcamfb_info {
    struct fb_info info; /* framebuffer information */
    void *addr; /* framebuffer address */
    unsigned int offset; /* framebuffer offset */
    char name[FB_NAME_MAXLENGTH]; /* framebuffer name */
};

需要 offset 是因為一開始 vmalloc 兩倍 buffer 大的空間,並透過 offset 分割成兩個 buffer 大小,來達到 double buffering。

增加 crop 功能

目標:

crop 的功能說明 Crop_factor 可以理解為擷取部分畫面,如圖所示,將本來圖片大小,只擷取部分想呈現的影像。

其中 crop size 也有規格對應的大小

研究:

vivid 的 webcam 是不支援 crop 的,但還是可以了解到 vivid 設定了大小 struct v4l2_rect crop_cap 又設定了 bool has_crop_cap; 來確定是否執行 crop, 由 vivid_s_fmt_vid_cap 擷取設定 crop 大小部分。

if (dev->has_crop_cap && !dev->has_compose_cap) {
	struct v4l2_rect min_r = {
		0, 0,
		r.width / MAX_ZOOM,
		factor * r.height / MAX_ZOOM
	};
	struct v4l2_rect max_r = {
		0, 0,
		r.width * MAX_ZOOM,
		factor * r.height * MAX_ZOOM
	};

	v4l2_rect_set_min_size(crop, &min_r);
	v4l2_rect_set_max_size(crop, &max_r);
	v4l2_rect_map_inside(crop, &dev->crop_bounds_cap);

透過 v4l2_rect_set_min_sizev4l2_rect_set_max_size 確定 crop 要比 max 小 min 大 ,假如沒有就設定 crop 的大小,v4l2_rect_map_inside 確保 crop 的大小,一定在 map 之中。

實際修改

module.c

unsigned char allow_cropping = 0;
module_param(allow_cropping, byte, 0);
MODULE_PARM_DESC(allow_cropping, "Allow image cropping by default\n");

device.h

struct v4l2_rect crop_cap;
struct v4l2_pix_format crop_output_format;
bool conv_crop_on; 

device.c

extern unsigned char allow_cropping;`
vcam->conv_crop_on = (bool) allow_cropping;

vcam_try_fmt_vid_cap

#include <media/v4l2-rect.h>
#define MAX_ZOOM  4
#define factor 3

if (dev->conv_crop_on) {
    struct v4l2_rect *crop = &dev->crop_cap;
    struct v4l2_rect r = { 0, 0, f->fmt.pix.width, f->fmt.pix.height};
    struct v4l2_rect min_r  = {
        0, 0,
        r.width * factor / MAX_ZOOM,
        r.height * factor / MAX_ZOOM
    };
    struct v4l2_rect max_r = {
        0, 0,
        r.width,
        r.height
    };
    v4l2_rect_set_min_size(crop, &min_r);
    v4l2_rect_set_max_size(crop, &max_r);
    dev->crop_output_format = dev->output_format;
    dev->crop_output_format.width = crop->width;
    dev->crop_output_format.height = crop->height;
    pr_debug("Output crop %d\n", dev->conv_crop_on);
    f->fmt.pix.width = dev->crop_output_format.width;
    f->fmt.pix.height = dev->crop_output_format.height;
}

vcam_g_fmt_vid_cap

if (dev->conv_crop_on){
    memcpy(&f->fmt.pix, &dev->crop_output_format, sizeof(struct v4l2_pix_format));
}

vcam_s_fmt_vid_cap

if(dev->conv_crop_on && !dev->conv_res_on){
    dev->crop_output_format = f->fmt.pix;
}

結果

當沒開啟 scalering 時 crop 將 width height 會縮小4倍

Video input : 0 (vcam_in 0: ok)
Format Video Capture:
	Width/Height      : 480/360
	Pixel Format      : 'RGB3' (24-bit RGB 8-8-8)
	Field             : None
	Bytes per Line    : 1440
	Size Image        : 518400

將圖片 crop 3/4 如下圖

可以看到圖片還是有錯誤,這主要原因為 bytesperline 及寫入的大小關係,只要在 bytesperline 不影響,就會顯示下圖

Video input : 0 (vcam_in 0: ok)
Format Video Capture:
	Width/Height      : 480/360
	Pixel Format      : 'RGB3' (24-bit RGB 8-8-8)
	Field             : None
	Bytes per Line    : 1920
	Size Image        : 691200

開啟 scaling -> 720p

Format Video Capture:
	Width/Height      : 960/540
	Pixel Format      : 'RGB3' (24-bit RGB 8-8-8)
	Field             : None
	Bytes per Line    : 3840
	Size Image        : 2073600