• Linux摄像头系列文章
  • Linux--虚拟摄像头驱动分析
  • 一、视频驱动框架
  • 二、函数调用过程
  • 虚拟视频驱动vivid-core.c分析
  • 分析vivid的open,read,ioctl过程
  • 三、虚拟摄像头测试
  • 使用xawtv摄像头应用程序
  • xawtv摄像头应用程序调用分析
  • 分析数据从驱动获取过程:
  • 四、总结怎么写摄像头驱动程序:
  • 参考资料

Linux摄像头系列文章

【Linux应用】Linux–V4L2摄像头应用编程

【Linux】Linux–V4L2视频驱动框架

Linux–虚拟摄像头驱动分析

本文基于Linux 5.4内核,虚拟摄像头驱动文件在drivers\media\platform\vivid目录下,本文分析了vivid的框架,简要使用摄像头测试软件xawtv对虚拟摄像头进行测试。

一、视频驱动框架

1.分配video_device

2.设置

3.注册:video_register_device

二、函数调用过程

虚拟视频驱动vivid-core.c分析

  • 先从入口函数看起
vivid_init
	//注册vivid设备和驱动
	platform_device_register(&vivid_pdev);
	platform_driver_register(&vivid_pdrv);
  • 在设备驱动匹配后,调用probe函数

vivid_probe vivid_create_instance /* 创建实例 */ dev = kzalloc(sizeof(*vivid_dev), GFP_KERNEL);/* 分配video_devicede */ v4l2_device_register /* 初始化v4l2_device */

/* 设置video_device */
	1.vfd->fops = &vivid_fops;
	  vfd->ioctl_ops = &vivid_ioctl_ops;
	  vfd->release = video_device_release_empty;
	2.vfd->v4l2_dev = &dev->v4l2_dev;
	3.设置"ctrl属性"(用于APP的ioctl)
	   	v4l2_ctrl_handler_setup(&dev->ctrl_hdl_vid_cap);
		v4l2_ctrl_handler_setup(&dev->ctrl_hdl_vid_out);
		...

     
	video_register_device(video_device, type:VFL_TYPE_GRABBER, nr)
        __video_register_device
        	vdev->cdev = cdev_alloc();
			vdev->cdev->ops = &v4l2_fops;
			video_devices[vdev->minor] = vdev;//将video_device放入全局数组中
			ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
			if (vdev->ctrl_handler == NULL)
			vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;

总结:

  1. 分配video_device:video_device_alloc()kzalloc()
  2. 设置video_device:.fops.ioctl_opsdev
  3. 注册video_device: video_register_device()

分析vivid的open,read,ioctl过程

  1. open
app:     open("/dev/video0",....)

drv: v4l2_fops.v4l2_open
vdev = video_devdata(filp); // 根据次设备号从数组中得到video_device
ret = vdev->fops->open(filp);
vivi_ioctl_ops.open
v4l2_fh_open

  1. read

app: read ....


drv: v4l2_fops.v4l2_read
struct video_device *vdev = video_devdata(filp);
ret = vdev->fops->read(filp, buf, sz, off);

  1. ioctl

app: ioctl


drv: v4l2_fops.unlocked_ioctl
v4l2_ioctl
struct video_device *vdev = video_devdata(filp);
ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);
video_ioctl2
video_usercopy(file, cmd, arg, __video_do_ioctl);
__video_do_ioctl
struct video_device *vfd=video_devdata(file);
根据APP传入的cmd来获得、设置"某些属性"

当应用层发生系统调用时,会先调用到字符设备的fops,经过v4l2的核心层,最终回调到video_device的fops 。

核心层v4l2_ctrl_handler会找到video_device的fops 。过程如下(在上文中通过v4l2_ctrl_handler_setup设置了某些属性的ioctl处理函数)

__video_do_ioctl
	struct video_device *vfd = video_devdata(file);
		v4l2_is_known_ioctl(cmd)
        info = &v4l2_ioctls[_IOC_NR(cmd)]; 		/* v4l2_ioctls数组存有全部IOCTL_INFO,含有													ioctl函数指针 */
        ret = info->func(ops, file, fh, arg);	/* 调用对应的ioctl函数 */

三、虚拟摄像头测试

使用xawtv摄像头应用程序

  • 准备工作:安装xawtv
    sudo apt-get install xawtv
  • 确定ubuntu的内核版本
    uname -a
    Linux 100ask 5.4.0-144-generic #161~18.04.1-Ubuntu SMP Fri Feb 10 15:55:22 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
  • 去www.kernel.org下载同版本的内核,解压后把drivers/media/video目录取出
  • 修改media/platform/vivid/下的Makefile
    KERN_DIR = /usr/src/linux-headers-5.4.0-144-generic
  • make
  1. sudo modprobe vivi
    sudo rmmod vivi
    sudo insmod ./vivi.ko
  2. ls /dev/video*
  3. xawtv -c /dev/videoX

RK3568 android11 移植 v4l2loopback 虚拟摄像头_数据

xawtv摄像头应用程序调用分析

  • 获取xawtv执行时调用的函数
strace -o xawtv.log xawtv
  • 1

xawtv.log大致调用流程如下

// 1~7都是在v4l2_open里调用
1. open
2. ioctl(4, VIDIOC_QUERYCAP

// 3~7 都是在get_device_capabilities里调用
3. for()
ioctl(4, VIDIOC_ENUMINPUT // 列举输入源,VIDIOC_ENUMINPUT/VIDIOC_G_INPUT/VIDIOC_S_INPUT不是必需的
4. for()
ioctl(4, VIDIOC_ENUMSTD // 列举标准(制式), 不是必需的
5. for()
ioctl(4, VIDIOC_ENUM_FMT // 列举格式

6. ioctl(4, VIDIOC_G_PARM
7. for()
ioctl(4, VIDIOC_QUERYCTRL // 查询属性(比如说亮度值最小值、最大值、默认值)

// 8~10都是通过v4l2_read_attr来调用的
8. ioctl(4, VIDIOC_G_STD // 获得当前使用的标准(制式), 不是必需的
9. ioctl(4, VIDIOC_G_INPUT
10. ioctl(4, VIDIOC_G_CTRL // 获得当前属性, 比如亮度是多少

11. ioctl(4, VIDIOC_TRY_FMT // 试试能否支持某种格式
12. ioctl(4, VIDIOC_S_FMT // 设置摄像头使用某种格式

// 13~16在v4l2_start_streaming
13. ioctl(4, VIDIOC_REQBUFS // 请求系统分配缓冲区
14. for()
ioctl(4, VIDIOC_QUERYBUF // 查询所分配的缓冲区
mmap
15. for ()
ioctl(4, VIDIOC_QBUF // 把缓冲区放入队列
16. ioctl(4, VIDIOC_STREAMON // 启动摄像头

// 17里都是通过v4l2_write_attr来调用的
17. for ()
ioctl(4, VIDIOC_S_CTRL // 设置属性
ioctl(4, VIDIOC_S_INPUT // 设置输入源
ioctl(4, VIDIOC_S_STD // 设置标准(制式), 不是必需的

// v4l2_nextframe > v4l2_waiton
18. v4l2_queue_all
v4l2_waiton
for ()
{
select(5, [4], NULL, NULL, { 5, 0}) = 1 (in [4], left { 4, 985979})
ioctl(4, VIDIOC_DQBUF // de-queue, 把缓冲区从队列中取出
// 处理, 之以已经通过mmap获得了缓冲区的地址, 就可以直接访问数据
ioctl(4, VIDIOC_QBUF // 把缓冲区放入队列
}



RK3568 android11 移植 v4l2loopback 虚拟摄像头_应用程序_02



  • xawtv的几大函数:
  1. v4l2_open
  2. v4l2_read_attr/v4l2_write_attr
  3. v4l2_start_streaming
  4. v4l2_nextframe/v4l2_waiton
  • 由应用程序反推摄像头驱动程序必需的11个ioctl
// 表示它是一个摄像头设备
.vidioc_querycap      = vidioc_querycap,


/* 用于列举、获得、测试、设置摄像头的数据的格式 */
.vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
.vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,

/* 缓冲区操作: 申请/查询/放入队列/取出队列 */
.vidioc_reqbufs = vidioc_reqbufs,
.vidioc_querybuf = vidioc_querybuf,
.vidioc_qbuf = vidioc_qbuf,
.vidioc_dqbuf = vidioc_dqbuf,

// 启动/停止
.vidioc_streamon = vidioc_streamon,
.vidioc_streamoff = vidioc_streamoff,

分析数据从驱动获取过程:

  1. 请求分配缓冲区: ioctl(4, VIDIOC_REQBUFS // 请求系统分配缓冲区
vb2_core_reqbufs(队列, p->memory, &p->count); // 队列在open函数用kzalloc(sizeof(*fh), 												GFP_KERNEL);初始化分配内存
  • 1
  1. 查询映射缓冲区:
ioctl(4, VIDIOC_QUERYBUF         // 查询所分配的缓冲区
     vb2_ioctl_querybuf        // 获得缓冲区的数据格式、大小、每一行长度、高度            
		vb2_querybuf
			vb2_core_querybuf
				call_void_bufop
					q->buf_ops->op(args);
  1. 把缓冲区放入队列:
ioctl(4, VIDIOC_QBUF             // 把缓冲区放入队列        
 vb2_ioctl_qbuf
	vb2_qbuf
		vb2_core_qbuf
			call_vb_qop(vb, buf_out_validate, vb);
				((vb)->vb2_queue->ops->op ? (vb)->vb2_queue->ops->op(args) : 0)
  1. 启动摄像头
ioctl(4, VIDIOC_STREAMON
 vb2_ioctl_streamon
    vb2_streamon
	vb2_core_streamon(q, type);
		vb2_start_streaming(q);
			call_qop
				q->start_streaming_called = 1;	/* Tell the driver to start streaming 													*/
				((q)->ops->op ? (q)->ops->op(args) : 0)
  1. 用select查询是否有数据
    /
/ 驱动程序里必定有: 产生数据、唤醒进程
    vb2_fop_poll
		vb2_poll
			vb2_core_poll(q, file, wait)
				//获取poll事件
				poll_requested_events	
				// 如果没有数据则休眠                			
          		poll_wait(file, &buf->done, wait);
			//被唤醒后,从队列的头部获得缓冲区
				vb = list_first_entry(&q->done_list, struct vb2_buffer,
    				done_entry);
谁来产生数据、谁来唤醒它?
 内核线程vivid_thread_vid_out_tick每30MS执行一次,它调用 
vivid_thread_vid_out_tick
	vb2_buffer_done
		call_void_memop(vb, finish, vb->planes[plane].mem_priv);//产生数据同步缓冲区
		wake_up(&q->done_wq);  // 唤醒进程
 
vb2_ioctl_dqbuf
	vb2_dqbuf
		vb2_core_dqbuf
			__vb2_get_done_vb
			call_void_bufop	/* Fill buffer information for the userspace */
			list_del(&vb->queued_entry);/* Remove from videobuf queue */
			__vb2_dqbuf(vb);/* go back to dequeued state */
style="color: rgb(153, 153, 153);">7
  1. 应用程序根据VIDIOC_DQBUF所得到缓冲区状态,知道是哪一个缓冲区有数据
    就去读对应的地址(该地址来自前面的mmap)

四、总结怎么写摄像头驱动程序:

  1. 分配video_device:video_device_alloc
  2. 设置
    .fops
    .ioctl_ops (里面需要设置11项)
    如果要用内核提供的缓冲区操作函数,还需要构造一个videobuf_queue_ops
  3. 注册: video_register_device