一、根据应用了解video驱动
一、通过分析应用程序的调用情况来了解驱动
xawtv是一个video的应用程序,能够驱动真实的摄像头与vivi。
在每个函数中都调用了大量的ioctrl函数,我们通过分析,将一些可有可无的ioctrl去掉,这样驱动就能更加简单,最终得到最简的驱动程序。(既只含有必要的ioctrl)。
以下为ioctl(区分必要与不必要,在v4l2_ioctl_ops中)
1.必要
.vidioc_querycap = cx25821_vidioc_querycap,
作用:cx25821_vidioc_querycap
const u32 cap_input = V4L2_CAP_VIDEO_CAPTURE |V4L2_CAP_READWRITE | V4L2_CAP_STREAMING;
const u32 cap_output = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_READWRITE;
cap->capabilities = cap_input | cap_output | V4L2_CAP_DEVICE_CAPS;
获取设备的属性(如:是否是摄像头,是否支持读写,等)
2.不必要
.vidioc_enum_input = cx25821_vidioc_enum_input,
.vidioc_g_input = cx25821_vidioc_g_input,
.vidioc_s_input = cx25821_vidioc_s_input,
作用:
用于选择输入源,一个video设备可以配置多个输入源
3.不必要(对于数字摄像头)
.vidioc_g_std = cx25821_vidioc_g_std,
.vidioc_s_std = cx25821_vidioc_s_std,
以及video_device中的
.tvnorms = CX25821_NORMS,
都可以去掉。其中.tvnorms服务于g_std于s_std。
4.必要
.vidioc_enum_fmt_vid_cap = cx25821_vidioc_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap = cx25821_vidioc_g_fmt_vid_cap,
.vidioc_try_fmt_vid_cap = cx25821_vidioc_try_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
用于列举,获取,测试,设置,摄像头所支持的格式。
5.必要
.vidioc_reqbufs = vb2_ioctl_reqbufs,
//.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
//.vidioc_create_bufs = vb2_ioctl_create_bufs,
.vidioc_querybuf = vb2_ioctl_querybuf,
.vidioc_qbuf = vb2_ioctl_qbuf,
.vidioc_dqbuf = vb2_ioctl_dqbuf,
缓存区操作。
6.不必要
int (*vidioc_queryctrl) (struct file *file, void *fh,
struct v4l2_queryctrl *a);
int (*vidioc_g_ctrl) (struct file *file, void *fh,
struct v4l2_control *a);
int (*vidioc_s_ctrl) (struct file *file, void *fh,
struct v4l2_control *a);
查询,获得,设置属性(如亮度,对比度)。
7.必要
.vidioc_streamon = vb2_ioctl_streamon,
.vidioc_streamoff = vb2_ioctl_streamoff,
启动,关闭,摄像头。
二、继续分析获取数据流程
1.应用程序调用ioctl的VIDIOC_REQBUFS来分配缓冲区(头部),最终是调用到v4l2_ioctl_ops中的vidioc_reqbufs
int vb2_ioctl_reqbufs(struct file *file, void *priv,
struct v4l2_requestbuffers *p)
{
struct video_device *vdev = video_devdata(file);
int res = __verify_memory_type(vdev->queue, p->memory, p->type);
if (res)
return res;
if (vb2_queue_is_busy(vdev, file))
return -EBUSY;
res = __reqbufs(vdev->queue, p);//利用到了,vdev中的queue与p
/* If count == 0, then the owner has released all buffers and he
is no longer owner of the queue. Otherwise we have a new owner. */
if (res == 0)
vdev->queue->owner = p->count ? file->private_data : NULL;
return res;
}
2.查询buf
int vb2_ioctl_querybuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
struct video_device *vdev = video_devdata(file);
/* No need to call vb2_queue_is_busy(), anyone can query buffers. */
return vb2_querybuf(vdev->queue, p);
}
int vb2_querybuf(struct vb2_queue *q, struct v4l2_buffer *b)
{
struct vb2_buffer *vb;
int ret;
if (b->type != q->type) {
dprintk(1, "wrong buffer type\n");
return -EINVAL;
}
if (b->index >= q->num_buffers) {
dprintk(1, "buffer index out of range\n");
return -EINVAL;
}
vb = q->bufs[b->index];
ret = __verify_planes_array(vb, b);
if (!ret)
__fill_v4l2_buffer(vb, b);
return ret;
}
query之后得到缓冲区的大小与一些信息,之后调用mmap才真正分配缓冲区内存。
3.将缓冲区入队
vb2_ioctl_qbuf
->list_add_tail(&vb->queued_entry, &q->queued_list);
4.streamon启动
5.select,当有数据来临是上报给应用(在队列的第一个节点上操作)
file_operations的poss调用到v4l2_file_operations中的poll
6.当有数据来临时,取出队列
.vidioc_dqbuf = vb2_ioctl_dqbuf,
三、如何写摄像头驱动程序
经过实验发现v4l2层完全没用,去掉也可以运行!!!
原因:
在Linux内核中,v4l2_device层和video_device层是一起工作的,v4l2_device层为video_device层提供了一些控制、配置等服务。但是并不是所有的摄像头驱动都需要使用v4l2_device层这个软件抽象层,有些摄像头驱动本身就比较简单,只需要少量的控制和配置服务,在这种情况下,只需要依靠video_device层就能够满足其基本的功能需求。因此,对于这些摄像头驱动,可以去掉v4l2_device层而直接使用video_device层来实现设备的运行。
流程:
1.分配
struct video_device *video_device_alloc(void)
2.设置
主要设置:
1. .fops = &video_fops,
static const struct v4l2_file_operations video_fops = {
.owner = THIS_MODULE,
.open = v4l2_fh_open,
.release = vb2_fop_release,
.read = vb2_fop_read,
.poll = vb2_fop_poll,
.unlocked_ioctl = video_ioctl2,
.mmap = vb2_fop_mmap,
};
2. .ioctl_ops = &video_ioctl_ops,(主要有11个)
static const struct v4l2_ioctl_ops video_ioctl_ops = {
.vidioc_querycap = cx25821_vidioc_querycap,
.vidioc_enum_fmt_vid_cap = cx25821_vidioc_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap = cx25821_vidioc_g_fmt_vid_cap,
.vidioc_try_fmt_vid_cap = cx25821_vidioc_try_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
.vidioc_reqbufs = vb2_ioctl_reqbufs,
.vidioc_querybuf = vb2_ioctl_querybuf,
.vidioc_qbuf = vb2_ioctl_qbuf,
.vidioc_dqbuf = vb2_ioctl_dqbuf,
.vidioc_streamon = vb2_ioctl_streamon,
.vidioc_streamoff = vb2_ioctl_streamoff,
.....
};
3.注册video_device
static inline int __must_check video_register_device(struct video_device *vdev,int type, int nr)
二、分析UVC源码:usb video class
一、自己如何写一个usb驱动程序(UVC:usb video class)
把前面的摄像头驱动中的东西全部放到usb_driver的probe中去。
uvc所在目录(4.1.15内核):\drivers\media\usb\uvc
1. uvc_driver.c
1.首先看入口函数
static int __init uvc_init(void)
{
int ret;
uvc_debugfs_init();
ret = usb_register(&uvc_driver.driver);
if (ret < 0) {
uvc_debugfs_cleanup();
return ret;
}
printk(KERN_INFO DRIVER_DESC " (" DRIVER_VERSION ")\n");
return 0;
}
struct uvc_driver uvc_driver = {
.driver = {
.name = "uvcvideo",
.probe = uvc_probe,//指向probe,里面有注册video
.disconnect = uvc_disconnect,
.suspend = uvc_suspend,
.resume = uvc_resume,
.reset_resume = uvc_reset_resume,
.id_table = uvc_ids,//支持的usb设备list
.supports_autosuspend = 1,
},
};
uvc_probe:
里面做的主要是分配,设置,注册video_device
UVC协议/硬件:通过下载官方手册
其中Control Interface是用于控制接口的,而Streaming是用于数据传输的!
在uvc中有两个概念:
1.unit:用于内部操作(比如PU就是process unit可以用于设置亮度,饱和度,等)
2.terminal:用于内外连接
有了uvc硬件相关的知识我们就可以分析:uvc从应用到驱动的调用过程了!
const struct v4l2_file_operations uvc_fops = {
.owner = THIS_MODULE,
.open = uvc_v4l2_open,//open
.release = uvc_v4l2_release,
.unlocked_ioctl = video_ioctl2,
#ifdef CONFIG_COMPAT
.compat_ioctl32 = uvc_v4l2_compat_ioctl32,
#endif
.read = uvc_v4l2_read,
.mmap = uvc_v4l2_mmap,
.poll = uvc_v4l2_poll,
#ifndef CONFIG_MMU
.get_unmapped_area = uvc_v4l2_get_unmapped_area,
#endif
};
uvc_probe
->uvc_register_video
->vdev->fops = &uvc_fops;
uvc_v4l2_open:
static int uvc_v4l2_open(struct file *file)
{
struct uvc_streaming *stream;
struct uvc_fh *handle;
int ret = 0;
uvc_trace(UVC_TRACE_CALLS, "uvc_v4l2_open\n");
stream = video_drvdata(file);
if (stream->dev->state & UVC_DEV_DISCONNECTED)
return -ENODEV;
ret = usb_autopm_get_interface(stream->dev->intf);
if (ret < 0)
return ret;
/* Create the device handle. */
handle = kzalloc(sizeof *handle, GFP_KERNEL);
if (handle == NULL) {
usb_autopm_put_interface(stream->dev->intf);
return -ENOMEM;
}
mutex_lock(&stream->dev->lock);
if (stream->dev->users == 0) {
ret = uvc_status_start(stream->dev, GFP_KERNEL);
if (ret < 0) {
mutex_unlock(&stream->dev->lock);
usb_autopm_put_interface(stream->dev->intf);
kfree(handle);
return ret;
}
}
stream->dev->users++;
mutex_unlock(&stream->dev->lock);
v4l2_fh_init(&handle->vfh, &stream->vdev);
v4l2_fh_add(&handle->vfh);
handle->chain = stream->chain;
handle->stream = stream;
handle->state = UVC_HANDLE_PASSIVE;
file->private_data = handle;
return 0;
}
video_ioctl2:
- vidioc_querycap
if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)//这里的stream->type就是上面的VS接口
cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
else
cap->device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;
这里的type应该是在usb摄像头被枚举时,分析其描述符时被设置的。
2 .vidioc_enum_fmt_vid_cap
static int uvc_ioctl_enum_fmt(struct uvc_streaming *stream,
struct v4l2_fmtdesc *fmt)
{
.....
if (fmt->type != stream->type || fmt->index >= stream->nformats)//说明VS中既有type也有format
return -EINVAL;
.....
format = &stream->format[fmt->index];//这个数组应该也是设备被枚举时分析描述符时被设置的
.....
}
- vidioc_g_fmt_vid_cap
format = stream->cur_format;//usb摄像头支持多种格式,每种格式有可分为多个分辨率(format:格式,frame:分辨率)
frame = stream->cur_frame;
.....
fmt->fmt.pix.pixelformat = format->fcc;
fmt->fmt.pix.width = frame->wWidth;
fmt->fmt.pix.height = frame->wHeight;
fmt->fmt.pix.field = V4L2_FIELD_NONE;
fmt->fmt.pix.bytesperline = format->bpp * frame->wWidth / 8;
fmt->fmt.pix.sizeimage = stream->ctrl.dwMaxVideoFrameSize;
fmt->fmt.pix.colorspace = format->colorspace;
fmt->fmt.pix.priv = 0;
4…vidioc_try_fmt_vid_cap
/* Check if the hardware supports the requested format, use the default
* format otherwise.
*///查看硬件是否支持用户要求的格式,复制使用默认格式
for (i = 0; i < stream->nformats; ++i) {
format = &stream->format[i];//这个数组是设备在枚举的时候,分析描述符的时候被设置的
if (format->fcc == fmt->fmt.pix.pixelformat)
break;
}
if (i == stream->nformats) {
format = stream->def_format;
fmt->fmt.pix.pixelformat = format->fcc;
}
/* Find the closest image size. The distance between image sizes is
* the size in pixels of the non-overlapping regions between the
* requested size and the frame-specified size.
*///使用最接近的分辨率去尝试硬件
rw = fmt->fmt.pix.width;
rh = fmt->fmt.pix.height;
maxd = (unsigned int)-1;
for (i = 0; i < format->nframes; ++i) {
__u16 w = format->frame[i].wWidth;
__u16 h = format->frame[i].wHeight;
d = min(w, rw) * min(h, rh);
d = w*h + rw*rh - 2*d;
if (d < maxd) {
maxd = d;
frame = &format->frame[i];
}
if (maxd == 0)
break;
}
- .vidioc_s_fmt_vid_cap 只是保存起来,并没有真正发给usb摄像头
//先try_format
ret = uvc_v4l2_try_format(stream, fmt, &probe, &format, &frame);
//再设置(先存起,当真正要用到时才发给硬件)
stream->ctrl = probe;
stream->cur_format = format;
stream->cur_frame = frame;
6.vidioc_reqbufs
->uvc_request_buffers(&stream->queue, rb);
->vb2_reqbufs(&queue->queue, rb);
7.vidioc_querybuf
->uvc_query_buffer(&stream->queue, buf);
8…mmap = uvc_v4l2_mmap,(分配内存并映射到用户空间)
->uvc_queue_mmap(&stream->queue, vma);
8.vidioc_qbuf(映射完得到内存之后入队列)
->uvc_queue_buffer(&stream->queue, buf);
list_add_tail(&vb->queued_entry, &q->queued_list);
9…vidioc_streamon(前面的设置都是保存在变量中,没有发送给usb摄像头,这里先发送前面设置的所有数据,然后启动usb摄像头传输)
->uvc_queue_streamon(&stream->queue, type);
->vb2_start_streaming
->vb2_buffer_done
->vb2_buffer_done
-> wake_up(&q->done_wq);
for (i = 0; i < q->num_buffers; ++i) {//当有数据到来就调用wake_up唤醒poll
vb = q->bufs[i];
if (vb->state == VB2_BUF_STATE_ACTIVE)
vb2_buffer_done(vb, VB2_BUF_STATE_QUEUED);
}
9…poll = uvc_v4l2_poll(调用poll查看有没有数据到来)
->uvc_queue_poll(&stream->queue, file, wait);
10.vidioc_dqbuf(有数据取出队列)
- .vidioc_streamoff(关闭传输)
以上都是涉及VS的过程(数据传输)而没有VC(数据控制)
我们以分析控制亮度为例:(同样是ioctl只不过是s_ctl/g_ctl)
uvc_query_ctrl(dev/*哪一个设备*/, UVC_SET_CUR, ctrl->entity->id/*哪一个unit/terminal*/,
dev->intfnum/*哪一个接口VS/VC*/, ctrl->info.selector,
uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
ctrl->info.size);
二:总结
1.uvc中有VS与VC
2.vc用于控制属性(如亮度),可以通过以下函数来设置
uvc_query_ctrl(dev/*哪一个设备*/, UVC_SET_CUR, ctrl->entity->id/*哪一个unit/terminal*/,
dev->intfnum/*哪一个接口VS/VC*/, ctrl->info.selector,
uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
ctrl->info.size);//类似函数去设置
3.vs用于控制数据传输,一个vc支持多个format而一个format有支持多个frame(分辨率)可以通过以下函数设置
ret = __uvc_query_ctrl(stream->dev, UVC_SET_CUR, 0, stream->intfnum,
probe ? UVC_VS_PROBE_CONTROL : UVC_VS_COMMIT_CONTROL, data,
size, uvc_timeout_param);
4.我们在设置format时,只是从已有的数据来的,而这些数据设备被枚举时分析描述符时被设置的来的
uvc驱动重点:
三、从零写UVC之分析描述符
1. bind1
分析usb_driver中的id_table
static struct usb_device_id myuvc_ids[] = {
/* Generic USB Video Class */
{ USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, 0) },//VC
/*{ USB_INTERFACE_INFO(USB_CLASS_VIDEO, 2, 0) },//VS通过vc可以找到vs*/
{},
};
主要分析USB_INTERFACE_INFO:
#define USB_INTERFACE_INFO(cl, sc, pr)
.match_flags = USB_DEVICE_ID_MATCH_INT_INFO,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0
解开宏定义:
bInterfaceClass = USB_CLASS_VIDEO(0x0e)
bInterfaceSubClass = 1
bInterfaceProtocol = 0
什么意思查看uvc规格书:
注意:VS接口属于VC接口,所以只配VC就行,不过要配VS也行。
2. bind2 (参考lsusb打印设备描述符)
接上摄像头之后,可以根据lsusb -v -d 0x厂家id; 得到摄像头的一些描述符!
struct usb_device *dev = interface_to_usbdev(intf);//通过intf获取usb_device
struct usb_device_descriptor *descriptor = &dev->descriptor;//一个usb设备只有一个usb设备描述符
printf("Device Descriptor:\n"
" bLength %5u\n"
" bDescriptorType %5u\n"
" bcdUSB %2x.%02x\n"
" bDeviceClass %5u\n"
" bDeviceSubClass %5u\n"
" bDeviceProtocol %5u\n"
" bMaxPacketSize0 %5u\n"
" idVendor 0x%04x\n"
" idProduct 0x%04x\n"
" bcdDevice %2x.%02x\n"
" iManufacturer %5u\n"
" iProduct %5u\n"
" iSerial %5u\n"
" bNumConfigurations %5u\n",
descriptor->bLength, descriptor->bDescriptorType,
descriptor->bcdUSB >> 8, descriptor->bcdUSB & 0xff,
descriptor->bDeviceClass,
descriptor->bDeviceSubClass,
descriptor->bDeviceProtocol,
descriptor->bMaxPacketSize0,
descriptor->idVendor, vendor, descriptor->idProduct, product,
descriptor->bcdDevice >> 8, descriptor->bcdDevice & 0xff,
descriptor->iManufacturer,
descriptor->iProduct,
descriptor->iSerialNumber,
descriptor->bNumConfigurations);
3. bind3 (打印配置描述符)一个usb设备对于一个设备描述符,一个设备描述符对应多个配置描述符
struct usb_host_config *host_config;//一个数组
struct usb_config_descriptor *config;//配置描述符
for(;i < descriptor->bNumConfigurations; ++i){
host_config = &dev->config[i];
config = &host_config->desc;
printk(" Configuration Descriptor: %d\n"
" bLength %5u\n"
" bDescriptorType %5u\n"
" wTotalLength 0x%04x\n"
" bNumInterfaces %5u\n"
" bConfigurationValue %5u\n"
" iConfiguration %5u\n"
" bmAttributes 0x%02x\n",
i,
config->bLength, config->bDescriptorType,
le16_to_cpu(config->wTotalLength),
config->bNumInterfaces, config->bConfigurationValue,
config->iConfiguration,
config->bmAttributes);
4.bind4 打印IAD信息(其中最重要的是告诉usb摄像头first interface与一共有多少个interface)
struct usb_interface_assoc_descriptor *assoc_desc;
for(;i < descriptor->bNumConfigurations; ++i){
assoc_desc = host_config->intf_assoc[0];//是一个指针数组,不需要取地址
printf(" Interface Association:\n"
" bLength %5u\n"
" bDescriptorType %5u\n"
" bFirstInterface %5u\n"
" bInterfaceCount %5u\n"
" bFunctionClass %5u\n"
" bFunctionSubClass %5u\n"
" bFunctionProtocol %5u\n"
" iFunction %5u\n",
assoc_desc->bLength,
assoc_desc->bDescriptorType,
assoc_desc->bFirstInterface,
assoc_desc->bInterfaceCount,
assoc_desc->bFunctionClass,
assoc_desc->bFunctionSubClass,
assoc_desc->bFunctionProtocol,
assoc_desc->iFunction);
}
5. bind5(打印接口描述符下的设置描述符)同样一个配置描述符下可以有多个接口描述符,而一共接口描述符下也有多个设置描述符。
注意:接口直接对应probe是否能够被调用,也就是是不是在id_table中!
struct usb_interface_descriptor *intfac_desc;//接口描述符
for(i = 0; i < descriptor->bNumConfigurations; ++i){
for(j = 0; j < intf->num_altsetting; ++j){
intfac_desc = &intf->altsetting[j].desc;/*接口描述符设置,而不是接口描述符,一个接口描述符可以有多个设置*/
printk(" Interface Descriptor altsetting: %d\n"
" bLength %5u\n"
" bDescriptorType %5u\n"
" bInterfaceNumber %5u\n"
" bAlternateSetting %5u\n"
" bNumEndpoints %5u\n"
" bInterfaceClass %5u\n"
" bInterfaceSubClass %5u\n"
" bInterfaceProtocol %5u\n"
" iInterface %5u\n",
j,
intfac_desc->bLength, intfac_desc->bDescriptorType, intfac_desc->bInterfaceNumber,
intfac_desc->bAlternateSetting, intfac_desc->bNumEndpoints, intfac_desc->bInterfaceClass, intfac_desc->bInterfaceSubClass, intfac_desc->bInterfaceProtocol,
intfac_desc->iInterface);
}
}
6. bind6 打印vs/vc自定义的描述符
略
7. bind7 打印端点描述符
struct usb_endpoint_descriptor *endpoint_desc;//端点描述符
for(i = 0; i < descriptor->bNumConfigurations; ++i){
for(j = 0; j < intf->num_altsetting; ++j){
/*打印端点描述符*/
for(m = 0; m < intfac_desc->bNumEndpoints; ++m){
endpoint_desc = &intf->altsetting[j].endpoint[m].desc;
printf(" Endpoint Descriptor:\n"
" bLength %5u\n"
" bDescriptorType %5u\n"
" bEndpointAddress 0x%02x EP %u %s\n"
" bmAttributes %5u\n"
" Transfer Type \n"
" Synch Type \n"
" Usage Type \n"
" wMaxPacketSize bytes\n"
" bInterval %5u\n",
endpoint_desc->bLength,
endpoint_desc->bDescriptorType,
endpoint_desc->bEndpointAddress,
endpoint_desc->bEndpointAddress & 0x0f,
(endpoint_desc->bEndpointAddress & 0x80) ? "IN" : "OUT",
endpoint_desc->bmAttributes,
endpoint_desc->bInterval);
}
}
}
四、从零写UVC之实现数据传输
开始写代码:参考
1.实现框架
static struct video_device *myuvc_dev;
int myuvc_open(struct file *filp)
{
return 0;
}
int myuvc_close(struct file *file)
{
return 0;
}
unsigned int myuvc_poll(struct file *file, poll_table *wait)
{
return 0;
}
long video_ioctl2(struct file *file,unsigned int cmd, unsigned long arg)
{
return 0;
}
int myuvc_mmap(struct file *file, struct vm_area_struct *vma)
{
return 0;
}
static const struct v4l2_file_operations myuvc_fops = {
.owner = THIS_MODULE,
.open = myuvc_open,
.release = myuvc_close,
.poll = myuvc_poll,
.unlocked_ioctl = video_ioctl2,
.mmap = myuvc_mmap,
};
static int myuvc_vidioc_querycap(struct file *file, void *priv,struct v4l2_capability *cap)
{
return 0;
}
static int myuvc_vidioc_enum_fmt_vid_cap(struct file *file, void *priv,struct v4l2_fmtdesc *f)
{
return 0;
}
static int myuvc_vidioc_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f)
{
return 0;
}
static int myuvc_vidioc_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f)
{
return 0;
}
static int myuvc_vidioc_s_fmt_vid_cap(struct file *file, void *priv,struct v4l2_format *f)
{
}
int myuvc_ioctl_reqbufs(struct file *file, void *priv, struct v4l2_requestbuffers *p)
{
}
int myuvc_ioctl_querybuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
}
int myuvc_ioctl_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
}
int myuvc_ioctl_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
}
int myuvc_ioctl_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
{
}
int myuvc_ioctl_streamoff(struct file *file, void *priv, enum v4l2_buf_type i)
{
}
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_ioctl_reqbufs,
.vidioc_querybuf = myuvc_ioctl_querybuf,
.vidioc_qbuf = myuvc_ioctl_qbuf,
.vidioc_dqbuf = myuvc_ioctl_dqbuf,
.vidioc_streamon = myuvc_ioctl_streamon,
.vidioc_streamoff = myuvc_ioctl_streamoff,
};
void myuvc_release(struct video_device *vdev)//不写这个函数,video_device注册可能出现问题
{
}
static int myuvc_probe(struct usb_interface *intf/*对于一个usb来说可能有多个intface,一个intface表示一个功能上的逻辑设备*/,const struct usb_device_id *id)
{
.....
/* 分配一个video_device结构体*/
myuvc_dev = video_device_alloc();
/* 设置结构体*/
myuvc_dev->fops = &myuvc_fops;
myuvc_dev->ioctl_ops = &myuvc_ioctl_ops;
myuvc_dev->release = myuvc_release;//不写这个函数,video_device注册可能出现问题
/*注册结构体*/
video_register_device(myuvc_dev, VFL_TYPE_GRABBER, -1);//写-1为自动分配一个设备号
.....
}
2. 填充函数
一、v4l2_ioctl_ops
摄像头选型:
1. myuvc_vidioc_querycap 查询摄像头的能力
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->capabilities = V4L2_CAP_STREAMING |V4L2_CAP_VIDEO_CAPTURE;//说明是视频捕捉设备,切不是使用read/write而是使用ioctl/mmap来读取数据
return 0;
}
2. myuvc_vidioc_enum_fmt_vid_cap摄像头支持哪种格式(我们假设只支持一种格式,可以通过接上usb摄像头通过lsusb来查看)
//参考uvc的fmts,关于该结构体的填充还是需要lsusb根据实际情况来填充,摄像头支持几种格式就填几种格式
static struct myuvc_format_desc myuvc_fmts[] = {
{
.name = "YUV 4:2:2 (YUYV)",
.guid = UVC_GUID_FORMAT_YUY2,
.fcc = V4L2_PIX_FMT_YUYV,
},
{
.name = "YUV 4:2:2 (YUYV)",
.guid = UVC_GUID_FORMAT_YUY2_ISIGHT,
.fcc = V4L2_PIX_FMT_YUYV,
},
};
static int myuvc_vidioc_enum_fmt_vid_cap(struct file *file, void *priv,struct v4l2_fmtdesc *f)
{
if (unlikely(f->index >= ARRAY_SIZE(myuvc_fmts)))
return -EINVAL;
strcpy(f->description, myuvc_fmts[f->index].name, sizeof(f->description));
f->pixelformat = myuvc_fmts[f->index].fcc;
return 0;
}
3. myuvc_vidioc_g_fmt_vid_cap 返回当前使用的格式
static struct v4l2_format myuvc_format;
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;
}
4. myuvc_vidioc_try_fmt_vid_cap 测试程序是否支持某种格式,并强制设置分辨率以及帧数
struct frame_desc {
int width;
int height;
};
//自定义分辨率,通过查看描述符获得
static struct frame_desc myuvc_frame[] = {{640,480},{352,288},{320,240},{176,144},{160,120}};
static int frame_idx = 1;//先定死分辨率
static int bBitsPerPixel = 16;//可以通过lsusb查找"bBitsPerPixel"获得,表示一个橡塑占多少字节
//尝试各种格式,并强制设置分辨率以及帧数
static int myuvc_vidioc_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f)
{
if (f && f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)//如果f不支持捕获就直接返回
return -EINVAL;
/*人工查看描述符,确定支持哪几种分辨率 通过lsusb*/
f->fmt.pix.width = myuvc_frame[frame_idx].width;
f->fmt.pix.height = myuvc_frame[frame_idx].height;
f->fmt.pix.bytesperline = (f->fmt.pix.width * bBitsPerPixel) >> 3;//每行占多少字节 = 每行多少像素x一个像素多少字节
f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height;//一帧多少字节 = 每行多少字节 * 多少行
return 0;
}
5. myuvc_vidioc_s_fmt_vid_cap 设置格式
//设置格式
static int myuvc_vidioc_s_fmt_vid_cap(struct file *file, void *priv,struct v4l2_format *f)
{
//先使用try_fmt尝试设置,并强制设置其分辨率
myuvc_vidioc_try_fmt_vid_cap(file, NULL, f);
//在将设置好的format赋值到我们自己定义的全局变量中去
memcpy(&myuvc_format, f, sizeof(myuvc_format));
return 0;
}
6.剩下的buf与steamon/off
通过查看其它厂家编写的uvc驱动发现:他们一些v4l2_ioctl_ops是直接调用videobuf2_core.c中提供的
.vidioc_reqbufs = vb2_ioctl_reqbufs,
.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
.vidioc_create_bufs = vb2_ioctl_create_bufs,
.vidioc_querybuf = vb2_ioctl_querybuf,
.vidioc_qbuf = vb2_ioctl_qbuf,
.vidioc_dqbuf = vb2_ioctl_dqbuf,
.vidioc_streamon = vb2_ioctl_streamon,
.vidioc_streamoff = vb2_ioctl_streamoff,
通过提问cht得到:
在Linux内核的uvc驱动中,如果需要自己编写驱动程序,则可以使用 v4l2-ioctl 框架处理ioctl命令。对于视频数据流应用程序来说,使用ioctl,它能够控制设备并请求或提供内存缓冲区地址给设备。
对于处理 v4l2_ioctl_ops 对象中的成员函数指针,我们可以选择是否直接使用videobuf2_core.c中定义的。一般而言,在开发过程中有两种选项:
直接使用videobuf2_core.c中定义的函数指针:可以在驱动模块中使用 vb2_* 系列函数来进行内存分配、管理和映射等操作。这些函数都由videobuf2_core.c文件导出,并且已经为大多数常见的操作类型实现了对应的函数指针。使用这些函数指针可以使驱动程序的编写过程更加简单,不必为每个操作自己创建独立的函数指针或进行繁琐的内存管理操作。
自定义功能类似的函数指针:如果驱动程序需要进行特定的内存分配操作或者需要自定义缓冲区管理逻辑,那么可以在当前驱动模块中定义功能类似的函数指针,然后在v4l2_ioctl_ops对象中填充这些函数指针即可。自定义函数指针可以实现更灵活的内存管理和缓冲区处理策略,并且可以根据具体需求自行进行优化和改进。
总之,在编写驱动程序过程中使用哪种方式取决于开发者对驱动程序需求的具体情况。如果当前驱动主要使用常见的操作类型,则建议直接使用 videobuf2_core.c 中函数指针;如果需要特殊的缓冲区管理策略或者需要在内存分配/映射时进行定制化操作,则可以选择自定义并填充功能类似的函数指针。
所以我们直接尝试使用内核通过的试试。
事实证明:uvc源码其实也是调用videobuf2_core.c中定义的ioctl
比如:.vidioc_querybuf = uvc_ioctl_querybuf,
uvc_ioctl_querybuf:
->uvc_query_buffer
->vb2_querybuf
二、v4l2_file_operations
通过查看其它厂家的驱动发现,里面的.poll, .mmap, .unlocked_ioctl都是使用videobuf2_core.c内核中定义的,我们也使用。
.poll = vb2_fop_poll,
.mmap = vb2_fop_mmap,
.unlocked_ioctl = video_ioctl2,
事实证明:uvc驱动中的mmap与poll也最终调用到videobuf2_core.c中定义的函数。
总结:
整个fops函数的填充我们自己写的有:
v4l2_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,
v4l2_file_operations:
.open = myuvc_open,
.release = myuvc_close,
五、修改驱动使支持自制摄像头
一、修改内核UVC使在虚拟机上支持
这是厂家提供手册,根据手册修改的uvc驱动,没什么可说的。
二、修改上一节我们自己写的uvc驱动,在虚拟机上支持
1. 修改全局变量
在我们自己写的uvc驱动中,唯一不通用的部分就是前面根据lsusb查出的参数所定义的一些变量。所以我们大致只需要根据摄像头lsusb的数据重新需改这些参数就行。
//自定义分辨率,通过查看描述符获得
static struct frame_desc myuvc_frame[] = {{640,480},{352,288},{320,240},{176,144},{160,120}};
static int frame_idx = 1;//先定死分辨率
static int bBitsPerPixel = 16;//可以通过lsusb查找"bBitsPerPixel"获得,表示一个橡塑占多少字节
如何使用lsusb:
0x1b3b为厂家id。
2.修改ioctl与fops
.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_ioctl_reqbufs,
.vidioc_querybuf = myuvc_ioctl_querybuf,
.vidioc_qbuf = myuvc_ioctl_qbuf,
.vidioc_dqbuf = myuvc_ioctl_dqbuf,
.vidioc_streamon = myuvc_ioctl_streamon,
.vidioc_streamoff = myuvc_ioctl_streamoff,
1.vidioc_querycap 一般不需要改
2.myuvc_vidioc_enum_fmt_vid_cap
//参考uvc的fmts,关于该结构体的填充还是需要lsusb根据实际情况来填充,摄像头支持几种格式就填几种格式
static struct myuvc_format_desc myuvc_fmts[] = {
{
.name = "YUV 4:2:2 (YUYV)",
.guid = UVC_GUID_FORMAT_YUY2,
.fcc = V4L2_PIX_FMT_YUYV,
},
{
.name = "YUV 4:2:2 (YUYV)",
.guid = UVC_GUID_FORMAT_YUY2_ISIGHT,
.fcc = V4L2_PIX_FMT_YUYV,
},
};
static int myuvc_vidioc_enum_fmt_vid_cap(struct file *file, void *priv,struct v4l2_fmtdesc *f)
{
if (unlikely(f->index >= ARRAY_SIZE(myuvc_fmts)))
return -EINVAL;
strcpy(f->description, myuvc_fmts[f->index].name, sizeof(f->description));
f->pixelformat = myuvc_fmts[f->index].fcc;
f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
return 0;
}
要修改myuvc_fmts结构体中的格式:
首先提供lsusb查看支持哪些格式,如何到对应的头文件中去
3.vidioc_g_fmt_vid_cap 不用改
4.vidioc_try_fmt_vid_cap
//尝试各种格式,并强制设置分辨率以及帧数
static int myuvc_vidioc_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f)
{
if (f && f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)//如果f不支持捕获就直接返回
return -EINVAL;
if(f->fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV)//如果不支持V4L2_PIX_FMT_YUYV这种格式
return -EINVAL;
/*人工查看描述符,确定支持哪几种分辨率 通过lsusb*/
f->fmt.pix.width = myuvc_frame[frame_idx].width;
f->fmt.pix.height = myuvc_frame[frame_idx].height;
f->fmt.pix.bytesperline = (f->fmt.pix.width * bBitsPerPixel) >> 3;//每行占多少字节 = 每行多少像素x一个像素多少字节
f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height;//一帧多少字节 = 每行多少字节 * 多少行
//f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;//可以提供lsusb查看
return 0;
}
改V4L2_PIX_FMT_YUYV为对应的格式。
注意:如果支持的是压缩格式,那么bBitsPerPixel全局变量需要设置为0.
而这时,一帧数据有多大呢?可以提供lsusb查看!
colorspace的设置:查看lsusb
5.vidioc_s_fmt_vid_cap 不需要改