前言
简单回顾下学习过程的文档,有张好图,但他弄不出来。。。
总结
myvivi 从应用到驱动
########################################################
# vivi 应用层程序调用流程:
########################################################
// 1. 打开设备节点 /dev/videoX
//
// 2. 查询摄像头属性
// VIDIOC_ENUMINPUT: 列举输入源
// VIDIOC_ENUMSTD: 列举制式标准
// VIDIOC_ENUM_EMT: 列举格式
// VIDIOC_QUERYCTRL: 查询属性(比如说亮度值最小值、最大值等)
//
// 3. 缓冲区分配与操作
// VIDIOC_REWBUFS :请求系统分配缓冲区
// 这里只分配缓冲区头部信息
// v4l2_buff 结构体
// 用于管理下面分配的真正数据缓冲区
// videobuf_rebufs()
// VIDIOC_QUERYBUF
// 查询所分配的缓冲区
// 获得缓冲区的数据格式、大小、每一行长度、高度
// mmap: 调用驱动的mmap函数分配内存
// v4l2_mmap |
// vivi_mmap |
// videobuf_mmap_mapper | 调用流程
// __videobuf_mmap_mapper |
// videobuf-vmalloc.c |
// vmalloc_user(pages) V
//
// VIDIOC_QBUF:查询缓冲区,把无数据缓冲区放入【本地队列】
// videobuf_qbuf |
// buf_prepare() |
// 调用驱动程序提供的函数做些预处理 |
// list_add_tail() | 调用流程
// 把缓冲区放入队列的尾部 |
// buf_queue() |
// 调用驱动程序提供的"入队列函数" V
//
// 4. 打开摄像头
// VIDIOC_STREAMON
// videobuf_streamon()
// q->streaming = 1
//
// 5. 等待数据到来
// select()
// v4l2_poll |
// vdev->fops->poll |
// 驱动中的 poll 函数 |
// 即 vivi_poll() |
// videobuf_poll_stream | 调用流程
// list_entry() |
// 从队列的头部获得缓冲区 |
// poll_wait()【在下面的定时器中被唤醒】 |
// 如果没有数据则睡眠 V
//
// 6. 有数据来了,判断是哪个缓冲区有数据
// VIDIOC_DQBUF
// vidioc_dqbuf() |
// stream_next_buffer() |
// 在队列里获得有数据的缓冲区 |
// list_del() | 调用流程
// 把它从队列中删掉 |
// videobuf_status() |
// 把这个缓冲区的状态返回给APP V
//
// 7. 从对应缓冲区取出数据
// 应用程序根据 VIDIOC_DQBUF 所得到缓冲区状态
// 知道是哪一个缓冲区有数据
// 读对应的地址(该地址来自前面的 mmap )
########################################################
# vivi 驱动编写及流程分析:
########################################################
# 编写总结:
// 1. 分配 video_device = video_device_alloc()
// 2. 设置:
// 2.1 video_device.fops: 正常通用的 open/read/write/close 等
// .open: 初始化队列,注册 videobuf_queue_ops
// videobuf_queue_vmalloc_init()
// // videobuf_queue_ops: 缓冲区操作函数
// // .buf_setup = myvivi_buffer_setup, APP 调用 ioctl VIDIOC_REQBUFS 时会导致此函数被调用 计算大小以免浪费
// //
// // .buf_prepare = myvivi_buffer_prepare, APP 调用 ioctl VIDIOC_QBUF 时导致此函数被调用
// // 0. 设置 videobuf
// // 1. 做些准备工作
// // 2. 调用 videobuf_iolock 为类型为 V4L2_MEMORY_USERPTR 的 videobuf 分配内存
// // 3. 设置状态
// //
// // .buf_queue = myvivi_buffer_queue, APP调用 ioctl VIDIOC_QBUF 时此函数被调用
// // 1. 先调用 buf_prepare 进行一些准备工作
// // 2. 把 buf 放入 stream 队列
// // 3. 调用 buf_queue(起通知、记录作用)
// // 把检查无数据的 videobuf 放入本地一个队列尾部
// // 数据来了会从本地队列取出 videobuf 填充数据
// //
// // .buf_release = myvivi_buffer_release, APP 不再使用队列时, 用它来释放内存
//
//
// .release: 释放相关资源
// videobuf_stop()
// videobuf_mmap_free()
// .mmap: videobuf_mmap_mapper()
//
// .ioctl = video_ioctl2 注:他会调用到下面的 ioctl_ops 的
//
// .poll: videobuf_poll_stream()
//
// 2.2 video_device.ioctl_ops: v4l2 特定的调用实现
// .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 = videobuf_reqbufs() | 缓冲区操作函数
// .vidioc_querybuf = myvivi_vidioc_querybuf = videobuf_querybuf() |
// .vidioc_qbuf = myvivi_vidioc_qbuf = videobuf_qbuf() |
// .vidioc_dqbuf = myvivi_vidioc_dqbuf = videobuf_dqbuf() |
//
// .vidioc_streamon = myvivi_vidioc_streamon = videobuf_streamon() | 摄像头启动/停止
// .vidioc_streamoff = myvivi_vidioc_streamoff = videobuf_streamoff() |
//
// 2.3 创建一个本地队列保存 buffer:【这里保存的是无数据的 buff 】
// 入队列:是在 VIDIOC_QBUF 中入队列的
// 出队列:是在定时器有数据时出的队列,然后以自身唤醒等待进程处理
// 3. 注册:
// video_register_device()
# 实现过程:
module_init(myvivi_init);
myvivi_init(void)
/* 1. 分配一个video_device结构体 */
// static struct video_device *myvivi_device;
myvivi_device = video_device_alloc();
/* 2. 设置 */
/* 2.1 空函数 */
myvivi_device->release = myvivi_release;
/* 2.2 操作函数*/
myvivi_device->fops = &myvivi_fops;
// 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,
// };
/* 2.3 ioctl 操作函数 */
myvivi_device->ioctl_ops = &myvivi_ioctl_ops;
// 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, = videobuf_reqbufs()
// .vidioc_querybuf = myvivi_vidioc_querybuf, = videobuf_querybuf()
// .vidioc_qbuf = myvivi_vidioc_qbuf, = videobuf_qbuf()
// .vidioc_dqbuf = myvivi_vidioc_dqbuf, = videobuf_dqbuf()
//
// // 摄像头启动/停止
// .vidioc_streamon = myvivi_vidioc_streamon, = videobuf_streamon()
// .vidioc_streamoff = myvivi_vidioc_streamoff, = videobuf_streamoff()
// };
/* 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);
# 定时器模拟的数据上报流程:
// 流程总结:
// 1. list_entry(): 从本地队列取出第一个 videobuf
// 2. myvivi_fillbuff(): 填充数据
// 3. list_del(): 把 videobuf 从本地队列中删除
// 4. wake_up(): 唤醒进程:唤醒 videobuf->done 上的进程
// 代码总结:
myvivi_open()
/* 队列操作 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);
/////////////////////////////////////
// 定时器到期后
myvivi_timer_function()
/* 1. 构造数据: 从队列头部取出第 1 个 videobuf, 填充数据
*/
/* 1.1 从本地队列取出第 1 个 videobuf */
vb = list_entry(myvivi_vb_local_queue.next, struct videobuf_buffer, queue);
/* 1.2 填充数据 */
vbuf = videobuf_to_vmalloc(vb);
memset(vbuf, 0xff, vb->size);
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);
/* 3. 修改timer的超时时间 : 30fps, 1秒里有30帧数据
* 每1/30 秒产生一帧数据
*/
mod_timer(&myvivi_timer, jiffies + HZ/30);