时间: 2020-09-03 00:08:26 人气: 2296 评论: 0
1. 背景
对业务开发来说,无法接触到BufferQueue,甚至不知道BufferQueue是什么东西。对系统来说,BufferQueue是很重要的传递数据的组件,Android显示系统依赖于BufferQueue,只要显示内容到“屏幕”(此处指抽象的屏幕,有时候还可以包含编码器),就一定需要用到BufferQueue,可以说在显示/播放器相关的领域中,BufferQueue无处不在。即使直接调用Opengl ES来绘制,底层依然需要BufferQueue才能显示到屏幕上。
弄明白BufferQueue,不仅可以增强对Android系统的了解,还可以弄明白/排查相关的问题,如为什么Mediacodec调用dequeueBuffer老是返回-1?为什么普通View的draw方法直接绘制内容即可,SurfaceView在draw完毕后还需要unlockCanvasAndPost?
注:本文分析的代码来自于Android6.0.1。
BufferQueue是Android显示系统的核心,它的设计哲学是生产者-消费者模型,只要往BufferQueue中填充数据,则认为是生产者,只要从BufferQueue中获取数据,则认为是消费者。有时候同一个类,在不同的场景下既可能是生产者也有可能是消费者。如SurfaceFlinger,在合成并显示UI内容时,UI元素作为生产者生产内容,SurfaceFlinger作为消费者消费这些内容。而在截屏时,SurfaceFlinger又作为生产者将当前合成显示的UI内容填充到另一个BufferQueue,截屏应用此时作为消费者从BufferQueue中获取数据并生产截图。
以下是Android官网对其的介绍:
以下是常见的BufferQueue使用步骤:
当然图形数据的生产者可以不用等待BufferQueue的回调再生产数据,而是一直生产数据然后入队到BufferQueue,直到BufferQueue满为止。图形数据的消费者也可以不用等BufferQueue的回调通知,每次都从BufferQueue中尝试获取数据,获取失败则尝试,只是这样效率比较低,需要不断的轮训BufferQueue(因为BufferQueue有同步阻塞和非同步阻塞两种机种,在非同步阻塞机制下获取数据失败不会阻塞该线程直到有数据才唤醒该线程,而是直接返回-1)。
同时使用BufferQueue的生产者和消费者往往处在不同的进程,BufferQueue内部使用共享内存和Binder在不同的进程传递数据,减少数据拷贝提高效率。
和BufferQueue有关的几个类分别是:
BufferQueue中用BufferSlot来存储GraphicBuffer,使用数组来存储一系列BufferSlot,数组默认大小为64。
GraphicBuffer用BufferState来表示其状态,有以下状态:
为什么需要这些状态呢? 假设不需要这些状态,实现一个简单的BufferQueue,假设是如下实现:
BufferQueue{ vector<GraphicBuffer> slots; void push(GraphicBuffer slot){ slots.push(slot); } GraphicBuffer pull(){ return slots.pull(); } }
生产者生产完数据后,通过调用BufferQueue的push函数将数据插入到vector中。消费者调用BufferQueue的pull函数出队一个Buffer数据。
上述实现的问题在于,生产者每次都需要自行创建GraphicBuffer,而消费者每次消费完数据后的GraphicBuffer就被释放了,GraphicBuffer没有得到循环利用。而在Android中,由于BufferQueue的生产者-消费者往往处于不同的进程,GraphicBuffer内部是需要通过共享内存来连接生成者-消费者进程的,每次创建GraphicBuffer,即意味着需要创建共享内存,效率较低。
而BufferQueue中用BufferState来表示GraphicBuffer的状态则解决了这个问题。每个GraphicBuffer都有当前的状态,通过维护GraphicBuffer的状态,完成GraphicBuffer的复用。
由于BufferQueue内部实现是BufferQueueCore,下文均用BufferQueueCore代替BufferQueue。先介绍下BufferQueueCore内部相应的数据结构,再介绍BufferQueue的状态扭转过程和生产-消费过程。
以下是Buffer的入队/出队操作和BufferState的状态扭转的过程,这里只介绍非同步阻塞模式。
核心数据结构如下:
BufferQueueDefs::SlotsType mSlots:用数组存放的Slot,数组默认大小为BufferQueueDefs::NUM_BUFFER_SLOTS,具体是64,代表所有的Slot std::set<int> mFreeSlots:当前所有的状态为FREE的Slot,这些Slot没有关联上具体的GraphicBuffer,后续用的时候还需要关联上GraphicBuffer std::list<int> mFreeBuffers:当前所有的状态为FREE的Slot,这些Slot已经关联上具体的GraphicBuffer,可以直接使用 Fifo mQueue:一个先进先出队列,保存了生产者生产的数据
在BufferQueueCore初始化时,由于此时队列中没有入队任何数据,按照上面的介绍,此时mFreeSlots应该包含所有的Slot,元素大小和mSlots一致,初始化代码如下:
for (int slot = 0; slot < BufferQueueDefs::NUM_BUFFER_SLOTS; ++slot) { mFreeSlots.insert(slot); }
当生产者可以生产图形数据时,首先向BufferQueue中申请一块GraphicBuffer。调用函数BufferQueueProducer.dequeueBuffer,如果当前BufferQueue中有可用的GraphicBuffer,则返回其对用的索引;如果不存在,则返回-1,代码在BufferQueueProducer,流程如下:
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; } }
关键在于寻找可用Slot,waitForFreeSlotThenRelock的流程如下:
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); } }
waitForFreeSlotThenRelock函数会尝试寻找一个可用的Slot,可用的Slot状态一定是FREE(因为是从两个FREE状态的列表中获取的),然后dequeueBuffer将状态改变为DEQUEUED,即完成了状态的扭转。
waitForFreeSlotThenRelock返回可用的Slot分为两种:
小结dequeueBuffer:尝试找到一个Slot,并完成Slot与GraphicBuffer的关联(如果需要),然后将Slot的状态由FREE扭转成DEQUEUED,返回Slot在BufferQueueCore中mSlots对应的索引。
dequeueBuffer函数获取到了可用Slot的索引后,通过requestBuffer获取到对应的GraphicBuffer。流程如下:
status_t BufferQueueProducer::requestBuffer(int slot, sp<GraphicBuffer>* buf) { // 1. 判断slot参数是否合法 if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) { BQ_LOGE("requestBuffer: slot index %d out of range [0, %d)", slot, BufferQueueDefs::NUM_BUFFER_SLOTS); return BAD_VALUE; } else if (mSlots[slot].mBufferState != BufferSlot::DEQUEUED) { BQ_LOGE("requestBuffer: slot %d is not owned by the producer " "(state = %d)", slot, mSlots[slot].mBufferState); return BAD_VALUE; } //2. 将mRequestBufferCalled置为true mSlots[slot].mRequestBufferCalled = true; *buf = mSlots[slot].mGraphicBuffer; return NO_ERROR; }
这一步不是必须的,业务层可以直接通过Slot的索引获取到对应的GraphicBuffer。
上文dequeueBuffer获取到一个Slot后,就可以在Slot对应的GraphicBuffer上完成图像数据的生产了,可以是View的主线程Draw过程,也可以是SurfaceView的子线程绘制过程,甚至可以是MediaCodec的解码过程。
填充完图像数据后,需要将Slot入队BufferQueueCore(数据写完了,可以传给生产者-消费者队列,让消费者来消费了),入队调用queueBuffer函数。queueBuffer的流程如下:
status_t BufferQueueProducer::queueBuffer(int slot, const QueueBufferInput &input, QueueBufferOutput *output) { // 1. 先判断传入的Slot是否合法 if (slot < 0 || slot >= maxBufferCount) { BQ_LOGE("queueBuffer: slot index %d out of range [0, %d)", slot, maxBufferCount); return BAD_VALUE; } //2. 将Buffer状态扭转成QUEUED,此步完成了Buffer的状态由DEQUEUED到QUEUED的过程 mSlots[slot].mFence = fence; mSlots[slot].mBufferState = BufferSlot::QUEUED; ++mCore->mFrameCounter; mSlots[slot].mFrameNumber = mCore->mFrameCounter; //3. 入队mQueue if (mCore->mQueue.empty()) { mCore->mQueue.push_back(item); frameAvailableListener = mCore->mConsumerListener; } // 4. 回调frameAvailableListener,告知消费者有数据入队了 if (frameAvailableListener != NULL) { frameAvailableListener->onFrameAvailable(item); } else if (frameReplacedListener != NULL) { frameReplacedListener->onFrameReplaced(item); } }
从上面的注释可以看到,queueBuffer的主要步骤如下:
小结queueBuffer:将Slot的状态扭转成QUEUED,并添加到mQueue中,最后通知消费者有数据入队。
在消费者接收到onFrameAvailable回调时或者消费者主动想要消费数据,调用acquireBuffer尝试向BufferQueueCore获取一个数据以供消费。消费者的代码在BufferQueueConsumer中,acquireBuffer流程如下:
status_t BufferQueueConsumer::acquireBuffer(BufferItem* outBuffer, nsecs_t expectedPresent, uint64_t maxFrameNumber) { //1. 如果队列为空,则直接返回 if (mCore->mQueue.empty()) { return NO_BUFFER_AVAILABLE; } //2. 取出mQueue队列的第一个元素,并从队列中移除 BufferQueueCore::Fifo::iterator front(mCore->mQueue.begin( 技术沙龙 教程文章 热点综合