Linux摄像头驱动学习之:(三)从零写虚拟驱动(仿照vivi.c)
本篇仿照vivi.c 写虚拟视频驱动,代码(myvivi.c+fillbuf.c+Makefile)如下:
//==========================myvivi.c=======================================
/* 仿照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");
//==========================fillbuf.c=======================================
/* Bars and Colors should match positions */ enum colors { WHITE, AMBAR, CYAN, GREEN, MAGENTA, RED, BLUE, BLACK, }; /* R G B */ #define COLOR_WHITE {204, 204, 204} #define COLOR_AMBAR {208, 208, 0} #define COLOR_CIAN { 0, 206, 206} #define COLOR_GREEN { 0, 239, 0} #define COLOR_MAGENTA {239, 0, 239} #define COLOR_RED {205, 0, 0} #define COLOR_BLUE { 0, 0, 255} #define COLOR_BLACK { 0, 0, 0} struct bar_std { u8 bar[8][3]; }; /* Maximum number of bars are 10 - otherwise, the input print code should be modified */ static struct bar_std bars[] = { { /* Standard ITU-R color bar sequence */ { COLOR_WHITE, COLOR_AMBAR, COLOR_CIAN, COLOR_GREEN, COLOR_MAGENTA, COLOR_RED, COLOR_BLUE, COLOR_BLACK, } }, { { COLOR_WHITE, COLOR_AMBAR, COLOR_BLACK, COLOR_WHITE, COLOR_AMBAR, COLOR_BLACK, COLOR_WHITE, COLOR_AMBAR, } }, { { COLOR_WHITE, COLOR_CIAN, COLOR_BLACK, COLOR_WHITE, COLOR_CIAN, COLOR_BLACK, COLOR_WHITE, COLOR_CIAN, } }, { { COLOR_WHITE, COLOR_GREEN, COLOR_BLACK, COLOR_WHITE, COLOR_GREEN, COLOR_BLACK, COLOR_WHITE, COLOR_GREEN, } }, }; #define NUM_INPUTS ARRAY_SIZE(bars) #define TO_Y(r, g, b) \ (((16829 * r + 33039 * g + 6416 * b + 32768) >> 16) + 16) /* RGB to V(Cr) Color transform */ #define TO_V(r, g, b) \ (((28784 * r - 24103 * g - 4681 * b + 32768) >> 16) + 128) /* RGB to U(Cb) Color transform */ #define TO_U(r, g, b) \ (((-9714 * r - 19070 * g + 28784 * b + 32768) >> 16) + 128) static unsigned char myvivi_cur_bars[8][3]; /* precalculate color bar values to speed up rendering */ static void myvivi_precalculate_bars(int input) { unsigned char r, g, b; int k, is_yuv; for (k = 0; k < 8; k++) { r = bars[input].bar[k][0]; g = bars[input].bar[k][1]; b = bars[input].bar[k][2]; is_yuv = 0; switch (myvivi_format.fmt.pix.pixelformat) { case V4L2_PIX_FMT_YUYV: case V4L2_PIX_FMT_UYVY: is_yuv = 1; break; case V4L2_PIX_FMT_RGB565: case V4L2_PIX_FMT_RGB565X: r >>= 3; g >>= 2; b >>= 3; break; case V4L2_PIX_FMT_RGB555: case V4L2_PIX_FMT_RGB555X: r >>= 3; g >>= 3; b >>= 3; break; } if (is_yuv) { myvivi_cur_bars[k][0] = TO_Y(r, g, b); /* Luma */ myvivi_cur_bars[k][1] = TO_U(r, g, b); /* Cb */ myvivi_cur_bars[k][2] = TO_V(r, g, b); /* Cr */ } else { myvivi_cur_bars[k][0] = r; myvivi_cur_bars[k][1] = g; myvivi_cur_bars[k][2] = b; } } } static void myvivi_gen_twopix(unsigned char *buf, int colorpos) { unsigned char r_y, g_u, b_v; unsigned char *p; int color; r_y = myvivi_cur_bars[colorpos][0]; /* R or precalculated Y */ g_u = myvivi_cur_bars[colorpos][1]; /* G or precalculated U */ b_v = myvivi_cur_bars[colorpos][2]; /* B or precalculated V */ for (color = 0; color < 4; color++) { p = buf + color; switch (myvivi_format.fmt.pix.pixelformat) { case V4L2_PIX_FMT_YUYV: switch (color) { case 0: case 2: *p = r_y; break; case 1: *p = g_u; break; case 3: *p = b_v; break; } break; case V4L2_PIX_FMT_UYVY: switch (color) { case 1: case 3: *p = r_y; break; case 0: *p = g_u; break; case 2: *p = b_v; break; } break; case V4L2_PIX_FMT_RGB565: switch (color) { case 0: case 2: *p = (g_u << 5) | b_v; break; case 1: case 3: *p = (r_y << 3) | (g_u >> 3); break; } break; case V4L2_PIX_FMT_RGB565X: switch (color) { case 0: case 2: *p = (r_y << 3) | (g_u >> 3); break; case 1: case 3: *p = (g_u << 5) | b_v; break; } break; case V4L2_PIX_FMT_RGB555: switch (color) { case 0: case 2: *p = (g_u << 5) | b_v; break; case 1: case 3: *p = (r_y << 2) | (g_u >> 3); break; } break; case V4L2_PIX_FMT_RGB555X: switch (color) { case 0: case 2: *p = (r_y << 2) | (g_u >> 3); break; case 1: case 3: *p = (g_u << 5) | b_v; break; } break; } } } static void myvivi_gen_line(char *basep, int inipos, int wmax, int hmax, int line, int count) { int w; int pos = inipos; /* We will just duplicate the second pixel at the packet */ wmax /= 2; /* Generate a standard color bar pattern */ for (w = 0; w < wmax; w++) { int colorpos = ((w + count) * 8/(wmax + 1)) % 8; myvivi_gen_twopix(basep + pos, colorpos); pos += 4; /* only 16 bpp supported for now */ } return; } static void myvivi_fillbuff(struct videobuf_buffer *vb) { int h , pos = 0; int hmax = vb->height; int wmax = vb->width; char *tmpbuf; void *vbuf = videobuf_to_vmalloc(vb); static int mv_count = 0; if (!vbuf) return; tmpbuf = kmalloc(wmax * 2, GFP_ATOMIC); if (!tmpbuf) return; for (h = 0; h < hmax; h++) { myvivi_gen_line(tmpbuf, 0, wmax, hmax, h, mv_count); memcpy(vbuf + pos, tmpbuf, wmax * 2); pos += wmax*2; } mv_count++; kfree(tmpbuf); }
==========================Makefile=======================================
KERN_DIR = /usr/src/linux-headers-2.6.31-14-generic all: make -C $(KERN_DIR) M=`pwd` modules clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf modules.order obj-m += myvivi.o
posted on 2015-11-21 14:48 sheldon_blogs 阅读(3267) 评论(1) 编辑 收藏 举报