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

系列文章

高通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!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容