2021-03-16

学习:http://www.mysixue.com/?p=131 和《v4l2驱动编写指南》 的总结
一、v4l2-dev.c

(1)模块初始化:

static int __init videodev_init(void)
	dev_t dev = MKDEV(VIDEO_MAJOR, 0);//#define VIDEO_MAJOR	81
	ret = register_chrdev_region(dev, VIDEO_NUM_DEVICES, VIDEO_NAME);//申请设备号 81 xxx #define VIDEO_NUM_DEVICES	256 #define VIDEO_NAME              "video4linux"
	ret = class_register(&video_class);//创建 /dev/video4linux

(2)video_device注册:

int __video_register_device(struct video_device *vdev, enum vfl_devnode_type type, int nr, int warn_if_nr_in_use,struct module *owner)
	/* Part 1: check device type */
		switch (type) {
		case VFL_TYPE_GRABBER:
		case VFL_TYPE_VBI:
		case VFL_TYPE_RADIO:
		case VFL_TYPE_SUBDEV:
		case VFL_TYPE_SDR:
		...
		name_base = "video";
		break;
		}
	/* Part 2: find a free minor, device node number and device index. */
		switch (type) {
		case VFL_TYPE_GRABBER:
		case VFL_TYPE_RADIO:
		...
		minor_offset = 64;
		minor_cnt = 64;
		break;
		}
		video_devices[vdev->minor] = vdev;
	/* Part 3: Initialize the character device */
		vdev->cdev = cdev_alloc();
		vdev->cdev->ops = &v4l2_fops;  //转发至video_device->v4l2_file_operations
		vdev->cdev->owner = owner;
		ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
	/* Part 4: register the device with sysfs */
		vdev->dev.class = &video_class;
		vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor);
		vdev->dev.parent = vdev->dev_parent;
		dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);
		ret = device_register(&vdev->dev);//  生成 /dev/video0等
	/* Part 5: Register the entity. */
		ret = video_register_media_controller(vdev, type);
	/* Part 6: Activate this minor. The char device can now be used. */
		set_bit(V4L2_FL_REGISTERED, &vdev->flags);

(3)当操作/dev/video0时对应的底层调用

static const struct file_operations v4l2_fops = {
	.owner = THIS_MODULE,
	.read = v4l2_read,
	.write = v4l2_write,
	.open = v4l2_open,
	.get_unmapped_area = v4l2_get_unmapped_area,
	.mmap = v4l2_mmap,
	.unlocked_ioctl = v4l2_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl = v4l2_compat_ioctl32,
#endif
	.release = v4l2_release,
	.poll = v4l2_poll,
	.llseek = no_llseek,
};

[1] v4l2_open

struct video_device *vdev;
			vdev = video_devdata(filp);
			if (vdev->fops->open) {
				if (video_is_registered(vdev))
					ret = vdev->fops->open(filp);

[2] v4l2_ioctl

struct video_device *vdev = video_devdata(filp);
			if (vdev->fops->unlocked_ioctl) {
				if (video_is_registered(vdev))
					ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);

二、相关结构体介绍
【 2.1 】struct video_device 表征片上camera控制器

struct video_device{
		const struct v4l2_file_operations *fops;//video_device的作用与struct file_operations之于cdev的作用一模一样. 主要目的是响应用户空间的open/read/write/ioctl/close等请求.
		/* sysfs */
		struct cdev *cdev;
		struct v4l2_device *v4l2_dev;//struct v4l2_subdev的父设备
		struct v4l2_ctrl_handler *ctrl_handler;//与硬件相关的一些设置属性(例如亮度、饱和度、对比度和清晰度等)对象(struct v4l2_ctrl)的链表 
		                                       //补充:描述属性对应的操作方法对象:struct v4l2_ctrl_ops
											   //相关api:(1) 初始化struct v4l2_ctrl_handler链表:v4l2_ctrl_handler_init(struct v4l2_ctrl_handler *hdl, unsigned int nr_of_controls_hint)
											   //        (2)清除v4l2_ctrl_handler, 释放相关资源:void v4l2_ctrl_handler_free(struct v4l2_ctrl_handler *hdl)
											   //        (3)向链表中新增一个非菜单式的控制变量:struct v4l2_ctrl *v4l2_ctrl_new_std(struct v4l2_ctrl_handler *hdl, const struct v4l2_ctrl_ops *ops, u32 id, s64 min, s64 max, u64 step, s64 def)
											   //        (4)向链表中新增一个菜单式控制变量:struct v4l2_ctrl *v4l2_ctrl_new_std_menu(struct v4l2_ctrl_handler *hdl, const struct v4l2_ctrl_ops *ops, u32 id, u8 max, u64 mask, u8 def)
											   //        (5)把@add中所有的控制变量都添加到@hdl中:int v4l2_ctrl_add_handler(struct v4l2_ctrl_handler *hdl, struct v4l2_ctrl_handler *add, v4l2_ctrl_filter filter)
											   //        (6)把所有控制变量的默认值设置到硬件里面:int v4l2_ctrl_handler_setup(struct v4l2_ctrl_handler *hdl)
		struct vb2_queue *queue;//v4l2_ioctl_ops主要提供控制接口, 而这里的vb2_queue则主要提供数据接口. 它与memory的管理密切相关, memory管理在V4L2中是一个比较庞大的框架
		/* device info */
		char name[32];
		int minor;
		/* V4L2 file handles */
		struct list_head	fh_list;//设备节点每被打开一次, V4L2核心层就会生成一个struct v4l2_fh与之对应. 因此一个video_device下可能会有多个v4l2_fh. 这里的链表头fh_list用于挂载所有隶属于本video_device的v4l2_fh.
		/* callbacks */
		void (*release)(struct video_device *vdev);//必须要有,否则报错:
												   //由于多种原因,对 video_device 的引用可以在最后一个应用关闭文件描述符后很长一段时间依然保持。
												   //它们甚至可以在设备己经注销后依然保持。因此,在 release()函数调用前,释放这个结构体是不安全的。
		const struct v4l2_ioctl_ops *ioctl_ops;//用户空间的ioctl请求最终会转发到这里来处理.
	};

【 2.2 】 struct v4l2_subdev 描述挂接在片上camera控制器的外接sensor,作为struct v4l2_device的子设备

struct v4l2_subdev {
	struct list_head list;
	struct module *owner;
	bool owner_v4l2_dev;
	u32 flags;
	struct v4l2_device *v4l2_dev;
	const struct v4l2_subdev_ops *ops;//用于描述如何操作外接硬件,
									  //当用户空间通过ioctl操作设备节点时, 
									  //主控器代码会调用v4l2_subdev_call(sd, o, f, args…)来调用subdev中定义的ops函数, 从而实现对subdev的控制
									/*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;
									};	
									*/
	const struct v4l2_subdev_internal_ops *internal_ops;//给v4l2核心层代码回调用的. 
														//当向核心层注册/注销subdev时, 核心层会回调这里的registered/unregistered. 
														//当用户空间打开/关闭设备节点时, 核心层会回调这里的open/close.
														/*struct v4l2_subdev_internal_ops {
															int (*registered)(struct v4l2_subdev *sd);//在v4l2_device_register_subdev中被调用
															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);
														};	
														*/
	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 list_head async_list;
	struct v4l2_async_subdev *asd;
	struct v4l2_async_notifier *notifier;
	struct v4l2_async_notifier *subdev_notifier;
	struct v4l2_subdev_platform_data *pdata;
};

三、v4l2_subdev 的 async机制

以camera为例, 硬件组成为片上camera控制器和外接sensor. 在控制器相关的代码里面可以通过async机制等待外接sensor初始化,当sensor初始化代码被调用时, 会通过async通知控制器代码, 然后控制器代码就可以开始创建设备节点(也就是注册video_device设备)等.
从代码细节上看, 控制器代码首先要定义一个或多个v4l2_async_subdev, 每个代表控制器想与哪个sensor进行匹配, 多个就组成了一个匹配列表. 然后控制器代码需要实现v4l2_async_notifier_operations中定义回调函数. 最后控制器代码把这两者一起封装成一个
v4l2_async_notifier。

【 3.1 】结构体介绍

struct v4l2_async_notifier {
	const struct v4l2_async_notifier_operations *ops;//当
													/*struct v4l2_async_notifier_operations {
														int (*bound)(struct v4l2_async_notifier *notifier, struct v4l2_subdev *subdev, struct v4l2_async_subdev *asd);
														int (*complete)(struct v4l2_async_notifier *notifier);
														void (*unbind)(struct v4l2_async_notifier *notifier,struct v4l2_subdev *subdev,struct v4l2_async_subdev *asd);
													};	
													*/
	unsigned int num_subdevs;
	unsigned int max_subdevs;
	struct v4l2_async_subdev **subdevs;
										/*struct v4l2_async_subdev {
											enum v4l2_async_match_type match_type;
											union {//针对不同的类型,在函数v4l2_async_find_match中进行不同的match操作
												//[1]V4L2_ASYNC_MATCH_FWNODE
												struct fwnode_handle *fwnode;
												//[2]V4L2_ASYNC_MATCH_DEVNAME
												const char *device_name;
												//[3]V4L2_ASYNC_MATCH_I2C
												struct {
													int adapter_id;
													unsigned short address;
												} i2c;
												//[4]V4L2_ASYNC_MATCH_CUSTOM
												struct {   V4L2_ASYNC_MATCH_CUSTOM
													bool (*match)(struct device *,struct v4l2_async_subdev *);
													void *priv;
												} custom;
											} match;
											struct list_head list;
										};
										*/
	struct v4l2_device *v4l2_dev;
	struct v4l2_subdev *sd;
	struct v4l2_async_notifier *parent;
	struct list_head waiting;
	struct list_head done;
	struct list_head list;
};

【 3.2 】源码追踪:当subdev初始化时, 它会调用v4l2_async_notifier_register:

//int v4l2_async_notifier_register(struct v4l2_device *v4l2_dev,struct v4l2_async_notifier *notifier)v4l2_async_notifier_register
	notifier->v4l2_dev = v4l2_dev;
	ret = __v4l2_async_notifier_register(notifier);
		struct v4l2_async_subdev *asd;
	  [1]list_add_tail(&asd->list, &notifier->waiting);
		
		[2]ret = v4l2_async_notifier_try_all_subdevs(notifier);
			struct v4l2_device *v4l2_dev =v4l2_async_notifier_find_v4l2_dev(notifier);
			struct v4l2_subdev *sd;
			list_for_each_entry(sd, &subdev_list, async_list) 
				struct v4l2_async_subdev *asd;
				[2.1]asd = v4l2_async_find_match(notifier, sd);
					 bool (*match)(struct v4l2_subdev *, struct v4l2_async_subdev *);
					 struct v4l2_async_subdev *asd;
					 list_for_each_entry(asd, &notifier->waiting, list) {
						/* bus_type has been verified valid before */
						switch (asd->match_type) {//根据不同的type,设置不同的match函数
						case V4L2_ASYNC_MATCH_CUSTOM:
							match = match_custom;
							break;
						case V4L2_ASYNC_MATCH_DEVNAME:
							match = match_devname;
							break;
						case V4L2_ASYNC_MATCH_I2C:
							match = match_i2c;
							break;
						case V4L2_ASYNC_MATCH_FWNODE:
							match = match_fwnode;
							break;
						default:
							/* Cannot happen, unless someone breaks us */
							WARN_ON(true);
							return NULL;
						}

						/* match cannot be NULL here */
						if (match(sd, asd))//执行对应的match
							return asd;
					}
				[2.2]ret = v4l2_async_match_notify(notifier, v4l2_dev, sd, asd);
						ret = v4l2_device_register_subdev(v4l2_dev, sd);
							sd->v4l2_dev = v4l2_dev;
							
						ret = v4l2_async_notifier_call_bound(notifier, sd, asd);
							return notifier->ops->bound(n, subdev, asd);
						/* Remove from the waiting list */
						/* Move from the global subdevice list to notifier's done */
						/* See if the sub-device has a notifier. If not, return here.*/
						return v4l2_async_notifier_try_all_subdevs(subdev_notifier);
				
		[3]ret = v4l2_async_notifier_try_complete(notifier);
				return v4l2_async_notifier_call_complete(notifier);
					return notifier->ops->complete(n);//在该函数中会注册video_device设备
		
		[4]list_add(&notifier->list, &notifier_list);

【 3.3 】v4l2_subdev自身还定义了如下API:

[1]初始化v4l2_subdev空间:

void v4l2_subdev_init(struct v4l2_subdev *sd, const struct v4l2_subdev_ops *ops)

[2]调用v4l2_subdev_xxx_ops中定义的函数:

#define v4l2_subdev_call(sd, o, f, args…)

[3]检查v4l2_subdev_xxx_ops中是否定义了某个函数:

#define v4l2_subdev_has_op(sd, o, f)

[4]用户空间可以通过ioctl VIDIOC_DQEVENT来等待某个v4l2_event, 此时v4l2核心层代码会调用v4l2_event_dequeue, block并等待事件产生.

  void v4l2_subdev_notify_event(struct v4l2_subdev *sd, const struct v4l2_event *ev)

[5]当subdev检测到event产生时(此时一般会有中断发生), subdev中断处理函数会调用v4l2_subdev_notify_event. v4l2_subdev_notify_event里面会做两件事:
一是调用v4l2_event_queue, 此时会唤醒用户空间的block, 并把事件传送给用户空间.
二是调用v4l2_subdev_notify(详见《v4l2 device APIs》), 通知v4l2_device有事件产生了.
源码追踪如下:

[6]片上控制器相关代码调用此API注册一个notifier:

int v4l2_async_notifier_register(struct v4l2_device *v4l2_dev, struct v4l2_async_notifier *notifier)

[7]片上控制器相关代码调用此API注销一个notifier:

void v4l2_async_notifier_unregister(struct v4l2_async_notifier *notifier)

[8]外接硬件相关代码调用此API向系统注册一个v4l2_subdev, 同时也会触发一次与notifier的匹配过程, 如果匹配上了则调用v4l2_async_notifier_operations中定义的回调函数.:

int v4l2_async_register_subdev(struct v4l2_subdev *sd)

[9]外接硬件相关代码调用此API注销一个v4l2_subdev:

void v4l2_async_unregister_subdev(struct v4l2_subdev *sd)

【 3.4 】v4l2_subdev 作为字符设备

int v4l2_device_register_subdev_nodes(struct v4l2_device *v4l2_dev) //生成的节点名称:/dev/v4l-subdev0

每个subdev都代表一个外接硬件, 这外接硬件可能是接在某个片上控制器上, 比如camera, 这种情况下我们需要针对这个控制器创建一个设备节点, 然后在控制器的代码中通过v4l2_subdev_call来操作外接硬件.除此之外, 这个外接硬件也可能直接接在I2C、SPI这些简单的片上外设上, 比如radio, 这种情况下就没有必要针对这些简单的外设创建设备节点, 而是直接针对外接硬件创建设备节点.
当然, 在片上外设+外接硬件的设计中, 也可以针对外接硬件创建设备节点.这样既可以通过控制器代码间接操作外接硬件, 也可以通过subdev设备节点之间操作外接硬件.此API的作用就是针对v4l2_dev下挂载的所有v4l2_subdev, 如果subdev.flags设置了V4L2_SUBDEV_FL_HAS_DEVNODE标志, 则为该subdev创建一个设备节点(设备节点的创建过程其实就是注册video_device).
subdev_do_ioctl会把v4l2规定的ioctl转换成v4l2_subdev定义了v4l2_subdev_ops(subdev定义了几组(core/tuner/audio/video/…), 每组都有各自的function ).

四、 内存管理与访问
【 4.1 】 概述
所谓数据, 其实就是与内存相关的问题. 最终的目的是使得内核与用户空间可方便的交换数据.
V4L2支持三种不同的数据交换方式:
[1] read和write : 借助字符设备的read/write接口, 这种方式数据需要在内核和用户之间拷贝, 访问速度会比较慢.涉及到大量的数据交换时, 不要用这种方式, 会影响性能.
[2] 内存映射缓冲区(V4L2_MEMORY_MMAP) : 是在内核空间开辟缓冲区,应用通过mmap()系统调用映射到用户地址空间。 这些缓冲区可以是大而连续DMA缓冲区、通过vmalloc()创建的虚拟缓冲区,或者直接在设备的IO内存中开辟的缓冲区(如果硬件支持).
[3] 用户空间缓冲区(V4L2_MEMORY_USERPTR) : 是在用户空间中开辟缓冲区,用户与内核空间之间交换缓冲区指针。很明显,在这种情况下是不需要mmap()调用的,但驱动需要额外的设计, 以便使之可访问用户空间内存.

Read和write方式属于帧IO 访问方式, 每一帧都需要用户和内核之间数据拷贝;
后两种是 流IO 访问方式, 不需要内存拷贝, 访问速度比较快.

内存映射缓冲区访问方式是比较常用的方式.

【 4.2 】核心数据结构
[1] vb2_queue代表一个videobuffer队列, vb2_buffer是这个队列中的基本单位, vb2_mem_ops是内存的操作函数集, vb2_ops用来管理队列.

struct vb2_queue {
		unsigned int			type; //取 enum v4l2_buf_type 中的成员
									/*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_PRIVATE              = 0x80,
									};
									*/
		unsigned int			io_modes;//	访问IO的方式:mmap、userptr、etc. 详见enum vb2_io_modes
									/*enum vb2_io_modes {
										VB2_MMAP	= BIT(0),
										VB2_USERPTR	= BIT(1),
										VB2_READ	= BIT(2),
										VB2_WRITE	= BIT(3),
										VB2_DMABUF	= BIT(4),
									};
									*/

		const struct vb2_ops		*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[]);
											//释放和获取设备操作锁
											void (*wait_prepare)(struct vb2_queue *q);
											void (*wait_finish)(struct vb2_queue *q);
											//对buffer的操作
											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);
											void (*stop_streaming)(struct vb2_queue *q);
											//把VB从v4l2核心层传递给驱动
											void (*buf_queue)(struct vb2_buffer *vb);
										};
										*/
		const struct vb2_mem_ops	*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);
												//获取用户空间视频缓冲区指针
												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		*(*vaddr)(void *buf_priv);
												void		*(*cookie)(void *buf_priv);
												//返回当期在用户空间的buffer数
												unsigned int	(*num_users)(void *buf_priv);
												//把缓冲区映射到用户空间
												int		(*mmap)(void *buf_priv, struct vm_area_struct *vma);
											};
																						*/
		const struct vb2_buf_ops	*buf_ops;//在用户空间和内核空间之间回调以传递缓冲区信息。
		struct vb2_buffer		*bufs[VB2_MAX_FRAME];//每个vb2_buffer代表一块内存, 这是存储池,与用户空间的struct v4l2_buffer对应
		unsigned int			num_buffers;//池子里面实际有多少个buf
	};

[2] 一个vb2_buffer代表内核空间的一块videobuffer. 一个vb2_queue中会有多个这样的buffer.

	struct vb2_buffer {
		struct vb2_queue	*vb2_queue;//指向该buf所隶属的vb2_queue
		unsigned int		memory;//该方法,其中传递实际数据
		enum vb2_buffer_state	state;//一个buffer的多种状态
								/*enum vb2_buffer_state {
									VB2_BUF_STATE_DEQUEUED,
									VB2_BUF_STATE_PREPARING,
									VB2_BUF_STATE_PREPARED,
									VB2_BUF_STATE_QUEUED,
									VB2_BUF_STATE_REQUEUEING,
									VB2_BUF_STATE_ACTIVE,
									VB2_BUF_STATE_DONE,
									VB2_BUF_STATE_ERROR,
								};
								*/
};

一块buffer可能有多种状态, 常见的情形如下:
 在驱动的传入队列中(VB2_BUF_STATE_QUEUED): 驱动程序将会对此队列中的缓冲区进行处理, 用户空间通过IOCTL:VIDIOC_QBUF把缓冲区放入到队列. 对于一个视频捕获设备, 传入队列中的缓冲区是空的, 驱动会往其中填充数据.
 在驱动的传出队列中(VB2_BUF_STATE_DONE): 这些缓冲区已由驱动处理过,对于一个视频捕获设备,缓存区已经填充了视频数据,正等用户空间来认领
 用户空间状态的队列(VB2_BUF_STATE_DEQUEUED) : 已经通过IOCTL:VIDIOC_DQBUF传出到用户空间的缓冲区, 此时缓冲区由用户空间拥有, 驱动无法访问.

[3]struct v4l2_buffer
一个v4l2_buffer在用户空间代表一块videobuf, 它与vb2_buffer一一对应. 不过需要注意的是, vb2_buffer里面包含了实际的存储空间; 但v4l2_buffer只是包含一些buffer info, 通过这些info, 用户空间可以用mmap或其它方式拿到实际的存储地址, 从而避免在用户空间与内核之间做数据拷贝.

	struct v4l2_buffer {
	__u32			index;//buffer序号
	__u32			type;//buffer类型, 见@enum v4l2_buf_type
	__u32			bytesused;//缓冲区已使用byte数
	struct timeval		timestamp;//时间戳,代表帧捕获的时间
	__u32			memory;//表示缓冲区是内存映射缓冲区还是用户空间缓冲区
	union {
		__u32           offset;//offset代表内核缓冲区的位置
		unsigned long   userptr;//userptr代表缓冲区的用户空间地址
		struct v4l2_plane *planes;
		__s32		fd;
	} m;
	/*
	(1)V4L2_MEMORY_MMAP方式, m.offset是内核空间图像数据存放的开始地址,
	会传递给mmap函数作为一个偏移, 通过mmap映射返回一个缓冲区指针p,
	p+byteused是图像数据在进程的虚拟地址空间所占区域; 
	(2)用户指针缓冲区的方式, 可以获取的图像数据开始地址的指针m.userptr,
	userptr是一个用户空间的指针, userptr+byteused便是所占的虚拟地址空间, 
	应用可以直接访问.
	*/
	__u32			length;//缓冲区大小, 单位byte
};
2021-03-26 20:54:33,596 - Model - INFO - Epoch 1 (1/200): 2021-03-26 20:57:40,380 - Model - INFO - Train Instance Accuracy: 0.571037 2021-03-26 20:58:16,623 - Model - INFO - Test Instance Accuracy: 0.718528, Class Accuracy: 0.627357 2021-03-26 20:58:16,623 - Model - INFO - Best Instance Accuracy: 0.718528, Class Accuracy: 0.627357 2021-03-26 20:58:16,623 - Model - INFO - Save model... 2021-03-26 20:58:16,623 - Model - INFO - Saving at log/classification/pointnet2_msg_normals/checkpoints/best_model.pth 2021-03-26 20:58:16,698 - Model - INFO - Epoch 2 (2/200): 2021-03-26 21:01:26,685 - Model - INFO - Train Instance Accuracy: 0.727947 2021-03-26 21:02:03,642 - Model - INFO - Test Instance Accuracy: 0.790858, Class Accuracy: 0.702316 2021-03-26 21:02:03,642 - Model - INFO - Best Instance Accuracy: 0.790858, Class Accuracy: 0.702316 2021-03-26 21:02:03,642 - Model - INFO - Save model... 2021-03-26 21:02:03,643 - Model - INFO - Saving at log/classification/pointnet2_msg_normals/checkpoints/best_model.pth 2021-03-26 21:02:03,746 - Model - INFO - Epoch 3 (3/200): 2021-03-26 21:05:15,349 - Model - INFO - Train Instance Accuracy: 0.781606 2021-03-26 21:05:51,538 - Model - INFO - Test Instance Accuracy: 0.803641, Class Accuracy: 0.738575 2021-03-26 21:05:51,538 - Model - INFO - Best Instance Accuracy: 0.803641, Class Accuracy: 0.738575 2021-03-26 21:05:51,539 - Model - INFO - Save model... 2021-03-26 21:05:51,539 - Model - INFO - Saving at log/classification/pointnet2_msg_normals/checkpoints/best_model.pth 我有类似于这样的一段txt文件,请你帮我写一段代码来可视化这些训练结果
02-06
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值