http://work-blog.readthedocs.io/en/latest/v4l2%20intro.html
V4L2(Video4Linux的缩写)是Linux下关于视频采集相关设备的驱动框架,为驱动和应用程序提供了一套统一的接口规范。
V4L2支持的设备十分广泛,但是其中只有很少一部分在本质上是真正的视频设备:
V4L2在Linux系统中的结构图如下:
从V4L2简单框图可以看出,V4L2是一个字符设备,而V4L2的大部分功能都是通过设备文件的ioctl导出的。
**可以将这些ioctl分类如下**:
capture ioctl list ID 描述 VIDIOC_ENUM_FMT 枚举设备所支持的所有数据格式 VIDIOC_S_FMT 设置数据格式 VIDIOC_G_FMT 获取数据格式 VIDIOC_TRY_FMT 与VIDIOC_S_FMT一样,但不会改变设备的状态 VIDIOC_REQBUFS 向设备请求视频缓冲区,即初始化视频缓冲区 VIDIOC_QUERYBUF 查询缓冲区的状态 VIDIOC_QBUF 从设备获取一帧视频数据 VIDIOC_DQBUF 将视频缓冲区归回给设备, VIDIOC_OVERLAY 开始或者停止overlay VIDIOC_G_FBUF 获取video overlay设备或OSD设备的framebuffer参数 VIDIOC_S_FBUF 设置framebuffer参数 VIDIOC_STREAMON 开始流I/O操作,capture or output device VIDIOC_STREAMOFF 关闭流I/O操作
TV Standard ID 描述 VIDIOC_ENUMSTD 枚举设备支持的所有标准 VIDIOC_G_STD 获取当前正在使用的标准 VIDIOC_S_STD 设置视频标准 VIDIOC_QUERYSTD 有的设备支持自动侦测输入源的视频标准,此时使用此ioctl查询侦测到的视频标准
Input / Output ID 描述 VIDIOC_ENUMINPUT 枚举所有input端口 VIDIOC_G_INPUT 获取当前正在使用的input端口 VIDIOC_S_INPUT 设置将要使用的input端口 VIDIOC_ENUMOUTPUT 枚举所有output端口 VIDIOC_G_OUTPUT 获取当前正在使用的output端口 VIDIOC_S_OUTPUT 设置将要使用的output端口 VIDIOC_ENUMAUDIO 枚举所有audio input端口 VIDIOC_G_AUDIO 获取当前正在使用的audio input端口 VIDIOC_S_AUDIO 设置将要使用的audio input端口 VIDIOC_ENUMAUDOUT 枚举所有audio output端口 VIDIOC_G_AUDOUT 获取当前正在使用的audio output端口 VIDIOC_S_AUDOUT 设置将要使用的audio output端口
controls ID 描述 VIDIOC_QUERYCTRL 查询指定control的详细信息 VIDIOC_G_CTRL 获取指定control的值 VIDIOC_S_CTRL 设置指定control的值 VIDIOC_G_EXT_CTRLS 获取多个control的值 VIDIOC_S_EXT_CTRLS 设置多个control的值 VIDIOC_TRY_EXT_CTRLS 与VIDIOC_S_EXT_CTRLS相同,但是不改变设备状态 VIDIOC_QUERYMENU 查询menu
controls ID 描述 VIDIOC_G_MODULATOR VIDIOC_S_MODULATOR VIDIOC_G_CROP VIDIOC_S_CROP VIDIOC_G_SELECTION VIDIOC_S_SELECTION VIDIOC_CROPCAP VIDIOC_G_ENC_INDEX VIDIOC_ENCODER_CMD VIDIOC_TRY_ENCODER_CMD VIDIOC_DECODER_CMD VIDIOC_TRY_DECODER_CMD VIDIOC_G_PARM VIDIOC_S_PARM VIDIOC_G_TUNER VIDIOC_S_TUNER VIDIOC_G_FREQUENCY VIDIOC_S_FREQUENCY VIDIOC_G_SLICED_VBI_CAP VIDIOC_LOG_STATUS VIDIOC_DBG_G_CHIP_IDENT VIDIOC_S_HW_FREQ_SEEK VIDIOC_ENUM_FRAMESIZES VIDIOC_ENUM_FRAMEINTERVALS VIDIOC_ENUM_DV_PRESETS VIDIOC_S_DV_PRESET VIDIOC_G_DV_PRESET VIDIOC_QUERY_DV_PRESET VIDIOC_S_DV_TIMINGS VIDIOC_G_DV_TIMINGS VIDIOC_DQEVENT VIDIOC_SUBSCRIBE_EVENT VIDIOC_UNSUBSCRIBE_EVENT VIDIOC_CREATE_BUFS VIDIOC_PREPARE_BUF
v4l2设备的基本操作流程如下:
struct capability cap; ioctl(fd,VIDIOC_QUERYCAP,&cap)
- 视频输入源的视频标准,VIDIOC_*_STD
- 视频数据的格式 , VIDIOC_*_FMT
- 视频输入端口, VIDIOC_*_INPUT
- 视频输出端口,VIDIOC_*_OUTPUT
**启动设备开始I/O操作**。V4L2支持一下三种I/O方式:
对于Capture device可以以如下方式启动设备:
释放资源并关闭设备。
在驱动层,V4L2为驱动编写者做了很多工作。只需要实现硬件相关的代码,并且注册相关设备即可。
硬件相关代码的编写,除了编写具体硬件的控制代码外,最主要的就是将代码与V4L2框架绑定。绑定主要分为以下两个部分:
提到关系绑定,就必须介绍下V4L2几个重要结构体。
v4l2_device,v4l2_subdev可以看作所有设备和子设备的基类。我们在编写自己的驱动时,往往需要继承这些设备基类,添加一些自己的数据成员。例如第三章要讲到的soc_camera_host结构体,就是继承v4l2_device,并添加了互斥锁、子设备列表等成员变量。
绑定的基本流程
根据需要”重载”v4l2_device或v4l2_subdev结构体,添加需要的结构体成员。例如 :
linux/include/media/soc_camera.h文件中soc_camera_host重载了v4l2_device:
struct soc_camera_host { struct v4l2_device v4l2_dev; struct list_head list; struct mutex host_lock; /* Protect during probing */ unsigned char nr; /* Host number */ void *priv; const char *drv_name; struct soc_camera_host_ops *ops; };linux/drivers/media/video/Ml86v7667.c中ml86v7667_priv结构体”重载”了v4l2_subdev:
struct ml86v7667_priv { struct v4l2_subdev sd; struct v4l2_ctrl_handler hdl; v4l2_std_id std; };
v4l2_device与V4L2框架的绑定:通过调用v4l2_device_register函数实现。例如,上面提到的soc_camera_host的绑定:
int soc_camera_host_register(struct soc_camera_host *ici) { struct soc_camera_host *ix; int ret; if (!ici || !ici->ops || !ici->ops->try_fmt || !ici->ops->set_fmt || !ici->ops->set_bus_param || !ici->ops->querycap || ((!ici->ops->init_videobuf || !ici->ops->reqbufs) && !ici->ops->init_videobuf2) || !ici->ops->add || !ici->ops->remove || !ici->ops->poll || !ici->v4l2_dev.dev) return -EINVAL; if (!ici->ops->set_crop) ici->ops->set_crop = default_s_crop; if (!ici->ops->get_crop) ici->ops->get_crop = default_g_crop; if (!ici->ops->cropcap) ici->ops->cropcap = default_cropcap; if (!ici->ops->set_parm) ici->ops->set_parm = default_s_parm; if (!ici->ops->get_parm) ici->ops->get_parm = default_g_parm; if (!ici->ops->enum_fsizes) ici->ops->enum_fsizes = default_enum_fsizes; mutex_lock(&list_lock); list_for_each_entry(ix, &hosts, list) { if (ix->nr == ici->nr) { ret = -EBUSY; goto edevreg; } } ret = v4l2_device_register(ici->v4l2_dev.dev, &ici->v4l2_dev); if (ret < 0) goto edevreg; list_add_tail(&ici->list, &hosts); mutex_unlock(&list_lock); mutex_init(&ici->host_lock); scan_add_host(ici); return 0; edevreg: mutex_unlock(&list_lock); return ret; }
v4l2_subdev与v4l2_device的绑定:通过v4l2_device_register_subdev函数,将subdev注册到根节点上。例如:
static int soc_camera_platform_probe(struct platform_device *pdev) { struct soc_camera_host *ici; struct soc_camera_platform_priv *priv; struct soc_camera_platform_info *p = pdev->dev.platform_data; struct soc_camera_device *icd; int ret; if (!p) return -EINVAL; if (!p->icd) { dev_err(&pdev->dev, "Platform has not set soc_camera_device pointer!\n"); return -EINVAL; } priv = kzalloc(sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; icd = p->icd; /* soc-camera convention: control's drvdata points to the subdev */ platform_set_drvdata(pdev, &priv->subdev); /* Set the control device reference */ icd->control = &pdev->dev; ici = to_soc_camera_host(icd->parent); v4l2_subdev_init(&priv->subdev, &platform_subdev_ops); v4l2_set_subdevdata(&priv->subdev, p); strncpy(priv->subdev.name, dev_name(&pdev->dev), V4L2_SUBDEV_NAME_SIZE); ret = v4l2_device_register_subdev(&ici->v4l2_dev, &priv->subdev); if (ret) goto evdrs; return ret; evdrs: platform_set_drvdata(pdev, NULL); kfree(priv); return ret; }
video_device与v4l2_device的绑定:将v4l2_device的地址赋值给video_device的v4l2_dev即可。
在v4l2 framework 简略版图中,绿色的方框都是需要我们绑定并实现的。
其中v4l2_file_operations和v4l2_ioctl_ops是必须实现的。而v4l2_subdev_ops下的八类ops中,v4l2_subdev_core_ops是必须实现的,其余需要根据设备类型选择实现的。比如video capture类设备需要实现v4l2_subdev_core_ops, v4l2_subdev_video_ops。
static struct v4l2_file_operations soc_camera_fops = {
.owner = THIS_MODULE,
.open = soc_camera_open,
.release = soc_camera_close,
.unlocked_ioctl = video_ioctl2,
.read = soc_camera_read,
.mmap = soc_camera_mmap,
.poll = soc_camera_poll,
};
static const struct v4l2_ioctl_ops soc_camera_ioctl_ops = {
.vidioc_querycap = soc_camera_querycap,
.vidioc_try_fmt_vid_cap = soc_camera_try_fmt_vid_cap,
.vidioc_g_fmt_vid_cap = soc_camera_g_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = soc_camera_s_fmt_vid_cap,
.vidioc_enum_fmt_vid_cap = soc_camera_enum_fmt_vid_cap,
.vidioc_enum_input = soc_camera_enum_input,
.vidioc_g_input = soc_camera_g_input,
.vidioc_s_input = soc_camera_s_input,
.vidioc_s_std = soc_camera_s_std,
.vidioc_g_std = soc_camera_g_std,
.vidioc_enum_framesizes = soc_camera_enum_fsizes,
.vidioc_reqbufs = soc_camera_reqbufs,
.vidioc_querybuf = soc_camera_querybuf,
.vidioc_qbuf = soc_camera_qbuf,
.vidioc_dqbuf = soc_camera_dqbuf,
.vidioc_create_bufs = soc_camera_create_bufs,
.vidioc_prepare_buf = soc_camera_prepare_buf,
.vidioc_streamon = soc_camera_streamon,
.vidioc_streamoff = soc_camera_streamoff,
.vidioc_cropcap = soc_camera_cropcap,
.vidioc_g_crop = soc_camera_g_crop,
.vidioc_s_crop = soc_camera_s_crop,
.vidioc_g_parm = soc_camera_g_parm,
.vidioc_s_parm = soc_camera_s_parm,
.vidioc_g_chip_ident = soc_camera_g_chip_ident,
#ifdef CONFIG_VIDEO_ADV_DEBUG
.vidioc_g_register = soc_camera_g_register,
.vidioc_s_register = soc_camera_s_register,
#endif
};
linuxdriversmediavideosoc_camera_platform.c中platform_subdev_ops的实现
static struct v4l2_subdev_video_ops platform_subdev_video_ops = {
.s_stream = soc_camera_platform_s_stream,
.enum_mbus_fmt = soc_camera_platform_enum_fmt,
.cropcap = soc_camera_platform_cropcap,
.g_crop = soc_camera_platform_g_crop,
.try_mbus_fmt = soc_camera_platform_fill_fmt,
.g_mbus_fmt = soc_camera_platform_fill_fmt,
.s_mbus_fmt = soc_camera_platform_fill_fmt,
.g_mbus_config = soc_camera_platform_g_mbus_config,
};
static struct v4l2_subdev_ops platform_subdev_ops = {
.core = &platform_subdev_core_ops,
.video = &platform_subdev_video_ops,
};
函数绑定只是将驱动所实现的函数赋值给相关的变量即可。