BufferQueue 学习总结(内附动态图)

一、前言

《最简单的BufferQueue测试程序(一)》
《最简单的BufferQueue测试程序(二)》
《最简单的BufferQueue测试程序(三)》

本文仅对BufferQueue最基本的操作接口进行讲解,不包含 SurfaceFlinger、Surface 等上层封装的概念介绍。

本文适用对象:Android小白
Android版本:8.1

阅读完本文后,你将了解如下内容:

  • 什么是BufferQueue
  • BufferQueue内部操作的原理是什么
  • BufferQueue跨进程操作是怎么实现的
  • 如何写一个最简单的BufferQueue测试程序

二、基本概念

首先来自Google官方的原图:

BufferQueue 学习总结(内附动态图)_第1张图片

但本人更喜欢下面这张图,摘自林学森的《深入理解Android内核设计思想》,该图更好的阐述了BufferQueue的基本操作流程:
在这里插入图片描述

buffer的5种状态:FREEDEQUEUEDQUEUEDACQUIREDSHARED

在较新的Android版本中(5.1及以上),buffer的状态由引用计数来表示,如下表(摘自 frameworks/native/include/gui/BufferSlot.h):

state mShared mDequeueCount mQueueCount mAcquireCount
FREE false 0 0 0
DEQUEUED false 1 0 0
QUEUED false 0 1 0
ACQUIRED false 0 0 1
SHARED true any any any

三、BufferQueue内部结构

BufferQueue核心代码由如下3部分组成:

  • BufferQueueCore
  • BufferQueueProducer
  • BufferQueueConsumer

BufferQueueCore 负责维护 BufferQueue 的基本数据结构,而 BufferQueueProducer 和 BufferQueueConsumer 则负责提供操作 BufferQueue 的基本接口。

BufferQueueCore在frameworks/native/include/gui/BufferQueueCore.h中定义。
下图为BufferQueueCore的内部数据结构图:
BufferQueue 学习总结(内附动态图)_第2张图片
说明:

成员变量 说明
mQueue 存放BufferItem的FIFO队列
mSlots BufferSlot结构体数组,数组长度为64
mFreeSlots BufferSlot状态为FREE,且没有GraphicBuffer与之相绑定的slot集合
mFreeBuffers BufferSlot状态为FREE,且有GraphicBuffer与之相绑定的slot集合
mActiveBuffers BufferSlot状态不为FREE(即DEQUEUED、QUEUED、ACQUIRED、SHARED)的slot集合。既然状态不是FREE,那么该BufferSlot必然有一个GraphicBuffer与之相绑定
mUnusedSlots 未参与使用的slot集合,由 mMaxBufferCount 决定

所以就有了如下等式:

mSlots = mFreeSlots + mFreeBuffers + mActiveBuffers + mUnusedSlots

四、基本函数

在《最简单的BufferQueue测试程序(一)》中,演示了一个BufferQueue的基本操作流程。dequeue/queue/acquire/release,这些基本函数内部到底做了什么操作呢?我们一起来研究一下。

dequeueBuffer()

dequeueBuffer() 默认优先从mFreeBuffers中获取slot,因为mFreeBuffers中的slot已经有buffer与之绑定过了,这样就不用再重新分配buffer了。过程如下:
BufferQueue 学习总结(内附动态图)_第3张图片
如果mFreeBuffers为空,则从mFreeSlots中获取slot:

  1. dequeueBuffer() 优先从mFreeBuffers中获取一个slot,同时将其对应的BufferSlot状态从FREE修改为DEQUEUED,并将该slot从mFreeBuffers迁移到mActiveBuffers中。
  2. 如果mFreeBuffers为空,则从mFreeSlots中获取slot,并为它分配一块指定大小的buffer,同时将其对应的BufferSlot状态从FREE修改为DEQUEUED,然后将该slot从mFreeSlots迁移到mActiveBuffers中。
  3. 将获取到的slot作为出参返回给调用者。如果该slot绑定的buffer是重新分配的,则返回值为BUFFER_NEEDS_REALLOCATION,否则为NO_ERROR。

可以看到,与mFreeBuffers相比,从mFreeSlots中获取slot时,要多一步分配内存的操作。当然,如果mFreeBuffers不为空,但是里面没有我们想要的buffer,比如buffer size不匹配,那么这时候也会通过触发Allocate的动作来重新分配buffer。

requestBuffer()

requestBuffer() 可以说是最没有表现力的一个函数了,因为它本质上不涉及到slot、state以及buffer的任何修改,它仅仅只是从给定的slot中取出与之绑定的GraphicBuffer指针,然后返回,仅此而已。

一般在dequeueBuffer()重新分配内存后(即函数返回值为BUFFER_NEEDS_REALLOCATION),才需要调用requestBuffer()来获取新的GraphicBuffer指针。

queueBuffer()

queueBuffer()的内部操作流程如下(以slot5为例):

BufferQueue 学习总结(内附动态图)_第4张图片

  1. queueBuffer()根据调用者传入的slot参数,将其对应的BufferSlot状态从DEQUEUED修改为QUEUED,并根据该BufferSlot的信息生成一个BufferItem对象,然后添加到mQueue队列中。
  2. 调用Consumer的Listener监听函数,通知Consumer可以被acquireBuffer了。

acquireBuffer()

acquireBuffer()内部操作流程如下:

BufferQueue 学习总结(内附动态图)_第5张图片

acquireBuffer()从mQueue队列中取出1个BufferItem,并作为出参返回给调用者,同时修改该BufferItem对应的slot状态:QUEUED —> ACQUIRED。

releaseBuffer()

releaseBuffer()内部操作流程如下(以slot0为例):
BufferQueue 学习总结(内附动态图)_第6张图片

  1. releaseBuffer()根据调用者传入的slot参数,将其对应的BufferSlot状态从ACQUIRED修改为FREE,并将该slot从mActiveBuffers中迁移到mFreeBuffers中。注意,这里并没有对该slot绑定的buffer进行任何解绑操作。
  2. 调用Producer的Listener监听函数,通知Producer可以dequeueBuffer了。

五、特殊函数

除了上面提到的dequeue/queue/acquire/release这些基本操作函数外,BufferQueue还为我们提供了一些特殊函数接口,方便调用者在一些非常规流程中使用。
这些特殊函数包括attach/detach/cancel等操作,在《最简单的BufferQueue测试程序(二)》中,已经演示了如何使用这些特殊函数。但是这些特殊接口内部到底又做了什么操作呢?我们一起往下看。

producer->attachBuffer()

描述: 给定1个GraphicBuffer指针,优先从mFreeSlots中获取一个slot,并与该GraphicBuffer绑定。slot从mFreeSlots/mFreeBuffers迁移到mActiveBuffers。
状态: FREE —> DEQUEUED

producer->detachBuffer()

描述: 给定1个slot,将该slot绑定的buffer解绑。slot从mActiveBuffers迁移到mFreeSlots。
状态: DEQUEUED —> FREE

producer->cancelBuffer()

描述: 给定1个slot,仅仅将该slot从mActiveBuffers迁移到mFreeBuffers,不对buffer进行任何操作。
状态: DEQUEUED —> FREE

consumer->attachBuffer()

描述: 给定1个GraphicBuffer指针,优先从mFreeSlots中获取一个slot,并与该GraphicBuffer绑定。slot从mFreeSlots/mFreeBuffers迁移到mActiveBuffers。
状态: FREE —> ACQUIRED

consumer->detachBuffer()

描述: 给定1个slot,将该slot绑定的buffer解绑。slot从mActiveBuffers迁移到mFreeSlots。
状态: ACQUIRED —> FREE

consumer->discardFreeBuffers

描述: 将mFreeBuffers中的slot全部迁移到mFreeSlots中,并释放所有绑定的buffers。
状态: FREE —> FREE

光看上面这些描述,可能大家还是不太清楚这些特殊函数的意义和用途。那么通过下面与基本函数的对比,我想大家应该会更容易理解一些。

Producer:

函数名 区别
attachBuffer 不涉及到buffer的分配动作
dequeueBuffer 可能会涉及到buffer的分配动作
函数名 区别
detachBuffer 释放buffer,slot —> mFreeSlots
cancelBuffer 不释放buffer,slot —> mFreeBuffers

Consumer:

函数名 区别
attachBuffer 直接从FREE —> ACQUIRED
acquireBuffer 必须是 QUEUED —> ACQUIRED
函数名 区别
detachBuffer 释放buffer,slot —> mFreeSlots
releaseBuffer 不释放buffer,slot —> mFreeBuffers

六、跨进程如何实现

在《最简单的BufferQueue测试程序(三)》中,演示了如何跨进程操作BufferQueue。在该示例中我们看到,client.cpp中对BufferQueue的远程操作代码,和《(一)》中本地执行的代码一模一样。但是前者是通过binder调用来实现跨进程访问,而后者则为本地函数直接调用,虽然二者在代码的表现形式上都是一样的,但是各自实现的路径却是有差别的。当然,殊途同归,最终BufferQueue的操作结果还是一样的

那么client.cpp中同样的代码,是如何实现跨进程通信的呢?

Binder 调用

client端:remote()->transact()
server端:onTransact()
BufferQueue 学习总结(内附动态图)_第7张图片

  1. client端通过 remote()->transact() 对binder驱动发起操作请求,并等待返回结果。
  2. binder驱动将该请求转发给对应的server端。
  3. server端通过onTransact()来响应client的操作请求,并将结果返回给client端。

虚函数

如下图所示:
BufferQueue 学习总结(内附动态图)_第8张图片

Class Base 为基类,Class A 和 Class B 均继承于 Class Base。由于Class Base内部为纯虚函数,因此不能直接对它进行实例化,只能对它的子类 Class A 或 Class B 进行实例化对象。

有时候因为软件设计需要,我们希望使用同一套代码来实现不同的软件逻辑。例如,我们希望当实现 Class A 对象时,则调用 Class A 的成员函数。当实现 Class B 对象时,则调用 Class B 的成员函数。那么这时候,只需要定义一个基类 Class Base 的指针 sp,让它分别指向不同的子类对象,就可以实现。

Bp 和 Bn

Bp: Binder Proxy,Binder远程代理,即client端。
Bn: Binder Native,Binder本地实现,即server端。

如果将上面两幅图融合在一起,就成了下面这样:
BufferQueue 学习总结(内附动态图)_第9张图片

于是,我们只需要定义一个基类的指针 sp,就可以使用同一套操作接口,来实现不同的操作路径。比如 sp->func1(),当sp指向 Class BpBase 对象时,它执行的是binder远程调用;当sp指向 Class BnBase 对象时,它执行的是本地函数func1()的直接调用。

因此,如果仅仅只看下面这段代码,是无法区分该BufferQueue的操作是在本地进程执行,还是在其它进程中执行的。

void main(void)
{
    sp<IGraphicBufferProducer> producer;
    sp<IGraphicBufferConsumer> consumer;
	...
	producer->dequeueBuffer();
	producer->requestBuffer();
	producer->queueBuffer();
	...
	consumer->acquireBuffer();
	consumer->releaseBuffer();
}

唯一区分的方法是,看producer和consumer所指向的对象,是在本地进程创建的,还是在远程进程中创建的。

七、总结

  1. buffer状态切换如下:
    BufferQueue 学习总结(内附动态图)_第10张图片

  2. 除了关注代码的逻辑,还需要确定当前是本地操作,还是远程调用;

  3. Android 规定,BufferQueue只能在Consumer进程中创建;

八、参考资料

示例代码下载:GitHub BufferQueue
Android自带BufferQueue测试程序:BufferQueue_test.cpp
玛法里奥赵四 CSDN博客:AndroidFramework – Binder中的Bn与Bp

你可能感兴趣的:(Android)