- 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;
总结:
- 分配
video_device
:video_device_alloc()
或kzalloc()
; - 设置
video_device
:.fops
、.ioctl_ops
、dev
; - 注册
video_device
:video_register_device()
;
分析vivid的open,read,ioctl过程
- 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
- read
app: read ....
drv: v4l2_fops.v4l2_read
struct video_device *vdev = video_devdata(filp);
ret = vdev->fops->read(filp, buf, sz, off);
- 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
- sudo modprobe vivi
sudo rmmod vivi
sudo insmod ./vivi.ko - ls /dev/video*
- xawtv -c /dev/videoX
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 // 把缓冲区放入队列
}
- xawtv的几大函数:
- v4l2_open
- v4l2_read_attr/v4l2_write_attr
- v4l2_start_streaming
- 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,
分析数据从驱动获取过程:
- 请求分配缓冲区: ioctl(4, VIDIOC_REQBUFS // 请求系统分配缓冲区
vb2_core_reqbufs(队列, p->memory, &p->count); // 队列在open函数用kzalloc(sizeof(*fh), GFP_KERNEL);初始化分配内存
- 1
- 查询映射缓冲区:
ioctl(4, VIDIOC_QUERYBUF // 查询所分配的缓冲区
vb2_ioctl_querybuf // 获得缓冲区的数据格式、大小、每一行长度、高度
vb2_querybuf
vb2_core_querybuf
call_void_bufop
q->buf_ops->op(args);
- 把缓冲区放入队列:
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)
- 启动摄像头
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)
- 用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
- 应用程序根据VIDIOC_DQBUF所得到缓冲区状态,知道是哪一个缓冲区有数据
就去读对应的地址(该地址来自前面的mmap)
四、总结怎么写摄像头驱动程序:
- 分配video_device:video_device_alloc
- 设置
.fops
.ioctl_ops (里面需要设置11项)
如果要用内核提供的缓冲区操作函数,还需要构造一个videobuf_queue_ops - 注册: video_register_device