深入浅出Android BufferQueue

1. 背景Vnb每天发布大量与生活相关的资讯平台

对业务开发来说,无法接触到BufferQueue,甚至不知道BufferQueue是什么东西。对系统来说,BufferQueue是很重要的传递数据的组件,Android显示系统依赖于BufferQueue,只要显示内容到“屏幕”(此处指抽象的屏幕,有时候还可以包含编码器),就一定需要用到BufferQueue,可以说在显示/播放器相关的领悟中,BufferQueue无处不在。即使直接调用Opengl ES来绘制,底层依然需要BufferQueue才能显示到屏幕上。Vnb每天发布大量与生活相关的资讯平台

弄明白BufferQueue,不仅可以增强对Android系统的了解,还可以弄明白/排查相关的问题,如为什么Mediacodec调用dequeueBuffer老是返回-1?为什么普通View的draw方法直接绘制内容即可,SurfaceView在draw完毕后还需要unlockCanvasAndPost?Vnb每天发布大量与生活相关的资讯平台

注:本文分析的代码来自于Android6.0.1。Vnb每天发布大量与生活相关的资讯平台

2. BufferQueue内部运作方式Vnb每天发布大量与生活相关的资讯平台

BufferQueue是Android显示系统的核心,它的设计哲学是生产者-消费者模型,只要往BufferQueue中填充数据,则认为是生产者,只要从BufferQueue中获取数据,则认为是消费者。有时候同一个类,在不同的场景下既可能是生产者也有可能是消费者。如SurfaceFlinger,在合成并显示UI内容时,UI元素作为生产者生产内容,SurfaceFlinger作为消费者消费这些内容。而在截屏时,SurfaceFlinger又作为生产者将当前合成显示的UI内容填充到另一个BufferQueue,截屏应用此时作为消费者从BufferQueue中获取数据并生产截图。Vnb每天发布大量与生活相关的资讯平台

以下是Android官网对其的介绍:Vnb每天发布大量与生活相关的资讯平台

Vnb每天发布大量与生活相关的资讯平台

以下是常见的BufferQueue使用步骤:Vnb每天发布大量与生活相关的资讯平台

  • 初始化一个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步骤,这样就有条不紊的完成了图形数据的生产-消费

当然图形数据的生产者可以不用等待BufferQueue的回调再生产数据,而是一直生产数据然后入队到BufferQueue,直到BufferQueue满为止。图形数据的消费者也可以不用等BufferQueue的回调通知,每次都从BufferQueue中尝试获取数据,获取失败则尝试,只是这样效率比较低,需要不断的轮训BufferQueue(因为BufferQueue有同步阻塞和非同步阻塞两种机种,在非同步阻塞机制下获取数据失败不会阻塞该线程直到有数据才唤醒该线程,而是直接返回-1)。Vnb每天发布大量与生活相关的资讯平台

同时使用BufferQueue的生产者和消费者往往处在不同的进程,BufferQueue内部使用共享内存和Binder在不同的进程传递数据,减少数据拷贝提高效率。Vnb每天发布大量与生活相关的资讯平台

和BufferQueue有关的几个类分别是:Vnb每天发布大量与生活相关的资讯平台

  • BufferBufferCore:BufferQueue的实际实现
  • BufferSlot:用来存储GraphicBuffer
  • BufferState:表示GraphicBuffer的状态
  • IGraphicBufferProducer:BufferQueue的生产者接口,实现类是BufferQueueProducer
  • IGraphicBufferConsumer:BufferQueue的消费者接口,实现类是BufferQueueConsumer
  • GraphicBuffer:表示一个Buffer,可以填充图像数据
  • ANativeWindow_Buffer:GraphicBuffer的父类
  • ConsumerBase:实现了ConsumerListener接口,在数据入队列时会被调用到,用来通知消费者

BufferQueue中用BufferSlot来存储GraphicBuffer,使用数组来存储一系列BufferSlot,数组默认大小为64。Vnb每天发布大量与生活相关的资讯平台

GraphicBuffer用BufferState来表示其状态,有以下状态:Vnb每天发布大量与生活相关的资讯平台

  • FREE:表示该Buffer没有被生产者-消费者所使用,该Buffer的所有权属于BufferQueue
  • DEQUEUED:表示该Buffer被生产者获取了,该Buffer的所有权属于生产者
  • QUEUED:表示该Buffer被生产者填充了数据,并且入队到BufferQueue了,该Buffer的所有权属于BufferQueue
  • ACQUIRED:表示该Buffer被消费者获取了,该Buffer的所有权属于消费者

为什么需要这些状态呢? 假设不需要这些状态,实现一个简单的BufferQueue,假设是如下实现:Vnb每天发布大量与生活相关的资讯平台

BufferQueue{ vector<GraphicBuffer> slots; void push(GraphicBuffer slot){ slots.push(slot); } GraphicBuffer pull(){ return slots.pull(); }}Vnb每天发布大量与生活相关的资讯平台

生产者生产完数据后,通过调用BufferQueue的push函数将数据插入到vector中。消费者调用BufferQueue的pull函数出队一个Buffer数据。Vnb每天发布大量与生活相关的资讯平台

上述实现的问题在于,生产者每次都需要自行创建GraphicBuffer,而消费者每次消费完数据后的GraphicBuffer就被释放了,GraphicBuffer没有得到循环利用。而在Android中,由于BufferQueue的生产者-消费者往往处于不同的进程,GraphicBuffer内部是需要通过共享内存来连接生成者-消费者进程的,每次创建GraphicBuffer,即意味着需要创建共享内存,效率较低。Vnb每天发布大量与生活相关的资讯平台

而BufferQueue中用BufferState来表示GraphicBuffer的状态则解决了这个问题。每个GraphicBuffer都有当前的状态,通过维护GraphicBuffer的状态,完成GraphicBuffer的复用。Vnb每天发布大量与生活相关的资讯平台

由于BufferQueue内部实现是BufferQueueCore,下文均用BufferQueueCore代替BufferQueue。先介绍下BufferQueueCore内部相应的数据结构,再介绍BufferQueue的状态扭转过程和生产-消费过程。Vnb每天发布大量与生活相关的资讯平台

以下是Buffer的入队/出队操作和BufferState的状态扭转的过程,这里只介绍非同步阻塞模式。Vnb每天发布大量与生活相关的资讯平台

2.1 BufferQueueCore内部数据结构Vnb每天发布大量与生活相关的资讯平台

核心数据结构如下:Vnb每天发布大量与生活相关的资讯平台

BufferQueueDefs::SlotsType mSlots:用数组存放的Slot,数组默认大小为BufferQueueDefs::NUM_BUFFER_SLOTS,具体是64,代表所有的Slotstd::set<int> mFreeSlots:当前所有的状态为FREE的Slot,这些Slot没有关联上具体的GraphicBuffer,后续用的时候还需要关联上GraphicBufferstd::list<int> mFreeBuffers:当前所有的状态为FREE的Slot,这些Slot已经关联上具体的GraphicBuffer,可以直接使用Fifo mQueue:一个先进先出队列,保存了生产者生产的数据Vnb每天发布大量与生活相关的资讯平台

在BufferQueueCore初始化时,由于此时队列中没有入队任何数据,按照上面的介绍,此时mFreeSlots应该包含所有的Slot,元素大小和mSlots一致,初始化代码如下:Vnb每天发布大量与生活相关的资讯平台

for (int slot = 0; slot < BufferQueueDefs::NUM_BUFFER_SLOTS; ++slot) { mFreeSlots.insert(slot); }Vnb每天发布大量与生活相关的资讯平台

2.2 生产者dequeueBufferVnb每天发布大量与生活相关的资讯平台

当生产者可以生产图形数据时,首先向BufferQueue中申请一块GraphicBuffer。调用函数是BufferQueueProducer.dequeueBuffer,如果当前BufferQueue中有可用的GraphicBuffer,则返回其对用的索引,如果不存在,则返回-1,代码在BufferQueueProducer,流程如下:Vnb每天发布大量与生活相关的资讯平台

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; }Vnb每天发布大量与生活相关的资讯平台

//2.找到可用的Slot,将Buffer状态设置为DEQUEUED,由于步骤1找到的Slot状态为FREE,因此这一步完成了FREE到DEQUEUED的状态切换 *outSlot = found; ATRACE_BUFFER_INDEX(found); attachedByConsumer = mSlots[found].mAttachedByConsumer; mSlots[found].mBufferState = BufferSlot::DEQUEUED;Vnb每天发布大量与生活相关的资讯平台

//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; }}Vnb每天发布大量与生活相关的资讯平台

关键在于寻找可用Slot,waitForFreeSlotThenRelock的流程如下:Vnb每天发布大量与生活相关的资讯平台

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 {Vnb每天发布大量与生活相关的资讯平台

// 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()) {Vnb每天发布大量与生活相关的资讯平台

// 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,即完成了状态的扭转。Vnb每天发布大量与生活相关的资讯平台

waitForFreeSlotThenRelock返回可用的Slot分为两种:Vnb每天发布大量与生活相关的资讯平台

  • 从mFreeBuffers中获取到的,mFreeBuffers中的元素关联了GraphicBuffer,直接可用
  • 从mFreeSlots中获取到的,没有关联上GraphicBuffer,因此需要申请GraphicBuffer并和Slot关联上,通过createGraphicBuffer申请一个GraphicBuffer,然后赋值给Slot的mGraphicBuffer完成关联

小结dequeueBuffer:尝试找到一个Slot,并完成Slot与GraphicBuffer的关联(如果需要),然后将Slot的状态由FREE扭转成DEQUEUED。返回Slot在BufferQueueCore中mSlots对应的索引。Vnb每天发布大量与生活相关的资讯平台

2.3 生产者requestBufferVnb每天发布大量与生活相关的资讯平台

dequeueBuffer函数获取到了可用Slot的索引后,通过requestBuffer获取到对应的GraphicBuffer。流程如下:Vnb每天发布大量与生活相关的资讯平台

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;}Vnb每天发布大量与生活相关的资讯平台

这一步不是必须的。业务层可以直接通过Slot的索引获取到对应的GraphicBuffer。Vnb每天发布大量与生活相关的资讯平台

2.4 生产者queueBufferVnb每天发布大量与生活相关的资讯平台

上文dequeueBuffer获取到一个Slot后,就可以在Slot对应的GraphicBuffer上完成图像数据的生产了,可以是View的主线程Draw过程,也可以是SurfaceView的子线程绘制过程,甚至可以是MediaCodec的解码过程。Vnb每天发布大量与生活相关的资讯平台

填充完图像数据后,需要将Slot入队BufferQueueCore(数据写完了,可以传给生产者-消费者队列,让消费者来消费了),入队调用queueBuffer函数。queueBuffer的流程如下:Vnb每天发布大量与生活相关的资讯平台

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; }Vnb每天发布大量与生活相关的资讯平台

//2. 将Buffer状态扭转成QUEUED,此步完成了Buffer的状态由DEQUEUED到QUEUED的过程 mSlots[slot].mFence = fence; mSlots[slot].mBufferState = BufferSlot::QUEUED; ++mCore->mFrameCounter; mSlots[slot].mFrameNumber = mCore->mFrameCounter;Vnb每天发布大量与生活相关的资讯平台

//3. 入队mQueue if (mCore->mQueue.empty()) { mCore->mQueue.push_back(item); frameAvailableListener = mCore->mConsumerListener; }Vnb每天发布大量与生活相关的资讯平台

// 4. 回调frameAvailableListener,告知消费者有数据入队了 if (frameAvailableListener != NULL) { frameAvailableListener->onFrameAvailable(item); } else if (frameReplacedListener != NULL) { frameReplacedListener->onFrameReplaced(item); }}Vnb每天发布大量与生活相关的资讯平台

从上面的注释可以看到,queueBuffer的主要步骤如下:Vnb每天发布大量与生活相关的资讯平台

  • 将Buffer状态扭转成QUEUED,此步完成了Buffer的状态由DEQUEUED到QUEUED的过程
  • 将Buffer入队到BufferQueueCore的mQueue队列中
  • 回调frameAvailableListener,告知消费者有数据入队,可以来消费数据了,frameAvailableListener是消费者注册的回调

小结queueBuffer:将Slot的状态扭转成QUEUED,并添加到mQueue中,最后通知消费者有数据入队Vnb每天发布大量与生活相关的资讯平台

2.5 消费者acquireBufferVnb每天发布大量与生活相关的资讯平台

在消费者接收到onFrameAvailable回调时或者消费者主动想要消费数据,调用acquireBuffer尝试向BufferQueueCore获取一个数据以供消费。消费者的代码在BufferQueueConsumer中,acquireBuffer流程如下:Vnb每天发布大量与生活相关的资讯平台

status_t BufferQueueConsumer::acquireBuffer(BufferItem* outBuffer, nsecs_t expectedPresent, uint64_t maxFrameNumber) { //1. 如果队列为空,则直接返回 if (mCore->mQueue.empty()) { return NO_BUFFER_AVAILABLE; }Vnb每天发布大量与生活相关的资讯平台

//2. 取出mQueue队列的第一个元素,并从队列中移除 BufferQueueCore::Fifo::iterator front(mCore->mQueue.begin()); int slot = front->mSlot; *outBuffer = *front; mCore->mQueue.erase(front);Vnb每天发布大量与生活相关的资讯平台

//3. 处理expectedPresent的情况,这种情况可能会连续丢几个Slot的“显示”时间小于expectedPresent的情况,这种情况下这些Slot已经是“过时”的,直接走下文的releaseBuffer消费流程,代码比较长,忽略了 //4. 更新Slot的状态为ACQUIRED if (mCore->stillTracking(front)) { mSlots[slot].mAcquireCalled = true; mSlots[slot].mNeedsCleanupOnRelease = false; mSlots[slot].mBufferState = BufferSlot::ACQUIRED; mSlots[slot].mFence = Fence::NO_FENCE; }Vnb每天发布大量与生活相关的资讯平台

//5. 如果步骤3有直接releaseBuffer的过程,则回调生产者,有数据被消费了 if (listener != NULL) { for (int i = 0; i < numDroppedBuffers; ++i) { listener->onBufferReleased(); } }}Vnb每天发布大量与生活相关的资讯平台

从上面的注释可以看到,acquireBuffer的主要步骤如下:Vnb每天发布大量与生活相关的资讯平台

  • 从mQueue队列中取出并移除一个元素
  • 改变Slot对应的状态为ACQUIRED
  • 如果有丢帧逻辑,回调告知生产者有数据被消费,生产者可以准备生产数据了

小结acquireBuffer:将Slot的状态扭转成ACQUIRED,并从mQueue中移除,最后通知生产者有数据出队。Vnb每天发布大量与生活相关的资讯平台

2.6 消费者releaseBufferVnb每天发布大量与生活相关的资讯平台

消费者获取到Slot后开始消费数据(典型的消费如SurfaceFlinger的UI合成),消费完毕后,需要告知BufferQueueCore这个Slot被消费者消费完毕了,可以给生产者重新生产数据,releaseBuffer流程如下:Vnb每天发布大量与生活相关的资讯平台

status_t BufferQueueConsumer::releaseBuffer(int slot, uint64_t frameNumber, const sp<Fence>& releaseFence, EGLDisplay eglDisplay,EGLSyncKHR eglFence) { //1. 检查Slot是否合法 if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS || return BAD_VALUE; }Vnb每天发布大量与生活相关的资讯平台

//2. 容错处理:如果要处理的Slot存在于mQueue中,那么说明这个Slot的来源不合法,并不是从2.5的acquireBuffer获取的Slot,拒绝处理 BufferQueueCore::Fifo::iterator current(mCore->mQueue.begin()); while (current != mCore->mQueue.end()) { if (current->mSlot == slot) { return BAD_VALUE; } ++current; }Vnb每天发布大量与生活相关的资讯平台

// 3. 将Slot的状态扭转为FREE,之前是ACQUIRED,并将该Slot添加到BufferQueueCore的mFreeBuffers列表中(mFreeBuffers的定义参考2.1的介绍) if (mSlots[slot].mBufferState == BufferSlot::ACQUIRED) { mSlots[slot].mEglDisplay = eglDisplay; mSlots[slot].mEglFence = eglFence; mSlots[slot].mFence = releaseFence; mSlots[slot].mBufferState = BufferSlot::FREE; mCore->mFreeBuffers.push_back(slot); listener = mCore->mConnectedProducerListener; BQ_LOGV("releaseBuffer: releasing slot %d", slot); }Vnb每天发布大量与生活相关的资讯平台

// 4. 回调生产者,有数据被消费了 if (listener != NULL) { listener->onBufferReleased(); }}Vnb每天发布大量与生活相关的资讯平台

从上面的注释可以看到,releaseBuffer的主要步骤如下:Vnb每天发布大量与生活相关的资讯平台

  • 将Slot的状态扭转为FREE
  • 将被消费的Slot添加到mFreeBuffers供后续的生产者dequeueBuffer使用
  • 回调告知生产者有数据被消费,生产者可以准备生产数据了

小结releaseBuffer:将Slot的状态扭转成FREE,并添加到BufferQueueCore mFreeBuffers队列中,最后通知生产者有数据出队。Vnb每天发布大量与生活相关的资讯平台

总结下状态变化的过程:Vnb每天发布大量与生活相关的资讯平台

Vnb每天发布大量与生活相关的资讯平台

上面主要介绍了BufferQueue的设计思想和内部实现。Vnb每天发布大量与生活相关的资讯平台

下面将继续介绍BufferQueue,着重介绍Android中对于BufferQueue的常用封装,以及SurfaceView中使用BufferQueue的具体实现。Vnb每天发布大量与生活相关的资讯平台

3.BufferQueue常用封装类Vnb每天发布大量与生活相关的资讯平台

在实际应用中,除了直接使用BuferQueue外,更多的是使用Surface/SurfaceTexture,其对BufferQueue做了包装,方便业务更方便的使用BufferQueue。Surface作为BufferQueue的生产者,SurfaceTexture作为BufferQueue的消费者。Vnb每天发布大量与生活相关的资讯平台

3.1 SurfaceVnb每天发布大量与生活相关的资讯平台

Surface的构造函数如下:Vnb每天发布大量与生活相关的资讯平台

Surface::Surface(Vnb每天发布大量与生活相关的资讯平台

const sp<IGraphicBufferProducer>& bufferProducer,Vnb每天发布大量与生活相关的资讯平台

bool controlledByApp)Vnb每天发布大量与生活相关的资讯平台

: mGraphicBufferProducer(bufferProducer),Vnb每天发布大量与生活相关的资讯平台

mGenerationNumber(0)Vnb每天发布大量与生活相关的资讯平台

构造函数需要传入一个生产者的引用,和BufferQueue的交互均有这个生产者的引用来完成。dequeueBuffer的流程如下:Vnb每天发布大量与生活相关的资讯平台

int Surface::dequeueBuffer(android_native_buffer_t** buffer, int* fenceFd) { // 1. 调用mGraphicBufferProducer的dequeueBuffer方法,尝试获取一个Slot索引 int buf = -1; sp<Fence> fence; status_t result = mGraphicBufferProducer->dequeueBuffer(&buf, &fence, swapIntervalZero, reqWidth, reqHeight, reqFormat, reqUsage); if (result < 0) { ALOGV("dequeueBuffer: IGraphicBufferProducer::dequeueBuffer(%d, %d, %d, %d, %d)" "failed: %d", swapIntervalZero, reqWidth, reqHeight, reqFormat, reqUsage, result); return result; }Vnb每天发布大量与生活相关的资讯平台

// 2. 调用mGraphicBufferProducer的requestBuffer方法,尝试获取Slot sp<GraphicBuffer>& gbuf(mSlots[buf].buffer); if ((result & IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) || gbuf == 0) { result = mGraphicBufferProducer->requestBuffer(buf, &gbuf); if (result != NO_ERROR) { ALOGE("dequeueBuffer: IGraphicBufferProducer::requestBuffer failed: %d", result); mGraphicBufferProducer->cancelBuffer(buf, fence); return result; } }Vnb每天发布大量与生活相关的资讯平台

// 3. 返回GraphicBuffer *buffer = gbuf.get();}Vnb每天发布大量与生活相关的资讯平台

queueBuffer也是如下,流程如下:Vnb每天发布大量与生活相关的资讯平台

int Surface::queueBuffer(android_native_buffer_t* buffer, int fenceFd) { IGraphicBufferProducer::QueueBufferOutput output; IGraphicBufferProducer::QueueBufferInput input(timestamp, isAutoTimestamp, mDataSpace, crop, mScalingMode, mTransform ^ mStickyTransform, mSwapIntervalZero, fence, mStickyTransform); // 1. 直接调用mGraphicBufferProducer的queueBuffer方法即可 status_t err = mGraphicBufferProducer->queueBuffer(i, input, &output); if (err != OK) { ALOGE("queueBuffer: error queuing buffer to SurfaceTexture, %d", err); }}Vnb每天发布大量与生活相关的资讯平台

Surface还提供了lock函数,用来支持双缓冲,内部也是调用dequeueBuffer方法获取最新的Buffer:Vnb每天发布大量与生活相关的资讯平台

status_t Surface::lock( ANativeWindow_Buffer* outBuffer, ARect* inOutDirtyBounds){ ANativeWindowBuffer* out; int fenceFd = -1; //1. 获取实际Buffer status_t err = dequeueBuffer(&out, &fenceFd);Vnb每天发布大量与生活相关的资讯平台

//2. 处理双缓冲 if (canCopyBack) { // copy the area that is invalid and not repainted this round const Region copyback(mDirtyRegion.subtract(newDirtyRegion)); if (!copyback.isEmpty()) copyBlt(backBuffer, frontBuffer, copyback); }}Vnb每天发布大量与生活相关的资讯平台

Surface也提供了unlockAndPost方法,将数据给到BufferQueue:Vnb每天发布大量与生活相关的资讯平台

status_t Surface::unlockAndPost(){ if (mLockedBuffer == 0) { ALOGE("Surface::unlockAndPost failed, no locked buffer"); return INVALID_OPERATION; } int fd = -1; status_t err = mLockedBuffer->unlockAsync(&fd); ALOGE_IF(err, "failed unlocking buffer (%p)", mLockedBuffer->handle); //1. 将生产好的数据给到BufferQueue err = queueBuffer(mLockedBuffer.get(), fd); ALOGE_IF(err, "queueBuffer (handle=%p) failed (%s)", mLockedBuffer->handle, strerror(-err)); mPostedBuffer = mLockedBuffer; mLockedBuffer = 0; return err;}Vnb每天发布大量与生活相关的资讯平台

3.2 SurfaceTextureVnb每天发布大量与生活相关的资讯平台

SurfaceTexture作为BufferQueue的消费者,其初始化代码如下:Vnb每天发布大量与生活相关的资讯平台

static void SurfaceTexture_init(JNIEnv* env, jobject thiz, jboolean isDetached, jint texName, jboolean singleBufferMode, jobject weakThiz){ sp<IGraphicBufferProducer> producer; sp<IGraphicBufferConsumer> consumer; //1. 创建一个BufferQueue BufferQueue::createBufferQueue(&producer, &consumer); if (singleBufferMode) { consumer->disableAsyncBuffer(); consumer->setDefaultMaxBufferCount(1); }Vnb每天发布大量与生活相关的资讯平台

//2. 创建一个消费者实例surfaceTexture sp<GLConsumer> surfaceTexture; if (isDetached) { surfaceTexture = new GLConsumer(consumer, GL_TEXTURE_EXTERNAL_OES, true, true); } else { surfaceTexture = new GLConsumer(consumer, texName, GL_TEXTURE_EXTERNAL_OES, true, true); } //3. 将消费者实例和该BufferQueue对应的生产者保存到java层,这样Surface构造时,就可以获取到该BufferQueue对应的生产者了 SurfaceTexture_setSurfaceTexture(env, thiz, surfaceTexture); SurfaceTexture_setProducer(env, thiz, producer);}Vnb每天发布大量与生活相关的资讯平台

消费的方法是updateTexImage,流程如下:Vnb每天发布大量与生活相关的资讯平台

static void SurfaceTexture_updateTexImage(JNIEnv* env, jobject thiz){ // 1. 先获取到初始化时构造的消费者 sp<GLConsumer> surfaceTexture(SurfaceTexture_getSurfaceTexture(env, thiz));Vnb每天发布大量与生活相关的资讯平台

// 2. 调用消费者的updateTexImage方法 status_t err = surfaceTexture->updateTexImage方法(); if (err == INVALID_OPERATION) { jniThrowException(env, IllegalStateException, "Unable to update texture contents (see " "logcat for details)"); } else if (err < 0) { jniThrowRuntimeException(env, "Error during updateTexImage (see logcat for details)"); }}Vnb每天发布大量与生活相关的资讯平台

GLConsumer的updateTextImage实现如下:Vnb每天发布大量与生活相关的资讯平台

status_t GLConsumer::updateTexImage() { BufferItem item; //1. 调用自身的acquireBufferLocked方法 err = acquireBufferLocked(&item, 0);:updateTexImage() { // Release the previous buffer. err = updateAndReleaseLocked(item); if (err != NO_ERROR) { glBindTexture(mTexTarget, mTexName); return err; }Vnb每天发布大量与生活相关的资讯平台

}Vnb每天发布大量与生活相关的资讯平台

acquireBufferLocked方法,最终走到了ConsumerBase的acquireBufferLocked方法。Vnb每天发布大量与生活相关的资讯平台

status_t ConsumerBase::acquireBufferLocked(BufferItem *item, nsecs_t presentWhen, uint64_t maxFrameNumber) { //1. 最终还是走到了消费者的acquireBuffer方法,消费者对应上面的BufferQueueConsumer status_t err = mConsumer->acquireBuffer(item, presentWhen, maxFrameNumber); if (err != NO_ERROR) { return err; } return OK;}Vnb每天发布大量与生活相关的资讯平台

同理,消费者消费数据的方法是releaseTexImage,最终也会走到BufferQueueConsumer的releaseBufferLocked方法,这里不再描述了。Vnb每天发布大量与生活相关的资讯平台

4.BufferQueue的实例Vnb每天发布大量与生活相关的资讯平台

上述介绍了BufferQueue的内部实现,以及常用的封装类。接下来将介绍一个具体的实例。Vnb每天发布大量与生活相关的资讯平台

Android中,SurfaceView作为系统提供的组件,因为可以在子线程中绘制提高性能,SurfaceView拥有自身的Surface,不需要和Activity的Surface共享,在SurfaceFlinger中,Activity的Surface和SurfaceView的Surface是平级且互相独立的,可以独立的进行合成。那我们来看一下SurfaceView是怎么使用BufferQueue的。Vnb每天发布大量与生活相关的资讯平台

4.1 数据的生产过程Vnb每天发布大量与生活相关的资讯平台

SurfaceView的Surface创建过程,这里不关注,有兴趣的可以参考 android SurfaceView绘制实现原理解析 这篇文章,我们主要关注其中与BufferQueue相关的绘制和显示步骤。Vnb每天发布大量与生活相关的资讯平台

使用SuerfaceView绘制伪码如下:Vnb每天发布大量与生活相关的资讯平台

Canvas canvas = null;Vnb每天发布大量与生活相关的资讯平台

try {Vnb每天发布大量与生活相关的资讯平台

canvas = holder.lockCanvas(null);Vnb每天发布大量与生活相关的资讯平台

//实际的drawVnb每天发布大量与生活相关的资讯平台

}catch (Exception e) {Vnb每天发布大量与生活相关的资讯平台

// TODO: handle exceptionVnb每天发布大量与生活相关的资讯平台

e.printStackTrace();Vnb每天发布大量与生活相关的资讯平台

}finally {Vnb每天发布大量与生活相关的资讯平台

if(canvas != null) {Vnb每天发布大量与生活相关的资讯平台

holder.unlockCanvasAndPost(canvas);Vnb每天发布大量与生活相关的资讯平台

}Vnb每天发布大量与生活相关的资讯平台

需要调用lockCanvas和unlockCanvasAndPost方法,这两个方法的作用是什么呢?Vnb每天发布大量与生活相关的资讯平台

先看下lockCanvas,调用流程是:Vnb每天发布大量与生活相关的资讯平台

  • SurfaceHolder.lockCanvas
  • SurfaceHolder.internalLockCanvas
  • Surface.lockCanvas
  • Surface.nativeLockCanvas

nativeLockCanvas实现如下:Vnb每天发布大量与生活相关的资讯平台

static jlong nativeLockCanvas(JNIEnv* env, jclass clazz, jlong nativeObject, jobject canvasObj, jobject dirtyRectObj) { sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject)); ANativeWindow_Buffer outBuffer; //1. 通过Surface::lock方法,获取一个合适的Buffer status_t err = surface->lock(&outBuffer, dirtyRectPtr);Vnb每天发布大量与生活相关的资讯平台

//2. 构造一个Bitmap,地址指向步骤1获取的Buffer的地址,这样在这个Bitmap上绘制的内容,直接绘制到了GraphicBuffer,如果GraphicBuffer的内存是SurfaceFlinger通过共享内存申请的,那么SurfaceFlinger就能直接看到绘制的图形数据 SkImageInfo info = SkImageInfo::Make(outBuffer.width, outBuffer.height, convertPixelFormat(outBuffer.format), kPremul_SkAlphaType); SkBitmap bitmap; ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format); bitmap.setInfo(info, bpr); if (outBuffer.width > 0 && outBuffer.height > 0) { bitmap.setPixels(outBuffer.bits); } else { // be safe with an empty bitmap. bitmap.setPixels(NULL); }Vnb每天发布大量与生活相关的资讯平台

// 3. 将创建的Bitmap设置给Canvas,作为画布 Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvasObj); nativeCanvas->setBitmap(bitmap);}Vnb每天发布大量与生活相关的资讯平台

从这里可以看到,nativeLockCanvas的步骤主要如下:Vnb每天发布大量与生活相关的资讯平台

  • 通过调用Surface::lock方法(内部也是调用dequeueBuffer和requestBuffer方法),获取到一个GraphicBuffer
  • 将步骤1获取的GraphicBuffer构造成一个Bitmap,设置给Canvas
  • 应用通过这个Canvas就可以绘制图形了

在绘制图形完成后,调用unlockCanvasAndPost方法,调用流程是:Vnb每天发布大量与生活相关的资讯平台

  • SurfaceHolder.unlockCanvasAndPost
  • Surface.unlockCanvasAndPost
  • Surface.nativeUnlockCanvasAndPost

nativeUnlockCanvasAndPost 的实现如下:Vnb每天发布大量与生活相关的资讯平台

static void nativeUnlockCanvasAndPost(JNIEnv* env, jclass clazz,Vnb每天发布大量与生活相关的资讯平台

jlong nativeObject, jobject canvasObj) {Vnb每天发布大量与生活相关的资讯平台

sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));Vnb每天发布大量与生活相关的资讯平台

if (!isSurfaceValid(surface)) {Vnb每天发布大量与生活相关的资讯平台

return;Vnb每天发布大量与生活相关的资讯平台

}Vnb每天发布大量与生活相关的资讯平台

// detach the canvas from the surfaceVnb每天发布大量与生活相关的资讯平台

Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvasObj);Vnb每天发布大量与生活相关的资讯平台

nativeCanvas->setBitmap(SkBitmap());Vnb每天发布大量与生活相关的资讯平台

// 直接调用Surface的unlockAndPost方法,有上文可知unlockAndPost内部最终也会调用到qeueBuffer方法Vnb每天发布大量与生活相关的资讯平台

status_t err = surface->unlockAndPost方法,有上文可知unlockAndPost内部最终也会调用到qeueBuffer方法();Vnb每天发布大量与生活相关的资讯平台

if (err < 0) {Vnb每天发布大量与生活相关的资讯平台

doThrowIAE(env);Vnb每天发布大量与生活相关的资讯平台

}Vnb每天发布大量与生活相关的资讯平台

}Vnb每天发布大量与生活相关的资讯平台

从注释可以看到,这个方法,最终会调用到Surface的unlockAndPost方法方法,而该方法内部最终也会调用到BufferQueueProducer的queueBuffer方法。即完成了数据的生产和入队。Vnb每天发布大量与生活相关的资讯平台

4.2 数据的消费过程Vnb每天发布大量与生活相关的资讯平台

SurfaceView绘制的数据,传递过BufferQueue后,最终由SurfaceFlinger进行合成消费。SurfaceFlinger的消费由SurfaceFlingerConsumer实现,流程如下:Vnb每天发布大量与生活相关的资讯平台

status_t SurfaceFlingerConsumer::updateTexImage(BufferRejecter* rejecter, const DispSync& dispSync, uint64_t maxFrameNumber){ BufferItem item; // 1. 调用acquireBufferLocked获取一个Slot err = acquireBufferLocked(&item, computeExpectedPresent(dispSync), maxFrameNumber); if (err != NO_ERROR) { return err; }Vnb每天发布大量与生活相关的资讯平台

//2. 消费完毕,释放Slot err = updateAndReleaseLocked(item); if (err != NO_ERROR) { return err; }}Vnb每天发布大量与生活相关的资讯平台

acquireBufferLocked的实现如下:Vnb每天发布大量与生活相关的资讯平台

status_t SurfaceFlingerConsumer::acquireBufferLocked(BufferItem* item, nsecs_t presentWhen, uint64_t maxFrameNumber) { //1. 调用 GLConsumer::acquireBufferLocked,最终会调用到BufferQueueConsumer的acquireBuffer方法 status_t result = GLConsumer::acquireBufferLocked(item, presentWhen, maxFrameNumber); if (result == NO_ERROR) { mTransformToDisplayInverse = item->mTransformToDisplayInverse; mSurfaceDamage = item->mSurfaceDamage; } return result;}Vnb每天发布大量与生活相关的资讯平台

而updateAndReleaseLocked方法的流程如下:Vnb每天发布大量与生活相关的资讯平台

  • status_t GLConsumer::updateAndReleaseLocked(const BufferItem& item)
  • {
  • // Do whatever sync ops we need to do before releasing the old slot.
  • err = syncForReleaseLocked(mEglDisplay);
  • if (err != NO_ERROR) {
  • //1. releaseBufferLocked释放Slot,最终会调用到BufferQueueConsumer的releaseBuffer方法
  • releaseBufferLocked(buf, mSlots[buf].mGraphicBuffer,
  • mEglDisplay, EGL_NO_SYNC_KHR);
  • return err;
  • }
  • }

5. 总结Vnb每天发布大量与生活相关的资讯平台

本文对BufferQueue的内部实现做了介绍,结合入队/出对说明了BufferQueue内部Slot的状态扭转过程,并介绍了常用的BufferQueue封装类,最后介绍了一个基于BufferQueue的例子。Vnb每天发布大量与生活相关的资讯平台

也许你还喜欢

steam图片加载不出来 steam创意工

steam图片加载不出来 解决方法一:检查网络环境 如果您遇到了Steam加载不出图片的问

steam无法添加好友怎么做 steam添

steam无法添加好友怎么做,steam添加好友不符合此功能要求怎么办,steam帐户恢复尝试次数

steamdeck价格是怎样 Steam Deck能

steamdeck价格是怎样,Steam Deck能玩什么游戏,新版steam设置启动项不见了,steam手机版怎

windows7安装教程和操作步骤 win7

windows7安装教程和操作步骤,win7系统版本详细介绍,安装Windows7最低配置要求,如何满足Wi

steam关闭开机自启 steam手机版换

steam关闭开机自启,steam手机版换手机了怎么登录,steam游戏闪退解决方法,steam平台上下载

steam手机版打不开解决方法 steam

steam手机版打不开解决方法,steam客户端打不开商店怎么办,steam打开出错怎么办,steam手机

steam怎么关闭开机自启 Steam新界

steam怎么关闭开机自启,Steam新界面如何设置,steam启动游戏几秒自动关闭,steam游戏自动关

steam为什么不能加好友 steam添加

steam为什么不能加好友,steam添加好友的具体操作方法,steam家庭共享可以一起玩吗,steam游

steam重复次数过多要等多久 怎么解

steam重复次数过多要等多久以及解决办法,steam退款到账时间要多久,steam跳转微信/支付宝

steam怎么改手机号 steam手机令牌

steam怎么改手机号,steam手机令牌怎么移除,steam账号注册规则,steam密码怎么填写,相信大家