Linux攝像頭UVC驅動第四篇--填充數據傳輸驅動簡單ioctl()

本文代碼參考 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 <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/videodev2.h>
#include <linux/vmalloc.h>
#include <linux/wait.h>
#include <asm/atomic.h>
#include <asm/unaligned.h>

#include <media/v4l2-common.h>
#include <media/v4l2-ioctl.h>

/* 參考 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");
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章