V4l2框架简述

1 硬件

常用的电脑摄像头是USB接口,主流的智能手机摄像头是MIPI接口,另外还有像树莓派等硬件使用的CSI接口的设备。常用的智能手机Camera采用的MIPI接口,电路框架以及电路图如下

MIPI接口CameraCamera传感器分前后两个,分别为前置摄像头和后置摄像头,两个camera都挂载在i2c总线上。

2 软件

linux设备文件

在linux中,所有的外设都被当做“文件”处理,就是我们常说的“设备文件”,可以像访问普通文件一样访问外设,对外设进行读写操作。linux驱动按照面向对象的不同划分 “面向字符设备”、“面向块设备”、“面向网络设备” 三大类设备。字符设备和块设备驱动在文件系统中形成类似的“虚拟文件“,即节点(node),在/dev目录下统一处理,系统调用open、read、write等进行操作,而面向网络接口的设备不会被映射到文件系统中,被分配的设备名类似eth0等等,它的驱动调用方式也不是open等。

V4l2驱动框架

V4l2(全称video for linux version2)是linux系统中的视频源捕获驱动框架。其广泛应用在嵌入式设备以及移动端、个人电脑设备上面,市面上的编码产品类如:SDV、手机、IPC、行车记录仪都会用到这个框架来进行视频采集。

在linux驱动中,V4l2是linux为视频设备提供的一套标准接口,主要包括一套V4l2相关的底层数据结构和V4l2相关的底层驱动的接口,作用是使得程序有发现和操作设备的能力。V4l2驱动框架分为 V4l2核心、V4l2下层接口两个部分,其中V4l2核心部分主要功能是构建内核中标准的视频设备驱动框架以及注册字符设备为上层用户提供访问接口,V4l2下层接口部分的主要功能是挂接底层硬件IC。

== 重要内容 ==(V4l2驱动框架分层)
第一层 V4l2核心驱动框架:video_device以及V4l2_device的创建、初始化、注册
第二层V4l2下层接口驱动框架:将camera sensor挂接到i2c总线上

v412驱动框架代码目录分布
//基于Allwinner linux 3.10版本内核分析
drivers/media/platform/sunxi-vfe/  :v4l2以及i2c注册等
drivers/media/platform/vivi.c :v4l2框架核心驱动(虚拟)
drivers/media/v4l2-core/... : v4l2框架中各个代码接口的实现
drivers/media/platform/sunxi-vfe/device/..	:底层硬件设备挂接

V4l2驱动的代码在drivers\media\v4l2-core文件夹下,打开目录可以看到如下一系列文件,看似很多其实可以归类。

  1. 比如:videobufxx_xxx.c系列文件是用于实现视频的内存分配,videobuf2_xxx系列文件是对应v4l2,而videobuf_xxx系列文件是对应于v4l(video for linux version1)。
  2. v4l2-dev.c对应于video_device的实现。
  3. v4l2-device.c对应 v4l2_device的实现。
  4. v4l2-ioctl.c对应 ioctl的实现。
  5. v4l2-subdev.c对应 42vl_subdev的实现等等。

vedio驱动代码位于drivers\media下,该路径下有很多子目录,其中比如i2c mmc usb tuners radio等目录都是对应各个subdev的实现,platform目录用于存放不同的soc的驱动代码。

        tuner-core.c
        v4l2-common.c 
        v4l2-compat-ioctl32.c
        v4l2-ctrls.c
        v4l2-dev.c //主要关于 video_device结构体的注册以及各种函数接口
        v4l2-device.c //主要关于 4vl2的设备支持 以及v4l2_device的注册以及各种函数接口
        v4l2-event.c
        v4l2-fh.c
        v4l2-int-device.c
        v4l2-ioctl.c //主要关于处理 v4l2设备的ioctl的通用框架
        v4l2-mem2mem.c 
        v4l2-of.c
        v4l2-subdev.c // 主要关于v4l2设备的子设备以及 v4l2_subdev的注册以及各种函数接口
        videobuf2-core.c
        videobuf2-dma-contig.c
        videobuf2-dma-sg.c
        videobuf2-vmalloc.c
        videobuf-core.c
        videobuf-dma-contig.c
        videobuf-dma-sg.c
        videobuf-dvb.c
        videobuf-vmalloc.c
        videobuf2-memops.c
        Kconfig
        Makefile

V4l2驱动框架图如下所示:

    用户空间       v42l 应用接口(open read write)
    -------------------------------------------------
    内核空间         
    第一层  v4l2核心驱动框架:drivers/media/platform/vivi.c
            v42l核心驱动 : 对应video_device(v4l2核心驱动框架)
            video设备驱动 : 对应v4l2_device(v4l2核心驱动框架)
    第二层  v4l2下层接口驱动框架:drivers/media/platform/sunxi-vfe/vfe.c
            video子设备驱动 :对应v4l2_subdev(v4l2下层接口驱动框架)
    -------------------------------------------------
               video设备接口
             -------------------
    物理底层   drivers/media/platform/sunxi-vfe/device/

    -------------------------------------------------

    v4l2核心:此部分视频设备核心。对应结构体video_device,是v4l2驱动框架向上层用户提供的接口
    video设备驱动:此部分驱动需要我们填充,代表一个v4l2设备,主要工作是根据 4vl2提供的驱动模型,完成对具体视频设备硬件底层实现,Linux提供4vl2示例代码:vivi.c
    video子设备驱动:4vl2驱动所连接的子设备,如摄像头传感器IC,解编码器IC,音频IC等等,对应结构体v4l2_subdev,是v4l2驱动框架向下提供的接口

    v42l 应用接口: 应用程序通过V4L2提供read()write()ioctl()编程接口,来控制操作视频设备,如:设置图像分辨率、视频数据格式、开始/结束视频捕获等等。
    video设备:视频设备按照输入输出来区分,摄像头属于输入设备,显示器属于输出设备
    video设备接口:本文讨论的是摄像头,所以此处的设备接口即摄像头接口,一般目前流行的camera接口有 mipi 和 usb,mipi是当前主流的便携式设备(手机)camera接口,当然还有其他接口比如CSI接口的硬件设备。
               本文项目也是mipi接口。一般的usb接口camera常见于台式或者笔记本上,usb协议有专门针对video设备的接口类

v412驱动核心结构体说明

struct video_device:保存管理V4l2_device设备数据,注册成功后以类似/dev/videoxxx的字符形式提供给上层,包含底层驱动结构体v4l2_device以及为上层提供的接口cdev。

struct v4l2_device:代表一个v4l2设备,保存设备实例的数据

struct v4l2_subdev:由于目前v4l2驱动随着需求以及硬件的变化越来越复杂,所以需要使用新的数据结构来进行数据处理。v4l2驱动需要支持音频,视频,解码,编码等ic,使得大部分v4l2设备都包含了多个子设备IC,例如,编解码器,传感器,摄像头控制器等,所以在/dev目录下不仅仅要建立v4l2的节点,还需要建立各个IC的设备节点,如fb、i2c、input、alsa等设备节点,通常这些IC通过i2c总线与主板连接,以上设备统称为sub-devices子设备,代表实实在在的camera等传感器设备。驱动需要与子设备进行通信,这些子系统一般情况下就是音频处理,编解码器等,camera常见的子设备有传感器和摄像头控制器,为了方便代码管理,内核v4l2提供了一个统一的接口给这些子设备,即v4l2_sbudev结构体。

//kernel/include/media/v4l2-dev.h
    struct video_device
    {
        const struct v4l2_file_operations *fops;//设备操作函数集
        struct cdev *cdev;      //字符设备 对应上层接口 open/read/write
        struct v4l2_device *v4l2_dev;   //v4l2 设备 
        int minor;    //system系统之 的 video-xxx的次设备号
        const struct v4l2_ioctl_ops *ioctl_ops;//具体功能的实现函数
        ...
        ...
    };

//kernel/include/media/v4l2-device.h
    struct v4l2_device {
        ...
        struct list_head subdevs;    //用链表管理注册的subdev
        ...
    };

//kernel/inlcude/media/v4l2-subdev.h
    struct v4l2_subdev {
        ...
        struct list_head list;     /* 链接至 v4l2_device */  
        struct v4l2_device *v4l2_dev; //struct v4l2_device
        const struct v4l2_subdev_ops *ops;//设备功能集
        ...
    };
    struct v4l2_subdev_ops {
        const struct v4l2_subdev_core_ops   *core;//通用操作集合
        const struct v4l2_subdev_tuner_ops  *tuner;//调谐器操作合集
        const struct v4l2_subdev_audio_ops  *audio;//音频操作合集
        const struct v4l2_subdev_video_ops  *video;//视频操作合集
        const struct v4l2_subdev_vbi_ops    *vbi;
        const struct v4l2_subdev_ir_ops     *ir;
        const struct v4l2_subdev_sensor_ops *sensor;
        const struct v4l2_subdev_pad_ops    *pad;
    };
open /dev/videoxx --> cdev->file_oprations ---> file_oprations->v4l2_file_oprations.open
v4l2核心驱动框架

内核提供了示例驱动:drivers/media/platform/vivi.c

主要工作:

  • 构建video_device结构,包括.fops以及.ioctl_ops
  • 初始化v4l2_device结构体(代表一个v4l2设备)
  • 注册video_device结构体(video_device是内核对v4l2_device的官方封装,用于管理v4l2_device数据),向上层用户提供访问接口
//v4l2_device设备的自定义封装
struct vivi_dev {
	struct list_head           vivi_devlist;
	struct v4l2_device     v4l2_dev;//v4l2_device设备
	struct video_device    vdev;
...
...
};

//file_operations的一个子集
static const struct v4l2_file_operations vivi_fops = {
	.owner      = THIS_MODULE,
	.open       = v4l2_fh_open,
	.release    = vb2_fop_release,
	.read       = vb2_fop_read,
	.poll       = vb2_fop_poll,
	.unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */
	.mmap       = vb2_fop_mmap,
};

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_enum_framesizes   = vidioc_enum_framesizes,
	.vidioc_reqbufs       = vb2_ioctl_reqbufs,
	.vidioc_create_bufs   = vb2_ioctl_create_bufs,
	.vidioc_prepare_buf   = vb2_ioctl_prepare_buf,
	.vidioc_querybuf      = vb2_ioctl_querybuf,
	.vidioc_qbuf          = vb2_ioctl_qbuf,
	.vidioc_dqbuf         = vb2_ioctl_dqbuf,
	.vidioc_enum_input    = vidioc_enum_input,
	.vidioc_g_input       = vidioc_g_input,
	.vidioc_s_input       = vidioc_s_input,
	.vidioc_enum_frameintervals = vidioc_enum_frameintervals,
	.vidioc_g_parm        = vidioc_g_parm,
	.vidioc_s_parm        = vidioc_s_parm,
	.vidioc_streamon      = vb2_ioctl_streamon,
	.vidioc_streamoff     = vb2_ioctl_streamoff,
	.vidioc_log_status    = v4l2_ctrl_log_status,
	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};

//struct video_device 的构造
static const struct video_device vivi_template = {
	.name       = "vivi",
	.fops       = &vivi_fops,//用户open相关内容
	.ioctl_ops  = &vivi_ioctl_ops, //用户iotrl对应的相关内容
	.release    = video_device_release_empty,
};

//注册camera设备
static int __init vivi_create_instance(int inst)
{
    struct vivi_dev *dev;
    struct video_device *vfd;
    int ret;

    //注册 v4l2_device设备,内部只是一些初始化v4l2_device行为,并无实质注册动作
    ret = v4l2_device_register(NULL, &dev->v4l2_dev);

    //注册 video_device设备: VFL_TYPE_GRABBER : 代表camera video设备类型
    ret = video_register_device(vfd, VFL_TYPE_GRABBER, video_nr);
}

Allwinner平台的核心驱动位于drivers/media/platform/sunxi-vfe

sunxi-vfe是Tina平台上的camera驱动框架,用于接收并行或者mipi接口的sensor信号或者是bt656格式的信号

VFE框架

  • 使用过程中可简单的看成是vfe模块+device模块+af driver+flash控制模块的方式;
  • vfe.c是驱动的主要功能实现,包括注册/注销、参数读取、与v4l2上层接口、与各device的下层接口、中断处理、buffer申请切换等;
  • device文件夹里面是各个sensor的器件层实现,一般包括上下电、初始化,各分辨率切换,yuv sensor包括绝大部分的v4l2定义的ioctl命令的实现;而raw sensor的话大部分ioctl命令在vfe层调用了ips的库实现,少数如曝光/增益调节会透过vfe层到实际器件层;
  • actuator文件夹内是各种vcm的驱动;
  • flash_light文件夹内是闪光灯控制接口的实现;
  • csi和mipi_csi为对csi接口和mipi接口的控制文件;
  • lib文件夹为isp的库文件;
/* 
 * vfe模块初始化
 * csi平台初始化、isp平台初始化、mipi平台初始化、flash平台初始化
 * 注册vfe_driver平台驱动
 */
 
//vfe_driver
static struct platform_driver vfe_driver = {
	.probe    = vfe_probe,	//匹配成功后调用
	.remove   = vfe_remove,
	.shutdown = vfe_shutdown,
	.driver = {
		.name   = VFE_MODULE_NAME,
		.owner  = THIS_MODULE,
		.of_match_table = sunxi_vfe_match, //匹配接口
		.pm     = &vfe_runtime_pm_ops,
	}
};
static const struct of_device_id sunxi_vfe_match[] = {
	{ .compatible = "allwinner,sunxi-vfe", },
	{},
};
static int vfe_probe(struct platform_device *pdev)
{
	//获取系统资源,获取配置参数,填充结构体,执行probe_work
	struct vfe_dev *dev;
	...
	INIT_DELAYED_WORK(&dev->probe_work, probe_work_handle);
	schedule_delayed_work(&dev->probe_work,msecs_to_jiffies(1));
}


//file_operations的一个子集
static const struct v4l2_file_operations vfe_fops = {
	.owner      	= THIS_MODULE,
	.open           = vfe_open,
	.release        = vfe_close,
	.read           = vfe_read,
	.poll           = vfe_poll,
	.unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */
	.mmap       	= vfe_mmap,
};

static const struct v4l2_ioctl_ops vfe_ioctl_ops = {
	.vidioc_querycap      = vidioc_querycap,
	.vidioc_enum_fmt_vid_cap  = vidioc_enum_fmt_vid_cap,
	.vidioc_enum_framesizes   = vidioc_enum_framesizes,
	.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_enum_input        = vidioc_enum_input,
	.vidioc_g_input           = vidioc_g_input,
	.vidioc_s_input           = vidioc_s_input,
	.vidioc_streamon          = vidioc_streamon,
	.vidioc_streamoff         = vidioc_streamoff,
	.vidioc_g_parm            = vidioc_g_parm,
	.vidioc_s_parm            = vidioc_s_parm,
	.vidioc_default      = vfe_param_handler,
};

//struct video_device 的构造
static struct video_device vfe_template[] = {
	[0] = {
		.name       = "vfe_0",
		.fops       = &vfe_fops,
		.ioctl_ops  = &vfe_ioctl_ops,
		.release    = video_device_release,
	},
	[1] = {
		...
	},
};

//注册camera设备
static void probe_work_handle(struct work_struct *work)
{
    struct vfe_dev *dev;
    struct video_device *vfd;
    int ret;

    //注册 v4l2_device设备,内部只是一些初始化v4l2_device行为,并无实质注册动作
    ret = v4l2_device_register(&dev->pdev->dev, &dev->v4l2_dev);

    //注册 video_device设备: VFL_TYPE_GRABBER : 代表camera video设备类型
    ret = video_register_device(vfd, VFL_TYPE_GRABBER, video_num);
}

v4l2下层接口驱动

不同的camera有不同的驱动,但是他都凌驾在i2c的控制器上。故实现camera的驱动,通常都是实现i2c_driver和i2c_client的相关内容。

//kernel/drivers/media/platform/sunxi-vfe/device/具体的元器件驱动
module_init(init_sensor);
static struct i2c_driver sensor_driver = {
	.driver = {
		.owner = THIS_MODULE,
		.name = SENSOR_NAME,	//"ov5640"
		.of_match_table = sernsor_match, //.compatible = "allwinner,sensor_ov5640"
	},
	.probe = sensor_probe,
	.remove = sensor_remove,
	.id_table = sensor_id,
};
static __init int init_sensor(void)
{
	//i2c_add_driver i2c驱动注册的封装函数
	return cci_dev_init_helper(&sensor_driver);
}
/****************************************/
static const struct v4l2_subdev_core_ops sensor_core_ops = {
	.g_chip_ident = sensor_g_chip_ident,
	.g_ctrl = sensor_g_ctrl,
	.s_ctrl = sensor_s_ctrl,
	.queryctrl = sensor_queryctrl,
	.reset = sensor_reset,
	.init = sensor_init,
	.s_power = sensor_power,
	.ioctl = sensor_ioctl,
};
static const struct v4l2_subdev_video_ops sensor_video_ops = {
	.enum_mbus_fmt = sensor_enum_fmt,
	.enum_framesizes = sensor_enum_size,
	.try_mbus_fmt = sensor_try_fmt,
	.s_mbus_fmt = sensor_s_fmt,
	.s_parm = sensor_s_parm,
	.g_parm = sensor_g_parm,
	.g_mbus_config = sensor_g_mbus_config,
};
static const struct v4l2_subdev_ops sensor_ops = {
	.core = &sensor_core_ops,
	.video = &sensor_video_ops,
};
/****************************************/
static int sensor_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	struct v4l2_subdev *sd;	//v4l2子设备
	struct sensor_info *info;	//传感器数据
	...
	cci_dev_probe_helper(sd, client, &sensor_ops, &cci_drv);	//i2c子设备初始化与sensor_ops绑定起来
	...
}

V4l2框架结构

Logo

更多推荐