系列文章
高通msm-V4L2-Camera驱动浅析1-初识
高通msm-V4L2-Camera驱动浅析2-框架详解
高通msm-V4L2-Camera驱动浅析3-session
高通msm-V4L2-Camera驱动浅析4-stream
高通msm-V4L2-Camera驱动浅析5-buffer
上一篇文章讲到传输图像的方式:
方法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!