Android BufferQueue
1、概述
Android显示系统依赖于BufferQueue,对于系统来说,BufferQueue是非常重要的传递数据的组件,只要显示内容到“屏幕”(此处指抽象的屏幕,有时候还可以包含编码器),就一定需要用到BufferQueue,可以说在显示/播放器相关的领域中,BufferQueue无处不在。即使直接调用Opengl ES来绘制,底层依然需要BufferQueue才能显示到屏幕上。
2、BufferQueue的设计思想
BufferQueue是Android显示系统的核心,它的设计哲学是生产者-消费者模型,只要往BufferQueue中填充数据,则认为是生产者,只要从BufferQueue中获取数据,则认为是消费者。有时候同一个类,在不同的场景下既可能是生产者也有可能是消费者。
如SurfaceFlinger,在合成并显示UI内容时,UI元素作为生产者生产内容,SurfaceFlinger作为消费者消费这些内容。而在截屏时,SurfaceFlinger又作为生产者将当前合成显示的UI内容填充到另一个BufferQueue,截屏应用此时作为消费者从BufferQueue中获取数据并生产截图。
3、基本操作流程
首先是Google官方的原图:
然后是摘自《深入理解Android内核设计思想》的流程图:
在上面的流程图中,我们可以看到几个关键信息点:
- Buffer的五种状态:FREE、DEQUEUED、QUEUED、ACQUIRED、SHARED
- Buffer的流转过程
- 以及需要哪些核心的数据结构、类去实现这一整套框架
下面针对这些关键点去学习BufferQueue的相关原理。
4、BufferQueue的使用 & buffer的状态
4.1 BufferQueue的使用步骤:
- 初始化一个BufferQueue
- 图形数据的生产者通过BufferQueue申请一块GraphicBuffer,对应图中的dequeueBuffer方法
- 申请到GraphicBuffer后,获取GraphicBuffer,通过函数requestBuffer获取
- 获取到GraphicBuffer后,通过各种形式往GraphicBuffer中填充图形数据后,然后将GraphicBuffer入队到BufferQueue中,对应上图中的queueBuffer方法
- 在新的GraphicBuffer入队BufferQueue时,BufferQueue会通过回调通知图形数据的消费者,有新的图形数据被生产出来了
- 然后消费者从BufferQueue中出队一个GraphicBuffer,对应图中的acquireBuffer方法
- 待消费者消费完图形数据后,将空的GraphicBuffer还给BufferQueue以便重复利用,此时对应上图中的releaseBuffer方法
- 此时BufferQueue再通过回调通知图形数据的生产者有空的GraphicBuffer了,图形数据的生产者又可以从BufferQueue中获取一个空的GraphicBuffer来填充数据
- 一直循环2-8步骤,这样就有条不紊的完成了图形数据的生产-消费
Note:(1)producer 和 consumer 都不需要等待BufferQueue的回调才能“生成” 或 “获取” buffer数据的,而是异步的过程需要注意的是,producer是不用等待BufferQueue的回调再生成数据,而是一直生成数据,然后入队到BufferQueue中,直到BufferQueue满为止。consumer也不用等BufferQueue的回调通知再去拿数据,而是不断地尝试从BufferQueue中获取数据,但是这样做的效率比较低,需要不断的轮询BufferQueue(因为BufferQueue有同步阻塞和非同步阻塞两种机制。同步阻塞机制下,获取数据失败会阻塞线程直到有数据才会唤醒该线程。非同步阻塞机制下,在获取数据失败的时候不会阻塞线程,而是直接返回-1)。(2)BufferQueue内部使用共享内存和Binder机制进行通信因为同时使用一个BufferQueue的producer和consumer往往处于不同的进程之中,因此涉及到了进程间通信。进程间通信的几个常见的方式有:“共享内存”、“socket”、“pipe”、“FIFO”、“MessageQueue”、“Semaphore”、“sinal”等等。BufferQueue内部使用共享内存和Binder机制在不同的进程之间传递数据,这样可以减少数据拷贝从而提高效率。
4.2 Buffer的状态
目前,Buffer的状态,都是通过各个状态的Buffer的量来表示状态,状态是根据3个uint32_t变量(mDequeueCount/mQueueCount/mAcquireCount)和1个bool变量(mShared)的值来进行判断的,如下表格就是各种状态下各个变量的组合情况:
Buffer状态 | 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 |
* frameworks/native/libs/gui/include/gui/BufferSlot.h struct BufferState { BufferState() : mDequeueCount(0), mQueueCount(0), mAcquireCount(0), mShared(false) { } uint32_t mDequeueCount; uint32_t mQueueCount; uint32_t mAcquireCount; bool mShared; ... ... };简单的解释下这几个状态:
- FREE:表示该Buffer没有被生产者-消费者所使用,该Buffer的所有权属于BufferQueue,可以被producer dequeue。
- DEQUEUED:表示该Buffer被生产者获取了,该Buffer的所有权属于生产者。
- QUEUED:表示该Buffer被生产者填充了数据,并且入队到BufferQueue了,该Buffer的所有权属于BufferQueue
- ACQUIRED:表示该Buffer被消费者获取了,该Buffer的所有权属于消费者
- SHARED:这是一个特殊的状态,SHARED的buffer并不会参与前面所说的状态迁移,置于这个状态的buffer处于共享Buffer模式。在这个模式中,它可以是除了FREE状态之外的其他任何状态,它可以被多次dequeued、queued或者acquired。这个共享buffer模式主要用于VR等低延时要求的场合。
Note:(1)为什么需要这些状态?假设不需要这些状态,实现一个简单的BufferQueue:
BufferQueue{ vector<GraphicBuffer> slots; void push(GraphicBuffer slot){ slots.push(slot); } GraphicBuffer pull(){ return slots.pull(); } }producer生产完数据之后,通过调用BufferQueue的push函数将数据插入到vector中。consumer调用BufferQueue的pull函数出对一个Buffer数据。那么上述实现的问题就在于,生产者每次都需要自行创建GraphicBuffer,而消费者每次消费完数据后的GraphicBuffer就会被释放了,这样就让GraphicBuffer没有得到循环利用。而在android中,由于BufferQueue的producer-consumer往往处于不同的进程中,GraphicBuffer内部需要通过共享内存来链接producer-consumer进程的,每次都去创建GraphicBuffer就意味着创建共享内存,这样效率很低且浪费资源。而BufferQueue的BufferSate就解决了这个问题,每个GraphicBuffer都有当前的状态,通过维护buffer的当前状态,完成对buffer的复用。
(2)Fence机制当producer dequeue一个buffer后,这个buffer就变成DEQEUED状态了。release Fence发信号后,producer就可以修改buffer的内容。Buffer绘制完后,queue到BufferQueue中,给Consumer进行消费。此时Buffer可能还没有真正绘制完成,必现要等对应的Fence发信号出来后,才真正完成。此时Buffer是BufferQueue持有,通过acquireBuffer流程,可以迁移到ACQUIRED状态。当buffer变成ACQUIRED状态,表示Buffer已经被Consumer获取,但是也必须要等对应的Fence发信号才能被Consumer读写,这个Fence是从Producer那边,queueBuffer的时候传过来的。我们将其称为acquire fence。
在显示系统中,实现流畅的绘制和显示,一般的buffer大致会经过如下这个流程:
FREE -> DEQUEUED -> QUEUED -> ACQUIRED -> FREE
5、BufferQueue相关的几个类
BufferBufferCore:BufferQueue的实际实现 BufferSlot:用来存储GraphicBuffer。 BufferState:表示GraphicBuffer的状态 IGraphicBufferProducer:BufferQueue的生产者接口,实现类是BufferQueueProducer IGraphicBufferConsumer:BufferQueue的消费者接口,实现类是BufferQueueConsumer GraphicBuffer:表示一个Buffer,可以填充图像数据 ANativeWindow_Buffer:GraphicBuffer的父类 ConsumerBase:实现了ConsumerListener接口,在数据入队列时会被调用到,用来通知消费者如上是BufferQueue中的几个重要的类,其中有三个核心的结构:
- BufferQueueCore
- BufferQueueProducer
- BufferQueueConsumer
layer的初始化函数onFirstRef:在layer的初始化函数onFirstRef中会创建BufferQueue,onFirstRef的函数实现如下:void Layer::onFirstRef() { // Creates a custom BufferQueue for SurfaceFlingerConsumer to use sp<IGraphicBufferProducer> producer;//sp是Google实现的一种强引用的智能指针 sp<IGraphicBufferConsumer> consumer; BufferQueue::createBufferQueue(&producer, &consumer, true); mProducer = new MonitoredProducer(producer, mFlinger, this); mSurfaceFlingerConsumer = new SurfaceFlingerConsumer(consumer, mTextureName, this); mSurfaceFlingerConsumer->setConsumerUsageBits(getEffectiveUsage(0)); mSurfaceFlingerConsumer->setContentsChangedListener(this); mSurfaceFlingerConsumer->setName(mName); if (mFlinger->isLayerTripleBufferingDisabled()) { mProducer->setMaxDequeuedBufferCount(2); } const sp<const DisplayDevice> hw(mFlinger->getDefaultDisplayDevice()); updateTransformHint(hw); }onFirstRef函数中关键的一步就是调用createBufferQueue创建BufferQueue,它的实现如下:void BufferQueue::createBufferQueue(sp<IGraphicBufferProducer>* outProducer, sp<IGraphicBufferConsumer>* outConsumer, bool consumerIsSurfaceFlinger) { sp<BufferQueueCore> core(new BufferQueueCore()); sp<IGraphicBufferProducer> producer(new BufferQueueProducer(core, consumerIsSurfaceFlinger)); sp<IGraphicBufferConsumer> consumer(new BufferQueueConsumer(core)); *outProducer = producer; *outConsumer = consumer; }createBufferQueue做了上述的三件事情
- 创建BufferQueueCore
- 创建BufferQueueProducer
- 创建BufferQueueConsumer
5.1 BufferQueueCore
基本的成员变量:
(1)BufferSlot
BufferSlot意为缓冲槽,一个用来存放buffer及其信息的地方,这个结构体有以下几个重要的成员变量:
主要来看这几个成员变量:
♦ mGraphicBuffer代表一块图形缓冲区GraphicBuffer,用于存储绘制图形的数据;
♦ mBufferState类型为BufferState,标记当前buffer slot所处的状态;
♦ mNeedsReallocation,是否需要重新分配这个buffer;
♦ mFence,用于资源同步
(2) 几个重要的队列和数组
在图形缓冲区队列的逻辑中,有几个队列/数组有着各自的定义。之所以需要划分这么多不同的数组,都是为了给BufferSlot分类,以便获取GraphicBuffer时更加高效
Note:(1)BufferQueueDefs::SlotsType mSlots用数组存放的Slot,数组默认大小为BufferQueueDefs::NUM_BUFFER_SLOTS,具体是64,代表所有的Slot。这个slots数组用来存储GraphicBuffer,一个BufferSlot绑定一个GraphicBuffer。 这个数组会映射到BufferQueueProducer/BufferQueueConsuer类中。(2)std::set<int> mFreeSlots//文件-->/frameworks/native/include/gui/BufferQueueDefs.hnamespace android {class BufferQueueCore;namespace BufferQueueDefs {typedef BufferSlot SlotsType[NUM_BUFFER_SLOTS];} }//文件-->/frameworks/native/include/ui/BufferQueueDefs.hnamespace android {namespace BufferQueueDefs {static constexpr int NUM_BUFFER_SLOTS = 64;//constexpr 是c++引入的关键字,用于编译时的常量与常量函数 } }
当前所有的状态为FREE的Slot,这些Slot没有关联上具体的GraphicBuffer,后续用的时候还需要关联上GraphicBuffer(3)std::list<int> mFreeBuffers
当前所有的状态为FREE的Slot,这些Slot已经关联上具体的GraphicBuffer,可以直接使用(4)std::list<int> mUnusedSlots;代表当前没有使用的BufferSlot,从上面的示意图可以看到,mUnusedSlots不参与上述环节的空闲slot。从上图来看,实际可用的最大slot个数为15,slot15-slot63都是没有分配给当前layer的,对于这个layer 而言,它可以申请的最大slot数是15个,避免浪费。
(4)Fifo mQueue
一个先进先出队列,用于同步模式下的queued状态下的buffer,保存了生产者生产的数据。(5)BufferQueue初始化在BufferQueueCore初始化时,由于此时队列中没有入队任何数据,按照上面的介绍,此时mFreeSlots应该包含所有的Slot,元素大小和mSlots一致,初始化代码如下:for (int slot = 0; slot < BufferQueueDefs::NUM_BUFFER_SLOTS; ++slot) { mFreeSlots.insert(slot); }(6)等式
mSlots = mFreeSlots + mFreeBuffers + mActiveBuffers + mUnusedSlots = NUM_BUFFER_SLOTS
5.2 BufferQueueProducer生产者
当生产者可以生产图形数据的时候,首先向BufferQueue中申请一块GraphicBuffer。调用函数是:BufferQueueProducer.dequeueBuffer,如果当前BufferQueue中有可用的GraphicBuffer,则返回其对用的索引,如果不存在,则返回-1。
(1)dequeueBuffer()
status_t BufferQueueProducer::dequeueBuffer(int *outSlot, sp<android::Fence> *outFence, bool async, uint32_t width, uint32_t height, PixelFormat format, uint32_t usage) { //1. 寻找可用的Slot,可用指的是Buffer状态为FREE status_t status = waitForFreeSlotThenRelock("dequeueBuffer", async, &found, &returnFlags); if (status != NO_ERROR) { return status; } //2.找到可用的Slot,将Buffer状态设置为DEQUEUED,由于步骤1找到的Slot状态为FREE,因此这一步完成了FREE到DEQUEUED的状态切换 *outSlot = found; ATRACE_BUFFER_INDEX(found); attachedByConsumer = mSlots[found].mAttachedByConsumer; mSlots[found].mBufferState = BufferSlot::DEQUEUED; //3. 找到的Slot如果需要申请GraphicBuffer,则申请GraphicBuffer,这里采用了懒加载机制,如果内存没有申请,申请内存放在生产者来处理 if (returnFlags & BUFFER_NEEDS_REALLOCATION) { status_t error; sp<GraphicBuffer> graphicBuffer(mCore->mAllocator->createGraphicBuffer(width, height, format, usage, &error)); graphicBuffer->setGenerationNumber(mCore->mGenerationNumber); mSlots[*outSlot].mGraphicBuffer = graphicBuffer; } }借用网络动图来展示此过程:
step2:mFreeBuffers不为空,则走下述流程:
step3:如果mFreeBuffers为空,则从mFreeSlots中获取slot:
Note:可以看到,与mFreeBuffers相比,从mFreeSlots中获取slot时,要多一步分配内存的操作。当然,如果mFreeBuffers不为空,但是里面没有我们想要的buffer,比如buffer size不匹配,那么这时候也会通过触发Allocate的动作来重新分配buffer。
- 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。
(2)waitForFreeSlotThenRelock()
从上面的动图演示可以看到,dequeueBuffer中一个关键动作在于寻找可用的slot。首先看源码解析:
waitForFreeSlotThenRelock函数会尝试寻找一个可用的Slot,可用的Slot状态一定是FREE(因为是从两个FREE状态的列表中获取的),然后dequeueBuffer将状态改变为DEQUEUED,即完成了状态的扭转。
status_t BufferQueueProducer::waitForFreeSlotThenRelock(const char* caller, bool async, int* found, status_t* returnFlags) const { //1. mQueue 是否太多 bool tooManyBuffers = mCore->mQueue.size()> static_cast<size_t>(maxBufferCount); if (tooManyBuffers) { } else { // 2. 先查找mFreeBuffers中是否有可用的,由2.1介绍可知,mFreeBuffers中的元素关联了GraphicBuffer,直接可用 if (!mCore->mFreeBuffers.empty()) { auto slot = mCore->mFreeBuffers.begin(); *found = *slot; mCore->mFreeBuffers.erase(slot); } else if (mCore->mAllowAllocation && !mCore->mFreeSlots.empty()) { // 3. 再查找mFreeSlots中是否有可用的,由2.1可知,初始化时会填充满这个列表,因此第一次调用一定不会为空。同时用这个列表中的元素需要关联上GraphicBuffer才可以直接使用,关联的过程由外层函数来实现 auto slot = mCore->mFreeSlots.begin(); // Only return free slots up to the max buffer count if (*slot < maxBufferCount) { *found = *slot; mCore->mFreeSlots.erase(slot); } } } tryAgain = (*found == BufferQueueCore::INVALID_BUFFER_SLOT) || tooManyBuffers; //4. 如果找不到可用的Slot或者Buffer太多(同步阻塞模式下),则可能需要等 if (tryAgain) { if (mCore->mDequeueBufferCannotBlock && (acquiredCount <= mCore->mMaxAcquiredBufferCount)) { return WOULD_BLOCK; } mCore->mDequeueCondition.wait(mCore->mMutex); } }