然后初始化缓存队列,videobuf_queue_vmalloc_init这个函数v4l2的videobuf提供的接口,作用是初始化一个缓存队列struct videobuf_queue,其中就设置了vivi_video_qops

struct videobuf_queue {
    /* 缓存 */
	struct videobuf_buffer     *bufs[VIDEO_MAX_FRAME];
    
	/* capture via mmap() + ioctl(QBUF/DQBUF) */
	struct list_head           stream; // 维护缓存队列的一个链表
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
static struct videobuf_queue_ops vivi_video_qops = {
	.buf_setup      = buffer_setup,
	.buf_prepare    = buffer_prepare,
	.buf_queue      = buffer_queue,
	.buf_release    = buffer_release,
};
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

这些函数会在操作缓存队列的时候会被调用

再接下来就是分配一个video_device,设置它,然后再注册它

上一篇文章我们分析了调用video_register_device会为video_device注册一个字符设备并生成设备节点,当应用层发生系统调用时,会先调用到字符设备的fops,经过v4l2的核心层,最终回调到video_device的fops

我们来看一看是如何设置video_device

static struct video_device vivi_template = {
	.name		= "vivi",
	.fops       = &vivi_fops,
	.ioctl_ops 	= &vivi_ioctl_ops,
	.release	= video_device_release,

	.tvnorms              = V4L2_STD_525_60,
	.current_norm         = V4L2_STD_NTSC_M,
};
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

可以看到vivi.c提供的video_device模板设置了vivi_fopsvivi_ioctl_ops,这两个结构体中有一大堆回调函数,我们先看一眼,稍后再具体分析

static const struct v4l2_file_operations vivi_fops = {
	.owner		= THIS_MODULE,
	.release        = vivi_close,
	.read           = vivi_read,
	.poll		= vivi_poll,
	.ioctl          = video_ioctl2, /* V4L2 ioctl handler */
	.mmap           = vivi_mmap,
};
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
static const struct v4l2_ioctl_ops vivi_ioctl_ops = {
	.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_s_std         = vidioc_s_std,
	.vidioc_enum_input    = vidioc_enum_input,
	.vidioc_g_input       = vidioc_g_input,
	.vidioc_s_input       = vidioc_s_input,
	.vidioc_streamon      = vidioc_streamon,
	.vidioc_streamoff     = vidioc_streamoff,
	.vidioc_queryctrl     = vidioc_queryctrl,
	.vidioc_g_ctrl        = vidioc_g_ctrl,
	.vidioc_s_ctrl        = vidioc_s_ctrl,
};
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

上面已经介绍了vivi.c大概做了什么事了,接下来我们按照v4l2的应用编写流程来具体分析每个回调函数的实现细节

这篇文章深入学习Linux摄像头(一)v4l2应用编程对v4l2应用编程作了详解的讲解

v4l2的操作流程

下面按照这些流程来分析虚拟摄像头驱动

至此,vivi就分析完成了

data-report-view="{"mod":"1585297308_001","spm":"1001.2101.3001.6548","dest":"http://iyenn.com/rec/2146287.html","extend1":"pc","ab":"new"}">>
id="article_content" class="article_content clearfix"> id="content_views" class="markdown_views prism-atom-one-light">

深入学习Linux摄像头系列

深入学习Linux摄像头(一)v4l2应用编程

深入学习Linux摄像头(二)v4l2驱动框架

深入学习Linux摄像头(三)虚拟摄像头驱动分析

深入学习Linux摄像头(四)三星平台fimc驱动详解

深入学习Linux摄像头(三)虚拟摄像头驱动分析

上一篇文章讲解了V4L2的驱动框架,这一节我们来分析一个驱动程序,Linux内核带有一个虚拟摄像头驱动(vivi.c),这个虚拟摄像头使用V4L2驱动框架编写,只是少了硬件操作,数据来源是虚拟的,这篇文章就来分析它

看一个驱动程序首先从入口开始看起

module_init(vivi_init);
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
static int __init vivi_init(void)
{
    vivi_create_instance(i);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

入口函数调用了vivi_create_instance

static int __init vivi_create_instance(int inst)
{
    struct vivi_dev *dev;
    struct video_device *vfd;

    /* 分配vivi_dev */
    dev = kzalloc(sizeof(*dev), GFP_KERNEL);
    
    /* 初始化缓存队列 */
    videobuf_queue_vmalloc_init(&dev->vb_vidq, &vivi_video_qops, // 赋值和初始化
                                NULL, &dev->slock, V4L2_BUF_TYPE_VIDEO_CAPTURE,
                                V4L2_FIELD_INTERLACED,
                                sizeof(struct vivi_buffer), dev);

    /* 分配video_device */
    vfd = video_device_alloc();

    /* 设置video_device */
	*vfd = vivi_template;
    
    /* 注册video_device */
    video_register_device(vfd, VFL_TYPE_GRABBER, video_nr);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

从上面可以看到,首分配一个vivi_dev,看一看vivi_dev

struct vivi_dev {
	struct v4l2_device 	   v4l2_dev;
    struct video_device        *vfd; 
    ...
    /* 像素格式 */
    struct vivi_fmt            *fmt;
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

然后初始化缓存队列,videobuf_queue_vmalloc_init这个函数v4l2的videobuf提供的接口,作用是初始化一个缓存队列struct videobuf_queue,其中就设置了vivi_video_qops

struct videobuf_queue {
    /* 缓存 */
	struct videobuf_buffer     *bufs[VIDEO_MAX_FRAME];
    
	/* capture via mmap() + ioctl(QBUF/DQBUF) */
	struct list_head           stream; // 维护缓存队列的一个链表
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
static struct videobuf_queue_ops vivi_video_qops = {
	.buf_setup      = buffer_setup,
	.buf_prepare    = buffer_prepare,
	.buf_queue      = buffer_queue,
	.buf_release    = buffer_release,
};
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

这些函数会在操作缓存队列的时候会被调用

再接下来就是分配一个video_device,设置它,然后再注册它

上一篇文章我们分析了调用video_register_device会为video_device注册一个字符设备并生成设备节点,当应用层发生系统调用时,会先调用到字符设备的fops,经过v4l2的核心层,最终回调到video_device的fops

我们来看一看是如何设置video_device

static struct video_device vivi_template = {
	.name		= "vivi",
	.fops       = &vivi_fops,
	.ioctl_ops 	= &vivi_ioctl_ops,
	.release	= video_device_release,

	.tvnorms              = V4L2_STD_525_60,
	.current_norm         = V4L2_STD_NTSC_M,
};
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

可以看到vivi.c提供的video_device模板设置了vivi_fopsvivi_ioctl_ops,这两个结构体中有一大堆回调函数,我们先看一眼,稍后再具体分析

static const struct v4l2_file_operations vivi_fops = {
	.owner		= THIS_MODULE,
	.release        = vivi_close,
	.read           = vivi_read,
	.poll		= vivi_poll,
	.ioctl          = video_ioctl2, /* V4L2 ioctl handler */
	.mmap           = vivi_mmap,
};
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
static const struct v4l2_ioctl_ops vivi_ioctl_ops = {
	.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_s_std         = vidioc_s_std,
	.vidioc_enum_input    = vidioc_enum_input,
	.vidioc_g_input       = vidioc_g_input,
	.vidioc_s_input       = vidioc_s_input,
	.vidioc_streamon      = vidioc_streamon,
	.vidioc_streamoff     = vidioc_streamoff,
	.vidioc_queryctrl     = vidioc_queryctrl,
	.vidioc_g_ctrl        = vidioc_g_ctrl,
	.vidioc_s_ctrl        = vidioc_s_ctrl,
};
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

上面已经介绍了vivi.c大概做了什么事了,接下来我们按照v4l2的应用编写流程来具体分析每个回调函数的实现细节

这篇文章深入学习Linux摄像头(一)v4l2应用编程对v4l2应用编程作了详解的讲解

v4l2的操作流程

下面按照这些流程来分析虚拟摄像头驱动

至此,vivi就分析完成了

data-report-view="{"mod":"1585297308_001","spm":"1001.2101.3001.6548","dest":"http://iyenn.com/rec/2146287.html","extend1":"pc","ab":"new"}">>
注:本文转载自blog.csdn.net的JT同学的文章"https://blog.csdn.net/weixin_42462202/article/details/99719750"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接

评论记录:

未查询到任何数据!