V4L2(video for linux) 可以支持多种设备,它可以有以下5种接口:
1、视频采集接口(video capture interface):这种应用的设备可以是高频头或者摄像头.V4L2的最初设计就是应用于这种功能的.下面也是着重讲解这种应用;
2、视频输出接口(video output interface):可以驱动计算机的外围视频图像设备——像可以输出电视信号格式的设备;
3、直接传输视频接口(video overlay interface):它的主要工作是把从视频采集设备采集过来的信号直接输出到输出设备之上,而不用经过系统的CPU;
4、视频间隔消隐信号接口(VBI interface):它可以使应用可以访问传输消隐期的视频信号;
5、收音机接口(radio interface):可用来处理从AM或FM高频头设备接收来的音频流;
V4L2驱动的主要功能是使程序有发现设备的能力和操作设备.它主要是用过一系列的回调函数来实现这些功能.像设置高频头的频率,帧频,视频压缩格式和图像像参数等等.
V4L2提供了三种不同的API来传输外围设备和用户空间的数据。下面就vivi(drivers/media/video/vivi.c)来讲解一个V4L2驱动的编写。注意它是一个虚拟的设备驱动,没有与实际的硬件打交道。
1、分析几个重要数据结构:
vivi.c包含头文件v4l2-device.h和v4l2-ioctl.h,其中v4l2-device.h中包含了v4l2-subdev.h,v4l2-subdev.h中又包含了v4l2-common.h,v4l2-common.h中包含了v4l2-dev.h。
在v4l2-dev.h中定义了结构体video_device和v4l2_file_operations;
在v4l2-ioctl.h中定义了结构体v4l2_ioctl_ops;
在v4l2-device.h中定义了结构体v4l2_device;
1) vivi_fops
static const struct v4l2_file_operations vivi_fops = {
.owner = THIS_MODULE,
.open = vivi_open,
.release = vivi_close,
.read = vivi_read,
.poll = vivi_poll,
.ioctl = video_ioctl2, /* V4L2 ioctl handler */
.mmap = vivi_mmap,
};
2) vivi_ioctl_ops
static const struct v4l2_ioctl_ops vivi_ioctl_ops = {
.vidioc_querycap = vidioc_querycap,
.vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
.vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
.vidioc_reqbufs = vidioc_reqbufs,
.vidioc_querybuf = vidioc_querybuf,
.vidioc_qbuf = vidioc_qbuf,
.vidioc_dqbuf = vidioc_dqbuf,
.vidioc_s_std = vidioc_s_std,
.vidioc_enum_input = vidioc_enum_input,
.vidioc_g_input = vidioc_g_input,
.vidioc_s_input = vidioc_s_input,
.vidioc_queryctrl = vidioc_queryctrl,
.vidioc_g_ctrl = vidioc_g_ctrl,
.vidioc_s_ctrl = vidioc_s_ctrl,
.vidioc_streamon = vidioc_streamon,
.vidioc_streamoff = vidioc_streamoff,
#ifdef CONFIG_VIDEO_V4L1_COMPAT
.vidiocgmbuf = vidiocgmbuf,
#endif
};
3) vivi_template
static struct video_device vivi_template = {
.name = "vivi",
.fops = &vivi_fops,
.ioctl_ops = &vivi_ioctl_ops,
.minor = -1,
.release = video_device_release,
.tvnorms = V4L2_STD_525_60,
.current_norm = V4L2_STD_NTSC_M,
};
其中函数vivi_xxx和vidioc_xxx都是在vivi.c中实现的。如果要基于某个硬件来实现V4L2的接口,那这些函数就需要调用硬件的驱动去实现。
4) vivi_dev
struct vivi_dev {
struct list_head vivi_devlist; //内核双向链表,在内核数据结构里有描述
struct semaphore lock; //信号量,防止竞态访问
int users; //用户数量计数
/* various device info */
unsigned int resources;
struct video_device video_dev; //这个成员是这个结构的核心,用面向对象的话来说就是基类
struct vivi_dmaqueue vidq; //DMA队列
/* Several counters */
int h,m,s,us,jiffies; //定时器定义
char timestr[13]; //其它一些资源变量.
};
像这样变义的结构在Linux C 中很普遍,这也是利用C来实现面向对象编程的强大方法。建立这个结构对象之后,所有的操作都是基于这个结构,或者这个结构派生出的来的其它结构。
5) vivi_fh
struct vivi_fh {
struct vivi_dev *dev;
/* video capture */
struct vivi_fmt *fmt;
unsigned int width,height;
struct videobuf_queue vb_vidq;
enum v4l2_buf_type type;
};
这个结构即是vivi_dev结构的更深层次封装,基于那个结构加入了更多的描述信息,如视频制式、视频画面大小、视频缓冲队列等等。在open的时候,会把这个结构赋给file结构中的private_data域。在释放设备时注销.其它的像ioctl,mmap,read,write等等都会用到这个结构,其实整个模块的编写的cdev差不多。只是视频设备的基类是video_device,而字符设备的基类是cdev而已。
2、数据传输方式:
在设备与应用程序之间有三种数据传输方式:
1)read与write这种方式,它像其它设备驱动一样,但是这种方式很慢,对于数据视频流不能满足其要求;
2)直接的内存访问,可以通过其映射方式来传输(IO数据流,交换指向缓冲区指针的方法);这是视频设备通常用的方法,采用mmap()的方法,即有内核空间里开辟内存,再在程序里把这部分的内存映射到程序空间。如果有设备内存,即直接映射到设备的内核,这种性能更高。
3)异步IO口访问,但是这种方法在V4L2模块中还没有实现。(重要:需要确认)
vivi中的mmap是利用第二种方法来实现的,这也是视频设备常用的方法:
static int
vivi_mmap(struct file *file, struct vm_area_struct * vma)
{
struct vivi_fh *fh = file->private_data;
int ret;
dprintk (1,"mmap called, vma=0x%08lx/n",(unsigned long)vma);
ret=videobuf_mmap_mapper(&fh->vb_vidq, vma);
dprintk (1,"vma start=0x%08lx, size=%ld, ret=%d/n",
(unsigned long)vma->vm_start,
(unsigned long)vma->vm_end-(unsigned long)vma->vm_start,
ret);
return ret;
}
videobuf_mmap_mapper(&fh->vb_vidq, vma); 这个核心函数把设备的I/O内存或者设备内存映射到系统为它开辟的虚拟内存。
3、操控设备的实现: ioctl
static int vivi_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
return video_usercopy(inode, file, cmd, arg, vivi_do_ioctl);
}
vivi_do_ioctl 这个函数里调用一些命令来设备V4L2模块中的一些结构参数来改变或者获取设备的参数