高通msm-V4L2-Camera驱动浅析5-buffer

##系列文章
高通msm-V4L2-Camera驱动浅析1-初识
高通msm-V4L2-Camera驱动浅析2-框架详解
高通msm-V4L2-Camera驱动浅析3-session

上一篇文章讲到传输图像的方式:

  • 方法1:通过【帧IO】访问方式
    使用read和write的方式,通过read读取每一帧数据,数据需要在内核和用户之间拷贝,这种方式访问速度会非常慢。

  • 方法2:通过【流IO】访问方式:

    • 内存映射缓冲区(V4L2_MEMORY_MMAP):在内核空间开辟缓冲区,应用通过mmap()系统调用映射到用户地址空间
    • 用户空间缓冲区(V4L2_MEMORY_USERPTR):在用户空间的应用中开辟缓冲区,用户与内核空间之间交换缓冲区指针。

因此**stream(流)**的概念就诞生了!
stream又涉及到图像的vb2_buffer,vb2_buffer又由vb2_queue 队列管理

推荐文章

Linux V4L2子系统-videobuf2框架分析(三)

V4L2 videobuffer2的介绍,数据流分析

buffer 类型

  • V4L2_BUF_TYPE_VIDEO_CAPTURE = 1,指定buf的类型为capture,用于视频捕获设备(单平面)
  • V4L2_BUF_TYPE_VIDEO_OUTPUT = 2,指定buf的类型output,用于视频输出设备
  • V4L2_BUF_TYPE_VIDEO_OVERLAY = 3,指定buf的类型为overlay,用于overlay设备
  • V4L2_BUF_TYPE_VBI_CAPTURE = 4,用于vbi捕获设备
  • V4L2_BUF_TYPE_VBI_OUTPUT = 5,用于vbi输出设备
  • V4L2_BUF_TYPE_SLICED_VBI_CAPTURE = 6,用于切片vbi捕获设备
  • V4L2_BUF_TYPE_SLICED_VBI_OUTPUT = 7,用于切片vbi输出设备
  • V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY = 8,用于视频输出overlay设备
  • V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE = 9,指定buf的类型为capture,用于视频捕获设备(多平面)

缓存区平面

一、相关结构体

1. vb2_queue

struct vb2_queue {
	unsigned int			type;// //buffer类型
	unsigned int			io_modes;//访问IO的方式:mmap、userptr 、dma
···
	const struct vb2_ops		*ops;//vb2_queue的操作函数集合
	const struct vb2_mem_ops	*mem_ops;//buffer memory操作集合
	const struct vb2_buf_ops	*buf_ops; //buffer的操作函数集合
···
	/* private: internal use only */
···
	unsigned int			memory;//当前使用的存储类型
	struct vb2_buffer		*bufs[VB2_MAX_FRAME];//图像buf(缓冲区)
	unsigned int			num_buffers;//已分配/使用的buf(缓冲区)数目
···
}

2. vb2_queue的操作函数集合:vb2_ops

struct vb2_ops {
//在分配内存之前,由VIDIOC_REQBUFS和VIDIOC_CREATE_BUFS处理程序调用
	int (*queue_setup)(struct vb2_queue *q,
			   unsigned int *num_buffers, unsigned int *num_planes,
			   unsigned int sizes[], struct device *alloc_devs[]);
//释放调用vb2函数时获得的所有锁;
	void (*wait_prepare)(struct vb2_queue *q);
//重新获取在前一个回调中释放的所有锁;
	void (*wait_finish)(struct vb2_queue *q);

//在分配缓冲区后调用一次(在MMAP情况下)或在获取新的USERPTR缓冲区后调用一次;
	int (*buf_init)(struct vb2_buffer *vb);
//buffer从用户空间入队时调用
	int (*buf_prepare)(struct vb2_buffer *vb);
//buffer出队返回给用户空间时调用
	void (*buf_finish)(struct vb2_buffer *vb);
//释放buffer
	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传递给驱动程序;
	void (*buf_queue)(struct vb2_buffer *vb);
};
  • buf_queue:
    将buffer传递给驱动程序;驱动程序可能在这个缓冲区上启动硬件操作;
    驱动程序应该通过调用vb2_buffer_done()函数来返回缓冲区;
    它总是在调用STREAMON IOCTL之后被调用;
    如果用户在调用STREAMON之前预排队缓冲区,则可能在start_streaming回调函数之前调用

3. buffer memory操作集合:vb2_mem_ops

struct vb2_mem_ops {
//MMAP类型所需的操作:。
	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);//释放视频内存

	unsigned int	(*num_users)(void *buf_priv);//返回内存缓冲区的当前用户数量

	int		(*mmap)(void *buf_priv, struct vm_area_struct *vma);//内核缓冲区映射到用户地址空间

//USERPTR类型需要的操作::
	void		*(*get_userptr)(struct device *dev, unsigned long vaddr,
					unsigned long size,
					enum dma_data_direction dma_dir);//获取用户空间内存;
	void		(*put_userptr)(void *buf_priv);//释放用户空间内存;

//DMABUF类型所需的操作:
	struct dma_buf *(*get_dmabuf)(void *buf_priv, unsigned long flags);
	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		(*prepare)(void *buf_priv);
	void		(*finish)(void *buf_priv);
···

}
  • USERPTR类型需要的操作:get_userptr, put_userptr。
  • MMAP类型所需的操作:alloc, put, num_users, MMAP。
  • 读写访问类型需要的操作:alloc, put, num_users, vaddr。
  • DMABUF类型所需的操作:attach_dmabuf, detach_dmabuf,map_dmabuf, unmap_dmabuf。

4. vb2_buf_ops:buffer的操作函数集合

struct vb2_buf_ops {
//验证给定的用户空间是否包含足够的缓冲区平面。
	int (*verify_planes_array)(struct vb2_buffer *vb, const void *pb);
//给定一个vb2_buffer填充用户空间结构。对于V4L2,是v4l2_buffer。
	void (*fill_user_buffer)(struct vb2_buffer *vb, void *pb);
//给定一个用户空间结构,填充vb2_buffer。
	int (*fill_vb2_buffer)(struct vb2_buffer *vb, const void *pb,
				struct vb2_plane *planes);
//从用户空间拷贝时间戳到内核
	void (*copy_timestamp)(struct vb2_buffer *vb, const void *pb);
};
  • 调用fill_user_buffer进行buffer数据填充

5.用户空间的buf:v4l2_buffer

struct v4l2_buffer {
	__u32			index;//buffer 序号
	__u32			type;//buffer类型
	__u32			bytesused;//缓冲区已使用byte数
	__u32			flags;
	__u32			field;
	struct timeval		timestamp;时间戳,代表帧捕获的时间
	struct v4l2_timecode	timecode;
	__u32			sequence;

	/* memory location */
	__u32			memory;//表示缓冲区是内存映射缓冲区还是用户空间缓冲区
	union {
		__u32           offset;//内核缓冲区的位置
		unsigned long   userptr;//缓冲区的用户空间指针
		struct v4l2_plane *planes;
		__s32		fd;//dma内存相关的文件描述符
	} m;
	__u32			length;
	__u32			reserved2;
	__u32			reserved;
};
  • 当 type = V4L2_MEMORY_MMAP方式
    m.offset是内核空间图像数据存放的开始地址,
    通过mmap映射返回一个缓冲区指针p,p+byteused是图像数据在进程的虚拟地址空间所占区域
  • 当 type = V4L2_MEMORY_USERPTR
    图像数据开始地址的指针m.userptr。
    userptr是一个用户空间的指针,userptr+byteused便是所占的虚拟地址空间,应用可以直接访问。

6. 内核空间对应的buf:vb2_buffer

struct vb2_buffer {
	struct vb2_queue	*vb2_queue;//这个驱动程序所属的队列
	unsigned int		index;//buffer序号
	unsigned int		type;//buffer类型
	unsigned int		memory;//buffer内存
	unsigned int		num_planes;//buffer中的位面数量
	struct vb2_plane	planes[VB2_MAX_PLANES];//位面
	u64			timestamp;//时间戳

	enum vb2_buffer_state	state;//buffer状态

	struct list_head	queued_entry;//表示buf可以入队的链表
	struct list_head	done_entry;//表示buf可以出队的链表

}

二、v4l2_buffer的使用

2.1 buffer是如何申请的?

  • 用户空间:通过VIDIOC_REQBUFS
    hardware/qcom/camera/QCamera2/stack/mm-camera-interface/src/mm_camera_stream.c
int32_t mm_stream_request_buf(mm_stream_t * my_obj)
{
    int32_t rc = 0;
    struct v4l2_requestbuffers bufreq;
    uint8_t buf_num = my_obj->total_buf_cnt;

    memset(&bufreq, 0, sizeof(bufreq));
    bufreq.count = buf_num;
    bufreq.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    bufreq.memory = V4L2_MEMORY_USERPTR;
    rc = ioctl(my_obj->fd, VIDIOC_REQBUFS, &bufreq);

    return rc;
}

bufreq.memory = 用户空间缓冲区(V4L2_MEMORY_USERPTR)
这里可以知道,高通使用的是用户空间缓冲区

  • 内核空间
    kernel/msm-4.9/drivers/media/platform/msm/camera_v2/camera/camera.c
static int camera_v4l2_reqbufs(struct file *filep, void *fh,
	struct v4l2_requestbuffers *req)
{
···
	ret = vb2_reqbufs(&sp->vb2_q, req);
···
	return ret;
}

申请内存的方式如下:

  • a. USERPTR:此种方式由用户态去申请空间(比如用vmalloc,申请的大小可能有限).在VIDIOC_QBUF时会将用户态信息v4l2_buffer转换为内核态下vb2_buffer信息
  • b. DMA的方式,也是由用户态去申请空间
  • c. MMAP方式: 此方式由内核态去申请

2.2 buffer是如何入队的?

  • 用户空间:通过VIDIOC_QBUF
int32_t mm_stream_qbuf(mm_stream_t *my_obj, mm_camera_buf_def_t *buf)
{
···
    rc = ioctl(my_obj->fd, VIDIOC_QBUF, &buffer);
···
}
  • 内核空间
static int camera_v4l2_qbuf(struct file *filep, void *fh,
	struct v4l2_buffer *pb)
{
···
	ret = vb2_qbuf(&sp->vb2_q, pb);
···
}

vb2_qbuf最终会调用到vb2_core_qbuf

int vb2_core_qbuf(struct vb2_queue *q, unsigned int index, void *pb)
{
	struct vb2_buffer *vb;
···

	/*
	 * Add to the queued buffers list, a buffer will stay on it until
	 * dequeued in dqbuf.
	 */
	list_add_tail(&vb->queued_entry, &q->queued_list);
	q->queued_count++;
	q->waiting_for_buffers = false;
	vb->state = VB2_BUF_STATE_QUEUED;
···

	/*
	 * If already streaming, give the buffer to driver for processing.
	 * If not, the buffer will be given to driver on next streamon.
	 */
	if (q->start_streaming_called)
		__enqueue_in_driver(vb);

	/* Fill buffer information for the userspace */
	if (pb)
		call_void_bufop(q, fill_user_buffer, vb, pb);

	/*
	 * If streamon has been called, and we haven't yet called
	 * start_streaming() since not enough buffers were queued, and
	 * we now have reached the minimum number of queued buffers,
	 * then we can finally call start_streaming().
	 */
	if (q->streaming && !q->start_streaming_called &&
	    q->queued_count >= q->min_buffers_needed) {
		ret = vb2_start_streaming(q);
		if (ret)
			return ret;
	}

	dprintk(1, "qbuf of buffer %d succeeded\n", vb->index);
	return 0;
}
  • 1.将buffer添加到队列里面,并且设置buffer状态为:VB2_BUF_STATE_QUEUED
    list_add_tail(&vb->queued_entry, &q->queued_list);
    vb->state = VB2_BUF_STATE_QUEUED;
    入队的意思:把用户缓冲区送给kernel使用

  • 2.如果已经开启流,则将buf交给驱动程序进行处理
    __enqueue_in_driver(vb);
    该函数会调用buf_queue将buf交给驱动程序进行处理

  • 3.把buffer信息填充到用户空间
    .fill_user_buffer = __fill_v4l2_buffer,

2.3 buffer数据在哪里被填充的?

  • buffe是在ISP填充这个数据的,填充好了之后,会发送一个中断信号。
  • 中断处理程序msm_isp_process_axi_irq设置当前buf的状态是填充好的,最终把buf添加到done buffers队列里

调用流程如下:

msm_isp_process_axi_irq ->
msm_isp_process_axi_irq_stream ->
msm_isp_process_done_buf ->
msm_isp_buf_done ->
msm_vb2_buf_done ->
vb2_buffer_done
void msm_isp_process_axi_irq_stream(···)
{
	struct msm_isp_buffer *done_buf = NULL;
···
    //填充buf
	done_buf = stream_info->buf[pingpong_bit];
···
    if (stream_info->pending_buf_info.is_buf_done_pending != 1) {
        //处理填充的buf
        msm_isp_process_done_buf(vfe_dev, stream_info,
				done_buf, time_stamp, frame_id);
    }
}

最终调用到vb2_buffer_done

void vb2_buffer_done(struct vb2_buffer *vb, enum vb2_buffer_state state)
{
    //buffer队列
	struct vb2_queue *q = vb->vb2_queue;
···

	/* 同步 buffers */
	for (plane = 0; plane < vb->num_planes; ++plane)
		call_void_memop(vb, finish, vb->planes[plane].mem_priv);

	spin_lock_irqsave(&q->done_lock, flags);
	if (state == VB2_BUF_STATE_QUEUED ||
	    state == VB2_BUF_STATE_REQUEUEING) {
		vb->state = VB2_BUF_STATE_QUEUED;
	} else {
		/* Add the buffer to the done buffers list */
		list_add_tail(&vb->done_entry, &q->done_list);
		vb->state = state;//设置buf状态
	}
	atomic_dec(&q->owned_by_drv_count);
	spin_unlock_irqrestore(&q->done_lock, flags);
···
}
  • 将缓冲区添加到done buffers链表中
    list_add_tail(&vb->done_entry, &q->done_list);
  • 设置buf状态:VB2_BUF_STATE_DONE
    vb->state = state;

2.4 buffer是如何出队的

  • 用户空间:VIDIOC_DQBUF
    hardware/qcom/camera/QCamera2/stack/mm-camera-interface/src/mm_camera_stream.c
int32_t mm_stream_read_msm_frame(mm_stream_t * my_obj,
                                 mm_camera_buf_info_t* buf_info,
                                 uint8_t num_planes)
{
    struct v4l2_buffer vb;

    vb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    vb.memory = V4L2_MEMORY_USERPTR;
    vb.m.planes = &planes[0];
    vb.length = num_planes;

    rc = ioctl(my_obj->fd, VIDIOC_DQBUF, &vb);
  • 内核空间
static int camera_v4l2_dqbuf(struct file *filep, void *fh,
	struct v4l2_buffer *pb)
{
···
	ret = vb2_dqbuf(&sp->vb2_q, pb, filep->f_flags & O_NONBLOCK);
···
}

最终调用

int vb2_core_dqbuf(struct vb2_queue *q, unsigned int *pindex, void *pb,
		   bool nonblocking)
{
	struct vb2_buffer *vb = NULL;
	int ret;
    //从done_list取出一个填充好的buffer
	ret = __vb2_get_done_vb(q, &vb, pb, nonblocking);

···
    //调用buf_finish方法
	call_void_vb_qop(vb, buf_finish, vb);

	/* Fill buffer information for the userspace */
	if (pb)
		call_void_bufop(q, fill_user_buffer, vb, pb);

	/* Remove from videobuf queue */
	list_del(&vb->queued_entry);
	q->queued_count--;

···

	/* go back to dequeued state */
	__vb2_dqbuf(vb);
····
}

__vb2_get_done_vb 将q->done_list 中的vb2_buffer中提出来,
通过fill_user_buffer 将vb2_buffer中的v4l2_buffer信息返回,并将其从q->done_list 中删除。

三、总结

  • 应用层和kernel层共同操作一个buffer queue。

  • 出队:应用层通过VIDIOC_DQBUF从buffer队列获取填充好的数据
    (此时如果队列内存在有效数据,那么kernel会返回,否则kernel阻塞,直到有效数据出现)

  • 入队:使用完后,再把应用层再通过VIDIOC_QBUF将buffer返回给kernel,就是入队,供kernel使用。

  • kernel 向应用层提供了两个接口:一个QUEUE_BUF,一个DEQUE_BUF*

  • kernel会记录buffer队列中哪一个包含有效数据,如果应用层DEQUEUE一个buffer,那么kernel会判断这个buffer是否已经填充了camera数据,如果是则返回,不是则阻塞。

  • kernel在收到camera数据传输完毕的中断后,会把队列中的一个buffer置为可用,同时唤醒阻塞在buffer queue上的进程。

  • kernel和应用的关系,就是生产者和消费者关系,kernel负责填充camera数据,app负责消费camera数据

stay hungry stay foolish!

  • 5
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值