read
app: read ....
-----------------------------------------------
drv: v4l2_fops.v4l2_read
struct video_device *vdev = video_devdata(filp);
ret = vdev->fops->read(filp, buf, sz, off);
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
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来获得、设置"某些属性"
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
当应用层发生系统调用时,会先调用到字符设备 的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) ] ;
ret = info-> func ( ops, file, fh, arg) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
三、虚拟摄像头测试
使用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摄像头应用程序调用分析
strace - o xawtv. log xawtv
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
xawtv.log大致调用流程如下
1. open
2. ioctl ( 4 , VIDIOC_QUERYCAP
3. for ( )
ioctl ( 4 , VIDIOC_ENUMINPUT
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. 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. ioctl ( 4 , VIDIOC_REQBUFS
14. for ( )
ioctl ( 4 , VIDIOC_QUERYBUF
mmap
15. for ( )
ioctl ( 4 , VIDIOC_QBUF
16. ioctl ( 4 , VIDIOC_STREAMON
17. for ( )
ioctl ( 4 , VIDIOC_S_CTRL
ioctl ( 4 , VIDIOC_S_INPUT
ioctl ( 4 , VIDIOC_S_STD
18. v4l2_queue_all
v4l2_waiton
for ( )
{
select ( 5 , [ 4 ] , NULL , NULL , { 5 , 0 } ) = 1 ( in [ 4 ] , left { 4 , 985979 } )
ioctl ( 4 , VIDIOC_DQBUF
ioctl ( 4 , VIDIOC_QBUF
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
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,
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
分析数据从驱动获取过程:
请求分配缓冲区: ioctl(4, VIDIOC_REQBUFS // 请求系统分配缓冲区
vb2_core_reqbufs(队列, p->memory, &p->count); // 队列在open函数用kzalloc(sizeof(*fh), GFP_KERNEL);初始化分配内存
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
查询映射缓冲区:
ioctl(4, VIDIOC_QUERYBUF // 查询所分配的缓冲区
vb2_ioctl_querybuf // 获得缓冲区的数据格式、大小、每一行长度、高度
vb2_querybuf
vb2_core_querybuf
call_void_bufop
q->buf_ops->op(args);
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
把缓冲区放入队列:
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)
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
启动摄像头
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)
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
用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);
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
谁来产生数据、谁来唤醒它? 内核线程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); // 唤醒进程
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
有数据后从队列里取出缓冲区 // 有那么多缓冲区,APP如何知道哪一个缓冲区有数据?调用VIDIOC_DQBUF ioctl(4, VIDIOC_DQBUF
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 */
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
应用程序根据VIDIOC_DQBUF所得到缓冲区状态,知道是哪一个缓冲区有数据 就去读对应的地址(该地址来自前面的mmap)
四、总结怎么写摄像头驱动程序:
分配video_device:video_device_alloc 设置 .fops .ioctl_ops (里面需要设置11项) 如果要用内核提供的缓冲区操作函数,还需要构造一个videobuf_queue_ops 注册: video_register_device
参考资料
韦东山嵌入式第三期
data-report-view="{"mod":"1585297308_001","spm":"1001.2101.3001.6548","dest":"https://blog.csdn.net/m0_61737429/article/details/129805621","extend1":"pc","ab":"new"}">>
id="article_content" class="article_content clearfix">
id="content_views" class="markdown_views prism-atom-one-dark">
class="toc">
Linux--虚拟摄像头 vivid驱动分析
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);
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
vivid_probe
vivid_create_instance
dev = kzalloc ( sizeof ( * vivid_dev) , GFP_KERNEL) ;
v4l2_device_register
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;
ret = cdev_add ( vdev-> cdev, MKDEV ( VIDEO_MAJOR, vdev-> minor) , 1 ) ;
if ( vdev-> ctrl_handler == NULL )
vdev-> ctrl_handler = vdev-> v4l2_dev-> ctrl_handler;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
总结:
分配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
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
read
app: read ....
-----------------------------------------------
drv: v4l2_fops.v4l2_read
struct video_device *vdev = video_devdata(filp);
ret = vdev->fops->read(filp, buf, sz, off);
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
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来获得、设置"某些属性"
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
当应用层发生系统调用时,会先调用到字符设备 的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) ] ;
ret = info-> func ( ops, file, fh, arg) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
三、虚拟摄像头测试
使用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摄像头应用程序调用分析
strace - o xawtv. log xawtv
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
xawtv.log大致调用流程如下
1. open
2. ioctl ( 4 , VIDIOC_QUERYCAP
3. for ( )
ioctl ( 4 , VIDIOC_ENUMINPUT
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. 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. ioctl ( 4 , VIDIOC_REQBUFS
14. for ( )
ioctl ( 4 , VIDIOC_QUERYBUF
mmap
15. for ( )
ioctl ( 4 , VIDIOC_QBUF
16. ioctl ( 4 , VIDIOC_STREAMON
17. for ( )
ioctl ( 4 , VIDIOC_S_CTRL
ioctl ( 4 , VIDIOC_S_INPUT
ioctl ( 4 , VIDIOC_S_STD
18. v4l2_queue_all
v4l2_waiton
for ( )
{
select ( 5 , [ 4 ] , NULL , NULL , { 5 , 0 } ) = 1 ( in [ 4 ] , left { 4 , 985979 } )
ioctl ( 4 , VIDIOC_DQBUF
ioctl ( 4 , VIDIOC_QBUF
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
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,
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
分析数据从驱动获取过程:
请求分配缓冲区: ioctl(4, VIDIOC_REQBUFS // 请求系统分配缓冲区
vb2_core_reqbufs(队列, p->memory, &p->count); // 队列在open函数用kzalloc(sizeof(*fh), GFP_KERNEL);初始化分配内存
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
查询映射缓冲区:
ioctl(4, VIDIOC_QUERYBUF // 查询所分配的缓冲区
vb2_ioctl_querybuf // 获得缓冲区的数据格式、大小、每一行长度、高度
vb2_querybuf
vb2_core_querybuf
call_void_bufop
q->buf_ops->op(args);
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
把缓冲区放入队列:
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)
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
启动摄像头
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)
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
用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);
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
谁来产生数据、谁来唤醒它? 内核线程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); // 唤醒进程
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
有数据后从队列里取出缓冲区 // 有那么多缓冲区,APP如何知道哪一个缓冲区有数据?调用VIDIOC_DQBUF ioctl(4, VIDIOC_DQBUF
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 */
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
应用程序根据VIDIOC_DQBUF所得到缓冲区状态,知道是哪一个缓冲区有数据 就去读对应的地址(该地址来自前面的mmap)
四、总结怎么写摄像头驱动程序:
分配video_device:video_device_alloc 设置 .fops .ioctl_ops (里面需要设置11项) 如果要用内核提供的缓冲区操作函数,还需要构造一个videobuf_queue_ops 注册: video_register_device
参考资料
韦东山嵌入式第三期
data-report-view="{"mod":"1585297308_001","spm":"1001.2101.3001.6548","dest":"https://blog.csdn.net/m0_61737429/article/details/129805621","extend1":"pc","ab":"new"}">>
评论记录:
回复评论: