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的五种状态:FREEDEQUEUEDQUEUEDACQUIREDSHARED
  • Buffer的流转过程
  • 以及需要哪些核心的数据结构、类去实现这一整套框架
    下面针对这些关键点去学习BufferQueue的相关原理。

4、BufferQueue的使用 & buffer的状态

 4.1 BufferQueue的使用步骤:

  1. 初始化一个BufferQueue
  2. 图形数据的生产者通过BufferQueue申请一块GraphicBuffer,对应图中的dequeueBuffer方法
  3. 申请到GraphicBuffer后,获取GraphicBuffer,通过函数requestBuffer获取
  4. 获取到GraphicBuffer后,通过各种形式往GraphicBuffer中填充图形数据后,然后将GraphicBuffer入队到BufferQueue中,对应上图中的queueBuffer方法
  5. 在新的GraphicBuffer入队BufferQueue时,BufferQueue会通过回调通知图形数据的消费者,有新的图形数据被生产出来了
  6. 然后消费者从BufferQueue中出队一个GraphicBuffer,对应图中的acquireBuffer方法
  7. 待消费者消费完图形数据后,将空的GraphicBuffer还给BufferQueue以便重复利用,此时对应上图中的releaseBuffer方法
  8. 此时BufferQueue再通过回调通知图形数据的生产者有空的GraphicBuffer了,图形数据的生产者又可以从BufferQueue中获取一个空的GraphicBuffer来填充数据
  9. 一直循环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
    Buffer的状态在代码中用BufferState描述,BufferState的定义如下:
* 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;

    ... ...
};
    简单的解释下这几个状态:
  1. FREE:表示该Buffer没有被生产者-消费者所使用,该Buffer的所有权属于BufferQueue,可以被producer dequeue。
  2. DEQUEUED:表示该Buffer被生产者获取了,该Buffer的所有权属于生产者。
  3. QUEUED:表示该Buffer被生产者填充了数据,并且入队到BufferQueue了,该Buffer的所有权属于BufferQueue
  4. ACQUIRED:表示该Buffer被消费者获取了,该Buffer的所有权属于消费者
  5. 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中的几个重要的类,其中有三个核心的结构:
  1. BufferQueueCore
  2. BufferQueueProducer
  3. BufferQueueConsumer
    BufferQueueCore 负责维护 BufferQueue 的基本数据结构,而 BufferQueueProducer 和 BufferQueueConsumer 则负责提供操作 BufferQueue 的基本接口
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做了上述的三件事情

  1. 创建BufferQueueCore
  2. 创建BufferQueueProducer
  3. 创建BufferQueueConsumer

5.1 BufferQueueCore

    BufferQueueCore在frameworks/native/include/gui/BufferQueueCore.h中定义。下图是BufferQueue的内部数据结构:

    基本的成员变量:


(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类中。
		
//文件-->/frameworks/native/include/gui/BufferQueueDefs.h
namespace android {
class BufferQueueCore;
namespace BufferQueueDefs {
    typedef BufferSlot SlotsType[NUM_BUFFER_SLOTS];
} }
//文件-->/frameworks/native/include/ui/BufferQueueDefs.h
namespace android {
namespace BufferQueueDefs {
     static constexpr int NUM_BUFFER_SLOTS = 64;//constexpr 是c++引入的关键字,用于编译时的常量与常量函数 } }
(2)std::set<int> mFreeSlots
当前所有的状态为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:
  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。

(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);
 }
}














全部评论

相关推荐

嵌入式求职之路:可以看我经验😂,https://www.nowcoder.com/share/jump/73221730841876945
点赞 评论 收藏
分享
04-25 18:13
五邑大学 Java
后来123321:大二两段实习太厉害了,我现在大二连面试都没有
点赞 评论 收藏
分享
评论
1
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务