本文学习自韦东山老师的摄像头驱动模块
目录
一 摄像头驱动程序学习切入点以及V4L2模型概览
二 简析虚拟视频驱动 VIVI.C
- 2.1 初始化、设置、注册过程
- 2.2 简析vivi.c的open,read,write,ioctl过程
三 虚拟摄像头驱动的启动过程简析
- 3.1 查看虚拟摄像头应用程序启动虚拟摄像头过程中都做了什么
- 3.2 分析数据的获取过程
四 编写摄像头驱动程序
- 4.1 第一步 :仿照vivi.c,搭建摄像头驱动程序框架
- 4.2 第二步 : 创建v4l2_ioctl_ops 并实现成员函数 vidioc_querycap()
- 4.3 第三步:填充 v4l2_ioctl_ops 结构体中 列举、获得、测试、设置 摄像头的数据的格式的相关函数
- 4.4 第四步 : 增加 缓冲区操作: 申请/查询/放入队列/取出队列 相关函数
- 4.5 第五步 : 增加 启动/停止
- 4.6 第六步 : 定时器定时填充数据并唤醒进程
一 摄像头驱动程序学习切入点以及V4L2模型概览
V4L2 : Video for Linux 2,是linux为视频设备提供的一套标准接口。我学习V4L2 的切入点,首先学习虚拟摄像头驱动程序vivi.c。学习vivi.c虚拟摄像头驱动的切入点如下:
打开虚拟机,将虚拟机置于最前台,插上USB摄像头,dmesg 看kernel输出LOG。有关键信息 :Found UVC 1.00 … 搜索该关键字,经过搜索,定位在 uvc_driver.c ,粗略的看一下代码,总结出如下模糊框架:
V4L2 大致框架:
硬件相关层
uvc_driver.c
uvc_probe
v4l2_device_register
uvc_register_chains
uvc_register_terms
uvc_register_video
uvc_register_video_device
// 从硬件相关层到核心层;
video_register_device == __video_register_device //drivers\media\v4l2-core\v4l2-dev.c
核心层
v4l2-dev.c
__video_register_device
//以下三步是字符设备相关步骤,v4l2_fops是特殊的字符设备fops
vdev->cdev = cdev_alloc();
vdev->cdev->ops = &v4l2_fops;
cdev_add
应用程序:
open read write
二 简析虚拟视频驱动 VIVI.C
2.1 初始化、设置、注册过程
主要分为三步
1.分配video_device
2.设置
3.注册:video_register_device
vivi_init
vivi_create_instance
v4l2_device_register // 不是主要, 只是用于初始化一些东西,比如自旋锁、引用计数
video_device_alloc
// 设置
1. vfd:
.fops = &vivi_fops,
.ioctl_ops = &vivi_ioctl_ops,
.release = video_device_release,
2.
vfd->v4l2_dev = &dev->v4l2_dev;
3. 设置"ctrl属性"(用于APP的ioctl):
v4l2_ctrl_handler_init(hdl, 11);
dev->volume = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
V4L2_CID_AUDIO_VOLUME, 0, 255, 1, 200);
dev->brightness = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
V4L2_CID_BRIGHTNESS, 0, 255, 1, 127);
dev->contrast = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
V4L2_CID_CONTRAST, 0, 255, 1, 16);
video_register_device(video_device, type:VFL_TYPE_GRABBER, nr)
__video_register_device
vdev->cdev = cdev_alloc();
vdev->cdev->ops = &v4l2_fops;
cdev_add
video_device[vdev->minor] = vdev;
if (vdev->ctrl_handler == NULL)
vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;
2.2 简析vivi.c的open,read,write,ioctl过程
1 open
app: open("/dev/video0",....)
------------------------------------------------------------------
drv: v4l2_fops.v4l2_open
vdev = video_devdata(filp); // 根据次设备号从数组中得到video_device
ret = vdev->fops->open(filp);
vivi_ioctl_ops.open
v4l2_fh_open
2 read
app: read ....
---------------------------------------------------
drv: v4l2_fops.v4l2_read
struct video_device *vdev = video_devdata(filp);
ret = vdev->fops->read(filp, buf, sz, off);
3 ioctl
app: ioctl
----------------------------------------------------
drv: v4l2_fops.unlocked_ioctl
v4l2_ioctl
struct video_device *vdev = video_devdata(filp);
ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);
video_ioctl2
video_usercopy(file, cmd, arg, __video_do_ioctl);
__video_do_ioctl
struct video_device *vfd = video_devdata(file);
根据APP传入的cmd来获得、设置"某些属性"
三 虚拟摄像头驱动的启动过程简析
3.1 查看虚拟摄像头应用程序启动虚拟摄像头过程中都做了什么
前期环境搭建步骤后面补充,这边直接记录陈述结果,测试虚拟摄像头,发现虚拟摄像头应用程序调用虚拟摄像头驱动的ioctl过程如下:
// 1~7都是在v4l2_open里调用
1. open
2. ioctl(4, VIDIOC_QUERYCAP
// 3~7 都是在get_device_capabilities里调用
3. for()
ioctl(4, VIDIOC_ENUMINPUT // 列举输入源,VIDIOC_ENUMINPUT/VIDIOC_G_INPUT/VIDIOC_S_INPUT不是必需的
4. for()
ioctl(4, VIDIOC_ENUMSTD // 列举标准(制式), 不是必需的
5. for()
ioctl(4, VIDIOC_ENUM_FMT // 列举格式
6. ioctl(4, VIDIOC_G_PARM
7. for()
ioctl(4, VIDIOC_QUERYCTRL // 查询属性(比如说亮度值最小值、最大值、默认值)
// 8~10都是通过v4l2_read_attr来调用的
8. ioctl(4, VIDIOC_G_STD // 获得当前使用的标准(制式), 不是必需的
9. ioctl(4, VIDIOC_G_INPUT
10. ioctl(4, VIDIOC_G_CTRL // 获得当前属性, 比如亮度是多少
11. ioctl(4, VIDIOC_TRY_FMT // 试试能否支持某种格式
12. ioctl(4, VIDIOC_S_FMT // 设置摄像头使用某种格式
// 13~16在v4l2_start_streaming
13. ioctl(4, VIDIOC_REQBUFS // 请求系统分配缓冲区
14. for()
ioctl(4, VIDIOC_QUERYBUF // 查询所分配的缓冲区
mmap
15. for ()
ioctl(4, VIDIOC_QBUF // 把缓冲区放入队列
16. ioctl(4, VIDIOC_STREAMON // 启动摄像头
// 17里都是通过v4l2_write_attr来调用的
17. for ()
ioctl(4, VIDIOC_S_CTRL // 设置属性
ioctl(4, VIDIOC_S_INPUT // 设置输入源
ioctl(4, VIDIOC_S_STD // 设置标准(制式), 不是必需的
// v4l2_nextframe > v4l2_waiton
18. v4l2_queue_all
v4l2_waiton
for ()
{
select(5, [4], NULL, NULL, {5, 0}) = 1 (in [4], left {4, 985979})
ioctl(4, VIDIOC_DQBUF // de-queue, 把缓冲区从队列中取出
// 处理, 之以已经通过mmap获得了缓冲区的地址, 就可以直接访问数据
ioctl(4, VIDIOC_QBUF // 把缓冲区放入队列
}
由此可知,虚拟摄像头应用程序经过一系列的 ioctl() 最终将虚拟摄像头驱动启动成功。那么应用程序调用了那么多vivi.c中的 ioctl(),是不是每一个ioctl()都是必要的呢,经过删减测试。发现如下11个 ioctl() 是必要的,其余的ioctl()可删减。
摄像头驱动程序必需的11个ioctl:
// 表示它是一个摄像头设备
.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_streamon = vidioc_streamon,
.vidioc_streamoff = vidioc_streamoff,
3.2 分析数据的获取过程
1 请求分配缓冲区:
ioctl(4, VIDIOC_REQBUFS // 请求系统分配缓冲区
videobuf_reqbufs(队列, v4l2_requestbuffers) // 队列在open函数用videobuf_queue_vmalloc_init()初始化
2 查询映射缓冲区:
ioctl(4, VIDIOC_QUERYBUF // 查询所分配的缓冲区
videobuf_querybuf() // 获得缓冲区的数据格式、大小、每一行长度、高度
mmap(参数里有"大小") // 在这里才分配缓存
v4l2_mmap
vivi_mmap
videobuf_mmap_mapper
videobuf-vmalloc.c里的__videobuf_mmap_mapper
mem->vmalloc = vmalloc_user(pages); // 在这里才给缓冲区分配空间
3 把缓冲区放入队列:
ioctl(4, VIDIOC_QBUF // 把缓冲区放入队列
videobuf_qbuf()
q->ops->buf_prepare(q, buf, field); // 调用驱动程序提供的函数做些预处理
list_add_tail(&buf->stream, &q->stream); // 把缓冲区放入队列的尾部
q->ops->buf_queue(q, buf); // 调用驱动程序提供的"入队列函数"
4 启动摄像头
ioctl(4, VIDIOC_STREAMON
videobuf_streamon
q->streaming = 1;
5 用select查询是否有数据
// 驱动程序里必定有: 产生数据、唤醒进程
v4l2_poll
vdev->fops->poll
vivi_poll
videobuf_poll_stream
// 从队列的头部获得缓冲区
buf = list_entry(q->stream.next, struct videobuf_buffer, stream);
// 如果没有数据则休眠
poll_wait(file, &buf->done, wait);
谁来产生数据、谁来唤醒它?
内核线程vivi_thread每30MS执行一次,它调用
vivi_thread_tick
vivi_fillbuff(fh, buf); // 构造数据
wake_up(&buf->vb.done); // 唤醒进程
6 有数据后从队列里取出缓冲区
// 有那么多缓冲区,APP如何知道哪一个缓冲区有数据?调用VIDIOC_DQBUF
ioctl(4, VIDIOC_DQBUF
vidioc_dqbuf
// 在队列里获得有数据的缓冲区
retval = stream_next_buffer(q, &buf, nonblocking);
// 把它从队列中删掉
list_del(&buf->stream);
// 把这个缓冲区的状态返回给APP
videobuf_status(q, b, buf, q->type);
7
应用程序根据VIDIOC_DQBUF所得到缓冲区状态,知道是哪一个缓冲区有数据 就去读对应的地址(该地址来自前面的mmap)
四 编写摄像头驱动程序
4.1 第一步 :仿照vivi.c,搭建摄像头驱动程序框架
仿照vivi.c,搭建摄像头驱动程序框架
工作:
1 分配一个video_device结构体 myvivi_device
2 创建 v4l2_file_operations 并且绑定到 myvivi_device
3 注册 myvivi_device
/* 仿照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. 注册 */ //注册摄像头类型 which device number
//注意:教程版本内核 该注册函数内部还会判断 时候已经编写release函数,和fops 结构。如果没有的话 注册失败
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");
4.2 第二步 : 创建v4l2_ioctl_ops 并实现成员函数 vidioc_querycap()
仿照vivi.c ,创建v4l2_ioctl_ops 并实现成员函数 vidioc_querycap(),实现该函数可以代表当前为一个摄像头设备。
工作:
1 构建 v4l2_ioctl_ops结构体
1.1 实现成员函数 vidioc_querycap ioctl(),表示是一个摄像头设备
2 设置 myvivi_device->ioctl_ops = &myvivi_ioctl_ops;
3 设置 v4l2_file_operations结构体的 .ioctl = video_ioctl2 ;
说明:此项是参考 vivi.c,当应用程序调用v4l2_file_operations 的 .ioctl时候,就会调用video_ioctl2,
而video_ioctl2最终调用到 v4l2_ioctl_ops结构体中的成员函数
#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 int myvivi_vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *cap)
{
strcpy(cap->driver, "myvivi");
strcpy(cap->card, "myvivi");
cap->version = 0x0001;
// 读取方式 目前使用 V4L2_CAP_STREAMING 读取方式
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 V4L2 提供的表针的ioctl 最终就会调用到 myvivi_ioctl_ops*/
};
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;
}
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");
4.3 第三步:填充 v4l2_ioctl_ops 结构体中 列举、获得、测试、设置 摄像头的数据的格式的相关函数
仿照vivi.c
工作:继续填充 v4l2_ioctl_ops 结构体 : 填充 用于 列举、获得、测试、设置 摄像头的数据的格式的相关函数
1 实现 vidioc_enum_fmt_vid_cap
说明 : 例举摄像头格式,当应用程序想查询我们的摄像头支持哪种格式的时候,发送 ioctl(4, VIDIOC_ENUMINPUT 指令,最终
调用到 vidioc_enum_fmt_vid_cap()
2 实现 vidioc_g_fmt_vid_cap
说明 : 返回当前摄像头所使用的格式
2.1 定义 struct v4l2_format
说明:用于存储当前格式,当应用程序想要获取当前摄像头格式的时候,直接返回该结构体
3 实现 vidioc_try_fmt_vid_cap
说明 : 测试摄像头驱动程序是否支持某种数据格式,即判断应用程序传递进来的摄像头格式:
struct v4l2_format->fmt.pix.pixelformat,是否等于 我们驱动程序唯一支持的格式:v4l2_fmtdesc->pixelformat = V4L2_PIX_FMT_YUYV
4 实现 vidioc_s_fmt_vid_cap
说明 : 设置摄像头数据格式
4.1 先测试是否支持应用程序传递进来的摄像头格式 struct v4l2_format,调用 vidioc_try_fmt_vid_cap函数,判断应用程
序传递进来的摄像头格式 f->fmt.pix.pixelformat是否等于我们驱动程序唯一支持的格式 v4l2_fmtdesc->pixelformat = V4L2_PIX_FMT_YUYV
4.2 将应用程序传递进来的格式 拷贝到 我们自定义的 struct v4l2_format
#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;
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;
}
/* 列举支持哪种格式 固定只支持一种格式 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");//先设置只支持一种格式,这种格式是 YUYV,描述为 “4:2:2, packed, YUYV”
f->pixelformat = V4L2_PIX_FMT_YUYV;//先设置只支持一种格式,这种格式是 YUYV
return 0;
}
/* 返回当前所使用的格式 */
static int myvivi_vidioc_g_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_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;
//格式,目前我们的驱动程序只支持 V4L2_PIX_FMT_YUYV 格式
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;
}
//设置摄像头数据格式 由于此时没有硬件支持,所以将应用传递下来的数据格式保存在 myvivi_format
static int myvivi_vidioc_s_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
//测试驱动程序是否支持某种(f)格式
int ret = myvivi_vidioc_try_fmt_vid_cap(file, NULL, f);
if (ret < 0)
return ret;
//将 f 拷贝到 myvivi_format
memcpy(&myvivi_format, f, sizeof(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 */
};
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;
}
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");
4.4 第四步 : 增加 缓冲区操作: 申请/查询/放入队列/取出队列 相关函数
工作:继续填充 v4l2_ioctl_ops 结构体 : 填充 用于 列举、获得、测试、设置 摄像头的数据的格式的相关函数
1
1.1 定义 videobuf_queue 队列
1.2 定义、初始化 spinlock_t 自旋锁 :
1.2.1 定义 spinlock_t 自旋锁:static spinlock_t myvivi_queue_slock;
1.2.2 初始化 spinlock_t 自旋锁: pin_lock_init(&myvivi_queue_slock);
2 在 v4l2_file_operations .open中 初始化 videobuf_queue 队列
说明 :在 v4l2_file_operations .open中 初始化 videobuf_queue 队列 : videobuf_queue_vmalloc_init()
videobuf_queue_vmalloc_init()参数分析如下
2.1 参数1:videobuf_queue 队列
2.2 参数2: videobuf_queue_ops 结构体, 初始化 videobuf_queue_ops结构体,定义初始化其中的操作函数
说明:填充videobuf_queue_ops结构体
2.2.1 填充 buf_setup()
说明 : APP调用ioctl VIDIOC_REQBUFS(请求系统分配缓冲区) 时,会调用驱动的结构体v4l2_ioctl_ops
的 .vidioc_reqbufs函数,在vidioc_reqbufs函数中,他会调用该函数buf_setup(),重新调整count
和size
2.2.2 填充 buf_prepare()
说明 : 在结构体v4l2_ioctl_ops的 .vidioc_qbuf函数(入队列操作)中调用,设置状态
struct videobuf_buffer->state = VIDEOBUF_PREPARED;
2.2.3 填充 buf_queue()
说明 : 设置状态 struct videobuf_buffer->state = VIDEOBUF_QUEUED; APP调用 ioctl VIDIOC_QBUF时,
2.2.3.1 首先调用 buf_prepare 进行一些准备工作,
2.2.3.2 把buf放入队列
2.2.3.3 调用 buf_queue (起通知作用),设置
2.2.4 填充 buf_release()
说明: APP不再使用队列时, 用它来释放内存
2.3 参数3:v4l2_buf_type ,选择 V4L2_BUF_TYPE_VIDEO_CAPTURE 类型
2.4 参数4:v4l2_field, V4L2_FILED_INTERLACED : 略 参考vivi.c代码参数
2.5 参数5:videobuf_buffer,buffer的头部大小
2.6 参数6:私有数据 NULL
3 添加 v4l2_file_operations .release函数,销毁队列
3.1 停止队列操作 videobuf_stop(&myvivi_vb_vidqueue);
3.2 删除队列 videobuf_mmap_free(&myvivi_vb_vidqueue);
4 实现缓冲区操作函数:
/* 缓冲区操作: 申请/查询/放入队列/取出队列 */
.vidioc_reqbufs = myvivi_vidioc_reqbufs,
.vidioc_querybuf = myvivi_vidioc_querybuf,
.vidioc_qbuf = myvivi_vidioc_qbuf,
.vidioc_dqbuf = myvivi_vidioc_dqbuf,
/* 继续填充 v4l2_ioctl_ops 结构体 :增加 缓冲区操作: 申请/查询/放入队列/取出队列 相关函数
* 1. vidioc_reqbufs : 申请缓冲区操作
* 2. vidioc_querybuf : 查询缓冲区
* 3. vidioc_qbuf : 放入队列操作
* 4. vidioc_dqbuf : 取出队列操作
*/
#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: 初始化 */
/* V4L2_BUF_TYPE_VIDEO_CAPTURE 类型*/
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的头部大小 */
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;
}
static int myvivi_vidioc_reqbufs(struct file *file, void *priv,
struct v4l2_requestbuffers *p)
{
return (videobuf_reqbufs(&myvivi_vb_vidqueue, p));
}
static int myvivi_vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
return (videobuf_querybuf(&myvivi_vb_vidqueue, p));
}
static int myvivi_vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
return (videobuf_qbuf(&myvivi_vb_vidqueue, p));
}
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");
4.5 第五步 : 增加 启动/停止
/* 仿照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的头部大小 */
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 unsigned int myvivi_poll(struct file *file, struct poll_table_struct *wait)
{
return videobuf_poll_stream(file, &myvivi_vb_vidqueue, wait);
}
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;
}
static int myvivi_vidioc_reqbufs(struct file *file, void *priv,
struct v4l2_requestbuffers *p)
{
return (videobuf_reqbufs(&myvivi_vb_vidqueue, p));
}
static int myvivi_vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
return (videobuf_querybuf(&myvivi_vb_vidqueue, p));
}
static int myvivi_vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
return (videobuf_qbuf(&myvivi_vb_vidqueue, p));
}
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 int myvivi_vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
{
return videobuf_streamon(&myvivi_vb_vidqueue);
}
static int myvivi_vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i)
{
videobuf_streamoff(&myvivi_vb_vidqueue);
return 0;
}
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,
// 启动/停止
.vidioc_streamon = myvivi_vidioc_streamon,
.vidioc_streamoff = myvivi_vidioc_streamoff,
};
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 */
.poll = myvivi_poll,
};
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");
4.6 定时器定时填充数据并唤醒进程
/* 仿照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;
static struct list_head myvivi_vb_local_queue;
static struct timer_list myvivi_timer;
#include "fillbuf.c"
/* 参考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)
{
/* 0. 设置videobuf */
vb->size = myvivi_format.fmt.pix.sizeimage;
vb->bytesperline = myvivi_format.fmt.pix.bytesperline;
vb->width = myvivi_format.fmt.pix.width;
vb->height = myvivi_format.fmt.pix.height;
vb->field = field;
/* 1. 做些准备工作 */
myvivi_precalculate_bars(0);
#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调用ioctl VIDIOC_QBUF时:
* 1. 先调用buf_prepare进行一些准备工作
* 2. 把buf放入stream队列
* 3. 调用buf_queue(起通知、记录作用)
*/
static void myvivi_buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb)
{
vb->state = VIDEOBUF_QUEUED;
/* 把videobuf放入本地一个队列尾部
* 定时器处理函数就可以从本地队列取出videobuf
*/
list_add_tail(&vb->queue, &myvivi_vb_local_queue);
}
/* 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的头部大小 */
myvivi_timer.expires = jiffies + 1;
add_timer(&myvivi_timer);
return 0;
}
static int myvivi_close(struct file *file)
{
del_timer(&myvivi_timer);
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 unsigned int myvivi_poll(struct file *file, struct poll_table_struct *wait)
{
return videobuf_poll_stream(file, &myvivi_vb_vidqueue, wait);
}
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;
}
static int myvivi_vidioc_reqbufs(struct file *file, void *priv,
struct v4l2_requestbuffers *p)
{
return (videobuf_reqbufs(&myvivi_vb_vidqueue, p));
}
static int myvivi_vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
return (videobuf_querybuf(&myvivi_vb_vidqueue, p));
}
static int myvivi_vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
return (videobuf_qbuf(&myvivi_vb_vidqueue, p));
}
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 int myvivi_vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
{
return videobuf_streamon(&myvivi_vb_vidqueue);
}
static int myvivi_vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i)
{
videobuf_streamoff(&myvivi_vb_vidqueue);
return 0;
}
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,
// 启动/停止
.vidioc_streamon = myvivi_vidioc_streamon,
.vidioc_streamoff = myvivi_vidioc_streamoff,
};
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 */
.poll = myvivi_poll,
};
static struct video_device *myvivi_device;
static void myvivi_release(struct video_device *vdev)
{
}
static void myvivi_timer_function(unsigned long data)
{
struct videobuf_buffer *vb;
void *vbuf;
struct timeval ts;
/* 1. 构造数据: 从队列头部取出第1个videobuf, 填充数据
*/
/* 1.1 从本地队列取出第1个videobuf */
if (list_empty(&myvivi_vb_local_queue)) {
goto out;
}
vb = list_entry(myvivi_vb_local_queue.next,
struct videobuf_buffer, queue);
/* Nobody is waiting on this buffer, return */
if (!waitqueue_active(&vb->done))
goto out;
/* 1.2 填充数据 */
vbuf = videobuf_to_vmalloc(vb);
//memset(vbuf, 0xff, vb->size);
myvivi_fillbuff(vb);
vb->field_count++;
do_gettimeofday(&ts);
vb->ts = ts;
vb->state = VIDEOBUF_DONE;
/* 1.3 把videobuf从本地队列中删除 */
list_del(&vb->queue);
/* 2. 唤醒进程: 唤醒videobuf->done上的进程 */
wake_up(&vb->done);
out:
/* 3. 修改timer的超时时间 : 30fps, 1秒里有30帧数据
* 每1/30 秒产生一帧数据
*/
mod_timer(&myvivi_timer, jiffies + HZ/30);
}
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);
/* 用定时器产生数据并唤醒进程 */
init_timer(&myvivi_timer);
myvivi_timer.function = myvivi_timer_function;
INIT_LIST_HEAD(&myvivi_vb_local_queue);
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");