Linux camera之V4L2子系统详解

文章目录

1、preface

复制代码
1、术语
v4l2 : Video for Linux two, 是V4L1的改进版

2、V4L2属于哪种类型的驱动?与ALSA类似,是用户空间应用程序和设备驱动的中间层,属于字符设备驱动

3、现代V4L2框架支持多种广泛的设备(不单单是摄像头设备),可以有以下几种接口
1)video capture interface, 视频采集-即摄像头
2) video output interface,视频输出-将图像编码为模拟视频信号
3) video overlay interface, 将采集的视频数据直接传输到现实设备,不需要cpu参与

4、对于视频采集,V4L2支持三种方式来采集图像:
1)内存映射方式(mmap),内存映射的方式采集速度较快,一般用于连续视频数据的采集,实际工作中的应用概率更高;
2)直接读取方式(read),直接读取的方式相对速度慢一些,所以常用于静态图片数据的采集;
3)用户指针。用户指针使用较少;

2、V4L2框架

1)总体框架

1、与ALSA套路是一样的,V4L2实现了驱动框架(不变部分),设备驱动则实现变化的部分;

2、videobuf2这一块是重点,也是难点;

3、总的来说V4L2作为一个内核库,向上提供稳定接口,向下提供通用接口给设备驱动使用;

2)从调用流程来看V4L2框架

3、UVC设备驱动

复制代码
1.UVC : usb video class,一个基于V4L2框架的通用USB摄像头设备驱动,旨在让USB摄像头等视频设备能像USB键盘、鼠标一样,在不同的操作系统上免驱使用,相对MIPI摄像头而言,复杂度低一些(MIPI使用更多的子系统),了解UVC也有助于理解MIPI驱动程序;

2.绝大多数现代USB摄像头都是UVC设备,一个符合UVC标准的USB摄像头,其硬件和通信协议都遵循了公开的规范,可以直接使用UVC设备驱动,不需要重新编写特定的USB摄像头设备驱动(免驱的原理)

3.源码目录
/android/kernel/fusion/4.19/drivers/media/usb/uvc

4.USB摄像头如何适配?厂家可以自行实现,或者直接使用内核标准的UVC设备驱动,直接打开UVC编译选项即可

5.由于UVC是通用驱动,会有很多兼容性代码,导致复杂度高,也可以参照其它厂家的usb摄像头驱动实现(代码量少一些)
/android/vendor/amlogic/common/kernel/common_5.4/drivers/media/usb/airspy
/android/vendor/amlogic/common/kernel/common_5.4/drivers/media/usb/cpia2

4、基于MIPI-CSI-2设备驱动

复制代码
1.参照0v5640实现 : 
/android/vendor/amlogic/common/kernel/common_5.4/drivers/media/i2c/ov5640.c

2.MIPI-CSI-2与HDMI类似,是一类复合接口(驱动包含I2C/MIPI驱动);

3. MIPI如何适配?配置设备树(设置多少lane、时钟)、寄存器配置;

5、V4L2基础源码分析

1)源码目录

复制代码
1.v4l2核心
/android/vendor/amlogic/common/kernel/common_5.4/drivers/media/v4l2-core
/android/vendor/amlogic/common/kernel/common_5.4/drivers/media/v4l2-core/v4l2-ioctl.c

2)数据结构

复制代码
1. video_device - 视频设备
/android/vendor/amlogic/common/kernel/common_5.4/include/media/v4l2-dev.h
struct video_device {
#if defined(CONFIG_MEDIA_CONTROLLER)  //meida子系统
    struct media_entity entity;
    struct media_intf_devnode *intf_devnode;
    struct media_pipeline pipe;
#endif
    struct v4l2_file_operations *fops;
    struct v4l2_device *v4l2_dev;  //v4l2设备
    struct v4l2_ctrl_handler *ctrl_handler;
    struct vb2_queue *queue;
    
    struct v4l2_ioctl_ops *ioctl_ops;
}

2.v4l2_file_operations
/android/vendor/amlogic/common/kernel/common_5.4/include/media/v4l2-dev.h
struct v4l2_file_operations {
    int (*read)();
    int (*mmap)();
}


3.v4l2_ioctl_ops
/android/vendor/amlogic/common/kernel/common_5.4/include/media/v4l2-ioctl.h
struct v4l2_ioctl_ops {
    int (*vidioc_querycap)(struct file *file, void *fh, struct v4l2_capability *cap);
    int (*vidioc_enum_fmt_vid_cap)(struct file *file, void *fh, struct v4l2_fmtdesc *f);
    int (*vidioc_reqbufs)(struct file *file, void *fh, struct v4l2_requestbuffers *b);
    int (*vidioc_streamon)(struct file *file, void *fh, enum v4l2_buf_type i);
    int (*vidioc_g_std)(struct file *file, void *fh, v4l2_std_id *norm);
    int (*vidioc_queryctrl)(struct file *file, void *fh, struct v4l2_queryctrl *a);
    ...
}

4.v4l2_device - V4L2设备
/android/vendor/amlogic/common/kernel/common_5.4/include/media/v4l2-device.h
struct v4l2_device {
	struct device *dev;
	struct media_device *mdev; //存放media device
	struct list_head subdevs;  //存放subdev
	spinlock_t lock;
	char name[V4L2_DEVICE_NAME_SIZE];
	struct v4l2_ctrl_handler *ctrl_handler;
	struct v4l2_prio_state prio;
	struct kref ref;
	void (*release)(struct v4l2_device *v4l2_dev);
};

3)源码分析

1. V4L2 core初始化
复制代码
#define VIDEO_MAJOR	81

1. V4L2 core初始化很简单,仅仅将自己注册为字符设备驱动
/android/vendor/amlogic/common/kernel/common_5.4/drivers/media/v4l2-core/v4l2-dev.c
videodev_init()
--register_chrdev_region(dev, VIDEO_NUM_DEVICES, VIDEO_NAME);
--class_register(&video_class);
2、V4L2设备驱动程序注册流程
复制代码
以UVC设备驱动为例

1.uvc_init
/android/vendor/amlogic/common/kernel/common_5.4/drivers/media/usb/uvc/uvc_driver.c
uvc_init()
--usb_register(&uvc_driver.driver);

2.uvc_probe
/android/vendor/amlogic/common/kernel/common_5.4/drivers/media/usb/uvc/uvc_driver.c
uvc_probe()
/android/vendor/amlogic/common/kernel/common_5.4/drivers/media/mc/mc-device.c
--media_device_init(&dev->mdev); //Initialize the media device. 后面会涉及,先mark
/android/vendor/amlogic/common/kernel/common_5.4/drivers/media/v4l2-core/v4l2-device.c
--v4l2_device_register(&intf->dev, &dev->vdev)
----__video_register_device
------// 根据次设备号把video_device结构体放入数组
------video_device[vdev->minor] = vdev;
------// 注册字符设备驱动程序
------vdev->cdev->ops = &v4l2_fops;
------vdev->cdev->owner = owner;
------ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
----uvc_ctrl_init_device(dev)
----uvc_scan_device(dev)
----uvc_register_chains()
------uvc_register_terms(dev, chain);
--------uvc_register_video(dev, stream);
----------uvc_register_video_device(dev, stream, &stream->vdev,&stream->queue, stream->type,&uvc_fops, &uvc_ioctl_ops);
------------vdev->v4l2_dev = &dev->vdev; //video_device和4l2_device建立联系
------------vdev->fops = fops;
------------vdev->ioctl_ops = ioctl_ops;

3.注册后,用户调用V4L2对应接口,然后由V4L2转发到具体的设备驱动
3、ioctl调用流程

1)两类ioctl

复制代码
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, INFO_FL_CLEAR(v4l2_fmtdesc, type)),
	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),
}

1)一类是无需特殊的代码来处理,APP的调用可以直达这些处理函数,这类ioctl使用宏DEFINE_V4L_STUB_FUNC来修饰
DEFINE_V4L_STUB_FUNC(g_fbuf)
DEFINE_V4L_STUB_FUNC(s_fbuf)
DEFINE_V4L_STUB_FUNC(expbuf)

2)其它ioctl需要特殊处理,比如对于`VIDIOC_ENUM_FMT`,它需要根据设备的类型分别枚举:

2)APP调用ioctl流程

4、buffer管理内核实现
1)buffer的操作流程
2)两套buffer
复制代码
1.V4L2框架-videobuf2
https://blog.csdn.net/u013904227/article/details/81054611

2.videobuf2用于连接V4L2驱动层与用户空间层,提供数据交流的通道,它可以分配并管理视频帧数据。videobuf 层实现了很多 ioctl 函数,包括 buffer 分配、入队、出队和数据流控制。

3.两套buffer管理
vb2_buffer 是 videobuf_buffer的改进,修复vb1的一些缺陷,支持更强大更灵活的内存管理
vb1:/android/vendor/amlogic/common/kernel/common_5.4/include/media/videobuf-core.h
vb2:/android/vendor/amlogic/common/kernel/common_5.4/include/media/videobuf2-core.h
3)数据结构
复制代码
1.vb2_v4l2_buffer
/android/vendor/amlogic/common/kernel/common_5.4/include/media/videobuf2-v4l2.h
struct vb2_v4l2_buffer {
    struct vb2_buffer	vb2_buf;
    struct vb2_plane	planes[VB2_MAX_PLANES];
}

2.vb2_buffer
/android/vendor/amlogic/common/kernel/common_5.4/include/media/videobuf2-core.h
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;

	struct vb2_plane	planes[VB2_MAX_PLANES]; //平面(现在摄像头技术,多个平面组成一个更加立体的平面效果)
	struct list_head	queued_entry;  //待处理队列
	struct list_head	done_entry;  //已处理队列
}

3.vb2_plane
/android/vendor/amlogic/common/kernel/common_5.4/include/media/videobuf2-core.h
struct vb2_plane {
	void			*mem_priv;  //用于存放图像数据的内存块
	struct dma_buf		*dbuf;
};

4.vb2_queue
/android/vendor/amlogic/common/kernel/common_5.4/include/media/videobuf2-core.hs
struct vb2_queue {
    struct device			*dev;
    struct vb2_ops		*ops;
	struct vb2_mem_ops	*mem_ops;
	struct vb2_buf_ops	*buf_ops;
	struct device	*alloc_devs[VB2_MAX_PLANES];
	struct vb2_buffer *bufs[VB2_MAX_FRAME];
}

5.vb2_mem_ops
/android/vendor/amlogic/common/kernel/common_5.4/include/media/videobuf2-core.h
struct vb2_mem_ops {
    void (*alloc)(struct device *dev,...);
    void		(*put)(void *buf_priv);
    void		(*prepare)(void *buf_priv);
    void		(*finish)(void *buf_priv);
    int (*mmap)(void *buf_priv);
}

6.vb2_ops
/android/vendor/amlogic/common/kernel/common_5.4/include/media/videobuf2-core.h
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[]);

	void (*wait_prepare)(struct vb2_queue *q);
	void (*wait_finish)(struct vb2_queue *q);

	int (*start_streaming)(struct vb2_queue *q, unsigned int count);
	void (*stop_streaming)(struct vb2_queue *q);

	void (*buf_queue)(struct vb2_buffer *vb);
	void (*buf_request_complete)(struct vb2_buffer *vb);
};

7.vb2_buf_ops
/android/vendor/amlogic/common/kernel/common_5.4/include/media/videobuf2-core.h
struct vb2_buf_ops {
	int (*verify_planes_array)(struct vb2_buffer *vb, const void *pb);
	void (*init_buffer)(struct vb2_buffer *vb);
	void (*fill_user_buffer)(struct vb2_buffer *vb, void *pb);
	int (*fill_vb2_buffer)(struct vb2_buffer *vb, struct vb2_plane *planes);
	void (*copy_timestamp)(struct vb2_buffer *vb, const void *pb);
};

结构体"类图"

4)videobuffer2的3个ops
复制代码
1.vb2_queue
/android/vendor/amlogic/common/kernel/common_5.4/include/media/videobuf2-core.h
struct vb2_queue {
    struct vb2_buf_ops *buf_ops:在用户空间、内核空间之间传递buffer信息
    struct vb2_mem_ops *mem_ops:分配内存用的回调函数
    struct vb2_ops *ops:硬件相关的回调函数
}

2.vb2_ops
/android/vendor/amlogic/common/kernel/common_5.4/include/media/videobuf2-core.h
struct vb2_ops {
    1.APP调用ioctl VIDIOC_REQBUFS或VIDIOC_CREATE_BUFS时,驱动程序在分配内存之前,会调用此函数。
	int (*queue_setup)(struct vb2_queue *q,...);
    2.APP调用ioctl VIDIOC_QBUF或VIDIOC_PREPARE_BUF时,驱动程序会在执行硬件操作前,调用此函数进行必要的初始化。
	int (*buf_prepare)(struct vb2_buffer *vb);
    3.APP调用ioctl VIDIOC_DQBUF后,在驱动程序返回用户空间之前,会调用此函数,可以在这个函数里修改buffer。或者驱动程序内部停止或暂停streaming时,也会调用此函数。
    void (*buf_finish)(struct vb2_buffer *vb);
	int (*start_streaming)(struct vb2_queue *q, unsigned int count); //驱动相关的"启动streaming"函数
	void (*stop_streaming)(struct vb2_queue *q); //驱动相关的"停止streaming"函数
    4.把buffer传送给驱动,驱动获得数据、填充好buffer后会调用vb2_buffer_done函数返还buffer。
	void (*buf_queue)(struct vb2_buffer *vb);
};

3.vb2_buf_ops
/android/vendor/amlogic/common/kernel/common_5.4/include/media/videobuf2-core.h
struct vb2_buf_ops {
    1.APP调用ioctl VIDIOC_DQBUF时,在驱动内部会调用此函数,用来验证这个buffer含有足够多的plane。
	int (*verify_planes_array)(struct vb2_buffer *vb, const void *pb);
	void (*init_buffer)(struct vb2_buffer *vb);
	2.APP调用ioctl VIDIOC_QUERYBUF、VIDIOC_PREPARE_BUF、VIDIOC_QBUF、VIDIOC_DQBUF,都会传入一个v4l2_buffer结构体
	void (*fill_user_buffer)(struct vb2_buffer *vb, void *pb);
	3.APP调用ioctl VIDIOC_QBUF时,传入一个v4l2_buffer结构体,驱动里会用它来填充vb2_buffer结构体。
	int (*fill_vb2_buffer)(struct vb2_buffer *vb, struct vb2_plane *planes);
};

4.vb2_mem_ops
/android/vendor/amlogic/common/kernel/common_5.4/include/media/videobuf2-core.h
struct vb2_mem_ops {
    1.分配真正用于存储视频数据的buffer,可能还分配私有数据
	void		*(*alloc)(struct device *dev, unsigned long attrs,...);
    2.返回这块内存的内核空间地址
	void		*(*vaddr)(void *buf_priv);
    3.把这块内存,映射到用户空间
	int		(*mmap)(void *buf_priv, struct vm_area_struct *vma);
};
5)vb2初始化
复制代码
uvc_queue_init(){
    queue->queue.type = type;
	queue->queue.io_modes = VB2_MMAP | VB2_USERPTR;
	queue->queue.drv_priv = queue;
	queue->queue.buf_struct_size = sizeof(struct uvc_buffer);
	/android/vendor/amlogic/common/kernel/common_5.4/drivers/media/common/videobuf2/videobuf2-vmalloc.c
	queue->queue.mem_ops = &vb2_vmalloc_memops; //使用videobuf2提供的内存分配方法
	queue->queue.ops = &uvc_queue_qops; //硬件相关操作
}

static const struct vb2_ops uvc_queue_qops = {
	.queue_setup = uvc_queue_setup,
	.buf_prepare = uvc_buffer_prepare,
	.buf_queue = uvc_buffer_queue,
	.buf_finish = uvc_buffer_finish,
	.wait_prepare = vb2_ops_wait_prepare,
	.wait_finish = vb2_ops_wait_finish,
	.start_streaming = uvc_start_streaming,
	.stop_streaming = uvc_stop_streaming,
};
6)videobuffer2情景分析
复制代码
1.申请buffer
APP ioctl VIDIOC_REQBUFS
------------------------------
	v4l_reqbufs // v4l2-ioctl.c
    	ops->vidioc_reqbufs(file, fh, p);
			vb2_ioctl_reqbufs // videobuf2-v4l2.c
    			vb2_core_reqbufs
    			    call_qop(q, queue_setup, q, &num_buffers, &num_planes,plane_sizes, q->alloc_devs);
    			        __vb2_queue_alloc()
    			            __vb2_buf_mem_alloc(vb);
    			            
2.把buffer放入队列
APP ioctl VIDIOC_QBUF
------------------------------
	v4l_qbuf // v4l2-ioctl.c
    	ops->vidioc_qbuf(file, fh, p);
			vb2_ioctl_qbuf // videobuf2-v4l2.c
                vb2_qbuf(vdev->queue, p);  // videobuf2-v4l2.c
					vb2_core_qbuf(q, b->index, b); // videobuf2-core.c
						ret = __buf_prepare(vb, pb);
								ret = __qbuf_mmap(vb, pb);  // videobuf2-core.c
								
3.把buffer取出队列
APP ioctl VIDIOC_DQBUF
------------------------------
	v4l_dqbuf // v4l2-ioctl.c
    	ops->vidioc_dqbuf(file, fh, p);
			vb2_ioctl_dqbuf // videobuf2-v4l2.c
				vb2_dqbuf(vdev->queue, p, file->f_flags & O_NONBLOCK);
					ret = vb2_core_dqbuf(q, NULL, b, nonblocking);			

4)小结
1、分配流程:
* 驱动程序初始化时,就构造了vb2_queue,这是"buffer的队列",一开始里面没有"buffer"
* APP调用ioctl VIDIOC_REQBUFS向驱动申请N个buffer
* 驱动程序分配n(n<=N)个vb2_buffer结构体,然后
  * 对于普通摄像头,还分配一个vb2_plane结构体、vb2_vmalloc_buf结构体,最后分配存数据的buffer
  * 对于多平面摄像头,给每个vb2_buffer分配多个"vb2_plane结构体、vb2_vmalloc_buf结构体、存数据的buffer"


2、入队列流程:
* APP调用ioctl VIDIOC_QBUF
* 驱动程序根据其index找到vb2_buffer
* 把这个vb2_buffer放入链表vb2_queue.queued_list

硬件驱动接收到数据后,比如URB传输完成后:

* 从链表vb2_queue.queued_list找到(但是不移除)vb2_buffer
* 把硬件数据存入vb2_buffer
* 把vb2_buffer放入链表vb2_queue.done_list

3、出队列流程:
* APP调用ioctl VIDIOC_DQBUF
* 驱动程序从链表vb2_queue.done_list取出并移除第1个vb2_buffer
* 驱动程序也把这个vb2_buffer从链表vb2_queue.queued_list移除

7、 V4L2-Subdev子系统

1)preface

1、V4L2-subdev - 用于抽象V4L2模块下的子模块;

2、如果当前平台涉及的模块众多,可以利用V4L2的Subdev子系统接口来拆分各个模块,使其变得易管理和扩展!

3、试想一下,假如将十几个模块都写在一个驱动里面,将会变得无比庞大且混乱,这就是引入V4L2-subdev的原因!

4、UVC框架也用到Subdev,但是没有具体实现,只是预留(因为UVC的模块相对固定,已经有规范约束,因此没有复杂变化拓扑结构)

2)subdev子系统源码分析

1、源码目录
复制代码
/android/vendor/amlogic/common/kernel/common_5.4/include/media/v4l2-subdev.h
/android/vendor/amlogic/common/kernel/common_5.4/drivers/media/v4l2-core/v4l2-subdev.c
2、数据结构
复制代码
1.v4l2_subdev
/android/vendor/amlogic/common/kernel/common_5.4/include/media/v4l2-subdev.h
struct v4l2_subdev {
#if defined(CONFIG_MEDIA_CONTROLLER)
  	struct media_entity entity;
#endif
  	struct list_head list;
  	struct module *owner;
  	bool owner_v4l2_dev;
  	u32 flags;
  	struct v4l2_device *v4l2_dev;
  	const struct v4l2_subdev_ops *ops;
  	const struct v4l2_subdev_internal_ops *internal_ops;
  	struct v4l2_ctrl_handler *ctrl_handler;
  	char name[V4L2_SUBDEV_NAME_SIZE];
  	u32 grp_id;
  	void *dev_priv;
  	void *host_priv;
  	struct video_device *devnode;
  	struct device *dev;
  	struct fwnode_handle *fwnode;
  	struct v4l2_subdev_platform_data *pdata;
}

2.v4l2_subdev_ops - v4l2下支持的subdev设备
/android/vendor/amlogic/common/kernel/common_5.4/include/media/v4l2-subdev.h
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;
};

3.v4l2_subdev_internal_ops
/android/vendor/amlogic/common/kernel/common_5.4/include/media/v4l2-subdev.h
struct v4l2_subdev_internal_ops {
	int (*registered)(struct v4l2_subdev *sd);
	void (*unregistered)(struct v4l2_subdev *sd);
	int (*open)(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh);
	int (*close)(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh);
	void (*release)(struct v4l2_subdev *sd);
};

4.从media_entity中提取v4l2_subdev
/android/vendor/amlogic/common/kernel/common_5.4/include/media/v4l2-subdev.h
#define media_entity_to_v4l2_subdev(ent)				\
({									\
	typeof(ent) __me_sd_ent = (ent);				\
									\
	__me_sd_ent ?							\
		container_of(__me_sd_ent, struct v4l2_subdev, entity) :	\
		NULL;							\
})

5.调用v4l2_subdev的内部方法
#define v4l2_subdev_call(sd, o, f, args...)				\
({								\
	struct v4l2_subdev *__sd = (sd);			\
	int __result;						\
	if (!__sd)						\
		__result = -ENODEV;				\
	else if (!(__sd->ops->o && __sd->ops->o->f))		\
		__result = -ENOIOCTLCMD;			\
	else if (v4l2_subdev_call_wrappers.o &&			\
		 v4l2_subdev_call_wrappers.o->f)		\
		__result = v4l2_subdev_call_wrappers.o->f(	\
						__sd, ##args);	\
	else							\
		__result = __sd->ops->o->f(__sd, ##args);	\
	__result;						\
})
3、源码分析

1、subdev比较好理解,一个模块对应一个subdev,ov5640摄像头的CCI就可以划分为一个subdev(I2C驱动),对于CSI接口,只需要配置使能MIPI控制器即可工作,后续无需用户介入,因此不需要抽象出subdev;

复制代码
1. ov5640_dev
struct ov5640_dev {
	struct i2c_client *i2c_client;
	struct v4l2_subdev sd;
	struct media_pad pad;
	...
}

2.subdev相关ops实现
static const struct v4l2_subdev_ops ov5640_subdev_ops = {
	.core = &ov5640_core_ops, //MIPI电源
	.video = &ov5640_video_ops,
	.pad = &ov5640_pad_ops,
};

static const struct v4l2_subdev_core_ops ov5640_core_ops = {
	.s_power = ov5640_s_power,
	.log_status = v4l2_ctrl_subdev_log_status,
	.subscribe_event = v4l2_ctrl_subdev_subscribe_event,
	.unsubscribe_event = v4l2_event_subdev_unsubscribe,
};

static const struct v4l2_subdev_video_ops ov5640_video_ops = {
	.g_frame_interval = ov5640_g_frame_interval,
	.s_frame_interval = ov5640_s_frame_interval,
	.s_stream = ov5640_s_stream,
};

static const struct v4l2_subdev_pad_ops ov5640_pad_ops = {
	.enum_mbus_code = ov5640_enum_mbus_code,
	.get_fmt = ov5640_get_fmt,
	.set_fmt = ov5640_set_fmt,
	.enum_frame_size = ov5640_enum_frame_size,
	.enum_frame_interval = ov5640_enum_frame_interval,
};

3. ov5640_probe
/android/vendor/amlogic/common/kernel/common_5.4/drivers/media/i2c/ov5640.c
static int ov5640_probe(struct i2c_client *client)
/android/vendor/amlogic/common/kernel/common_5.4/drivers/media/v4l2-core/v4l2-i2c.c
--v4l2_i2c_subdev_init(&sensor->sd, client, &ov5640_subdev_ops); //注册subdev
/android/vendor/amlogic/common/kernel/common_5.4/drivers/media/v4l2-core/v4l2-subdev.c
----v4l2_subdev_init(sd, ops);
/android/vendor/amlogic/common/kernel/common_5.4/drivers/media/mc/mc-entity.c
--media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);

4.挂进v4l2_device链表
/android/vendor/amlogic/common/kernel/common_5.4/include/media/v4l2-device.h
struct v4l2_device {
	struct device *dev;
	struct media_device *mdev; //存放media device
	struct list_head subdevs;  //存放subdev
}

3)subdev的注册与使用

复制代码
1.v4l2_device_register_subdev
/android/vendor/amlogic/common/kernel/common_5.4/drivers/media/v4l2-core/v4l2-device.c
int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev, struct v4l2_subdev *sd);
--media_device_register_entity(v4l2_dev->mdev, entity);
--list_add_tail(&sd->list, &v4l2_dev->subdevs); // 核心代码

2.v4l2_device_register_subdev_nodes - 遍历v4l2_device链表里各个subdev,如果它想暴露给APP,就把它注册为普通字符设备
/android/vendor/amlogic/common/kernel/common_5.4/drivers/media/v4l2-core/v4l2-device.c
v4l2_device_register_subdev_nodes()
    struct video_device *vdev;
	vdev->fops = &v4l2_subdev_fops;
	err = __video_register_device(vdev, VFL_TYPE_SUBDEV, -1, 1,
					      sd->owner);
		name_base = "v4l-subdev";
		vdev->cdev->ops = &v4l2_fops;
		ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
		
3.subdev就是一个标准设备驱动,因此有需要的时候也可以直接给APP通过设备节点调用,比如ISP驱动,厂家不开源,因此内核调不到ISP的接口,此时可以通过APP来调用;


4. 内核态使用subdev
可以直接调用subdev里的操作函数,也可以使用下面的宏:
#define v4l2_subdev_call(sd, o, f, args...)				\
	(!(sd) ? -ENODEV : (((sd)->ops->o && (sd)->ops->o->f) ?	\
		(sd)->ops->o->f((sd), ##args) : -ENOIOCTLCMD))
		
5.用户态使用subdev
/android/vendor/amlogic/common/arm_isp/test/v4l2_test_media/media-v4l2/libv4l2subdev.c
App:   ioctl(fd, cmd, arg)
--------------
kernel: 
v4l2_fops.unlocked_ioctl, 即v4l2_ioctl
	ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);
		v4l2_subdev_fops.unlocked_ioctl, 即subdev_ioctl
			video_usercopy(file, cmd, arg, subdev_do_ioctl);

注册subdev后,内核里结构体如下:

8、厂商改进的V4L2

1)V4L2提供了通用基础框架,厂商可以根据自家产品开进一步发自己的多媒体框架,比如

复制代码
1、全志基于linux 内核v4l2 框架实现自己Soc的camera 驱动框架 - VIN。
https://v853.docs.aw-ol.com/soft/soft_camera/
linux-4.9/drivers/media/platform/sunxi-vin

2、AML - VIN/VOUT
/android/vendor/amlogic/common/kernel/common_5.4/drivers/amlogic/media/vout
/android/vendor/amlogic/common/kernel/common_5.4/drivers/amlogic/media/vin

9、v4l2-util调试工具

复制代码
1.v4l2-ctl --help
General/Common options:
  --all              display all information available
  -C, --get-ctrl <ctrl>[,<ctrl>...]  get the value of the controls [VIDIOC_G_EXT_CTRLS]
  -c, --set-ctrl <ctrl>=<val>[,<ctrl>=<val>...]
 
2.查询指定V4L2设备的详细信息
v4l2-ctl -D -d /dev/v4l-subdev0
相关推荐
2501_944525542 小时前
Flutter for OpenHarmony 个人理财管理App实战 - 账户详情页面
android·java·开发语言·前端·javascript·flutter
yueyuexiaokeai12 小时前
linux kernel常用函数整理
linux·c语言
郝亚军4 小时前
ubuntu-18.04.6-desktop-amd64安装步骤
linux·运维·ubuntu
Konwledging4 小时前
kernel-devel_kernel-headers_libmodules
linux
Web极客码4 小时前
CentOS 7.x如何快速升级到CentOS 7.9
linux·运维·centos
一位赵5 小时前
小练2 选择题
linux·运维·windows
代码游侠5 小时前
学习笔记——Linux字符设备驱动开发
linux·arm开发·驱动开发·单片机·嵌入式硬件·学习·算法
不急不躁1236 小时前
Android16 GTS GtsPermissionTestcases 测试,跳过权限检查
android
Lw老王要学习6 小时前
CentOS 7.9达梦数据库安装全流程解析
linux·运维·数据库·centos·达梦