虚拟摄像头驱动的过程理解透彻了,那么真实摄像头驱动的程序将会十分容易,
本文将总结虚拟摄像头驱动实现的详细细节。相信弄透后字符设备驱动将会十分清晰。
零、字符设备编写思路
简单字符设备常规的方法是单层,实现入口、出口修饰一下即可,而复杂一点的字符设备驱动则采用分层的架构,内核为我们提供核心层及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、增加 获取支持的视频格式)
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函数
未完待续。。。。。