linux应用项目(二)摄像头(2)从零写一个V4L2虚拟摄像头驱动之详细分析

时间:2022-06-15 19:45:20

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

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

零、字符设备编写思路

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

一、框架搭建

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

linux应用项目(二)摄像头(2)从零写一个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的环境,来创造我们虚拟驱动的环境。

linux应用项目(二)摄像头(2)从零写一个V4L2虚拟摄像头驱动之详细分析

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

linux应用项目(二)摄像头(2)从零写一个V4L2虚拟摄像头驱动之详细分析

二、加入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中:

linux应用项目(二)摄像头(2)从零写一个V4L2虚拟摄像头驱动之详细分析

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)调用框图

linux应用项目(二)摄像头(2)从零写一个V4L2虚拟摄像头驱动之详细分析

linux应用项目(二)摄像头(2)从零写一个V4L2虚拟摄像头驱动之详细分析


三、增加列举、获得、测试、设置摄像头的数据格式的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函数





未完待续。。。。。