/* 参考 drivers/media/video/uvc下的一系列文件 */
1、12个ioctl
(1)查询属性,是否为摄像头设备
static int myuvc_vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *cap)
{
memset(cap, 0, sizeof *cap);///v4l2_capability是属性结构体,清零
strcpy(cap->driver, "myuvc");//结构体填充, driver 域需要和 struct video_device 中的 name 匹配
strcpy(cap->card, "myuvc");
cap->version = 1; //驱动版本号
cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;//设置属性(视频捕捉设备,streaming表示用ioctl来读写视频而不是read/write函数)
read和write应该是写入和读出数据的,应该是作为单纯的数据交换的方式来处理。而ioctl则是控制read和write一些选项的。比如:你做了一个通用的读写IO端口的驱动模块。read和write是从端口读写数据的,但是更改读写的端口,这个操作应该如何处理呢?显然用ioctl来实现比较合理。比如你的read和write是可以阻塞的,或者不能阻塞的,或者对设备文件的读写是可以并发的,或者是不可以并发的,这些都可以写成可以用ioctl来配置的情况。后面为了可以用ioctl来实现模块不同的IO特点。
return 0;
}
- struct v4l2_capability
- {
- u8 driver[16]; // 驱动名字
- u8 card[32]; // 设备名字
- u8 bus_info[32]; // 设备在系统中的位置
- u32 version; // 驱动版本号
- u32 capabilities; // 设备支持的操作
- u32 reserved[4]; // 保留字段
- };
如:driver:uvcvideo,card:UVC Camera (046d:0825),bus_info:usb-0000:02:03.0-1,version:0x100,capabilities:0x4000001
#define V4L2_CAP_VIDEO_CAPTURE0x00000001 /* Is a video capture device */
#define V4L2_CAP_VIDEO_OUTPUT 0x00000002 /* Is a video output device */
#define V4L2_CAP_VIDEO_OVERLAY 0x00000004 /* Can do video overlay */
#define V4L2_CAP_VBI_CAPTURE 0x00000010 /* Is a raw VBI capture device */
#define V4L2_CAP_STREAMING 0x04000000 /* streaming I/O ioctls */
(2)列举摄像头数据的格式(参考Uvc_v4l2.c)
static int myuvc_vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_fmtdesc *f)
{
/* 人工查看描述符可知我们用的摄像头只支持1种格式 */
if (f->index >= 1)//v4l2_fmtdesc结构体的index成员
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");//格式描述
f->pixelformat = V4L2_PIX_FMT_YUYV; //像素格式为YUYV
return 0;
}
- struct v4l2_fmtdesc
- {
- u32 index; // 要查询的格式序号,应用程序设置
- enum v4l2_buf_type type; // 帧类型,应用程序设置
- u32 flags; // 是否为压缩格式
- u8 description[32]; // 格式名称
- u32 pixelformat; // 格式
- u32 reserved[4]; // 保留
- };
,uvc驱动会分析描述符,把格式存在数组format里面。数组的初始化:
根据guid来确定格式
需要看数组uvc_fmts(有格式的名字,guid,宏(表示哪种格式))
这里YUY2的十六进制是59 55 59 32,跟之前的guid一样
(3)返回当前所使用的格式
//定义1个全局变量,把设置的格式存储返回
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);
}
- struct v4l2_format {
- enum v4l2_buf_type type;
- union {
- struct v4l2_pix_format pix; /* V4L2_BUF_TYPE_VIDEO_CAPTURE */
- struct v4l2_window win; /* V4L2_BUF_TYPE_VIDEO_OVERLAY */
- struct v4l2_vbi_format vbi; /* V4L2_BUF_TYPE_VBI_CAPTURE */
- struct v4l2_sliced_vbi_format sliced; /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */
- __u8 raw_data[200]; /* user-defined */
- } fmt;
- };
- 其中
- enum v4l2_buf_type {
- V4L2_BUF_TYPE_VIDEO_CAPTURE = 1,
- V4L2_BUF_TYPE_VIDEO_OUTPUT = 2,
- V4L2_BUF_TYPE_VIDEO_OVERLAY = 3,
- ...
- V4L2_BUF_TYPE_PRIVATE = 0x80,
- };
- struct v4l2_pix_format {
- __u32 width;
- __u32 height;
- __u32 pixelformat;
- enum v4l2_field field;
- __u32 bytesperline; /* for padding, zero if unused */
- __u32 sizeimage;
- enum v4l2_colorspace colorspace;
- __u32 priv; /* private data, depends on pixelformat */
- };
(4)测试驱动程序是否支持某种格式(类型、像素格式)
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)//如果像素格式不是YUYV,就输出错误
return -EINVAL;
/* 调整format的width, height, 格式的分辨率
* 计算bytesperline, sizeimage
*/
/* 人工查看描述符, 确定支持哪几种分辨率 *//可以把多种分辨率设置成结构体数组/
f->fmt.pix.width = frames[frame_idx].width;//某种分辨率的宽度
f->fmt.pix.height = frames[frame_idx].height;//某种分辨率的高度
f->fmt.pix.bytesperline =
(f->fmt.pix.width * bBitsPerPixel) >> 3;//每行的字节数,每个像素用多少位表示,可以查看描述符(这个参数主要是考虑到字节对齐)
-v:显示USB设备的详细信息,d<厂商:产品>:仅显示指定厂商和产品编号的设备
f->fmt.pix.sizeimage =
f->fmt.pix.height * f->fmt.pix.bytesperline;//图片大小
return 0;
}
(5)设置该格式
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);//测试一下是否支持此格式,格式强制设置为f
if (ret < 0)
return ret;
//void *memcpy(void *dest, const void *src, size_t n);
从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中
memcpy(&myuvc_format, f, sizeof(myuvc_format));//把格式赋给myuvc_format
return 0;
}
(6)申请缓存
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;
- struct v4l2_requestbuffers {
- __u32 count;
- enum v4l2_buf_type type;
- enum v4l2_memory memory;
- __u32 reserved[2];
- };
- 其中
- enum v4l2_memory {
- V4L2_MEMORY_MMAP = 1,
- V4L2_MEMORY_USERPTR = 2,
- V4L2_MEMORY_OVERLAY = 3,
- };
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); //分配缓存的总大小,缓存数*缓存的大小,mem是整块内存起始地址,通常32位Linux内核地址空间划分0~3G为用户空间,3~4G为内核空间
if (mem != NULL)//如果
break;
}
if (mem == NULL) {
ret = -ENOMEM;
goto done;
}
/* 这些缓存是一次性作为一个整体来分配的 */
memset(&myuvc_queue, 0, sizeof(myuvc_queue)); //对存储缓存的队列进行操作
//放入2个队列
INIT_LIST_HEAD(&myuvc_queue.mainqueue);
INIT_LIST_HEAD(&myuvc_queue.irqqueue);struct list_head mainqueue; /* 供APP消费用 */struct list_head irqqueue; /* 供底层驱动生产用 */
//针对每一个缓存进行操作
for (i = 0; i < nbuffers; ++i) {
myuvc_queue.buffer[i].buf.index = i;//第几个buffer
myuvc_queue.buffer[i].buf.m.offset = i * bufsize;//buffer的偏移值
myuvc_queue.buffer[i].buf.length = myuvc_format.fmt.pix.sizeimage;//buffer的长度(图像的大小)
myuvc_queue.buffer[i].buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//buffer的类型(视频捕捉类)
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;//buffer的状态
init_waitqueue_head(&myuvc_queue.buffer[i].wait);//初始化等待队列,最后的视频数据是放到一个一个缓冲区,应用程序在读某个缓冲区的时候。有可能因为缓冲区还没有数据就会休眠。缓冲区里面应该有一个队列(用来存储要读这个缓冲区的进程)
参考http://blog.csdn.net/lanmanck/article/details/4770103
就是要求写资料到buffer 的 process放到 wq 这个 wait_queue 里
}
myuvc_queue.mem = mem;//总buffer的地址(队列里面地址)
myuvc_queue.count = nbuffers;//队列里缓冲区的个数
myuvc_queue.buf_size = bufsize;//每个缓冲区的大小,页对齐后的大小
ret = nbuffers;
done:
return ret;
}
- truct v4l2_buffer
- {
- u32 index; //buffer 序号
- enum v4l2_buf_type type; //buffer 类型
- u32 byteused; //buffer 中已使用的字节数
- u32 flags; // 区分是MMAP 还是USERPTR
- enum v4l2_field field;
- struct timeval timestamp; // 获取第一个字节时的系统时间
- struct v4l2_timecode timecode;
- u32 sequence; // 队列中的序号
- enum v4l2_memory memory; //IO 方式,被应用程序设置
- union m{
- u32 offset; // 缓冲帧地址,只对MMAP 有效
- unsigned long userptr;
- };
- u32 length; // 缓冲帧长度
- u32 input;
- u32 reserved;
- };
(7)查询缓存
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));//拷贝buffer
/* 更新flags */
if (myuvc_queue.buffer[v4l2_buf->index].vma_use_count)//vma_use_count如果被mmap到用户空间就加1,就设置标志
v4l2_buf->flags |= V4L2_BUF_FLAG_MAPPED; //mmap内存映射函数把内核空间内存映射到用户空间
flags 为缓存当前状态(常见值有 V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_DONE,分别代表当前缓存已经映射、缓存可以采集数据、缓存可以提取数据)
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;
}
(8)把缓存放入队列
static int myuvc_vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *v4l2_buf)
{
struct myuvc_buffer *buf;
/* 0. APP传入的v4l2_buf可能有问题, 要做判断 *,类型判断/
if (v4l2_buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE ||
v4l2_buf->memory != V4L2_MEMORY_MMAP) {
return -EINVAL;
}
//索引值大于我们设定的索引值
if (v4l2_buf->index >= myuvc_queue.count) {
return -EINVAL;
}
buf = &myuvc_queue.buffer[v4l2_buf->index];
if (buf->state != VIDEOBUF_IDLE) {
return -EINVAL;
}
/* 1. 修改状态 */(入列)
buf->state = VIDEOBUF_QUEUED;
buf->buf.bytesused = 0;
把buf放入两个队列(注释参考后面)
/* 2. 放入2个队列 */
/* 队列1: 供APP使用
* 当缓冲区没有数据时,放入mainqueue队列
* 当缓冲区有数据时, APP从mainqueue队列中取出
*/
list_add_tail(&buf->stream, &myuvc_queue.mainqueue); struct list_head stream;
struct list_head irq;
关于list_add_tail函数参考http://blog.csdn.net/qingkongyeyue/article/details/76089257
/* 队列2: 供产生数据的函数使用
* 当采集到数据时,从irqqueue队列中取出第1个缓冲区,存入数据
*/
list_add_tail(&buf->irq, &myuvc_queue.irqqueue);
return 0;
}
(9)把缓冲区取出队列
static int myuvc_vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *v4l2_buf)
{
/* APP发现数据就绪后, 从mainqueue里取出这个buffer */
struct myuvc_buffer *buf;
int ret = 0;
if (list_empty(&myuvc_queue.mainqueue)) {//队列非空
ret = -EINVAL;
goto done;
}
buf = list_first_entry(&myuvc_queue.mainqueue, struct myuvc_buffer, stream);//取出第1个buffer
switch (buf->state) {
case VIDEOBUF_ERROR:
ret = -EIO;
case VIDEOBUF_DONE:
buf->state = VIDEOBUF_IDLE;
break;
case VIDEOBUF_IDLE:
case VIDEOBUF_QUEUED:
case VIDEOBUF_ACTIVE:
default:
ret = -EINVAL;
goto done;
}
list_del(&buf->stream);//删除第1个buf
done:
return ret;
}
#################################################
假设我们有两个队列,两个buf
产生了一个数据后,驱动程序会从irqqueqe队列里取出第一个buf1,把数据放到里面去。
app取一个数据,从队列mainqueue里取出一个buf1
APP处理完后,会把被处理的buf放入队列尾部