1.概述
Linux系统上的Video设备多种多样,如通过Camera Host控制器接口连接的摄像头,通过USB总线连接的摄像头等。为了兼容更多的硬件,Linux内核抽象了V4L2(Video for Linux Two)子系统。V4L2子系统是Linux内核中关于Video(视频)设备的API接口,是V4L(Video for Linux)子系统的升级版本。V4L2子系统向上为虚拟文件系统提供了统一的接口,应用程序可通过虚拟文件系统访问Video设备。V4L2子系统向下给Video设备提供接口,同时管理所有Video设备。Video设备又分为主设备和从设备,对于Camera来说,Camera Host控制器为主设备,负责图像数据的接收和传输,从设备为Camera Sensor,一般为I2C接口,可通过从设备控制Camera采集图像的行为,如图像的大小、图像的FPS等。主设备可通过v4l2_subdev_call
的宏调用从设备提供的方法,反过来从设备可以调用主设备的notify
方法通知主设备某些事件发生了。
2.V4L2子系统
V4L(Video for Linux)是Linux内核中关于视频设备的API接口,涉及视频设备的音频和视频信息采集及处理、视频设备的控制。V4L出现于Linux内核2.1版本,经过修改bug和添加功能,Linux内核2.5版本推出了V4L2(Video for Linux Two)子系统,功能更多且更稳定。V4L2的主设备号是81,次设备号范围0~255,这些次设备号又分为多类设备,如视频设备(次设备号范围0-63)、Radio(收音机)设备(次设备号范围64-127)、Teletext设备(次设备号范围192-223)、VBI设备(次设备号范围224-255)。V4L2设备对应的设备节点有/dev/videoX、/dev/vbiX、/dev/radioX
。这里只讨论视频设备,视频设备对应的设备节点是/dev/videoX
。视频设备以高频头或Camera为输入源,Linux内核驱动该类设备,接收相应的视频信息并处理。
2.1.V4L2主设备数据结构
V4L2主设备实例使用struct v4l2_device
结构体表示,v4l2_device
是V4L2子系统的入口,管理着V4L2子系统的主设备和从设备。简单设备可以仅分配这个结构体,但在大多数情况下,都会将这个结构体嵌入到一个更大的结构体中。需要与媒体框架整合的驱动必须手动设置dev->driver_data
,指向包含v4l2_device
结构体实例的驱动特定设备结构体。这可以在注册V4L2设备实例前通过dev_set_drvdata()
函数完成。同时必须设置v4l2_device
结构体的mdev
域,指向适当的初始化并注册过的media_device
实例。
对于视频设备,Camera控制器可以视为主设备,接在Camera控制器上的摄像头可以视为从设备。V4L2子系统使用v4l2_device
结构体管理设备,设备的具体操作方法根据设备类型决定,若是视频设备,则需要注册video_device
结构体,并提供相应的操作方法。
[include/media/v4l2-device.h]
struct v4l2_device {
struct device *dev; // 父设备指针
#if defined(CONFIG_MEDIA_CONTROLLER) // 多媒体设备配置选项
// 用于运行时数据流的管理,
struct media_device *mdev;
#endif
// 注册的子设备的v4l2_subdev结构体都挂载此链表中
struct list_head subdevs;
// 同步用的自旋锁
spinlock_t lock;
// 独一无二的设备名称,默认使用driver name + bus ID
char name[V4L2_DEVICE_NAME_SIZE];
// 被一些子设备回调的通知函数,但这个设置与子设备相关。子设备支持的任何通知必须在
// include/media/<subdevice>.h 中定义一个消息头。
void (*notify)(struct v4l2_subdev *sd, unsigned int notification, void *arg);
// 提供子设备(主要是video和ISP设备)在用户空间的特效操作接口,
// 比如改变输出图像的亮度、对比度、饱和度等等
struct v4l2_ctrl_handler *ctrl_handler;
// 设备优先级状态
struct v4l2_prio_state prio;
/* BKL replacement mutex. Temporary solution only. */
struct mutex ioctl_lock;
// struct v4l2_device结构体的引用计数,等于0时才释放
struct kref ref;
// 引用计数ref为0时,调用release函数进行释放资源和清理工作
void (*release)(struct v4l2_device *v4l2_dev);
};
- 1
使用v4l2_device_register
注册v4l2_device
结构体.如果v4l2_dev->name
为空,则它将被设置为从dev
中衍生出的值(为了更加精确,形式为驱动名后跟bus_id
)。如果在调用v4l2_device_register
前已经设置好了,则不会被修改。如果dev
为NULL,则必须在调用v4l2_device_register
前设置v4l2_dev->name
。可以基于驱动名和驱动的全局atomic_t
类型的实例编号,通过v4l2_device_set_name()
设置name
。这样会生成类似ivtv0
、ivtv1
等名字。若驱动名以数字结尾,则会在编号和驱动名间插入一个破折号,如:cx18-0
、cx18-1
等。dev
参数通常是一个指向pci_dev
、usb_interface
或platform_device
的指针,很少使其为NULL,除非是一个ISA设备或者当一个设备创建了多个PCI设备,使得v4l2_dev
无法与一个特定的父设备关联。
使用v4l2_device_unregister
卸载v4l2_device
结构体。如果dev->driver_data
域指向 v4l2_dev
,将会被重置为NULL。主设备注销的同时也会自动注销所有子设备。如果你有一个热插拔设备(如USB设备),则当断开发生时,父设备将无效。由于v4l2_device
有一个指向父设备的指针必须被清除,同时标志父设备
已消失,所以必须调用v4l2_device_disconnect
函数清理v4l2_device
中指向父设备的dev
指针。v4l2_device_disconnect
并不注销主设备,因此依然要调用v4l2_device_unregister
函数注销主设备。
[include/media/v4l2-device.h]
// 注册v4l2_device结构体,并初始化v4l2_device结构体
// dev-父设备结构体指针,若为NULL,在注册之前设备名称name必须被设置,
// v4l2_dev-v4l2_device结构体指针
// 返回值-0成功,小于0-失败
int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
// 卸载注册的v4l2_device结构体
// v4l2_dev-v4l2_device结构体指针
void v4l2_device_unregister(struct v4l2_device *v4l2_dev)
// 设置设备名称,填充v4l2_device结构体中的name成员
// v4l2_dev-v4l2_device结构体指针
// basename-设备名称基本字符串
// instance-设备计数,调用v4l2_device_set_name后会自加1
// 返回值-返回设备计数自加1的值
int v4l2_device_set_name(struct v4l2_device *v4l2_dev,
const char *basename, atomic_t *instance)
// 热插拔设备断开时调用此函数
// v4l2_dev-v4l2_device结构体指针
void v4l2_device_disconnect(struct v4l2_device *v4l2_dev);
同一个硬件的情况下。如ivtvfb
驱动是一个使用ivtv
硬件的帧缓冲驱动,同时alsa
驱动也使用此硬件。可以使用如下例程遍历所有注册的设备:
static int callback(struct device *dev, void *p)
{
struct v4l2_device *v4l2_dev = dev_get_drvdata(dev);
/* 测试这个设备是否已经初始化 */
if (v4l2_dev == NULL)
return 0;
...
return 0;
}
int iterate(void *p)
{
struct device_driver *drv;
int err;
/* 在PCI 总线上查找ivtv驱动。
pci_bus_type是全局的. 对于USB总线使用usb_bus_type。 */
drv = driver_find("ivtv", &pci_bus_type);
/* 遍历所有的ivtv设备实例 */
err = driver_for_each_device(drv, NULL, p, callback);
put_dri