/* 参考 drivers/media/video/uvc下的一系列文件 */
(1)查询属性,是否为摄像头设备
/* A2 确定是不是视频设备*/
/* 参考uvc_v4l2.c文件的uvc_v4l2_do_ioctl函数 */
static int myuvc_vidioc_querycap(struct file *file, void *fh,
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;//驱动版本号
//设置属性(视频捕捉设备,streaming表示用ioctl来读写视频而不是read/write函数)
cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
return 0;
}
v4l2_capability 结构体:
struct v4l2_capability
{
u8 driver[16]; // 驱动名字
u8 card[32]; // 设备名字
u8 bus_info[32]; // 设备在系统中的位置
u32 version; // 驱动版本号
u32 capabilities; // 设备支持的操作
u32 reserved[4]; // 保留字段
};
#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种格式(format),而一种格式下有多种frame */
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]; // 保留
};
(3)返回当前所使用的格式
static struct v4l2_format myuvc_format;//定义1个全局变量,把设置的格式存储返回
/* 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;
}
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 bBitsPerPixel = 16; /* lsusb -v -d 0x1e4e: "bBitsPerPixel" */
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;//每行的字节数,每个像素用多少位表示,可以查看描述符(这个参数主要是考虑到字节对齐)
f->fmt.pix.sizeimage =
f->fmt.pix.height * f->fmt.pix.bytesperline;
return 0;
}
**(5)设置该格式**
/* A6 尝试支持莫种格式之后就设置这种格式 */
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;
//从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中
memcpy(&myuvc_format, f, sizeof(myuvc_format));//把格式赋给myuvc_format
return 0;
}
struct list_head mainqueue; /* 供APP消费用 */
struct list_head irqqueue; /* 供底层驱动生产用 */
/* A7 APP调用该ioctl让驱动程序分配若干缓冲区,APP将从这些缓存中读取到视频数据 */
/* 参考uvc_v4l2.c中的 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是整块内存起始地址,通常32位Linux内核地址空间划分0~3G为用户空间,3~4G为内核空间
mem = vmalloc_32(nbuffers * bufsize);
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);
//针对每一个缓存进行操作
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;
init_waitqueue_head(&myuvc_queue.buffer[i].wait);//初始化等待队列,最后的视频数据是放到一个一个缓冲区,应用程序在读某个缓冲区的时候。有可能因为缓冲区还没有数据就会休眠。缓冲区里面应该有一个队列(用来存储要读这个缓冲区的进程)
}
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)查询缓存
/* A8 查询缓存状态,比如: 地址信息(APP可以用mmap进行地址映射) */\
/* 参考uvc_queue_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], sizeof(*v4l2_buf));
/* 更新flag
* enum videobuf_state {
* VIDEOBUF_NEEDS_INIT = 0,
* VIDEOBUF_PREPARED = 1,
* VIDEOBUF_QUEUED = 2,
* VIDEOBUF_ACTIVE = 3,
* VIDEOBUF_DONE = 4,
* VIDEOBUF_ERROR = 5,
* VIDEOBUF_IDLE = 6,
* };
flags 为缓存当前状态(常见值有 V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_DONE,分别代表当前缓存已经映射、缓存可以采集数据、缓存可以提取数据)
*/
if (myuvc_queue.buffer[v4l2_buf->index].vma_use_count)//vma_use_count如果被mmap到用户空间就加1,就设置标志
v4l2_buf->flags |= V4L2_BUF_FLAG_MAPPED; //mmap内存映射函数把内核空间内存映射到用户空间
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)把缓存放入队列
/* A10 将申请的缓冲区放入队列,底层的硬件操作函数将会把数据放入到这个队列的缓冲区中 */
/* 参考uvc_queue_buffer函数 */
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;
/* 2.放入两个队列 */
/* 队列1: 供APP使用
* 当缓冲区没有数据时,放入mianqueue队列
* 当缓冲区有数据时,APP从mainqueue队列中取出数据
*/
list_add_tail(&buf->stream, &myuvc_queue.mainqueue);
/* 队列2: 供产生数据的函数使用
* 当采集到数据时,从irqqueue队列中取出第一个缓冲区,存入数据
*/
list_add_tail(&buf->irq, &myuvc_queue.irqqueue);
return 0;
}
(9)把缓冲区取出队列
/* 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;
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放入队列尾部