本文代码参考 drivers/media/video/uvc !!!
主要工作如下:
工作1 填充 .vidioc_querycap
cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
工作2 填充 .vidioc_enum_fmt_vid_cap,枚举支持那些格式。
由前面 VS打印的 自定义描述符可知,该摄像头只支持 VS_FORMAT_UNCOMPRESSED
(不压缩的原始的视频格式)一种格式,那么它的原始的视频格式是什么呢? 查看 GUID,对比uvc驱动中的 uvc_fmts 数组确定它的格式为YUYV。
strcpy(f->description, "4:2:2, packed, YUYV");
f->pixelformat = V4L2_PIX_FMT_YUYV;
工作3 填充 .vidioc_g_fmt_vid_cap 返回当前所使用的格式
3.1 定义 static struct v4l2_format myuvc_format; 存储格式信息
3.2 memcpy(f, &myuvc_format, sizeof(myuvc_format));将格式信息拷贝给用户空间
工作4 填充 .vidioc_try_fmt_vid_cap 测试驱动程序是否支持某种格式, 强制设置该格式
由前面 vs的自定义描述符可知该摄像头支持哪几种分辨率,也可以通过 lsusb -v -d 0x1e4e 命令查看该摄像头支持哪些分辨率。可知该摄像头支持5中分辨率
4.1 定义分辨率结构体 struct frame_desc
4.2 定义分辨率结构体数组 static struct frame_desc frames[];
4.3 将该摄像头分辨率强制设置为 352*288
4.4 设置每个像素占用的字节数以及计算一帧图像的大小,其中每个像素占用的字节数 可以在描述符中查到。
工作5 填充 .vidioc_s_fmt_vid_cap ,将该设备设置为 某一种格式
工作6 填充 .vidioc_reqbufs,即分配缓存
6.1 定义 struct myuvc_queue 即UVC缓存队列
6.2 定义 struct myuvc_buffer 即UVC缓存结构体,并且包含于 myuvc_queue
6.2.1 定义struct v4l2_buffer buf;
6.2.2 wait_queue_head_t wait; /* APP要读某个缓冲区,如果无数据,在此休眠 */
...
6.3 定义 struct list_head mainqueue,即供APP消费用队列,并且包含于 myuvc_queue
6.4 定义 struct list_head irqqueue,即供底层驱动生产用,并且包含于 myuvc_queue
6.5 void *mem;代表分配缓存地址,包含于 myuvc_queue
6.6 分配缓存 mem = vmalloc_32(nbuffers * bufsize);这些缓存是一次性作为一个整体来分配的
6.7 初始化该缓存 memset(&myuvc_queue, 0, sizeof(myuvc_queue));
6.8 初始化 mainqueue、irqqueue队列
INIT_LIST_HEAD(&myuvc_queue.mainqueue);
INIT_LIST_HEAD(&myuvc_queue.irqqueue);
6.9 设置该大块缓存中的每一个单元小块缓存信息。如大小,格式等信息。
6.9.1 length = myuvc_format.fmt.pix.sizeimage;
6.9.2 offset = i * bufsize
6.9.3 type = V4L2_BUF_TYPE_VIDEO_CAPTURE
6.9.4 init_waitqueue_head(&myuvc_queue.buffer[i].wait);此时没有数据,休眠
工作7 填充 .vidioc_querybuf 即查询缓存状态, 比如地址信息(APP可以用mmap进行映射)
7.1 填充 .vidioc_qbuf 即把缓冲区放入队列,将缓冲区放入放入2个队列。
7.1.1 将缓冲区放入队列1,队列1供APP使用 ,当缓冲区没有数据时,放入mainqueue队列,当缓冲区有数据时, APP从mainqueue队列中取出。
list_add_tail(&buf->stream, &myuvc_queue.mainqueue);
7.1.2 将缓冲区放入队列2,队列2供产生数据的函数使用,当采集到数据时,从irqqueue队列中取出第1个缓冲区,存入数据。
list_add_tail(&buf->queue, &myuvc_queue.irqqueue);
工作8 填充 .vidioc_dqbuf 即 APP通过poll/select确定有数据后, 把缓存从队列中取出来。APP发现数据就绪后, 从mainqueue里取出这个buffer
关于队列操作
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* 参考 drivers/media/video/uvc !!!*/
struct frame_desc {
int width;
int height;
};
/* 参考uvc_video_queue定义一些结构体 */
struct myuvc_buffer {
struct v4l2_buffer buf;
int state;
int vma_use_count; /* 表示是否已经被mmap */
wait_queue_head_t wait; /* APP要读某个缓冲区,如果无数据,在此休眠 */
struct list_head stream;
struct list_head irq;
};
struct myuvc_queue {
void *mem;
int count;
int buf_size;
struct myuvc_buffer buffer[32];
struct list_head mainqueue; /* 供APP消费用 */
struct list_head irqqueue; /* 供底层驱动生产用 */
};
static struct myuvc_queue myuvc_queue;
static struct video_device *myuvc_vdev;
static struct v4l2_format myuvc_format;
static struct frame_desc frames[] = {{640, 480}, {352, 288}, {320, 240}, {176, 144}, {160, 120}};
static int frame_idx = 1;//指定为 352, 288
static int bBitsPerPixel = 16; /* lsusb -v -d 0x1e4e: "bBitsPerPixel" */
/* A2 参考 uvc_v4l2_do_ioctl */
static int myuvc_vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *cap)
{
memset(cap, 0, sizeof *cap);
strcpy(cap->driver, "myuvc");
strcpy(cap->card, "myuvc");
cap->version = 1;
/* V4L2_CAP_VIDEO_CAPTURE 代表是视频捕获设备。 V4L2_CAP_STREAMING代表我们不是用read、write来读取视频设备,而是用IOctl等接口*/
cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
break;
return 0;
}
/* A3 列举支持哪种格式
* 参考: uvc_fmts 数组
*/
static int myuvc_vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_fmtdesc *f)
{
/*
人工查看描述符可知我们用的摄像头只支持1种格式
查看前面 Linux摄像头UVC驱动第二篇--描述符分析 文章,查看VS打印的 自定义描述符可知,该摄像头只支持 VS_FORMAT_UNCOMPRESSED (不压缩的原始的视频格式)
一种格式,那么它的原始的视频格式是什么呢? 查看 GUID,确定它的格式。
*/
if (f->index >= 1)
return -EINVAL;
/* 支持什么格式呢?
* 查看VideoStreaming Interface的描述符,
* 得到GUID为"59 55 59 32 00 00 10 00 80 00 00 aa 00 38 9b 71"
*/
strcpy(f->description, "4:2:2, packed, YUYV");//格式名称 YUYV 的十六机制 --> 59 55 59 32 ... 即GUID
f->pixelformat = V4L2_PIX_FMT_YUYV; //格式宏
return 0;
}
/* A4 返回当前所使用的格式 */
static int myuvc_vidioc_g_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
memcpy(f, &myuvc_format, sizeof(myuvc_format));
return (0);
}
/* A5 测试驱动程序是否支持某种格式, 强制设置该格式
* 参考: uvc_v4l2_try_format
* myvivi_vidioc_try_fmt_vid_cap
*/
static int myuvc_vidioc_try_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
{
return -EINVAL;
}
if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV)
return -EINVAL;
/* 调整format的width, height,
* 计算bytesperline, sizeimage
*/
/* 人工查看描述符, 确定支持哪几种分辨率,指定为 352, 288。查看自定义描述符打印 FRAME相关 */
f->fmt.pix.width = frames[frame_idx].width;
f->fmt.pix.height = frames[frame_idx].height;
/*
每一个像素占用多少个字节,也可以在自定义描述符打印中找出,即 bBitsPerPixel,也可以 lsusb -v -d 0x1e4e: "bBitsPerPixel" 得到
*/
f->fmt.pix.bytesperline =
(f->fmt.pix.width * bBitsPerPixel) >> 3;
f->fmt.pix.sizeimage =
f->fmt.pix.height * f->fmt.pix.bytesperline;
return 0;
}
/* A6 参考 myvivi_vidioc_s_fmt_vid_cap */
static int myuvc_vidioc_s_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
int ret = myuvc_vidioc_try_fmt_vid_cap(file, NULL, f);
if (ret < 0)
return ret;
memcpy(&myuvc_format, f, sizeof(myuvc_format));
return 0;
}
static int myuvc_free_buffers(void)
{
kfree(myuvc_queue.mem);
memset(&myuvc_queue, 0, sizeof(myuvc_queue));
return 0;
}
/* A7 APP调用该ioctl让驱动程序分配若干个缓存, APP将从这些缓存中读到视频数据
* 参考: uvc_alloc_buffers
*/
static int myuvc_vidioc_reqbufs(struct file *file, void *priv,
struct v4l2_requestbuffers *p)
{
int nbuffers = p->count;
int bufsize = PAGE_ALIGN(myuvc_format.fmt.pix.sizeimage);
unsigned int i;
void *mem = NULL;
int ret;
if ((ret = myuvc_free_buffers()) < 0)
goto done;
/* Bail out if no buffers should be allocated. */
if (nbuffers == 0)
goto done;
/* Decrement the number of buffers until allocation succeeds. */
for (; nbuffers > 0; --nbuffers) {
mem = vmalloc_32(nbuffers * bufsize);
if (mem != NULL)
break;
}
if (mem == NULL) {
ret = -ENOMEM;
goto done;
}
/* 这些缓存是一次性作为一个整体来分配的 */
memset(&myuvc_queue, 0, sizeof(myuvc_queue));
INIT_LIST_HEAD(&myuvc_queue.mainqueue);
INIT_LIST_HEAD(&myuvc_queue.irqqueue);
for (i = 0; i < nbuffers; ++i) {
myuvc_queue.buffer[i].buf.index = i;
myuvc_queue.buffer[i].buf.m.offset = i * bufsize;
myuvc_queue.buffer[i].buf.length = myuvc_format.fmt.pix.sizeimage;
myuvc_queue.buffer[i].buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
myuvc_queue.buffer[i].buf.sequence = 0;
myuvc_queue.buffer[i].buf.field = V4L2_FIELD_NONE;
myuvc_queue.buffer[i].buf.memory = V4L2_MEMORY_MMAP;
myuvc_queue.buffer[i].buf.flags = 0;
myuvc_queue.buffer[i].state = VIDEOBUF_IDLE;
init_waitqueue_head(&myuvc_queue.buffer[i].wait);
}
myuvc_queue.mem = mem;
myuvc_queue.count = nbuffers;
myuvc_queue.buf_size = bufsize;
ret = nbuffers;
done:
return ret;
}
/* A8 查询缓存状态, 比如地址信息(APP可以用mmap进行映射)
* 参考 uvc_query_buffer
*/
static int myuvc_vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *v4l2_buf)
{
int ret = 0;
if (v4l2_buf->index >= myuvc_queue.count) {
ret = -EINVAL;
goto done;
}
memcpy(v4l2_buf, myuvc_queue.buffer[v4l2_buf->index].buf, sizeof(*v4l2_buf));
/* 更新flags */
if (myuvc_queue.buffer[v4l2_buf->index].vma_use_count)
v4l2_buf->flags |= V4L2_BUF_FLAG_MAPPED;
switch (myuvc_queue.buffer[v4l2_buf->index].state) {
case VIDEOBUF_ERROR:
case VIDEOBUF_DONE:
v4l2_buf->flags |= V4L2_BUF_FLAG_DONE;
break;
case VIDEOBUF_QUEUED:
case VIDEOBUF_ACTIVE:
v4l2_buf->flags |= V4L2_BUF_FLAG_QUEUED;
break;
case VIDEOBUF_IDLE:
default:
break;
}
done:
return ret;
}
/* A10 把缓冲区放入队列, 底层的硬件操作函数将会把数据放入这个队列的缓存
* 参考: uvc_queue_buffer
*/
static int myuvc_vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *v4l2_buf)
{
struct myuvc_buffer *buf = &myuvc_queue.buffer[v4l2_buf->index];
/* 1. 修改状态 */
buf->state = VIDEOBUF_QUEUED;
v4l2_buf->bytesused = 0;
/* 2. 放入2个队列 */
/* 队列1: 供APP使用
* 当缓冲区没有数据时,放入mainqueue队列
* 当缓冲区有数据时, APP从mainqueue队列中取出
*/
list_add_tail(&buf->stream, &myuvc_queue.mainqueue);
/* 队列2: 供产生数据的函数使用
* 当采集到数据时,从irqqueue队列中取出第1个缓冲区,存入数据
*/
list_add_tail(&buf->queue, &myuvc_queue.irqqueue);
return 0;
}
/* A11 启动传输
* 参考: uvc_video_enable(video, 1)
*/
static int myuvc_vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
{
/* 1. 向USB摄像头设置参数 */
/* 2. 分配设置URB */
/* 3. 提交URB以接收数据 */
return 0;
}
/* A13 APP通过poll/select确定有数据后, 把缓存从队列中取出来
* 参考: uvc_dequeue_buffer
*/
static int myuvc_vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *v4l2_buf)
{
/* APP发现数据就绪后, 从mainqueue里取出这个buffer */
struct myuvc_buffer *buf = &myuvc_queue.buffer[v4l2_buf->index];
list_del(&buf->stream);
return 0;
}
/*
* A14 之前已经通过mmap映射了缓存, APP可以直接读数据
* A15 再次调用myuvc_vidioc_qbuf把缓存放入队列
* A16 poll...
*/
/* A17 停止 */
static int myuvc_vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i)
{
return 0;
}
static const struct v4l2_ioctl_ops myuvc_ioctl_ops = {
// 表示它是一个摄像头设备
.vidioc_querycap = myuvc_vidioc_querycap,
/* 用于列举、获得、测试、设置摄像头的数据的格式 */
.vidioc_enum_fmt_vid_cap = myuvc_vidioc_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap = myuvc_vidioc_g_fmt_vid_cap,
.vidioc_try_fmt_vid_cap = myuvc_vidioc_try_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = myuvc_vidioc_s_fmt_vid_cap,
/* 缓冲区操作: 申请/查询/放入队列/取出队列 */
.vidioc_reqbufs = myuvc_vidioc_reqbufs,
.vidioc_querybuf = myuvc_vidioc_querybuf,
.vidioc_qbuf = myuvc_vidioc_qbuf,
.vidioc_dqbuf = myuvc_vidioc_dqbuf,
// 启动/停止
.vidioc_streamon = myuvc_vidioc_streamon,
.vidioc_streamoff = myuvc_vidioc_streamoff,
};
/* A1 */
static int myuvc_open(struct file *file)
{
return 0;
}
/* A9 把缓存映射到APP的空间,以后APP就可以直接操作这块缓存 */
static int myuvc_mmap(struct file *file, struct vm_area_struct *vma)
{
return 0;
}
/* A12 APP调用POLL/select来确定缓存是否就绪(有数据) */
static unsigned int myuvc_poll(struct file *file, struct poll_table_struct *wait)
{
return 0;
}
/* A18 关闭 */
static int myuvc_close(struct file *file)
{
return 0;
}
static const struct v4l2_file_operations myuvc_fops = {
.owner = THIS_MODULE,
.open = myuvc_open,
.release = myuvc_close,
.mmap = myuvc_mmap,
.ioctl = video_ioctl2, /* V4L2 ioctl handler */
.poll = myuvc_poll,
};
static void myuvc_release(struct video_device *vdev)
{
}
static int myuvc_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
static int cnt = 0;
struct usb_device *dev = interface_to_usbdev(intf);
struct usb_device_descriptor *descriptor = &dev->descriptor;
struct usb_host_config *hostconfig;
struct usb_config_descriptor *config;
struct usb_interface_assoc_descriptor *assoc_desc;
struct usb_interface_descriptor *interface;
struct usb_endpoint_descriptor *endpoint;
int i, j, k, l, m;
unsigned char *buffer;
int buflen;
int desc_len;
int desc_cnt;
printk("myuvc_probe : cnt = %d\n", cnt++);
if (cnt == 2)
{
/* 1. 分配一个video_device结构体 */
myuvc_vdev = video_device_alloc();
/* 2. 设置 */
/* 2.1 */
myuvc_vdev->release = myuvc_release;
/* 2.2 */
myuvc_vdev->fops = &myuvc_fops;
/* 2.3 */
myuvc_vdev->ioctl_ops = &myuvc_ioctl_ops;
/* 3. 注册 */
video_register_device(myuvc_vdev, VFL_TYPE_GRABBER, -1);
}
return 0;
}
static void myuvc_disconnect(struct usb_interface *intf)
{
static int cnt = 0;
printk("myuvc_disconnect : cnt = %d\n", cnt++);
if (cnt == 2)
{
video_unregister_device(myuvc_vdev);
video_device_release(myuvc_vdev);
}
}
static struct usb_device_id myuvc_ids[] = {
/* Generic USB Video Class */
{ USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, 0) }, /* VideoControl Interface */
{ USB_INTERFACE_INFO(USB_CLASS_VIDEO, 2, 0) }, /* VideoStreaming Interface */
{}
};
/* 1. 分配usb_driver */
/* 2. 设置 */
static struct usb_driver myuvc_driver = {
.name = "myuvc",
.probe = myuvc_probe,
.disconnect = myuvc_disconnect,
.id_table = myuvc_ids,
};
static int myuvc_init(void)
{
/* 3. 注册 */
usb_register(&myuvc_driver);
return 0;
}
static void myuvc_exit(void)
{
usb_deregister(&myuvc_driver);
}
module_init(myuvc_init);
module_exit(myuvc_exit);
MODULE_LICENSE("GPL");