《最简单的BufferQueue测试程序(一)》
《最简单的BufferQueue测试程序(二)》
《最简单的BufferQueue测试程序(三)》
本文仅对BufferQueue最基本的操作接口进行讲解,不包含 SurfaceFlinger、Surface 等上层封装的概念介绍。
本文适用对象:Android小白
Android版本:8.1
阅读完本文后,你将了解如下内容:
首先来自Google官方的原图:
但本人更喜欢下面这张图,摘自林学森的《深入理解Android内核设计思想》,该图更好的阐述了BufferQueue的基本操作流程:
buffer的5种状态:FREE
、 DEQUEUED
、 QUEUED
、 ACQUIRED
、 SHARED
。
在较新的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核心代码由如下3部分组成:
BufferQueueCore 负责维护 BufferQueue 的基本数据结构,而 BufferQueueProducer 和 BufferQueueConsumer 则负责提供操作 BufferQueue 的基本接口。
BufferQueueCore在frameworks/native/include/gui/BufferQueueCore.h中定义。
下图为BufferQueueCore的内部数据结构图:
说明:
成员变量 | 说明 |
---|---|
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() 默认优先从mFreeBuffers中获取slot,因为mFreeBuffers中的slot已经有buffer与之绑定过了,这样就不用再重新分配buffer了。过程如下:
如果mFreeBuffers为空,则从mFreeSlots中获取slot:
- dequeueBuffer() 优先从mFreeBuffers中获取一个slot,同时将其对应的BufferSlot状态从FREE修改为DEQUEUED,并将该slot从mFreeBuffers迁移到mActiveBuffers中。
- 如果mFreeBuffers为空,则从mFreeSlots中获取slot,并为它分配一块指定大小的buffer,同时将其对应的BufferSlot状态从FREE修改为DEQUEUED,然后将该slot从mFreeSlots迁移到mActiveBuffers中。
- 将获取到的slot作为出参返回给调用者。如果该slot绑定的buffer是重新分配的,则返回值为BUFFER_NEEDS_REALLOCATION,否则为NO_ERROR。
可以看到,与mFreeBuffers相比,从mFreeSlots中获取slot时,要多一步分配内存的操作。当然,如果mFreeBuffers不为空,但是里面没有我们想要的buffer,比如buffer size不匹配,那么这时候也会通过触发Allocate的动作来重新分配buffer。
requestBuffer() 可以说是最没有表现力的一个函数了,因为它本质上不涉及到slot、state以及buffer的任何修改,它仅仅只是从给定的slot中取出与之绑定的GraphicBuffer指针,然后返回,仅此而已。
一般在dequeueBuffer()重新分配内存后(即函数返回值为BUFFER_NEEDS_REALLOCATION),才需要调用requestBuffer()来获取新的GraphicBuffer指针。
queueBuffer()的内部操作流程如下(以slot5为例):
- queueBuffer()根据调用者传入的slot参数,将其对应的BufferSlot状态从DEQUEUED修改为QUEUED,并根据该BufferSlot的信息生成一个BufferItem对象,然后添加到mQueue队列中。
- 调用Consumer的Listener监听函数,通知Consumer可以被acquireBuffer了。
acquireBuffer()内部操作流程如下:
acquireBuffer()从mQueue队列中取出1个BufferItem,并作为出参返回给调用者,同时修改该BufferItem对应的slot状态:QUEUED —> ACQUIRED。
releaseBuffer()内部操作流程如下(以slot0为例):
- releaseBuffer()根据调用者传入的slot参数,将其对应的BufferSlot状态从ACQUIRED修改为FREE,并将该slot从mActiveBuffers中迁移到mFreeBuffers中。注意,这里并没有对该slot绑定的buffer进行任何解绑操作。
- 调用Producer的Listener监听函数,通知Producer可以dequeueBuffer了。
除了上面提到的dequeue/queue/acquire/release这些基本操作函数外,BufferQueue还为我们提供了一些特殊函数接口,方便调用者在一些非常规流程中使用。
这些特殊函数包括attach/detach/cancel等操作,在《最简单的BufferQueue测试程序(二)》中,已经演示了如何使用这些特殊函数。但是这些特殊接口内部到底又做了什么操作呢?我们一起往下看。
描述: 给定1个GraphicBuffer指针,优先从mFreeSlots中获取一个slot,并与该GraphicBuffer绑定。slot从mFreeSlots/mFreeBuffers迁移到mActiveBuffers。
状态: FREE —> DEQUEUED
描述: 给定1个slot,将该slot绑定的buffer解绑。slot从mActiveBuffers迁移到mFreeSlots。
状态: DEQUEUED —> FREE
描述: 给定1个slot,仅仅将该slot从mActiveBuffers迁移到mFreeBuffers,不对buffer进行任何操作。
状态: DEQUEUED —> FREE
描述: 给定1个GraphicBuffer指针,优先从mFreeSlots中获取一个slot,并与该GraphicBuffer绑定。slot从mFreeSlots/mFreeBuffers迁移到mActiveBuffers。
状态: FREE —> ACQUIRED
描述: 给定1个slot,将该slot绑定的buffer解绑。slot从mActiveBuffers迁移到mFreeSlots。
状态: ACQUIRED —> FREE
描述: 将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中同样的代码,是如何实现跨进程通信的呢?
client端:remote()->transact()
server端:onTransact()
- client端通过 remote()->transact() 对binder驱动发起操作请求,并等待返回结果。
- binder驱动将该请求转发给对应的server端。
- server端通过onTransact()来响应client的操作请求,并将结果返回给client端。
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: Binder Proxy,Binder远程代理,即client端。
Bn: Binder Native,Binder本地实现,即server端。
于是,我们只需要定义一个基类的指针 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所指向的对象,是在本地进程创建的,还是在远程进程中创建的。
示例代码下载:GitHub BufferQueue
Android自带BufferQueue测试程序:BufferQueue_test.cpp
玛法里奥赵四 CSDN博客:AndroidFramework – Binder中的Bn与Bp