fuzidage
专注嵌入式、linux驱动 、arm裸机研究

导航

 

1 架构

V4L2是Video for linux2的简称, linux中关于视频设备的内核驱动。在Linux中,视频设备是设备文件,可以像访问普通文件一样对其进行读写,V4L2注册的字符设备节点有2种:

  1. /dev/videoX 视频设备节点
  2. /dev/v4l-subdevX 子设备节点

V4L2一般支持3中采集方式:内存映射方式mmap,直接读取read, 用户指针userpointer方式。

1.1 层次关系

层次架构如下:
图1:
image
图2:
image

用户层:

  • 用户通过系统调用open(/dev/video_XXX), ioctl进入驱动。主要通过libv4l库来操作摄像头。

v4l2 core层:

  • 最上层:对接用户的v4l2_fops,v4l2_dev对接底层的v4l2_device
  • core层:v4l2_device主要是管理视频设备驱动、videobuf2-core主要是管理缓冲队列的数据(分配,释放,出队,入队等)、v4l2_sub_dev主要是管理视频设备的子系统,如: camera,vcodec, display.

soc video driver层:

  • video_device: 具体的视频设备,比如video0表示camera0, video1代表camera1, video2代码display0等等,和v4l2_device对接,v4l2_device管理具体的video_device,如:Linux提供v4l2示例代码:vivi.c
  • vb_queue:videobuf2-core层对接,利用vb2_ops进行交互

sensor_subdev层:

  • 具体的sensor驱动, 主要上电、提供工作时钟、视频图像裁剪、流IO开启等,实现各种设备控制方法供上层调用并注册v4l2_subdev。

主设备:

Camera Host控制器为主设备,负责图像数据的接收和传输. V4L2的主设备号是81,次设备号范围0~255.

从设备:

从设备为Camera Sensor,一般为I2C接口,可通过从设备控制Camera采集图像的行为,如图像的大小、图像的FPS等。

这些次设备号又分为多类设备:

视频设备(次设备号范围0-63)
Radio(收音机)设备(次设备号范围64-127)
Teletext设备(次设备号范围192-223)
VBI设备(次设备号范围224-255)

1.2 video device和v4l2 sub device

image

可以看到isp被定义成一个video device, 子模块cif i2c定义为v4l2 sub device.i2c也可以看作是一个v4l2 sub device.

  • v4l2_device表示一个v4l2实例,在V4L2驱动中,使用v4l2_device来表示摄像头控制器

  • v4l2_subdev来表示具体camera,也就是一个i2c_client.

  • v4l2_device里有一个v4l2_subdev链表,管理多个v4l2_subdev设备

1.3 video 主设备和video从设备

image

Video设备又分为主设备和从设备:

1.对于Camera来说,Camera Host控制器主设备,负责图像数据的接收和传输。
2.从设备Camera Sensor,一般为I2C接口。

主设备可通过v4l2_subdev_call的宏调用从设备提供的方法,反过来从设备可以调用主设备的notify回调方法通知主设备某些事件发生了。

core核心层则通过v4l2_file_operationsv4l2_ioctl_ops来控制video设备。

2 v4l2源码结构

2.1 一个完整的v4l2视频驱动包括

1.字符设备驱动:
V4L2本身就是一个字符设备,具有字符设备所有的特性,暴露接口给用户空间;

2.V4L2驱动核心:
主要是构建一个内核中标准视频设备驱动的框架,为视频操作提供统一的接口函数;

3.平台V4L2设备驱动:
在V4L2框架下,根据平台自身的特性实现与平台相关的V4L2驱动部分,包括注册video_device和v4l2_device;

4.具体的sensor驱动:
主要上电、提供工作时钟、视频图像裁剪、流IO开启等,实现各种设备控制方法供上层调用并注册v4l2_subdev。

2.2 源码目录结构

linux_5.10/drivers/media/v4l2-core$ ls ## v4l2 core框架核心
Kconfig        v4l2-compat-ioctl32.c  v4l2-fh.c               v4l2-jpeg.c     videobuf-core.c
Makefile       v4l2-ctrls.c           v4l2-flash-led-class.c  v4l2-mc.c       videobuf-dma-contig.c
tuner-core.c   v4l2-dev.c             v4l2-fwnode.c           v4l2-mem2mem.c  videobuf-dma-sg.c
v4l2-async.c   v4l2-device.c          v4l2-h264.c             v4l2-spi.c      videobuf-vmalloc.c
v4l2-clk.c     v4l2-dv-timings.c      v4l2-i2c.c              v4l2-subdev.c
v4l2-common.c  v4l2-event.c           v4l2-ioctl.c            v4l2-trace.c

linux_5.10/include/media$ ls  ## v4l2相关头文件
cec.h             dvb-usb-ids.h          rc-core.h          v4l2-fh.h               videobuf2-dma-sg.h
cec-notifier.h    dvb_vb2.h              rc-map.h           v4l2-flash-led-class.h  videobuf2-dvb.h
cec-pin.h         fwht-ctrls.h           tpg                v4l2-fwnode.h           videobuf2-memops.h
davinci           h264-ctrls.h           tuner.h            v4l2-h264.h             videobuf2-v4l2.h
demux.h           hevc-ctrls.h           tuner-types.h      v4l2-image-sizes.h      videobuf2-vmalloc.h
dmxdev.h          i2c                    tveeprom.h         v4l2-ioctl.h            videobuf-core.h
drv-intf          imx.h                  v4l2-async.h       v4l2-jpeg.h             videobuf-dma-contig.h
dvb_ca_en50221.h  media-dev-allocator.h  v4l2-clk.h         v4l2-mc.h               videobuf-dma-sg.h
dvb_demux.h       media-device.h         v4l2-common.h      v4l2-mediabus.h         videobuf-vmalloc.h
dvbdev.h          media-devnode.h        v4l2-ctrls.h       v4l2-mem2mem.h          vp8-ctrls.h
dvb_frontend.h    media-entity.h         v4l2-dev.h         v4l2-rect.h             vsp1.h
dvb_math.h        media-request.h        v4l2-device.h      v4l2-subdev.h
dvb_net.h         mpeg2-ctrls.h          v4l2-dv-timings.h  videobuf2-core.h
dvb_ringbuffer.h  rcar-fcp.h             v4l2-event.h       videobuf2-dma-contig.h

linux_5.10/drivers/media/platform$ ls  ##平台soc硬件video实例
am437x          exynos-gsc         mtk-jpeg       qcom           s3c-camif  sunxi
aspeed-video.c  fsl-viu.c          mtk-mdp        rcar_drif.c    s5p-g2d    ti-vpe
atmel           imx-pxp.c          mtk-vcodec     rcar-fcp.c     s5p-jpeg   via-camera.c
cadence         imx-pxp.h          mtk-vpu        rcar_fdp1.c    s5p-mfc    via-camera.h
coda            Kconfig            mx2_emmaprp.c  rcar_jpu.c     sh_vou.c   video-mux.c
m2m-deinterlace.c  omap           rcar-vin          vsp1
davinci         Makefile           omap3isp       renesas-ceu.c  sti        xilinx
exynos4-is      marvell-ccic       pxa_camera.c   rockchip       stm32

2.3 v4l2 core核心层说明

  1. v4l2-dev.c:对接上层VFS, 应用程序open(”/dev/videox“);的字符设备节点。

  2. v4l2-ioctl.c:ioctl命令相当的多,这里我们最多常用到的是buffer的申请

  3. v4l2-device.c:主要包含一些v4l2设备一些公共api,用于绑定v4l2设备结构体以及注册子设备节点。存在的作用就是便于访问video设备的子设备,因为它的数据域包含了一个struct list_head subdevs用于遍历子设备的。

  4. v4l2-fh.c:fh就是file handle的意思,就是消息队列。这些v4l2_fh结构体最终都会链接到strcut video_device中的struct list_head fh_list链表中,用于存放其它模块发过来的消息

  5. v4l2-subdev.c:所有操作子设备的集合。接口会被字符设备对应的接口调用。v4l2-dev.c 调用-->v4l2-subdev.c

  6. v4l2-ctrls.c:主要是视频设备控制操作的api集合

  7. videobuf2-core.c:v4l2的申请内存,暴露给具体平台设备驱动标准接口,层次是:

    device_driver(vivi.c)
    	->videobuf2-core.c
    		->videobuf2-vmalloc.c
    
模块 描述
核心模块 由v4l2-dev.c实现,主要作用包括申请字符主设备号、注册class和提供video device注册注册等相关函数。
V4L2框架 由v4l2-device.c、v4l2-subdev.c、v4l2-fh.c、v4l2-ctrls.c、v4l2-async.c、v4l2-fwnode.c、v4l2-i2c.c、v4l2-spi.c等文件实现,构建v4l2框架。
videobuf管理 由videobuf2-core.c、videobuf2-dma-contig.c、videobuf-dma-sg.c、videobuf2-memops.c、videobuf2-vmalloc.c、v4l2-mem2mem.c等文件实现,完成videobuffer的分配、管理和注销。
ioctl框架 由v4l2-ioctl.c、v4l2-compat-ioctl32.c 文件实现,构建v4l2_ioctl框架。

image

3 数据结构

3.1 不同结构体之间的关联

image

v4l2_device:对视频设备的整体进行抽象,可以看成是一个纽带,将各个子设备联系在一起,通常它会嵌入在其他结构体中以提供v4l2框架的功能,比如strcut isp_device;v4l2_device有一个subdevs链表存放子设备。v4l2_device有一个mdev指向media_device结构。

v4l2_subdev:对子设备进行抽象,该结构体中包含的struct v4l2_subdev_ops是一个完备的操作函数集,用于对接各种不同的子设备,比如video、audio、sensor等,同时还有一个核心的函数集struct v4l2_subdev_core_ops,提供更通用的功能。子设备驱动根据设备特点实现该函数集中的某些函数即可;

video_device:用于向系统注册字符设备节点,以便用户空间可以进行交互,包括各类设置以及数据buffer的获取等,在该结构体中也能看到struct v4l2_ioctl_opsstruct vb2_queue结构体字段,这些与上文中的应用层代码编写息息相关;

  • 如果子设备不需要与应用层交互,struct v4l2_subdev中内嵌的video_device也可以不向系统注册字符设备;

  • v4l2_devicev4l2_subdev来进行抽象,以v4l2_device来代表整个输入设备,以v4l2_subdev来代表子模块,比如CSISensor等;

3.1.0 v4l2_device

//include/media/v4l2-device.h
struct v4l2_device {
    struct device *dev; // 父设备指针
    #if defined(CONFIG_MEDIA_CONTROLLER) // 多媒体设备配置选项
    struct media_device *mdev;
    #endif
    struct list_head subdevs;
    spinlock_t lock;
    // 独一无二的设备名称,默认使用driver name + bus ID
    char name[V4L2_DEVICE_NAME_SIZE];
    void (*notify)(struct v4l2_subdev *sd, unsigned int notification, void *arg);
    // 提供子设备(主要是video和ISP设备)的控件操作接口,
    // 比如改变输出图像的亮度、对比度、饱和度等等
    struct v4l2_ctrl_handler *ctrl_handler;
    void (*release)(struct v4l2_device *v4l2_dev);
};

v4l2_device用来描述一个v4l2设备实例,可以包含多个子设备,对应的是例如 I2C、CSI、MIPI 等设备。

3.1.0.1 v4l2_device相关API

注册函数:

int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev);

image

可以看到一般注册v4l2_device还会设置mdev域,指向media_device实例。

// 注册v4l2_device结构体
// dev-父设备结构体指针,若为NULL,在注册之前设备名称name必须被设置,
int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
// 卸载注册的v4l2_device结构体
void v4l2_device_unregister(struct v4l2_device *v4l2_dev)
// 设置设备名称,填充v4l2_device结构体中的name成员
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);

3.1.1 v4l2_subdev

子设备就是像某一颗具体的camera, disp, vdec, venc都可以作为子设备。

image

3.1.1.1 初始化v4l2_subdev结构体

v4l2_subdev_init(sd, &ops); //初始化v4l2_subdev
//v4l2_i2c_subdev_init,对于i2c client设备,可以调用该函数

media_entity_pads_init(&sd->entity, npads, pads);//假如子设备还作为media_entity, 有用media前后级联,还需调用media_entity_pads_init
media_entity_pads_init(&sd->entity, 1, isp_sdev->pads);
media_entity_cleanup(&sd->entity);//销毁级联的entity pad

//set private data
v4l2_set_subdevdata(&dev->sd, dev);
//如果是i2c client设备,还可以用如下api设置 private data
static inline void i2c_set_clientdata(struct i2c_client *dev, void *data){
	dev_set_drvdata(&dev->dev, data);
}
static inline void *i2c_get_clientdata(const struct i2c_client *dev){
	return dev_get_drvdata(&dev->dev);
}

3.1.1.2 注册/注销subdev

int v4l2_device_register_subdev(v4l2_dev, sd);
void v4l2_device_unregister_subdev(sd);

3.1.1.3 子设备的异步动态注册--异步通知

早期的内核版本中,V4L2子设备的注册是通过静态定义和手动调用注册函数的方式完成的。然而,这种方法限制了子设备的动态添加和移除能力。所以为了支持更灵活的子设备注册和管理,以及异步通知机制,Linux内核引入了struct v4l2_async_notifier结构体,该结构体和相关的异步通知机制允许驱动程序在运行时动态添加或移除子设备,并通知V4L2核心进行注册或注销。例如:AHD camera, 支持热插拔的摄像头,插入时注册子设备,拔出删除子设备。

3.1.1.3.1 v4l2_async_notifier
struct v4l2_async_notifier {
 const struct v4l2_async_notifier_operations *ops;//异步通知操作的函数指针,用于处理注册和注销子设备的回调函数。
 struct v4l2_device *v4l2_dev;
 struct v4l2_subdev *sd;
 struct v4l2_async_notifier *parent;
 struct list_head asd_list;
 struct list_head waiting;
 struct list_head done;
 struct list_head list;
};

v4l2_async_notifier_parse_fwnode_endpoints(struct device *dev,
					   struct v4l2_async_notifier *notifier,
					   size_t asd_struct_size,
					   parse_endpoint_func parse_endpoint);//解析endpoint并注册到media模块

//新版本api
int v4l2_async_notifier_register(struct v4l2_device *v4l2_dev, struct v4l2_async_notifier *notifier);//新版本,将异步通知器注册到V4L2设备
v4l2_async_notifier_register(&isp_dev->v4l2_dev, notifier);
int v4l2_async_subdev_notifier_register(struct v4l2_subdev *sd,
					struct v4l2_async_notifier *notifier);
void v4l2_async_notifier_unregister(struct v4l2_async_notifier *notifier);//注销异步通知器

//旧版本api
int v4l2_async_nf_register(struct v4l2_device *v4l2_dev, struct v4l2_async_notifier *notifier);//旧版本,将异步通知器注册到V4L2设备
void v4l2_async_nf_unregister(struct v4l2_async_notifier *notifier);//注销异步通知器

//异步subdev设备的话,一般注册也是用异步的注册子设备函数
int v4l2_async_register_subdev(struct v4l2_subdev * sd);

举个例子对camera sensor imx585来说,调用一个v4l2_async_register_subdev_sensor_common即可。

v4l2_async_register_subdev_sensor_common:

image

imx585 subdev注册:
image

3.1.1.3.1.1 v4l2_async_notifier_operations
struct v4l2_async_notifier_operations {
	int (*bound)(struct v4l2_async_notifier *notifier,
		     struct v4l2_subdev *subdev,
		     struct v4l2_async_subdev *asd);//// 设备绑定时的处理函数,比如配置v4l2_mbus_config,配置v4l2_mbus_type
	int (*complete)(struct v4l2_async_notifier *notifier);//异步设备列表完成时的处理函数
	void (*unbind)(struct v4l2_async_notifier *notifier,
		       struct v4l2_subdev *subdev,
		       struct v4l2_async_subdev *asd);//设备解绑时的处理函数
};

3.1.1.2 v4l2_subdev_ops

跳转到v4l2_subdev_ops

3.1.1.3 从设备通知主设备

如果子设备需要通知它的v4l2_device主设备一个事件:

v4l2_subdev_notify(sd,notification,arg);

//include/media/v4l2-device.h
// 从设备通知主设备,最终回调到v4l2_device的notify函数
static inline void v4l2_subdev_notify(struct v4l2_subdev *sd,
	unsigned int notification, void *arg) {
    if (sd && sd->v4l2_dev && sd->v4l2_dev->notify)
    	sd->v4l2_dev->notify(sd, notification, arg);
}

3.1.2 video_device

struct video_device
{
    const struct v4l2_file_operations *fops;
    struct cdev *cdev; //vdev->cdev->ops = &v4l2_fops; 字符设备描述符
    struct v4l2_device *v4l2_dev;
    struct v4l2_ctrl_handler *ctrl_handler;
    struct vb2_queue *queue;
    const struct v4l2_ioctl_ops *ioctl_ops;
    …………
};

3.1.2.1 video_device注册

跳转到video_device注册

3.1.2.2 v4l2_ioctl_ops

跳转到v4l2_ioctl_ops

3.2 v4l2设备类型

/**
 * enum vfl_devnode_type - type of V4L2 device node
 *
 * @VFL_TYPE_VIDEO:	for video input/output devices
 * @VFL_TYPE_VBI:	for vertical blank data (i.e. closed captions, teletext)
 * @VFL_TYPE_RADIO:	for radio tuners
 * @VFL_TYPE_SUBDEV:	for V4L2 subdevices
 * @VFL_TYPE_SDR:	for Software Defined Radio tuners
 * @VFL_TYPE_TOUCH:	for touch sensors
 * @VFL_TYPE_MAX:	number of VFL types, must always be last in the enum
 */
enum vfl_devnode_type {
	VFL_TYPE_VIDEO,//视频
	VFL_TYPE_VBI,//字幕等
	VFL_TYPE_RADIO,//高频头
	VFL_TYPE_SUBDEV,//v4l2子设备
	VFL_TYPE_SDR,
	VFL_TYPE_TOUCH,//触摸传感器
	VFL_TYPE_MAX /* Shall be the last one */
};
vfl_devnode_type Device name Usage
VFL_TYPE_VIDEO /dev/videoX for video input/output devices
VFL_TYPE_VBI /dev/vbiX for vertical blank data (i.e. closed captions, teletext)
VFL_TYPE_RADIO /dev/radioX for radio tuners
VFL_TYPE_SUBDEV /dev/v4l-subdevX for V4L2 subdevices
VFL_TYPE_SDR /dev/swradioX for Software Defined Radio (SDR) tuners
VFL_TYPE_TOUCH /dev/v4l-touchX for touch sensors

3.3 v4l2_buf类型

enum v4l2_buf_type {
	V4L2_BUF_TYPE_VIDEO_CAPTURE        = 1,
	V4L2_BUF_TYPE_VIDEO_OUTPUT         = 2,
	V4L2_BUF_TYPE_VIDEO_OVERLAY        = 3,
	V4L2_BUF_TYPE_VBI_CAPTURE          = 4,
	V4L2_BUF_TYPE_VBI_OUTPUT           = 5,
	V4L2_BUF_TYPE_SLICED_VBI_CAPTURE   = 6,
	V4L2_BUF_TYPE_SLICED_VBI_OUTPUT    = 7,
	V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY = 8,
	V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE = 9,
	V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE  = 10,
	V4L2_BUF_TYPE_SDR_CAPTURE          = 11,
	V4L2_BUF_TYPE_SDR_OUTPUT           = 12,
	V4L2_BUF_TYPE_META_CAPTURE         = 13,
	V4L2_BUF_TYPE_META_OUTPUT	   = 14,
	/* Deprecated, do not use */
	V4L2_BUF_TYPE_PRIVATE              = 0x80,
};
  1. video capture interface(捕获): 视频采集接口,这种接口应用于摄像头,v4l2在最初设计的时候就是应用于这种功能
  2. video output interface(输出): 视频输出接口,将静止图像或图像序列编码为模拟视频信号,通过此接口,应用程序可以控制编码过程 并将图像从用户空间移动到驱动程序
  3. video overlay interface(预览): 视频直接传输接口,可以将采集到的视频数据直接传输到显示设备,不需要cpu参与,这种方式的显示图 像的效率比其他方式高得多

3.3.1 v4l2_mbus_framefmt-像素格式

https://www.kernel.org/doc/html/v4.9/media/uapi/v4l/subdev-formats.html#v4l2-mbus-pixelcode

包含了RGB格式,Bayer Format RAW 格式,YUV格式等。

3.3.2 v4l2_mbus_type-媒体设备接口类型

/**
 * enum v4l2_mbus_type - media bus type
 * @V4L2_MBUS_UNKNOWN:	unknown bus type, no V4L2 mediabus configuration
 * @V4L2_MBUS_PARALLEL:	parallel interface with hsync and vsync
 * @V4L2_MBUS_BT656:	parallel interface with embedded synchronisation, can
 *			also be used for BT.1120
 * @V4L2_MBUS_CSI1:	MIPI CSI-1 serial interface
 * @V4L2_MBUS_CCP2:	CCP2 (Compact Camera Port 2)
 * @V4L2_MBUS_CSI2_DPHY: MIPI CSI-2 serial interface, with D-PHY
 * @V4L2_MBUS_CSI2_CPHY: MIPI CSI-2 serial interface, with C-PHY
 * @V4L2_MBUS_INVALID:	invalid bus type (keep as last)
 */
enum v4l2_mbus_type {
	V4L2_MBUS_UNKNOWN,
	V4L2_MBUS_PARALLEL,
	V4L2_MBUS_BT656,
	V4L2_MBUS_CSI1,
	V4L2_MBUS_CCP2,
	V4L2_MBUS_CSI2_DPHY,
	V4L2_MBUS_CSI2_CPHY,
	V4L2_MBUS_INVALID,
};

3.4 v4l2_ioctl_info

位于drivers\media\v4l2-core\v4l2-ioctl.c是用户到内核下cmd中转的固定静态结构体变量:

static const struct v4l2_ioctl_info v4l2_ioctls[] = {
	IOCTL_INFO(VIDIOC_QUERYCAP, v4l_querycap, v4l_print_querycap, 0),
	IOCTL_INFO(VIDIOC_ENUM_FMT, v4l_enum_fmt, v4l_print_fmtdesc, 0),
	IOCTL_INFO(VIDIOC_G_FMT, v4l_g_fmt, v4l_print_format, 0),
	IOCTL_INFO(VIDIOC_S_FMT, v4l_s_fmt, v4l_print_format, INFO_FL_PRIO),
	IOCTL_INFO(VIDIOC_REQBUFS, v4l_reqbufs, v4l_print_requestbuffers, INFO_FL_PRIO | INFO_FL_QUEUE),
	IOCTL_INFO(VIDIOC_QUERYBUF, v4l_querybuf, v4l_print_buffer, INFO_FL_QUEUE | INFO_FL_CLEAR(v4l2_buffer, length)),
	IOCTL_INFO(VIDIOC_G_FBUF, v4l_stub_g_fbuf, v4l_print_framebuffer, 0),
	IOCTL_INFO(VIDIOC_S_FBUF, v4l_stub_s_fbuf, v4l_print_framebuffer, INFO_FL_PRIO),
	IOCTL_INFO(VIDIOC_OVERLAY, v4l_overlay, v4l_print_u32, INFO_FL_PRIO),
	IOCTL_INFO(VIDIOC_QBUF, v4l_qbuf, v4l_print_buffer, INFO_FL_QUEUE),
	IOCTL_INFO(VIDIOC_EXPBUF, v4l_stub_expbuf, v4l_print_exportbuffer, INFO_FL_QUEUE | INFO_FL_CLEAR(v4l2_exportbuffer, flags)),
	IOCTL_INFO(VIDIOC_DQBUF, v4l_dqbuf, v4l_print_buffer, INFO_FL_QUEUE),
	IOCTL_INFO(VIDIOC_STREAMON, v4l_streamon, v4l_print_buftype, INFO_FL_PRIO | INFO_FL_QUEUE),
	IOCTL_INFO(VIDIOC_STREAMOFF, v4l_streamoff, v4l_print_buftype, INFO_FL_PRIO | INFO_FL_QUEUE),
	IOCTL_INFO(VIDIOC_G_PARM, v4l_g_parm, v4l_print_streamparm, INFO_FL_CLEAR(v4l2_streamparm, type)),
	IOCTL_INFO(VIDIOC_S_PARM, v4l_s_parm, v4l_print_streamparm, INFO_FL_PRIO),
	IOCTL_INFO(VIDIOC_G_STD, v4l_stub_g_std, v4l_print_std, 0),
	IOCTL_INFO(VIDIOC_S_STD, v4l_s_std, v4l_print_std, INFO_FL_PRIO),
	IOCTL_INFO(VIDIOC_ENUMSTD, v4l_enumstd, v4l_print_standard, INFO_FL_CLEAR(v4l2_standard, index)),
	IOCTL_INFO(VIDIOC_ENUMINPUT, v4l_enuminput, v4l_print_enuminput, INFO_FL_CLEAR(v4l2_input, index)),
	IOCTL_INFO(VIDIOC_G_CTRL, v4l_g_ctrl, v4l_print_control, INFO_FL_CTRL | INFO_FL_CLEAR(v4l2_control, id)),
	IOCTL_INFO(VIDIOC_S_CTRL, v4l_s_ctrl, v4l_print_control, INFO_FL_PRIO | INFO_FL_CTRL),
......
};

3.4.1 v4l2_ioctl_ops

struct v4l2_ioctl_ops {
	/* ioctl callbacks */

	/* VIDIOC_QUERYCAP handler */
	int (*vidioc_querycap)(struct file *file, void *fh,
			       struct v4l2_capability *cap);

	/* VIDIOC_ENUM_FMT handlers */
	int (*vidioc_enum_fmt_vid_cap)(struct file *file, void *fh,
				       struct v4l2_fmtdesc *f);
	int (*vidioc_enum_fmt_vid_overlay)(struct file *file, void *fh,
					   struct v4l2_fmtdesc *f);
	int (*vidioc_enum_fmt_vid_out)(struct file *file, void *fh,
				       struct v4l2_fmtdesc *f);
	int (*vidioc_enum_fmt_sdr_cap)(struct file *file, void *fh,
				       struct v4l2_fmtdesc *f);
	int (*vidioc_enum_fmt_sdr_out)(struct file *file, void *fh,
				       struct v4l2_fmtdesc *f);
	int (*vidioc_enum_fmt_meta_cap)(struct file *file, void *fh,
					struct v4l2_fmtdesc *f);
	int (*vidioc_enum_fmt_meta_out)(struct file *file, void *fh,
					struct v4l2_fmtdesc *f);

	/* VIDIOC_G_FMT handlers */
	int (*vidioc_g_fmt_vid_cap)(struct file *file, void *fh,
				    struct v4l2_format *f);
	int (*vidioc_g_fmt_vid_overlay)(struct file *file, void *fh,
					struct v4l2_format *f);
	int (*vidioc_g_fmt_vid_out)(struct file *file, void *fh,
				    struct v4l2_format *f);
	int (*vidioc_g_fmt_vid_out_overlay)(struct file *file, void *fh,
					    struct v4l2_format *f);
	int (*vidioc_g_fmt_vbi_cap)(struct file *file, void *fh,
				    struct v4l2_format *f);
	int (*vidioc_g_fmt_vbi_out)(struct file *file, void *fh,
				    struct v4l2_format *f);
	int (*vidioc_g_fmt_sliced_vbi_cap)(struct file *file, void *fh,
					   struct v4l2_format *f);
	int (*vidioc_g_fmt_sliced_vbi_out)(struct file *file, void *fh,
					   struct v4l2_format *f);
	int (*vidioc_g_fmt_vid_cap_mplane)(struct file *file, void *fh,
					   struct v4l2_format *f);
	int (*vidioc_g_fmt_vid_out_mplane)(struct file *file, void *fh,
					   struct v4l2_format *f);
	int (*vidioc_g_fmt_sdr_cap)(struct file *file, void *fh,
				    struct v4l2_format *f);
	int (*vidioc_g_fmt_sdr_out)(struct file *file, void *fh,
				    struct v4l2_format *f);
	int (*vidioc_g_fmt_meta_cap)(struct file *file, void *fh,
				     struct v4l2_format *f);
	int (*vidioc_g_fmt_meta_out)(struct file *file, void *fh,
				     struct v4l2_format *f);
........

我们可以通过字符设备 /dev/videox直接或者间接调用到v4l2_ioctls[],再转接到具体v4l2_ioctl_ops.

image

ioctl的流程见:

v4l2 ioctl调用层次

3.5 subdev_ioctl

v4l2_subdev支持的命令,/dev/v4l-subdevx支持的命令如下:

位于drivers\media\v4l2-core\v4l2-subdev.c:

static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg){
	struct video_device *vdev = video_devdata(file);
	struct v4l2_subdev *sd = vdev_to_v4l2_subdev(vdev);
	struct v4l2_fh *vfh = file->private_data;
	struct v4l2_subdev_fh *subdev_fh = to_v4l2_subdev_fh(vfh);
	bool ro_subdev = test_bit(V4L2_FL_SUBDEV_RO_DEVNODE, &vdev->flags);
	int rval;

	switch (cmd) {
	case VIDIOC_SUBDEV_QUERYCAP: 
	case VIDIOC_QUERYCTRL:
	case VIDIOC_QUERY_EXT_CTRL:
	case VIDIOC_QUERYMENU:
	case VIDIOC_G_CTRL:
	case VIDIOC_S_CTRL:
	case VIDIOC_G_EXT_CTRLS:
	case VIDIOC_S_EXT_CTRLS:
	case VIDIOC_TRY_EXT_CTRLS:
	case VIDIOC_DQEVENT:
	case VIDIOC_DQEVENT_TIME32: 
	case VIDIOC_SUBSCRIBE_EVENT:
	case VIDIOC_UNSUBSCRIBE_EVENT:
	case VIDIOC_LOG_STATUS: 
	case VIDIOC_SUBDEV_G_FMT:
	case VIDIOC_SUBDEV_S_FMT: 
	case VIDIOC_SUBDEV_G_CROP:
	case VIDIOC_SUBDEV_S_CROP: 
......
	case VIDIOC_SUBDEV_ENUM_MBUS_CODE: {
		struct v4l2_subdev_mbus_code_enum *code = arg;

		memset(code->reserved, 0, sizeof(code->reserved));
		return v4l2_subdev_call(sd, pad, enum_mbus_code, subdev_fh->pad,
					code);
	}
	case VIDIOC_SUBDEV_ENUM_FRAME_SIZE: {
		struct v4l2_subdev_frame_size_enum *fse = arg;

		memset(fse->reserved, 0, sizeof(fse->reserved));
		return v4l2_subdev_call(sd, pad, enum_frame_size, subdev_fh->pad,
					fse);
	}
	case VIDIOC_SUBDEV_G_FRAME_INTERVAL:
.......
	return 0;
}

3.5.1 v4l2_subdev_ops

https://www.cnblogs.com/fuzidage/p/18462540#v4l2_subdev_ops实例

/**
 * struct v4l2_subdev_ops - Subdev operations
 *
 * @core: pointer to &struct v4l2_subdev_core_ops. Can be %NULL
 * @tuner: pointer to &struct v4l2_subdev_tuner_ops. Can be %NULL
 * @audio: pointer to &struct v4l2_subdev_audio_ops. Can be %NULL
 * @video: pointer to &struct v4l2_subdev_video_ops. Can be %NULL
 * @vbi: pointer to &struct v4l2_subdev_vbi_ops. Can be %NULL
 * @ir: pointer to &struct v4l2_subdev_ir_ops. Can be %NULL
 * @sensor: pointer to &struct v4l2_subdev_sensor_ops. Can be %NULL
 * @pad: pointer to &struct v4l2_subdev_pad_ops. Can be %NULL
 */
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;
};

调用v4l2_subdev_init就会注册/dev/v4l-subdevx,把v4l2_subdev_ops实例注册进去:

image

3.5.1.1 调用层次

subdev_ioctl
	subdev_do_ioctl
	//通过v4l2_subdev_call转接到具体的v4l2_subdev_ops实例的成员函数,包括
    //v4l2_subdev_core_ops,v4l2_subdev_video_ops,
    //v4l2_subdev_sensor_ops v4l2_subdev_pad_ops等

image

3.5.1.2 v4l2_subdev_core_ops

参考[v4l2实例.md的3.2 v4l2_subdev_ops实例]
https://www.cnblogs.com/fuzidage/p/18462540#v4l2_subdev_ops实例

3.5.1.3 v4l2_subdev_video_ops

参考[v4l2实例.md的3.2 v4l2_subdev_ops实例]
https://www.cnblogs.com/fuzidage/p/18462540#v4l2_subdev_ops实例

3.5.1.4 v4l2_subdev_sensor_ops

参考[v4l2实例.md的3.2 v4l2_subdev_ops实例]
https://www.cnblogs.com/fuzidage/p/18462540#v4l2_subdev_ops实例

3.5.1.5 v4l2_subdev_pad_ops

参考[v4l2实例.md的3.2 v4l2_subdev_ops实例]

3.5.1.6 internal_ops

驱动不会调用这些接口,只有v4l2 framework会调用这些ops。

  1. registered:在这个subdev被registered的时候被调用。
  2. open:在这个subdev的用户态节点被open的时候被调用。
  3. close:当用户态节点被close的时候被调用,要注意unregistered调用之后可能会调用close回调

4 v4l2调用流程

4.1 用户态v4l2调用流程

image

  1. 打开设备,查询设备能力集,设置video input参数,视频采集方式、格式。
  2. requst buffermmap获得访问地址, 送图往底层QBUF
  3. 启动流媒体STREAMON
  4. 将数据取出(DQBUF),处理(process), 放回(QBUF),这一步骤循环操作
  5. 关闭流媒体STREAMOFF,Munmap ,close

4.1.1 v4l2用户ioctl交互命令

//v4l2-ioctl.h去include videodev2.h
    
/*
 *	I O C T L   C O D E S   F O R   V I D E O   D E V I C E S
 *
 */
#define VIDIOC_QUERYCAP		 _IOR('V',  0, struct v4l2_capability)/* 获取设备支持的操作 /
#define VIDIOC_ENUM_FMT         _IOWR('V',  2, struct v4l2_fmtdesc)
#define VIDIOC_G_FMT		_IOWR('V',  4, struct v4l2_format)/ 获取设置支持的视频格式 /
#define VIDIOC_S_FMT		_IOWR('V',  5, struct v4l2_format)/ 设置捕获视频的格式 /
#define VIDIOC_REQBUFS		_IOWR('V',  8, struct v4l2_requestbuffers)/ 向驱动提出申请内存的请求 /
#define VIDIOC_QUERYBUF		_IOWR('V',  9, struct v4l2_buffer)/ 向驱动查询申请到的内存 /
#define VIDIOC_G_FBUF		 _IOR('V', 10, struct v4l2_framebuffer)
#define VIDIOC_S_FBUF		 _IOW('V', 11, struct v4l2_framebuffer)
#define VIDIOC_OVERLAY		 _IOW('V', 14, int)
#define VIDIOC_QBUF		_IOWR('V', 15, struct v4l2_buffer)/ 将空闲的内存加入可捕获视频的队列 /
#define VIDIOC_EXPBUF		_IOWR('V', 16, struct v4l2_exportbuffer)
#define VIDIOC_DQBUF		_IOWR('V', 17, struct v4l2_buffer)/ 将已经捕获好视频的内存拉出已捕获视频的队列 /
#define VIDIOC_STREAMON		 _IOW('V', 18, int)/ 打开视频流 /
#define VIDIOC_STREAMOFF	 _IOW('V', 19, int)/ 关闭视频流 /
#define VIDIOC_G_PARM		_IOWR('V', 21, struct v4l2_streamparm)
#define VIDIOC_S_PARM		_IOWR('V', 22, struct v4l2_streamparm)
#define VIDIOC_G_STD		 _IOR('V', 23, v4l2_std_id)
#define VIDIOC_S_STD		 _IOW('V', 24, v4l2_std_id)
#define VIDIOC_ENUMSTD		_IOWR('V', 25, struct v4l2_standard)
#define VIDIOC_ENUMINPUT	_IOWR('V', 26, struct v4l2_input)
#define VIDIOC_G_CTRL		_IOWR('V', 27, struct v4l2_control)/ 获取当前命令值 /
#define VIDIOC_S_CTRL		_IOWR('V', 28, struct v4l2_control)/ 设置新的命令值 /
#define VIDIOC_G_TUNER		_IOWR('V', 29, struct v4l2_tuner) / 获取调谐器信息 /
#define VIDIOC_S_TUNER		 _IOW('V', 30, struct v4l2_tuner)/ 设置调谐器信息 /
#define VIDIOC_G_AUDIO		 _IOR('V', 33, struct v4l2_audio)
#define VIDIOC_S_AUDIO		 _IOW('V', 34, struct v4l2_audio)
#define VIDIOC_QUERYCTRL	_IOWR('V', 36, struct v4l2_queryctrl) / 查询驱动是否支持该命令 /
#define VIDIOC_QUERYMENU	_IOWR('V', 37, struct v4l2_querymenu)
#define VIDIOC_G_INPUT		 _IOR('V', 38, int)
#define VIDIOC_S_INPUT		_IOWR('V', 39, int)
#define VIDIOC_G_EDID		_IOWR('V', 40, struct v4l2_edid)
#define VIDIOC_S_EDID		_IOWR('V', 41, struct v4l2_edid)
#define VIDIOC_G_OUTPUT		 _IOR('V', 46, int)
#define VIDIOC_S_OUTPUT		_IOWR('V', 47, int)
#define VIDIOC_ENUMOUTPUT	_IOWR('V', 48, struct v4l2_output)
#define VIDIOC_G_AUDOUT		 _IOR('V', 49, struct v4l2_audioout)
#define VIDIOC_S_AUDOUT		 _IOW('V', 50, struct v4l2_audioout)
#define VIDIOC_G_MODULATOR	_IOWR('V', 54, struct v4l2_modulator)
#define VIDIOC_S_MODULATOR	 _IOW('V', 55, struct v4l2_modulator)
#define VIDIOC_G_FREQUENCY	_IOWR('V', 56, struct v4l2_frequency)
#define VIDIOC_S_FREQUENCY	 _IOW('V', 57, struct v4l2_frequency)
#define VIDIOC_CROPCAP		_IOWR('V', 58, struct v4l2_cropcap)
#define VIDIOC_G_CROP		_IOWR('V', 59, struct v4l2_crop)
#define VIDIOC_S_CROP		 _IOW('V', 60, struct v4l2_crop)
#define VIDIOC_G_JPEGCOMP	 _IOR('V', 61, struct v4l2_jpegcompression)
#define VIDIOC_S_JPEGCOMP	 _IOW('V', 62, struct v4l2_jpegcompression)
#define VIDIOC_QUERYSTD		 _IOR('V', 63, v4l2_std_id)
#define VIDIOC_TRY_FMT		_IOWR('V', 64, struct v4l2_format)
#define VIDIOC_ENUMAUDIO	_IOWR('V', 65, struct v4l2_audio)
#define VIDIOC_ENUMAUDOUT	_IOWR('V', 66, struct v4l2_audioout)
#define VIDIOC_G_PRIORITY	 _IOR('V', 67, __u32) /* enum v4l2_priority */
#define VIDIOC_S_PRIORITY	 _IOW('V', 68, __u32) /* enum v4l2_priority */

4.1.2 ioctl命令说明(常用)

命令太多,仅说明常用命令。

4.1.2.1 VIDIOC_QUERYCAP

查询设备的功能。获得设备支持的功能(capture、output、overlay…)

4.1.2.2 VIDIOC_S_PRIORITY/VIDIOC_G_PRIORITY

当多个应用程序共享设备时,可能需要为它们分配不同的优先级。

4.1.2.3 输入和输出设备

VIDIOC_ENUMINPUT //枚举视频输入设备有多少个
VIDIOC_G_INPUT //获取当前的视频输入设备
VIDIOC_S_INPUT //设置视频输入设备
VIDIOC_ENUMOUTPUT //枚举视频输出设备
VIDIOC_G_OUTPUT //获取当前视频输出设备
VIDIOC_S_OUTPUT //设置视频输出设备
VIDIOC_ENUMAUDIO //枚举音频输入设备
VIDIOC_G_AUDIO //获取当前音频输入设备
VIDIOC_S_AUDIO //设置音频输入设备
VIDIOC_ENUMAUDOUT //枚举音频输出设备
VIDIOC_G_OUTPUT //获取音频输出设备
VIDIOC_S_AUDOUT //设置音频输出设备

一个 video 设备节点可能对应多个视频源。如果上层想在多个视频输入间切换,那么就要调用ioctl(fd, VIDIOC_S_INPUT, &input)来切换。

struct v4l2_input {
    __u32 index; /* Which input */
    __u8 name[32]; /* Label */
    __u32 type; /* Type of input */
    __u32 audioset; /* Associated audios (bitfield) */
    __u32 tuner; /* Associated tuner */
    v4l2_std_id std;
    __u32 status;
    __u32 reserved[4];
};

我们可以通过VIDIOC_ENUMINPUT分别列举一个input的信息。

4.1.2.3.1 VIDIOC_S_INPUT

v4l2 ioctl调用层次

找到v4l2_ioctls[]:调用具体v4l2_ioctl_ops中的videoc_s_input函数。

image

4.1.2.4 图像控制属性

VIDIOC_QUERYCTRL //查询指定的control详细信息
VIDIOC_QUERYMENU //查询menu
VIDIOC_G_CTRL //获取设备指定control的当前信息
VIDIOC_S_CTRL //设置设备指定的control

4.1.2.5 图像格式

VIDIOC_ENUM_FMT //枚举设备支持的图像格式
VIDIOC_G_FMT //获取当前设备的图像格式
VIDIOC_S_FMT //设置图像格式
VIDIOC_TRY_FMT //测试设备是否支持此格式
struct v4l2_pix_format {
    u32 width; // 帧宽,单位像素
    u32 height; // 帧高,单位像素
    u32 pixelformat; // 帧格式
    enum v4l2_field field;
    u32 bytesperline;
    u32 sizeimage;
    enum v4l2_colorspace colorspace;
    u32 priv;
};

struct v4l2_format {
	enum v4l2_buf_type type; // 帧类型,应用程序设置
    union fmt {
        struct v4l2_pix_format pix; // 视频设备使用
        struct v4l2_window win;
        struct v4l2_vbi_format vbi;
        struct v4l2_sliced_vbi_format sliced;
        u8 raw_data[200];
    };
};

struct v4l2_fmtdesc{
    u32 index; // 要查询的格式序号,应用程序设置
    enum v4l2_buf_type type; // 帧类型,应用程序设置
    u32 flags; // 是否为压缩格式
    u8 description[32]; // 格式名称
    u32 pixelformat; // 格式
    u32 reserved[4]; // 保留
};

rgb和yuv格式汇总如下:

2.9.1. Packed RGB formats — The Linux Kernel documentation

2.10. YUV Formats — The Linux Kernel documentation

4.1.2.6 图像裁剪缩放

VIDIOC_CROPCAP //获取图像裁剪缩放能力
VIDIOC_G_CROP //获取当前的裁剪矩阵
VIDIOC_S_CROP //设置裁剪矩阵
    
//裁剪缩放能力 对应命令VIDIOC_CROPCAP
struct v4l2_cropcap {
    enum v4l2_buf_type type; // 数据流的类型,应用程序设置
    struct v4l2_rect bounds; // 这是 camera 的镜头能捕捉到的窗口大小的局限
    struct v4l2_rect defrect; // 定义默认窗口大小,包括起点位置及长,宽的大小,大小以像素为单位
    struct v4l2_fract pixelaspect; // 定义了图片的宽高比
};

//裁剪属性, 对应命令VIDIOC_S_CROP
struct v4l2_crop {
    enum v4l2_buf_type type;
    struct v4l2_rect c;
}

4.1.2.7 vb buffer输入输出

VIDIOC_REQBUFS //申请缓存
VIDIOC_QUERYBUF //获取缓存信息
VIDIOC_QBUF //将缓存放入队列中
VIDIOC_DQBUF //将缓存从队列中取出

1.申请缓冲区 VIDIOC_REQBUFS

struct v4l2_requestbuffers{
    u32 count; // 缓冲区内缓冲帧的数目
    enum v4l2_buf_type type; // 缓冲帧数据格式
    enum v4l2_memory memory; // 区别是内存映射还是用户指针方式
    u32 reserved[2];
};
enum v4l2_memoy {
	V4L2_MEMORY_MMAP, V4L2_MEMORY_USERPTR
};

2.获取vb的地址,长度信息。VIDIOC_QUERYBUF

struct v4l2_buffer
{
    u32 index; //buffer 序号
    enum v4l2_buf_type type; //buffer 类型
    u32 byteused; //buffer 中已使用的字节数
    u32 flags; // 区分是MMAP 还是USERPTR
    enum v4l2_field field;
    struct timeval timestamp; // 获取第一个字节时的系统时间
    struct v4l2_timecode timecode;
    u32 sequence; // 队列中的序号
    enum v4l2_memory memory; //IO 方式,被应用程序设置
    union m	{
        u32 offset; // 缓冲帧地址,只对MMAP 有效
        unsigned long userptr;
    };
    u32 length; // 缓冲帧长度
    u32 input;
    u32 reserved;
};

3.内存映射MMAP 及定义一个结构体来映射每个vb buffer。

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)
/* 参数:
addr 映射起始地址,一般为NULL ,让内核自动选择
length 被映射内存块的长度
prot 标志映射后能否被读写,其值为PROT_EXEC,PROT_READ,PROT_WRITE, PROT_NONE
flags 确定此内存映射能否被其他进程共享,MAP_SHARED,MAP_PRIVATE
fd,offset, 确定被映射的内存地址 返回成功映射后的地址,不成功返回MAP_FAILED ((void*)-1)
*/

4.vb buffer输入输出, VIDIOC_QBUF,VIDIOC_DQBUF

struct v4l2_buffer v4l2_buffer;
for(i = 0; i < nr_bufs; i++) {
    memset(&v4l2_buffer, 0, sizeof(struct v4l2_buffer));
    v4l2_buffer.index = i; //想要放入队列的缓存
    v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    v4l2_buffer.memory = V4L2_MEMORY_MMAP;
    ret = ioctl(fd, VIDIOC_QBUF, &v4l2_buffer);
    if(ret < 0) {
        printf("Unable to queue buffer.\n");
        return -1;
    }
}

4.1.2.8 数据启停

VIDIOC_STREAMON, VIDIOC_STREAMOFF

type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd, VIDIOC_STREAMON, &type);

4.1.3 应用流程举例

image

1.打开/dev/video0/dev/v4l-subdev3,查询设备能力,打印出是video capture设备

image

2.VIDIOC_S_FMT命令设置fmt=nv21, buf_type=V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE等参数。

VIDIOC_REQBUFS申请4张buffer, memory_type=V4L2_MEMORY_MMAP

dev->buffers = (struct buffer *)calloc(dev->req_count, sizeof(*(dev->buffers)));用户空间分连续内存4张buffer。进行mmap。

image

3.VIDIOC_QBUF送4张v4l2_buffer下去。

VIDIOC_SUBDEV_S_FRAME_INTERVAL/dev/v4l-subdev3进行设置。细节见[v4l2实例.md的3.2 v4l2_subdev_ops实例的3.2.2.2 s_frame_interval]

VIDIOC_STREAMON启动抓捕图像。

image

4.VIDIOC_DQBUF取出v4l2_buffer,然后再VIDIOC_QBUF重新送往驱动vbq。最后VIDIOC_STREAMOFF停止流,关闭/dev/video0/dev/v4l-subdev3.关于buffer的

4.2 内核态v4l2调用流程

4.2.1 video_device注册

image

  1. 首先建立struct video_device实例,比如用来描述ISP,初始化isp硬件参数,私有数据;实现fops函数集。

  2. 通过video_register_device向提供注册;

    1. 设置cdev->ops=&v4l2_fops

    2. cdev_add,按照字符设备驱动框架注册,主设备号81

      #define VIDEO_MAJOR 81

    3. 创建类

    4. 注册sysfs

4.2.2 v4l2 ioctl调用层次

image

一步步进来看:

image

platform/rockchip/rga/rga.c:435:        .unlocked_ioctl = video_ioctl2,
platform/xilinx/xilinx-dma.c:639:       .unlocked_ioctl = video_ioctl2,
platform/rcar_fdp1.c:2184:      .unlocked_ioctl = video_ioctl2,
platform/fsl-viu.c:1336:        .unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */
platform/via-camera.c:741:      .unlocked_ioctl = video_ioctl2,
platform/atmel/atmel-isi.c:951: .unlocked_ioctl = video_ioctl2,
platform/atmel/atmel-isc-base.c:1643:   .unlocked_ioctl = video_ioctl2,
test-drivers/vimc/vimc-capture.c:201:   .unlocked_ioctl = video_ioctl2,
test-drivers/vivid/vivid-core.c:625:    .unlocked_ioctl = video_ioctl2,
test-drivers/vivid/vivid-core.c:636:    .unlocked_ioctl = video_ioctl2,
// video_ioctl2的实例定义一般在linux_5.10/drivers/media/platform下,例如rockchip的rga:(rga_fops.unlocked_ioctl=video_ioctl2)
static const struct v4l2_file_operations rga_fops = {
	.owner = THIS_MODULE,
	.open = rga_open,
	.release = rga_release,
	.poll = v4l2_m2m_fop_poll,
	.unlocked_ioctl = video_ioctl2,
	.mmap = v4l2_m2m_fop_mmap,
};
//再到__video_do_ioctl到v4l2_ioctls, 调用具体的实例struct v4l2_ioctl_ops, 
static const struct v4l2_ioctl_info v4l2_ioctls[] = {
	IOCTL_INFO(VIDIOC_QUERYCAP, v4l_querycap, v4l_print_querycap, 0),
	IOCTL_INFO(VIDIOC_ENUM_FMT, v4l_enum_fmt, v4l_print_fmtdesc, 0),
	IOCTL_INFO(VIDIOC_G_FMT, v4l_g_fmt, v4l_print_format, 0),
	IOCTL_INFO(VIDIOC_S_FMT, v4l_s_fmt, v4l_print_format, INFO_FL_PRIO),
	IOCTL_INFO(VIDIOC_REQBUFS, v4l_reqbufs, v4l_print_requestbuffers, INFO_FL_PRIO | INFO_FL_QUEUE),
	IOCTL_INFO(VIDIOC_QUERYBUF, v4l_querybuf, v4l_print_buffer, INFO_FL_QUEUE | INFO_FL_CLEAR(v4l2_buffer, length)),
	IOCTL_INFO(VIDIOC_G_FBUF, v4l_stub_g_fbuf, v4l_print_framebuffer, 0),
	IOCTL_INFO(VIDIOC_S_FBUF, v4l_stub_s_fbuf, v4l_print_framebuffer, INFO_FL_PRIO),
	IOCTL_INFO(VIDIOC_OVERLAY, v4l_overlay, v4l_print_u32, INFO_FL_PRIO),
	IOCTL_INFO(VIDIOC_QBUF, v4l_qbuf, v4l_print_buffer, INFO_FL_QUEUE),
	IOCTL_INFO(VIDIOC_EXPBUF, v4l_stub_expbuf, v4l_print_exportbuffer, INFO_FL_QUEUE | INFO_FL_CLEAR(v4l2_exportbuffer, flags)),
	IOCTL_INFO(VIDIOC_DQBUF, v4l_dqbuf, v4l_print_buffer, INFO_FL_QUEUE),
	IOCTL_INFO(VIDIOC_STREAMON, v4l_streamon, v4l_print_buftype, INFO_FL_PRIO | INFO_FL_QUEUE),
	IOCTL_INFO(VIDIOC_STREAMOFF, v4l_streamoff, v4l_print_buftype, INFO_FL_PRIO | INFO_FL_QUEUE),
	IOCTL_INFO(VIDIOC_G_PARM, v4l_g_parm, v4l_print_streamparm, INFO_FL_CLEAR(v4l2_streamparm, type)),
	IOCTL_INFO(VIDIOC_S_PARM, v4l_s_parm, v4l_print_streamparm, INFO_FL_PRIO),
	IOCTL_INFO(VIDIOC_G_STD, v4l_stub_g_std, v4l_print_std, 0),
	IOCTL_INFO(VIDIOC_S_STD, v4l_s_std, v4l_print_std, INFO_FL_PRIO),
	IOCTL_INFO(VIDIOC_ENUMSTD, v4l_enumstd, v4l_print_standard, INFO_FL_CLEAR(v4l2_standard, index)),
	IOCTL_INFO(VIDIOC_ENUMINPUT, v4l_enuminput, v4l_print_enuminput, INFO_FL_CLEAR(v4l2_input, index)),
	IOCTL_INFO(VIDIOC_G_CTRL, v4l_g_ctrl, v4l_print_control, INFO_FL_CTRL | INFO_FL_CLEAR(v4l2_control, id)),
	IOCTL_INFO(VIDIOC_S_CTRL, v4l_s_ctrl, v4l_print_control, INFO_FL_PRIO | INFO_FL_CTRL),
	IOCTL_INFO(VIDIOC_G_TUNER, v4l_g_tuner, v4l_print_tuner, INFO_FL_CLEAR(v4l2_tuner, index)),
	IOCTL_INFO(VIDIOC_S_TUNER, v4l_s_tuner, v4l_print_tuner, INFO_FL_PRIO),
	IOCTL_INFO(VIDIOC_G_AUDIO, v4l_stub_g_audio, v4l_print_audio, 0),
	IOCTL_INFO(VIDIOC_S_AUDIO, v4l_stub_s_audio, v4l_print_audio, INFO_FL_PRIO),
	......
};
//__video_do_ioctl函数中:
info = &v4l2_ioctls[_IOC_NR(cmd)];
ret = info->func(ops, file, fh, arg);

//最终调用到这里,比如rockchip的rga:c
static const struct v4l2_ioctl_ops rga_ioctl_ops = {
	.vidioc_querycap = vidioc_querycap,
	.vidioc_enum_fmt_vid_cap = vidioc_enum_fmt,
	.vidioc_g_fmt_vid_cap = vidioc_g_fmt,
	.vidioc_try_fmt_vid_cap = vidioc_try_fmt,
	.vidioc_s_fmt_vid_cap = vidioc_s_fmt,
	.vidioc_enum_fmt_vid_out = vidioc_enum_fmt,
	.vidioc_g_fmt_vid_out = vidioc_g_fmt,
	.vidioc_try_fmt_vid_out = vidioc_try_fmt,
	.vidioc_s_fmt_vid_out = vidioc_s_fmt,
	.vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs,
	.vidioc_querybuf = v4l2_m2m_ioctl_querybuf,
	.vidioc_qbuf = v4l2_m2m_ioctl_qbuf,
	.vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf,
	.vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf,
	.vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs,
	.vidioc_expbuf = v4l2_m2m_ioctl_expbuf,
	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
	.vidioc_streamon = v4l2_m2m_ioctl_streamon,
	.vidioc_streamoff = v4l2_m2m_ioctl_streamoff,
	.vidioc_g_selection = vidioc_g_selection,
	.vidioc_s_selection = vidioc_s_selection,
};
```

当然也可以自己去实现这些v4l2_ioctl_ops,比如上图中的isp_video_ioctl_ops

5 引入Media Framework

当多个v4l2设备级联,需要构建成pipeLine关系时,需要引入Media Controller Framework来描述和控制多媒体硬件中复杂的信号流和处理拓扑,而V4L2则用于具体的视频帧采集和处理。

5.0 media拓扑结构

比如下面的pipeLine流程:

image

image

模块之间相互独立,模块用struct media_entity进行抽象:

struct media_entity:包含设备的通用数据、名称、类型、功能、标志、端口数量和链接信息。每个 media_entity 有多个 media_pad(端口)和 media_link(连接其他实体的链接),共同构成设备的拓扑结构。实体通过 media_device_initmedia_device_register 注册,使用完毕后通过 media_device_cleanup 清理;

struct media_pad:pad可以认为是端口,与其他模块进行联系的媒介;每个模块分别有source pad和sink pad构成数据通路;

struct media_link: pad通过link来建立连接,指定source和sink,即可将通路建立起来;数据通路选择问题,可以在驱动初始化的时候进行链接创建,比如isp_create_links

因此,只需要将struct media_entity嵌入到特定子模块中,最终便可以将子模块串联起来,构成数据流。

那么dts中的各个entity关系如下:

image

首先是camera sensor,这里是gc4653节点, 该节点只有一个port, 所以pad为0,out表示该pad为source pad,in表示sink pad。节点里面有一个remote-endpoint,表示绑定的外部entity(也就是cif_in_ep)。

然后是cif_v4l2节点,节点里面有一个cif_in_ep,反过来它的remote-endpoint就为gc4653_out

同理cif_out_episp_in_ep也是这样的关系。

5.1 media framework数据结构

5.1.1 media device结构体

image

  • media_device:与v4l2_device类似,也是负责将各个子模块集中进行管理,同时在注册的时候,会向系统注册设备节点,方便用户层进行操作;
  • media_entity、media_pad、media_link这几个结构体会添加到media_device的链表中,同时它们结构体的开始字段都需是struct media_gobj,该结构中的mdev将会指向它所属的media_device。这种设计方便结构之间的查找;
  • media_entity中包含多个media_pad,同时media_pad又会指向它所属的media_entity
  • 上图最底下2个结构体,media_graphmedia_entity的集合, media_pipeline包含media_graph

5.2 media pipeline API

/* 初始化entity的pads */
int media_entity_pads_init(struct media_entity *entity, u16 num_pads,
        struct media_pad *pads);

/* 在两个entity之间创建link */
int media_create_pad_links(const struct media_device *mdev,
      const u32 source_function,
      struct media_entity *source,
      const u16 source_pad,
      const u32 sink_function,
      struct media_entity *sink,
      const u16 sink_pad,
      u32 flags,
      const bool allow_both_undefined);
/*media_graph的初始化*/
__must_check int media_graph_walk_init(
	struct media_graph *graph, struct media_device *mdev);
/* 开始graph的遍历,从指定的entity开始 */
void media_graph_walk_start(struct media_graph *graph,
       struct media_entity *entity);
/*获取下一个 media_entity*/
struct media_entity *media_graph_walk_next(struct media_graph *graph);
/* 启动pipeline */
__must_check int media_pipeline_start(struct media_entity *entity,
          struct media_pipeline *pipe);
/*media_graph反初始化*/
void media_graph_walk_cleanup(struct media_graph *graph);

5.3 media ioctl

/* ioctls */  //Y:\linux_5.10\include\uapi\linux\media.h
#define MEDIA_IOC_DEVICE_INFO	_IOWR('|', 0x00, struct media_device_info)//获取media_device_info
#define MEDIA_IOC_ENUM_ENTITIES	_IOWR('|', 0x01, struct media_entity_desc)//枚举media_entity
#define MEDIA_IOC_ENUM_LINKS	_IOWR('|', 0x02, struct media_links_enum)//枚举media_link
#define MEDIA_IOC_SETUP_LINK	_IOWR('|', 0x03, struct media_link_desc)//MEDIA_IOC_SETUP_LINK
#define MEDIA_IOC_G_TOPOLOGY	_IOWR('|', 0x04, struct media_v2_topology)
#define MEDIA_IOC_REQUEST_ALLOC	_IOR ('|', 0x05, int)

#define MEDIA_LNK_FL_ENABLED			(1 << 0)//使能link

5.4 用户态media framework示例

请参考:https://git.ideasonboard.org/media-ctl.git

6 videobuf2机制

6.0 结构体

6.0.1 v4l2_buffer

struct v4l2_buffer {
	__u32			index;
	__u32			type;
	__u32			bytesused;
	__u32			flags;
	__u32			field;
#ifdef __KERNEL__
	struct __kernel_v4l2_timeval timestamp;
#else
	struct timeval		timestamp;
#endif
	struct v4l2_timecode	timecode;
	__u32			sequence;

	/* memory location */
	__u32			memory;
	union {
		__u32           offset;
		unsigned long   userptr;
		struct v4l2_plane *planes;
		__s32		fd;
	} m;
	__u32			length;
	__u32			reserved2;
	union {
		__s32		request_fd;
		__u32		reserved;
	};
};

6.0.2 vb2_buffer

struct vb2_buffer {
	struct vb2_queue	*vb2_queue;
	unsigned int		index;
	unsigned int		type;
	unsigned int		memory;
	unsigned int		num_planes;
	u64			timestamp;
	struct media_request	*request;
	struct media_request_object	req_obj;
	enum vb2_buffer_state	state;
	unsigned int		synced:1;
	unsigned int		prepared:1;
	unsigned int		copied_timestamp:1;
	unsigned int		need_cache_sync_on_prepare:1;
	unsigned int		need_cache_sync_on_finish:1;
	struct vb2_plane	planes[VB2_MAX_PLANES];
	struct list_head	queued_entry;
	struct list_head	done_entry;
	......
};

6.0.3 vb2_queue

struct vb2_queue {
	unsigned int			type;
	unsigned int			io_modes;
	struct device			*dev;
	unsigned long			dma_attrs;
	unsigned int			bidirectional:1;
	unsigned int			fileio_read_once:1;
	unsigned int			fileio_write_immediately:1;
	unsigned int			allow_zero_bytesused:1;
	unsigned int		   quirk_poll_must_check_waiting_for_buffers:1;
	unsigned int			supports_requests:1;
	unsigned int			requires_requests:1;
	unsigned int			uses_qbuf:1;
	unsigned int			uses_requests:1;
	unsigned int			allow_cache_hints:1;

	const struct vb2_ops		*ops;
	const struct vb2_mem_ops	*mem_ops;
	const struct vb2_buf_ops	*buf_ops;
	unsigned int			memory;
	enum dma_data_direction		dma_dir;
	struct vb2_buffer		*bufs[VB2_MAX_FRAME];
	unsigned int			num_buffers;
	struct list_head		queued_list;
	unsigned int			queued_count;
	atomic_t			owned_by_drv_count;
	struct list_head		done_list;
	spinlock_t			done_lock;
	wait_queue_head_t		done_wq;
	struct vb2_fileio_data		*fileio;
	struct vb2_threadio_data	*threadio;
	char				name[32];
	......
};

V4L2的buffer管理是通过videobuf2来完成的,它充当用户空间和驱动之间的中间层,并提供low-level,模块化的内存管理功能;

6.0.3.1 vb2_mem_ops

struct vb2_mem_ops {
	void		*(*alloc)(struct device *dev, unsigned long attrs,
				  unsigned long size,
				  enum dma_data_direction dma_dir,
				  gfp_t gfp_flags);
	void		(*put)(void *buf_priv);
	struct dma_buf *(*get_dmabuf)(void *buf_priv, unsigned long flags);

	void		*(*get_userptr)(struct device *dev, unsigned long vaddr,
					unsigned long size,
					enum dma_data_direction dma_dir);
	void		(*put_userptr)(void *buf_priv);

	void		(*prepare)(void *buf_priv);
	void		(*finish)(void *buf_priv);

	void		*(*attach_dmabuf)(struct device *dev,
					  struct dma_buf *dbuf,
					  unsigned long size,
					  enum dma_data_direction dma_dir);
	void		(*detach_dmabuf)(void *buf_priv);
	int		(*map_dmabuf)(void *buf_priv);
	void		(*unmap_dmabuf)(void *buf_priv);

	void		*(*vaddr)(void *buf_priv);
	void		*(*cookie)(void *buf_priv);

	unsigned int	(*num_users)(void *buf_priv);

	int		(*mmap)(void *buf_priv, struct vm_area_struct *vma);
};

video buffer支持三种类型的struct vb2_mem_ops

  • vb2_dma_sg_memops(DMA scatter/gather memory):在虚拟地址和物理地址上都是分散。几乎所有用户空间buffer都是这种类型,在内核空间中,这种类型的buffer并不总是能够满足需要,因为这要求硬件可以进行分散的DMA操作。
  • vb2_vmalloc_memops(vmalloc memory):虚拟地址连续,物理分散。也就是通过vmalloc分配的buffer,换句话说很难使用DMA来操作这些buffer。
  • vb2_dma_contig_memops(DMA contig memory):在分段式系统上面分配这种类型的buffer是不可靠的,但是简单DMA控制器只能够适用于这种类型的buffer。

image

image

6.0.3.2 vb2_buf_ops

struct vb2_buf_ops {
	int (*verify_planes_array)(struct vb2_buffer *vb, const void *pb);//验证是否包含足够的Planes
	void (*init_buffer)(struct vb2_buffer *vb);
	void (*fill_user_buffer)(struct vb2_buffer *vb, void *pb);//给定一个 vb2_buffer 填充 userspace 结构
	int (*fill_vb2_buffer)(struct vb2_buffer *vb, struct vb2_plane *planes);//给定一个userspace填充vb2_buffer
	void (*copy_timestamp)(struct vb2_buffer *vb, const void *pb);
};

image

6.0.3.3 vb2_ops

struct vb2_ops {
	int (*queue_setup)(struct vb2_queue *q,
			   unsigned int *num_buffers, unsigned int *num_planes,
			   unsigned int sizes[], struct device *alloc_devs[]);//VIDIOC_REQBUFS 和 VIDIOC_CREATE_BUFS
	void (*wait_prepare)(struct vb2_queue *q);
	void (*wait_finish)(struct vb2_queue *q);
	int (*buf_out_validate)(struct vb2_buffer *vb);
	int (*buf_init)(struct vb2_buffer *vb);//在分配缓冲区后调用一次
	int (*buf_prepare)(struct vb2_buffer *vb);//在每次将缓冲区入队之前调用
	void (*buf_finish)(struct vb2_buffer *vb);//在每次将缓冲区出队之前调用
	void (*buf_cleanup)(struct vb2_buffer *vb);//在释放缓冲区之前调用一次
	int (*start_streaming)(struct vb2_queue *q, unsigned int count);//启动stream
	void (*stop_streaming)(struct vb2_queue *q);//停止stream
	void (*buf_queue)(struct vb2_buffer *vb);
	void (*buf_request_complete)(struct vb2_buffer *vb);//将缓冲区 Vb 传递给驱动程序
};

以TI的omap为例,可以看到实例是一个omap_vout_vb2_ops

image

6.1 videobuf2数据流程图

image

  • struct vb2_buffer:一张视频内存,简单点就叫vb;
  • vb2_queue:描述vb2_buffer的队列,其中struct vb2_buffer *bufs[]是存放buffer节点的数组,该数组中的成员代表了vb2 buffer,并将在queued_listdone_list两个队列中进行流转;(入队(enqueue)就是将vb2_buffer加入queued_list,同理出队(dequeue)就是vb2_queuedone_list取出vb2_buffer)
    • struct vb2_buf_ops:buffer的操作函数集,由驱动来实现,并由框架通过call_bufop宏来对特定的函数进行调用;
    • struct vb2_mem_ops:内存buffer分配函数接口,buffer类型分为三种:
      • 1)虚拟地址和物理地址都分散,可以通过dma-sg来完成;
      • 2)物理地址分散,虚拟地址连续,可以通过vmalloc分配;
      • 3)物理地址连续,可以通过dma-contig来完成;三种类型也vb2框架中都有实现,框架可以通过call_memop来进行调用;一般使用第三种,因为图像处理engine的dma操作都需要物理地址连续。
    • struct vb2_ops:vb2队列操作函数集,由驱动来实现对应的接口,并在框架中通过call_vb_qop宏被调用;

soc板卡驱动Driver specific层则需要实现vb_buf_opsvb2_ops

6.1.1 buffer申请

image

当用户VIDIOC_REQBUFS时,调用到板卡驱动的isp_video_reqbufs,或者直接带入v4l2自带的vb2_ioctl_reqbufs ,或者带入v4l2_m2m_reqbufs。本质上都是去调用vb2_core_reqbufs:

6.1.1.1 vb2_core_reqbufs流程

板卡驱动中自行实现vb2_ops.queue_setup.

image

然后调用__vb2_queue_alloc分配videobuffer.

	for (buffer = 0; buffer < num_buffers; ++buffer) {
		/* Allocate videobuf buffer structures */
		vb = kzalloc(q->buf_struct_size, GFP_KERNEL);//(1)分配结构体
		if (!vb) {
			dprintk(q, 1, "memory alloc for buffer struct failed\n");
			break;
		}
		//__vb2_buf_mem_alloc展开
        for (plane = 0; plane < vb->num_planes; ++plane) {
            /* Memops alloc requires size to be page aligned. */
            unsigned long size = PAGE_ALIGN(vb->planes[plane].length);

            /* Did it wrap around? */
            if (size < vb->planes[plane].length)
                goto free;

            mem_priv = call_ptr_memop(vb, alloc,
                    q->alloc_devs[plane] ? : q->dev,
                    q->dma_attrs, size, q->dma_dir, q->gfp_flags);
        }
	}

call_ptr_memop(vb,alloc)继续展开:调用板卡驱动的struct vb2_mem_ops的.alloc,如果板卡驱动没有定义那么有3种内存分配方式:

image

  1. vmalloc方式:
417 const struct vb2_mem_ops vb2_vmalloc_memops = {
418         .alloc          = vb2_vmalloc_alloc,
419         .put            = vb2_vmalloc_put,
420         .get_userptr    = vb2_vmalloc_get_userptr,
421         .put_userptr    = vb2_vmalloc_put_userptr,
}
  1. dma contig方式:
/*********************************************/
/*       DMA CONTIG exported functions       */
/*********************************************/
const struct vb2_mem_ops vb2_dma_contig_memops = {
	.alloc		= vb2_dc_alloc,
	.put		= vb2_dc_put,
	.get_dmabuf	= vb2_dc_get_dmabuf,
	.cookie		= vb2_dc_cookie,
	.vaddr		= vb2_dc_vaddr,
	.mmap		= vb2_dc_mmap,
	.get_userptr	= vb2_dc_get_userptr,
	.put_userptr	= vb2_dc_put_userptr,
	.prepare	= vb2_dc_prepare,
	.finish		= vb2_dc_finish,
	.map_dmabuf	= vb2_dc_map_dmabuf,
	.unmap_dmabuf	= vb2_dc_unmap_dmabuf,
	.attach_dmabuf	= vb2_dc_attach_dmabuf,
	.detach_dmabuf	= vb2_dc_detach_dmabuf,
	.num_users	= vb2_dc_num_users,
};
  1. dma sg方式:
const struct vb2_mem_ops vb2_dma_sg_memops = {
	.alloc		= vb2_dma_sg_alloc,
	.put		= vb2_dma_sg_put,
	.get_userptr	= vb2_dma_sg_get_userptr,
	.put_userptr	= vb2_dma_sg_put_userptr,
	.prepare	= vb2_dma_sg_prepare,
	.finish		= vb2_dma_sg_finish,
	.vaddr		= vb2_dma_sg_vaddr,
	.mmap		= vb2_dma_sg_mmap,
	.num_users	= vb2_dma_sg_num_users,
	.get_dmabuf	= vb2_dma_sg_get_dmabuf,
	.map_dmabuf	= vb2_dma_sg_map_dmabuf,
	.unmap_dmabuf	= vb2_dma_sg_unmap_dmabuf,
	.attach_dmabuf	= vb2_dma_sg_attach_dmabuf,
	.detach_dmabuf	= vb2_dma_sg_detach_dmabuf,
	.cookie		= vb2_dma_sg_cookie,
};

6.1.2 buffer enqueue

v4l2_ioctl
	->v4l_qbuf
		->vb2_ioctl_qbuf//驱动也可以自行实现如isp_video_qbuf
			->vb2_qbuf
				->vb2_core_qbuf
					->rga_buf_queue

image

当用户VIDIOC_QBUF时, 比如调用到板卡驱动isp_video_qbuf,调用vb2_qbuf.

vb2_queue_or_prepare_buf用来将准备好struct vb2_buffer将器转换成struct vb2_v4l2_buffer.

vb2_core_qbuf先完成enqueue入列前的准备工作__buf_prepare。注意vb2_buffer有3种类型:

6.1.2.0 vb2_buffer存储类型

enum vb2_memory {
	VB2_MEMORY_UNKNOWN	= 0,
	VB2_MEMORY_MMAP		= 1,
	VB2_MEMORY_USERPTR	= 2,
	VB2_MEMORY_DMABUF	= 4,
};

6.1.2.1 v4l2_buffer存储类型

enum v4l2_memory {
	V4L2_MEMORY_MMAP             = 1,
	V4L2_MEMORY_USERPTR          = 2,
	V4L2_MEMORY_OVERLAY          = 3,
	V4L2_MEMORY_DMABUF           = 4,
};

​ 然后调用call_vb_qop(vb, buf_prepare,vb),调用板卡驱动的isp_video_buffer_prepare完成buffer的len, addr, stride等参数配置。

入队本质就是把vb2_buffer加入vb2_queue中的queued_list队列,如下:

image

int vb2_core_qbuf(struct vb2_queue *q, unsigned int index, void *pb,
		  struct media_request *req){
	...
	list_add_tail(&vb->queued_entry, &q->queued_list);
	q->queued_count++;
	vb->state = VB2_BUF_STATE_QUEUED;
}

然后__enqueue_in_driver完成入列,调用板卡驱动的struct vb2_ops rga_qops.buf_queue函数完成入队。

image

6.1.3 buffer dequeue

image

当用户VIDIOC_DQBUF时, 比如调用板卡驱动的isp_video_dqbuf

omap3isp/ispvideo.c:1281:       .vidioc_dqbuf                   = isp_video_dqbuf,
mtk-jpeg/mtk_jpeg_core.c:611:   .vidioc_dqbuf                   = v4l2_m2m_ioctl_dqbuf,
omap/omap_vout.c:1277:  .vidioc_dqbuf                           = vb2_ioctl_dqbuf,

可看到可以自行实现.vidioc_dqbuf,上图为omap3isp/ispvideo.cisp_video_dqbuf函数, 也可以用v4l2自带的函数如v4l2_m2m_ioctl_dqbuf,vb2_ioctl_dqbuf。(omap3isp是TI德州仪器的图像信号处理器 (ISP) 驱动,除了TI还有很多nxp,mtk等等)

isp_video_dqbuf v4l2_m2m_ioctl_dqbuf vb2_ioctl_dqbuf这三个函数本质都是调用vb2_dqbuf, 而vb2_dqbuf本质就是取出vb2_queue中done_list中的vb2_buffer,进行dequeue:

image

6.1.4 stream on

image

当用户VIDIOC_STREAMON时, 比如调用板卡驱动的isp_video_streamon

  1. media_pipeline_start(&video->video.entity, &pipe->pipe);启动媒体pipeline.c

  2. isp_video_get_graph_data(video, pipe);进行media pipeline的资料参数获取,详细见5.2 media pipeline API

  3. vb2_streamon(&vb2_queue, type);

    1. v4l_vb2q_enable_media_source(q);调用media_deviceenable_source:

image

2. 调用`vb2_start_streaming(q);`

    1. `__enqueue_in_driver`,进入板卡驱动调用`isp_video_buffer_queue`, driver负责填满buffer

    2. `isp_video_start_streaming`,调用`omap3isp_pipeline_set_stream`启动pipeline, 前面media pipeline定义好了pipeline,在`isp_create_links`的时候建立entrys关联:

image

        1. 调用`isp_pipeline_enable`调用`pipeline`中各个子模块`subdev`的回调函数,例如下面的`aewb`子模块:

image

最终就到了寄存器配置环节了,enable hardware

参考资料

Media subsystem kernel internal API(媒体内核态api):
https://www.kernel.org/doc/html/v4.10/media/media_kapi.html
https://docs.kernel.org/driver-api/media/
媒体基础设施用户空间 API:
https://docs.kernel.org/userspace-api/media/index.html

Video for Linux API version 2 specification(V4L2用户态api)
https://docs.kernel.org/userspace-api/media/v4l/v4l2.html
https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/v4l2.html

posted on 2024-10-13 16:29  fuzidage  阅读(231)  评论(0编辑  收藏  举报