本文代碼參考 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");