摄像头驱动程序必需的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,
分析数据的获取过程
1 请求分配缓冲区: ioctl(4, VIDIOC_REQBUFS // 请求系统分配缓冲区
videobuf_reqbufs(队列q, 申请缓冲区个数v4l2_requestbuffers)
// 队列在open函数用videobuf_queue_vmalloc_init初始化
// 注意:这个IOCTL只是分配缓冲区的头部信息,真正的缓存还没有分配
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); //把它从队列中删掉
videobuf_status(q, b, buf, q->type); //把这个缓冲区的状态返回给APP
7 应用程序根据VIDIOC_DQBUF所得到缓冲区状态,知道是哪一个缓冲区有数据
就去读对应的地址(该地址来自前面的mmap)
怎么写摄像头驱动程序
1 分配video_device: video_device_alloc
2. 设置
.fops
.ioctl_ops (里面需要设置11项)
如果要用内核提供的缓冲区操作函数,还需要构造一个videobuf_queue_ops
3. 注册: video_register_device
程序
static struct list_head myvivi_vb_local_queue;
static struct timer_list myvivi_timer;
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");
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);
}
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;
}
用于列举、获得、测试、设置摄像头的数据的格式
/* 列举支持哪种格式 */
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;
}
File operations
/* 队列操作1: 定义 */
static struct videobuf_queue myvivi_vb_vidqueue;
static spinlock_t myvivi_queue_slock;
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);
}
Videobuf operations
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,
};
/* 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;
}