虚拟摄像头驱动的过程理解透彻了,那么真实摄像头驱动的程序将会十分容易,

本文将总结虚拟摄像头驱动实现的详细细节。相信弄透后字符设备驱动将会十分清晰。

零、字符设备编写思路

简单字符设备常规的方法是单层,实现入口、出口修饰一下即可,而复杂一点的字符设备驱动则采用分层的架构,内核为我们提供核心层及API,然后我们实现硬件部分的驱动,摄像头驱动便是如此,应重点把握里面的几个重要结构体及系统调用过程。
1、简单字符设备

2、复杂设备驱动

一、框架搭建

内核在V4l2-dev.c (linux-3.4.2\drivers\media\video)中提供了V4L2的核心函数。我们再来看一下整体框架:


我们要做的是写个硬件相关驱动,其中用到了核心层V4l2-dev提供的API函数。比如内核 中的vivi.c,是一个虚拟视频驱动+虚拟摄像头的例子。实际中我们需要检测到摄像头设备,然后在调用注册函数,产生/dev/video节点。比如USB摄像头,检测到有USB摄像头插入后,调用注册函数,产生设备节点。现在我们写一个虚拟摄像头驱动,加载驱动后就产生设备节点。

(1)仿真vivi.c拷贝头文件。

(2)写入口函数、出口函数

(3)GPL协议

(4)一贯思路:分配,设置,注册

(5)video_register_device中使用了release ,fops   所以也设置一下。

代码如下:

/* 仿照vivi.c */
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/pci.h>
#include <linux/random.h>
#include <linux/version.h>
#include <linux/mutex.h>
#include <linux/videodev2.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/kthread.h>
#include <linux/highmem.h>
#include <linux/freezer.h>
#include <media/videobuf-vmalloc.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>

static const struct v4l2_file_operations myvivi_fops = {
	.owner		= THIS_MODULE,
};


static struct video_device *myvivi_device;

static void myvivi_release(struct video_device *vdev)
{
}

static int myvivi_init(void)
{
    int error;
    
    /* 1. 分配一个video_device结构体 */
    myvivi_device = video_device_alloc();

    /* 2. 设置 */
    myvivi_device->release = myvivi_release;
    myvivi_device->fops    = &myvivi_fops;

    /* 3. 注册 */
    error = video_register_device(myvivi_device, VFL_TYPE_GRABBER, -1);
    
    return error;
}

static void myvivi_exit(void)
{
    video_unregister_device(myvivi_device);
    video_device_release(myvivi_device);
}

module_init(myvivi_init);
module_exit(myvivi_exit);
MODULE_LICENSE("GPL");
(6)static inline int __must_check video_register_device(struct video_device *vdev,
int type, int nr)函数解析

内核中说明

 * __video_register_device - register video4linux devices
 * @vdev: video device structure we want to register
 * @type: type of device to register
 * @nr:   which device node number (0 == /dev/video0, 1 == /dev/video1, ...
 *             -1 == first free)

参数1:video_device结构体

参数2:type:设备类型?

参数3:设备节点后缀  -1 分配一个最小。或者自己固定

(7)video_device结构体

上面分配,设置了半天的结构体到底有什么成员呢?我们需要设置结构体的中的那些重要成员呢,请看下面

重点设置:fops、ioctl_ops、release

struct video_device
{
#if defined(CONFIG_MEDIA_CONTROLLER)
	struct media_entity entity;
#endif
	/* device ops */
	const struct v4l2_file_operations *fops;

	/* sysfs */
	struct device dev;		/* v4l device */
	struct cdev *cdev;		/* character device */

	/* Set either parent or v4l2_dev if your driver uses v4l2_device */
	struct device *parent;		/* device parent */
	struct v4l2_device *v4l2_dev;	/* v4l2_device parent */

	/* Control handler associated with this device node. May be NULL. */
	struct v4l2_ctrl_handler *ctrl_handler;

	/* Priority state. If NULL, then v4l2_dev->prio will be used. */
	struct v4l2_prio_state *prio;

	/* device info */
	char name[32];
	int vfl_type;
	/* 'minor' is set to -1 if the registration failed */
	int minor;
	u16 num;
	/* use bitops to set/clear/test flags */
	unsigned long flags;
	/* attribute to differentiate multiple indices on one physical device */
	int index;

	/* V4L2 file handles */
	spinlock_t		fh_lock; /* Lock for all v4l2_fhs */
	struct list_head	fh_list; /* List of struct v4l2_fh */

	int debug;			/* Activates debug level*/

	/* Video standard vars */
	v4l2_std_id tvnorms;		/* Supported tv norms */
	v4l2_std_id current_norm;	/* Current tvnorm */

	/* callbacks */
	void (*release)(struct video_device *vdev);

	/* ioctl callbacks */
	const struct v4l2_ioctl_ops *ioctl_ops;

	/* serialization lock */
	struct mutex *lock;
};

(8)测试

首先在虚拟上直接加载驱动的时候,会提示无法找到函数,这里我们通过加载vivi的环境,来创造我们虚拟驱动的环境。


测试结果如下:xawtv并不能识别出我们的/dev/video0,因为没有提供相关函数,如myvivi_vidioc_querycap,应用程序无法从中获得是否是视频捕获设备。


二、加入v4l2_ioctl_ops中的必要ioctl函数。并实现vidioc_querycap

(1)代码如下

static int myvivi_vidioc_querycap(struct file *file, void  *priv,
					struct v4l2_capability *cap)
{
	strcpy(cap->driver, "myvivi");
	strcpy(cap->card, "myvivi");
	cap->version = 0x0001;
	cap->capabilities =	V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
	return 0;
}

static const struct v4l2_ioctl_ops myvivi_ioctl_ops = {
        // 表示它是一个摄像头设备
        .vidioc_querycap      = myvivi_vidioc_querycap,
#if 0    
        /* 用于列举、获得、测试、设置摄像头的数据的格式 */
        .vidioc_enum_fmt_vid_cap  = myvivi_vidioc_enum_fmt_vid_cap,
        .vidioc_g_fmt_vid_cap     = myvivi_vidioc_g_fmt_vid_cap,
        .vidioc_try_fmt_vid_cap   = myvivi_vidioc_try_fmt_vid_cap,
        .vidioc_s_fmt_vid_cap     = myvivi_vidioc_s_fmt_vid_cap,
        
        /* 缓冲区操作: 申请/查询/放入队列/取出队列 */
        .vidioc_reqbufs       = myvivi_vidioc_reqbufs,
        .vidioc_querybuf      = myvivi_vidioc_querybuf,
        .vidioc_qbuf          = myvivi_vidioc_qbuf,
        .vidioc_dqbuf         = myvivi_vidioc_dqbuf,
        
        // 启动/停止
        .vidioc_streamon      = myvivi_vidioc_streamon,
        .vidioc_streamoff     = myvivi_vidioc_streamoff,   
#endif
};

static const struct v4l2_file_operations myvivi_fops = {
	.owner		= THIS_MODULE,
    .ioctl      = video_ioctl2, /* V4L2 ioctl handler */
};


static struct video_device *myvivi_device;

static void myvivi_release(struct video_device *vdev)
{
}


static int myvivi_init(void)
{
    int error;
    
    /* 1. 分配一个video_device结构体 */
    myvivi_device = video_device_alloc();

    /* 2. 设置 */
    myvivi_device->release = myvivi_release;
    myvivi_device->fops    = &myvivi_fops;
    myvivi_device->ioctl_ops = &myvivi_ioctl_ops;

    /* 3. 注册 */
    error = video_register_device(myvivi_device, VFL_TYPE_GRABBER, -1);
    
    return error;
}

(2)注意增加的是v4l2_ioctl_ops 类型里面的函数,即  myvivi_device->ioctl_ops = &myvivi_ioctl_ops;

(3)提供第一个ioctl函数  vidioc_querycap

static const struct v4l2_ioctl_ops myvivi_ioctl_ops = {
        // 表示它是一个摄像头设备
        .vidioc_querycap      = myvivi_vidioc_querycap,
#if 0    
        /* 用于列举、获得、测试、设置摄像头的数据的格式 */
        .vidioc_enum_fmt_vid_cap  = myvivi_vidioc_enum_fmt_vid_cap,
        .vidioc_g_fmt_vid_cap     = myvivi_vidioc_g_fmt_vid_cap,
        .vidioc_try_fmt_vid_cap   = myvivi_vidioc_try_fmt_vid_cap,
        .vidioc_s_fmt_vid_cap     = myvivi_vidioc_s_fmt_vid_cap,
        
        /* 缓冲区操作: 申请/查询/放入队列/取出队列 */
        .vidioc_reqbufs       = myvivi_vidioc_reqbufs,
        .vidioc_querybuf      = myvivi_vidioc_querybuf,
        .vidioc_qbuf          = myvivi_vidioc_qbuf,
        .vidioc_dqbuf         = myvivi_vidioc_dqbuf,
        
        // 启动/停止
        .vidioc_streamon      = myvivi_vidioc_streamon,
        .vidioc_streamoff     = myvivi_vidioc_streamoff,   
#endif
};
v4l2_ioctl_ops结构体很大,还好不是所有都是必须的,下面列出了一些必须的ioctl函数。后面会一一详细说明功能。

v4l2_ioctl_ops(V4l2-ioctl.h (linux-3.4.2\include\media)

struct v4l2_ioctl_ops{
	/* ioctl callbacks */

	/* VIDIOC_QUERYCAP handler */
	int (*vidioc_querycap)(struct file *file, void *fh, struct v4l2_capability *cap);判断它是否是一个视频捕获设备
	/* VIDIOC_ENUM_FMT handlers */
	int (*vidioc_enum_fmt_vid_cap)     (struct file *file, void *fh,
					    struct v4l2_fmtdesc *f);                     获取当前驱动支持的视频格式

	/* VIDIOC_G_FMT handlers */
	int (*vidioc_g_fmt_vid_cap)    (struct file *file, void *fh,
					struct v4l2_format *f);                          读取当前驱动的频捕获格式
	/* VIDIOC_S_FMT handlers */
	int (*vidioc_s_fmt_vid_cap)    (struct file *file, void *fh,
					struct v4l2_format *f);                          设置当前驱动的频捕获格式
	/* VIDIOC_TRY_FMT handlers */
	int (*vidioc_try_fmt_vid_cap)    (struct file *file, void *fh,
					  struct v4l2_format *f);                        验证当前驱动的显示格式
	/* Buffer handlers */ 
	int (*vidioc_reqbufs) (struct file *file, void *fh, struct v4l2_requestbuffers *b); 分配内存
	int (*vidioc_querybuf)(struct file *file, void *fh, struct v4l2_buffer *b);      把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址
	int (*vidioc_qbuf)    (struct file *file, void *fh, struct v4l2_buffer *b);      把数据从缓存中读取出来
	int (*vidioc_dqbuf)   (struct file *file, void *fh, struct v4l2_buffer *b);      把数据放回缓存队列
		/* Stream on/off */
	int (*vidioc_streamon) (struct file *file, void *fh, enum v4l2_buf_type i);      开始视频显示函数
	int (*vidioc_streamoff)(struct file *file, void *fh, enum v4l2_buf_type i);      结束视频显示函数
}

参考vivi.c中:

static int myvivi_vidioc_querycap(struct file *file, void  *priv,
					struct v4l2_capability *cap)
{
	strcpy(cap->driver, "myvivi");
	strcpy(cap->card, "myvivi");
	cap->version = 0x0001;
	cap->capabilities =	V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
	return 0;
}
vidioc_querycap作用:

应用程序调用vidioc_querycap分析,用来判断它是否是一个视频捕获设备。

static int vidioc_querycap(struct file * file,void *priv,struct v4l2_capability *cap)
{
    /**
        先来看看这个v4l2_capability
        struct v4l2_capability {
            __u8    driver[16];     //驱动名字
            __u8    card[32];       //设备名字
            __u8    bus_info[32];   //设备在系统中的位置 Location of the device in the system
            __u32   version;        //驱动的版本号
            __u32   capabilities;   //设备能力集
            __u32   reserved[4];    //保留字段,驱动必须要将这个数组设置为0
        };
    */
    struct vivi_fh *fh = priv;

    //从vivi_fh中获取vivi_dev
    struct vivi_dev *dev = fh->dev;

    strcpy(cap->driver,"vivi");   //驱动的名字
    strcpy(cap->card,"vivi");     //设备的名字
    //设备在系统中的位置 
    strlcpy(cap->bus_info,dev->v4l2_dev.name,sizeof(cap->bus_info));
    cap->version = VIVI_VERSION; //版本号

    //能力集合
    //V4L2_CAP_VIDEO_CAPTURE   : 是一个视频捕捉设备
    //V4L2_CAP_STREAMING       :支持ioctl系统调用来获取数据
    //V4L2_CAP_READWRITE       :支持read/write系统调用来获取数据   
    cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | V4L2_CAP_READWRITE;

    return 0;
}
这里我们知道了这个设备是一个视频捕获设备,且支持系统调用来获取数据。

(4)v4l2_file_operations结构体

应用程序要调用ioctl ,所以这里提供。

static const struct v4l2_file_operations myvivi_fops = {
.owner = THIS_MODULE,
        .ioctl      = video_ioctl2, /* V4L2 ioctl handler */
};

(5)file_operations结构体(核心层,应用程序系统调用首先会调用这里的)和  v4l2_file_operations结构体(硬件层,核心层会调用到这里)

都在V4l2-dev.h里面定义。 (linux-3.4.2\include\media)

static const struct file_operations v4l2_fops = {
	.owner = THIS_MODULE,
	.read = v4l2_read,
	.write = v4l2_write,
	.open = v4l2_open,
	.get_unmapped_area = v4l2_get_unmapped_area,
	.mmap = v4l2_mmap,
	.unlocked_ioctl = v4l2_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl = v4l2_compat_ioctl32,
#endif
	.release = v4l2_release,
	.poll = v4l2_poll,
	.llseek = no_llseek,
};

struct v4l2_file_operations {
	struct module *owner;
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	long (*ioctl) (struct file *, unsigned int, unsigned long);
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
#ifdef CONFIG_COMPAT
	long (*compat_ioctl32) (struct file *, unsigned int, unsigned long);
#endif
	unsigned long (*get_unmapped_area) (struct file *, unsigned long,
				unsigned long, unsigned long, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*open) (struct file *);
	int (*release) (struct file *);
};

(6)调用过程:

应用程序ioctl --》核心层file_operations v4l2_fops里面的.unlocked_ioctl = v4l2_ioctl被调用。v4l2_ioctl里面调用:vdev->fops->ioctl(filp, cmd, arg);设备fops里面的ioctl:v4l2_file_operations myvivi_fops里面.ioctl      = video_ioctl2被调用。video_ioctl2--》video_usercopy(__video_do_ioctl)--》v4l2_ioctl_ops myvivi_ioctl_ops里面的ioctl被调用。

用户层调用ioctl(),经过v4l2_ioctl —->video_ioctl2——>__video_do_ioctl()。 

(7)调用框图




三、增加列举、获得、测试、设置摄像头的数据格式的ioctl

1、增加  .vidioc_enum_fmt_vid_cap(获取支持的视频格式
vivi.c中这么做:支持很多格式,定义了vivi_fmt。
static int vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,
					struct v4l2_fmtdesc *f)
{
	struct vivi_fmt *fmt;

	if (f->index >= ARRAY_SIZE(formats))
		return -EINVAL;

	fmt = &formats[f->index];

	strlcpy(f->description, fmt->name, sizeof(f->description));
	f->pixelformat = fmt->fourcc;
	return 0;
}
v4l2_fmtdesc是要返回给用户空间的,用户空间调用这个ioctl来获取支持的视频格式。
我们只支持一种V4L2_PIX_FMT_YUYV:改为如下即可
static int myvivi_vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,
					struct v4l2_fmtdesc *f)
{
	if (f->index >= 1)
		return -EINVAL;

	strcpy(f->description, "4:2:2, packed, YUYV");
	f->pixelformat = V4L2_PIX_FMT_YUYV;
	return 0;
}
2、增加.vidioc_g_fmt_vid_cap(返回当前视频捕获格式)

vivi里这么做:构造了v4l2_format结构体给用户空间
(内核驱动空间返回给用户空间v4l2_format结构体)
static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
					struct v4l2_format *f)
{
	struct vivi_dev *dev = video_drvdata(file);

	f->fmt.pix.width        = dev->width;    宽度
	f->fmt.pix.height       = dev->height;   高度
	f->fmt.pix.field        = dev->field;
	f->fmt.pix.pixelformat  = dev->fmt->fourcc;
	f->fmt.pix.bytesperline =                每一行长度
		(f->fmt.pix.width * dev->fmt->depth) >> 3;
	f->fmt.pix.sizeimage =                   
		f->fmt.pix.height * f->fmt.pix.bytesperline;
	if (dev->fmt->fourcc == V4L2_PIX_FMT_YUYV ||   整个图像的大小
	    dev->fmt->fourcc == V4L2_PIX_FMT_UYVY)
		f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
	else
		f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
	return 0;
}
我们直接定义一个v4l2_format结构体。
static int myvivi_vidioc_g_fmt_vid_cap(struct file *file, void *priv,
					struct v4l2_format *f)
{
    memcpy(f, &myvivi_format, sizeof(myvivi_format));
	return (0);
}
3、增加.vidioc_try_fmt_vid_cap(测试驱动程序是否支持某种格式)
用户空间与内核驱动空间比较v4l2_format结构体
/* 测试驱动程序是否支持某种格式 */
static int myvivi_vidioc_try_fmt_vid_cap(struct file *file, void *priv,
			struct v4l2_format *f)
{
	unsigned int maxw, maxh;
        enum v4l2_field field;

   	if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV) //不支持这个  就返回错误
        return -EINVAL;

	field = f->fmt.pix.field;

	if (field == V4L2_FIELD_ANY) {
		field = V4L2_FIELD_INTERLACED;
	} else if (V4L2_FIELD_INTERLACED != field) {
		return -EINVAL;
	}

	maxw  = 1024; //设备最大支持的宽度、高度
	maxh  = 768;

    /* 调整format的width, height, 
     * 计算bytesperline, sizeimage
     */
	v4l_bound_align_image(&f->fmt.pix.width, 48, maxw, 2,
			      &f->fmt.pix.height, 32, maxh, 0, 0);
	f->fmt.pix.bytesperline =
		(f->fmt.pix.width * 16) >> 3;  //16颜色深度
	f->fmt.pix.sizeimage =
		f->fmt.pix.height * f->fmt.pix.bytesperline;

	return 0;
}

4、增加.vidioc_s_fmt_vid_cap(设置当前驱动的视频捕获格式) 
用户空间来设置内核驱动空间v4l2_format结构体
vivi.c这么做
static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
					struct v4l2_format *f)
{
	struct vivi_dev *dev = video_drvdata(file);
	struct vb2_queue *q = &dev->vb_vidq;

	int ret = vidioc_try_fmt_vid_cap(file, priv, f);
	if (ret < 0)
		return ret;

	if (vb2_is_streaming(q)) {
		dprintk(dev, 1, "%s device busy\n", __func__);
		return -EBUSY;
	}

	dev->fmt = get_format(f);
	dev->width = f->fmt.pix.width;
	dev->height = f->fmt.pix.height;
	dev->field = f->fmt.pix.field;

	return 0;
}
我们直接拷贝到结构体之中去,实际存在硬件的时候,是要操作给硬件摄像头的。现在存到结构体中。
static int myvivi_vidioc_s_fmt_vid_cap(struct file *file, void *priv,
					struct v4l2_format *f)
{
	int ret = myvivi_vidioc_try_fmt_vid_cap(file, NULL, f);
	if (ret < 0)
		return ret;

    memcpy(&myvivi_format, f, sizeof(myvivi_format));
    
	return ret;
}

四、增加缓冲区操作的ioctl函数





未完待续。。。。。

GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐