1 架构
V4L2是Video for linux2的简称, linux中关于视频设备的内核驱动。在Linux中,视频设备是设备文件,可以像访问普通文件一样对其进行读写,V4L2注册的字符设备节点有2种:
/dev/videoX 视频设备节点
/dev/v4l-subdevX 子设备节点
V4L2一般支持3中采集方式:内存映射方式mmap,直接读取read, 用户指针userpointer
方式。
1.1 层次关系
层次架构如下:
图1:
图2:
用户层:
- 用户通过系统调用
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.cvb_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
可以看到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从设备
Video设备又分为主设备和从设备:
1.对于Camera来说,Camera Host控制器
为主设备
,负责图像数据的接收和传输。
2.从设备
为Camera Sensor
,一般为I2C接口。
主设备可通过v4l2_subdev_call
的宏调用从设备提供的方法,反过来从设备可以调用主设备的notify回调
方法通知主设备某些事件发生了。
core核心层则通过v4l2_file_operations
和v4l2_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核心层说明
-
v4l2-dev.c:对接上层VFS, 应用程序
open(”/dev/videox“);
的字符设备节点。 -
v4l2-ioctl.c:ioctl命令相当的多,这里我们最多常用到的是buffer的申请
-
v4l2-device.c:主要包含一些v4l2设备一些公共api,用于绑定v4l2设备结构体以及注册子设备节点。存在的作用就是便于访问video设备的子设备,因为它的数据域包含了一个
struct list_head subdevs
用于遍历子设备的。 -
v4l2-fh.c:fh就是file handle的意思,就是消息队列。这些v4l2_fh结构体最终都会链接到
strcut video_device
中的struct list_head fh_list
链表中,用于存放其它模块发过来的消息 -
v4l2-subdev.c:所有操作子设备的集合。接口会被字符设备对应的接口调用。
v4l2-dev.c 调用-->v4l2-subdev.c
-
v4l2-ctrls.c:主要是视频设备控制操作的api集合
-
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框架。 |
3 数据结构
3.1 不同结构体之间的关联
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_ops
和struct vb2_queue
结构体字段,这些与上文中的应用层代码编写息息相关;
-
如果子设备不需要与应用层交互,
struct v4l2_subdev
中内嵌的video_device也可以不向系统注册字符设备; -
v4l2_device
和v4l2_subdev
来进行抽象,以v4l2_device
来代表整个输入设备,以v4l2_subdev
来代表子模块,比如CSI
、Sensor
等;
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);
可以看到一般注册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都可以作为子设备。
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:
imx585 subdev注册:
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
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注册
3.1.2.2 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,
};
video capture interface(捕获)
: 视频采集接口,这种接口应用于摄像头,v4l2在最初设计的时候就是应用于这种功能video output interface(输出)
: 视频输出接口,将静止图像或图像序列编码为模拟视频信号,通过此接口,应用程序可以控制编码过程 并将图像从用户空间移动到驱动程序video overlay interface(预览)
: 视频直接传输接口,可以将采集到的视频数据直接传输到显示设备,不需要cpu参与,这种方式的显示图 像的效率比其他方式高得多
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
.
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
/**
* 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
实例注册进去:
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等
3.5.1.2 v4l2_subdev_core_ops
参考[v4l2实例.md的3.2 v4l2_subdev_ops实例]
。
3.5.1.3 v4l2_subdev_video_ops
参考[v4l2实例.md的3.2 v4l2_subdev_ops实例]
。
3.5.1.4 v4l2_subdev_sensor_ops
参考[v4l2实例.md的3.2 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。
registered
:在这个subdev被registered的时候被调用。open
:在这个subdev的用户态节点被open的时候被调用。close
:当用户态节点被close的时候被调用,要注意unregistered调用之后可能会调用close回调
4 v4l2调用流程
4.1 用户态v4l2调用流程
- 打开设备,查询设备能力集,设置
video input
参数,视频采集方式、格式。 requst buffer
并mmap
获得访问地址, 送图往底层QBUF- 启动流媒体
STREAMON
- 将数据取出(
DQBUF
),处理(process
), 放回(QBUF
),这一步骤循环操作 - 关闭流媒体
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_ioctls[]
:调用具体v4l2_ioctl_ops
中的videoc_s_input
函数。
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 应用流程举例
1.打开/dev/video0
和/dev/v4l-subdev3
,查询设备能力,打印出是video capture设备
。
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。
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
启动抓捕图像。
4.VIDIOC_DQBUF
取出v4l2_buffer
,然后再VIDIOC_QBUF
重新送往驱动vbq。最后VIDIOC_STREAMOFF
停止流,关闭/dev/video0
和/dev/v4l-subdev3
.关于buffer的
4.2 内核态v4l2调用流程
4.2.1 video_device注册
-
首先建立
struct video_device
实例,比如用来描述ISP,初始化isp硬件参数,私有数据;实现fops
函数集。 -
通过
video_register_device
向提供注册;-
设置
cdev->ops=&v4l2_fops
-
cdev_add
,按照字符设备驱动框架注册,主设备号81#define VIDEO_MAJOR 81
-
创建类
-
注册
sysfs
-
4.2.2 v4l2 ioctl调用层次
一步步进来看:
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
流程:
模块之间相互独立,模块用struct media_entity
进行抽象:
struct media_entity
:包含设备的通用数据、名称、类型、功能、标志、端口数量和链接信息。每个 media_entity
有多个 media_pad
(端口)和 media_link
(连接其他实体的链接),共同构成设备的拓扑结构。实体通过 media_device_init
和 media_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关系如下:
首先是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_ep
和isp_in_ep
也是这样的关系。
5.1 media framework数据结构
5.1.1 media device结构体
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_graph
是media_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。
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);
};
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
。
6.1 videobuf2数据流程图
struct vb2_buffer
:一张视频内存,简单点就叫vb;vb2_queue
:描述vb2_buffer
的队列,其中struct vb2_buffer *bufs[]
是存放buffer节点的数组,该数组中的成员代表了vb2 buffer
,并将在queued_list
和done_list
两个队列中进行流转;(入队(enqueue
)就是将vb2_buffer加入queued_list
,同理出队(dequeue
)就是vb2_queue
的done_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操作都需要物理地址连续。
- 1)虚拟地址和物理地址都分散,可以通过
struct vb2_ops
:vb2队列操作函数集,由驱动来实现对应的接口,并在框架中通过call_vb_qop宏
被调用;
soc板卡驱动Driver specific
层则需要实现vb_buf_ops
和vb2_ops
;
6.1.1 buffer申请
当用户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
.
然后调用__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种内存分配方式:
- 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,
}
- 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,
};
- 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
当用户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
队列,如下:
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
函数完成入队。
6.1.3 buffer dequeue
当用户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.c
的isp_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:
6.1.4 stream on
当用户VIDIOC_STREAMON
时, 比如调用板卡驱动的isp_video_streamon
:
-
media_pipeline_start(&video->video.entity, &pipe->pipe);
启动媒体pipeline.c -
isp_video_get_graph_data(video, pipe);
进行media pipeline
的资料参数获取,详细见5.2 media pipeline API -
vb2_streamon(&vb2_queue, type);
v4l_vb2q_enable_media_source(q);
调用media_device
的enable_source
:
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关联:
1. 调用`isp_pipeline_enable`调用`pipeline`中各个子模块`subdev`的回调函数,例如下面的`aewb`子模块:
最终就到了寄存器配置环节了,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