USB摄像头驱动之实现数据传输2_实现简单函数

/* 参考 drivers/media/video/uvc下的一系列文件 */

1、12个ioctl

(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;
}

(6)申请缓存
USB摄像头驱动之实现数据传输2_实现简单函数_第1张图片

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,把数据放到里面去。
USB摄像头驱动之实现数据传输2_实现简单函数_第2张图片
app取一个数据,从队列mainqueue里取出一个buf1
在这里插入图片描述
APP处理完后,会把被处理的buf放入队列尾部
USB摄像头驱动之实现数据传输2_实现简单函数_第3张图片

USB摄像头驱动之实现数据传输2_实现简单函数_第4张图片

你可能感兴趣的:(jz2440)