1.1th(搭建起虚拟摄像头驱动框架)
/* 仿照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. 注册 */
//参2:VFL_TYPE_GRABBER是所注册的设备类型;参3:nr指which device numbe(0 == /dev/video0, 1 == /dev/video1, ... -1 == first free第一个空闲的设备)
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");
(1)将该驱动程序上传到ubuntu并编译
(2)将ubuntu上的所有的“/dev/video”都卸载掉:
(3)安装这个编译好的驱动模块
直接安装的话会报错:
使用dmesg命令来查看报错原因:
提示这些函数未识别,因为myvivi.ko还依赖其他驱动程序
使用“sudo modprobe vivi”命令来修改这个错误:
modprobe命令会自动去安装vivi驱动程序所依赖的其他驱动程序。
(4)然后卸载vivi并安装我们编译好的myvivi驱动模块
但这个设备节点现在还用不了,它并不认为这个设备是一个摄像头设备,因为这个驱动程序里面什么都还没有,里面连一个表明它是摄像头设备的ioctl都没有。
现在这个程序只是一个框架,它只是一个空壳子。
2.2th(在上一程序基础上加入vidioc_querycap(表示它是一个摄像头设备)这个ioctl)
/* 应用程序会调用这个ioctl来查询它的性能,看看它是不是一个摄像头设备*/
static int myvivi_vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *cap)
/* 仿照vivi.c 进行删减*/
{
strcpy(cap->driver, "myvivi");
strcpy(cap->card, "myvivi");
cap->version = 0x0001;
/* (1)V4L2_CAP_VIDEO_CAPTURE--视频的捕捉设备;(2)应用程序来访问摄像头时可通过两种方式获得数据,
一个是ioctl,另外一个可以通过read函数,此处我们先不提供read函数, 直接用V4L2_CAP_STREAMING */
cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
return 0;
}
tatic 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, /* 它最终会调用到myvivi_ioctl_ops结构体里面的那些函数 */
};
(1)编译驱动程序并装载
(2)测试这个虚拟摄像头驱动程序
现在可以出来这个摄像头界面,只不过里面没有任何东西,并且有一大堆错误,但至少框架已经出来了。
3.3th(继续添加ioctl(用于列举、获得、测试、设置摄像头的数据的格式))
static int myvivi_vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *cap)
{
strcpy(cap->driver, "myvivi"); //strcpy(name,name1)将name1的字符串复制到name中
strcpy(cap->card, "myvivi");
cap->version = 0x0001;
cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
return 0;
}
/* 列举支持哪种格式 */
static int myvivi_vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_fmtdesc *f)
{
/* 这里我们只支持一种格式, 当应用程序想查询设备支持哪一些格式的时候,它会多次调用VIDIOC_ENUMINPUT这个ioctl */
if (f->index >= 1) //只支持一种格式,这种格式叫做YUYV
return -EINVAL;
strcpy(f->description, "4:2:2, packed, YUYV"); //这里给description赋值一个字符串
f->pixelformat = V4L2_PIX_FMT_YUYV; //只支持这一种格式
return 0;
}
/* 返回当前所使用的格式 */
static int myvivi_vidioc_g_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
/*在前面先定义myvivi_format这么一个结构体,当前程序中我们还没有初始化这个结构体,当我们想获取当前所使用的
格式时通过参数f将myvivi_format拷贝回应用程序 */
memcpy(f, &myvivi_format, sizeof(myvivi_format));
return (0);
}
/* 测试驱动程序是否支持某种格式 */
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;
f->fmt.pix.sizeimage =
f->fmt.pix.height * f->fmt.pix.bytesperline;
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)); //把传进来的参数f拷贝到myvivi_format
return ret;
}
static const struct v4l2_ioctl_ops myvivi_ioctl_ops = {
// 表示它是一个摄像头设备
.vidioc_querycap = myvivi_vidioc_querycap,
/* 用于列举、获得、测试、设置摄像头的数据的格式 */
.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,
#if 0
/* 缓冲区操作: 申请/查询/放入队列/取出队列 */
.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 */
};
(1)编译驱动程序并装载
(2)测试这个虚拟摄像头驱动程序
本程序的实验现象和2th程序一样。
4.4th(继续添加ioctl(缓冲区操作: 申请/查询/放入队列/取出队列))
/* 仿照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 struct v4l2_format myvivi_format;
/* 队列操作1: 定义 */
static struct videobuf_queue myvivi_vb_vidqueue; //定义一个队列
static spinlock_t myvivi_queue_slock; //定义自旋锁
/*
参考documentations/video4linux/v4l2-framework.txt:
或者参考drivers\media\video\videobuf-core.c
ops->buf_setup - calculates the size of the video buffers and avoid they
to waste more than some maximum limit of RAM;
ops->buf_prepare - fills the video buffer structs and calls
videobuf_iolock() to alloc and prepare mmaped memory;
ops->buf_queue - advices the driver that another buffer were
requested (by read() or by QBUF);
ops->buf_release - frees any buffer that were allocated.
*/
/* ------------------------------------------------------------------
Videobuf operations
------------------------------------------------------------------*/
/* APP调用ioctl VIDIOC_REQBUFS时会导致此函数被调用,它重新调整count和size */
static int myvivi_buffer_setup(struct videobuf_queue *vq, unsigned int *count, unsigned int *size)
{
*size = myvivi_format.fmt.pix.sizeimage;
if (0 == *count)
*count = 32;
return 0;
}
/* APP调用ioctlVIDIOC_QBUF时导致此函数被调用, 它会填充video_buffer结构体并调用videobuf_iolock来分配内存 */
static int myvivi_buffer_prepare(struct videobuf_queue *vq, struct videobuf_buffer *vb,
enum v4l2_field field)
{
/* 1. 做些准备工作 */
#if 0
/* 2. 调用videobuf_iolock为类型为V4L2_MEMORY_USERPTR的videobuf分配内存 */
if (VIDEOBUF_NEEDS_INIT == buf->vb.state) {
rc = videobuf_iolock(vq, &buf->vb, NULL);
if (rc < 0)
goto fail;
}
#endif
/* 3. 设置状态 */
vb->state = VIDEOBUF_PREPARED;
return 0;
}
/* APP调用ioctlVIDIOC_QBUF时:
* 1. 先调用buf_prepare进行一些准备工作
* 2. 把buf放入队列
* 3. 调用buf_queue(起通知作用)
*/
static void myvivi_buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb)
{
vb->state = VIDEOBUF_QUEUED;
//list_add_tail(&buf->vb.queue, &vidq->active);
}
/* APP不再使用队列时, 用它来释放内存 */
static void myvivi_buffer_release(struct videobuf_queue *vq,
struct videobuf_buffer *vb)
{
videobuf_vmalloc_free(vb);
vb->state = VIDEOBUF_NEEDS_INIT;
}
static struct videobuf_queue_ops myvivi_video_qops = {
.buf_setup = myvivi_buffer_setup, /* 计算大小以免浪费 */
.buf_prepare = myvivi_buffer_prepare,
.buf_queue = myvivi_buffer_queue,
.buf_release = myvivi_buffer_release,
};
/* ------------------------------------------------------------------
File operations for the device
------------------------------------------------------------------*/
static int myvivi_open(struct file *file)
{
/* 队列操作2: 初始化 */
videobuf_queue_vmalloc_init(&myvivi_vb_vidqueue, &myvivi_video_qops,
NULL, &myvivi_queue_slock, V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_INTERLACED,
sizeof(struct videobuf_buffer), NULL); /* 倒数第2个参数是buffer的头部大小;倒数第1个参数是私有数据,我们这里不需要 */
return 0;
}
static int myvivi_close(struct file *file)
{
videobuf_stop(&myvivi_vb_vidqueue); //停止这个队列操作
videobuf_mmap_free(&myvivi_vb_vidqueue); //释放这个队列
return 0;
}
static int myvivi_mmap(struct file *file, struct vm_area_struct *vma)
{
return videobuf_mmap_mapper(&myvivi_vb_vidqueue, vma);
}
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 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;
}
/* 返回当前所使用的格式 */
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);
}
/* 测试驱动程序是否支持某种格式 */
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;
f->fmt.pix.sizeimage =
f->fmt.pix.height * f->fmt.pix.bytesperline;
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;
}
/* 缓冲区操作: 申请buffer */
static int myvivi_vidioc_reqbufs(struct file *file, void *priv,
struct v4l2_requestbuffers *p)
{
return (videobuf_reqbufs(&myvivi_vb_vidqueue, p));
}
/* 缓冲区操作: 查询buffer */
static int myvivi_vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
return (videobuf_querybuf(&myvivi_vb_vidqueue, p));
}
/* 缓冲区操作: 把buffer放入队列 */
static int myvivi_vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
return (videobuf_qbuf(&myvivi_vb_vidqueue, p));
}
/* 缓冲区操作: 把buffer取出队列 */
static int myvivi_vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
return (videobuf_dqbuf(&myvivi_vb_vidqueue, p,
file->f_flags & O_NONBLOCK));
}
static const struct v4l2_ioctl_ops myvivi_ioctl_ops = {
// 表示它是一个摄像头设备
.vidioc_querycap = myvivi_vidioc_querycap,
/* 用于列举、获得、测试、设置摄像头的数据的格式 */
.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,
#if 0
// 启动/停止
.vidioc_streamon = myvivi_vidioc_streamon,
.vidioc_streamoff = myvivi_vidioc_streamoff,
#endif
};
static const struct v4l2_file_operations myvivi_fops = {
.owner = THIS_MODULE,
.open = myvivi_open,
.release = myvivi_close,
.mmap = myvivi_mmap,
.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. 设置 */
/* 2.1 */
myvivi_device->release = myvivi_release;
/* 2.2 */
myvivi_device->fops = &myvivi_fops;
/* 2.3 */
myvivi_device->ioctl_ops = &myvivi_ioctl_ops;
/* 2.4 队列操作
* a. 定义/初始化一个队列(会用到一个spinlock)
*/
spin_lock_init(&myvivi_queue_slock); //初始化自旋锁
/* 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");
(1)编译驱动程序并装载
(2)测试这个虚拟摄像头驱动程序
测试的错误提示信息就不大一样了,现在只缺少怎么构造、生成数据。
5.5th(继续添加ioctl(摄像头启动/停止和poll函数(查询是否有数据)))
在上一个程序基础上增加下面几行代码:
(1)编译驱动程序并装载
(2)测试这个虚拟摄像头驱动程序
这个程序中我们提供了poll函数,测试时会出现“select timeout”这样的提示信息,下面就要构造数据来唤醒进程了。
6.6th(继续添加ioctl(摄像头启动/停止和poll函数(查询是否有数据)))
下面就要构造数据来唤醒进程了。当我们应用程序调用poll的时候,它在队列的第一个buffer的done(即&buf->done)上休眠了,我们需要去把 &buf->done给唤醒。
在vivi.c里面它是创建了一个内核线程,这个内核线程平时是休眠的。本程序我们用定时器产生数据并唤醒进程。
在open函数中add_timer(把这个定时器告诉内核)。我们写驱动程序有一条规则,就是这些资源只有真正用到的时候再去分配,这里定时器timer只有真正用到它的时候才把它放到内核里面去: