深入浅出Android BufferQueue

2019-05-16 14:54:19 浏览数 (1)

1. 背景

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

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

注:本文分析的代码来自于Android6.0.1。

2. BufferQueue内部运作方式

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

以下是Android官网对其的介绍:

以下是常见的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步骤,这样就有条不紊的完成了图形数据的生产-消费

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

同时使用BufferQueue的生产者和消费者往往处在不同的进程,BufferQueue内部使用共享内存和Binder在不同的进程传递数据,减少数据拷贝提高效率。

和BufferQueue有关的几个类分别是:

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

BufferQueue中用BufferSlot来存储GraphicBuffer,使用数组来存储一系列BufferSlot,数组默认大小为64。

GraphicBuffer用BufferState来表示其状态,有以下状态:

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

为什么需要这些状态呢? 假设不需要这些状态,实现一个简单的BufferQueue,假设是如下实现:

代码语言:javascript复制
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的状态扭转的过程,这里只介绍非同步阻塞模式。

2.1 BufferQueueCore内部数据结构

核心数据结构如下:

代码语言:javascript复制
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:一个先进先出队列,保存了生产者生产的数据

在BufferQueueCore初始化时,由于此时队列中没有入队任何数据,按照上面的介绍,此时mFreeSlots应该包含所有的Slot,元素大小和mSlots一致,初始化代码如下:

代码语言:javascript复制
for (int slot = 0; slot < BufferQueueDefs::NUM_BUFFER_SLOTS;   slot) {        mFreeSlots.insert(slot);    }
2.2 生产者dequeueBuffer

当生产者可以生产图形数据时,首先向BufferQueue中申请一块GraphicBuffer。调用函数是BufferQueueProducer.dequeueBuffer,如果当前BufferQueue中有可用的GraphicBuffer,则返回其对用的索引,如果不存在,则返回-1,代码在BufferQueueProducer,流程如下:

代码语言:javascript复制
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的流程如下:

代码语言:javascript复制
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分为两种:

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

小结dequeueBuffer:尝试找到一个Slot,并完成Slot与GraphicBuffer的关联(如果需要),然后将Slot的状态由FREE扭转成DEQUEUED。返回Slot在BufferQueueCore中mSlots对应的索引。

2.3 生产者requestBuffer

dequeueBuffer函数获取到了可用Slot的索引后,通过requestBuffer获取到对应的GraphicBuffer。流程如下:

代码语言:javascript复制
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。

2.4 生产者queueBuffer

上文dequeueBuffer获取到一个Slot后,就可以在Slot对应的GraphicBuffer上完成图像数据的生产了,可以是View的主线程Draw过程,也可以是SurfaceView的子线程绘制过程,甚至可以是MediaCodec的解码过程。

填充完图像数据后,需要将Slot入队BufferQueueCore(数据写完了,可以传给生产者-消费者队列,让消费者来消费了),入队调用queueBuffer函数。queueBuffer的流程如下:

代码语言:javascript复制
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的主要步骤如下:

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

小结queueBuffer:将Slot的状态扭转成QUEUED,并添加到mQueue中,最后通知消费者有数据入队。

2.5 消费者acquireBuffer

在消费者接收到onFrameAvailable回调时或者消费者主动想要消费数据,调用acquireBuffer尝试向BufferQueueCore获取一个数据以供消费。消费者的代码在BufferQueueConsumer中,acquireBuffer流程如下:

代码语言:javascript复制
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());           int slot = front->mSlot;        *outBuffer = *front;        mCore->mQueue.erase(front);
        //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;        }
        //5. 如果步骤3有直接releaseBuffer的过程,则回调生产者,有数据被消费了        if (listener != NULL) {            for (int i = 0; i < numDroppedBuffers;   i) {                listener->onBufferReleased();            }        }
}

从上面的注释可以看到,acquireBuffer的主要步骤如下:

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

小结acquireBuffer:将Slot的状态扭转成ACQUIRED,并从mQueue中移除,最后通知生产者有数据出队。

2.6 消费者releaseBuffer

消费者获取到Slot后开始消费数据(典型的消费如SurfaceFlinger的UI合成),消费完毕后,需要告知BufferQueueCore这个Slot被消费者消费完毕了,可以给生产者重新生产数据,releaseBuffer流程如下:

代码语言:javascript复制
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;        }
        //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;        }
         // 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);            }
           // 4. 回调生产者,有数据被消费了           if (listener != NULL) {               listener->onBufferReleased();           }}

从上面的注释可以看到,releaseBuffer的主要步骤如下:

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

小结releaseBuffer:将Slot的状态扭转成FREE,并添加到BufferQueueCore mFreeBuffers队列中,最后通知生产者有数据出队。

总结下状态变化的过程:

上面主要介绍了BufferQueue的设计思想和内部实现。

下面将继续介绍BufferQueue,着重介绍Android中对于BufferQueue的常用封装,以及SurfaceView中使用BufferQueue的具体实现。

3.BufferQueue常用封装类

在实际应用中,除了直接使用BuferQueue外,更多的是使用Surface/SurfaceTexture,其对BufferQueue做了包装,方便业务更方便的使用BufferQueue。Surface作为BufferQueue的生产者,SurfaceTexture作为BufferQueue的消费者。

3.1 Surface

Surface的构造函数如下:

代码语言:javascript复制
Surface::Surface(        const sp<IGraphicBufferProducer>& bufferProducer,        bool controlledByApp)    : mGraphicBufferProducer(bufferProducer),      mGenerationNumber(0)

构造函数需要传入一个生产者的引用,和BufferQueue的交互均有这个生产者的引用来完成。dequeueBuffer的流程如下:

代码语言:javascript复制
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;    }    // 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;        }    }
    // 3. 返回GraphicBuffer    *buffer = gbuf.get();}

queueBuffer也是如下,流程如下:

代码语言:javascript复制
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);    }}

Surface还提供了lock函数,用来支持双缓冲,内部也是调用dequeueBuffer方法获取最新的Buffer:

代码语言:javascript复制
status_t Surface::lock(        ANativeWindow_Buffer* outBuffer, ARect* inOutDirtyBounds){
    ANativeWindowBuffer* out;    int fenceFd = -1;    //1. 获取实际Buffer    status_t err = dequeueBuffer(&out, &fenceFd);
    //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);    }}

Surface也提供了unlockAndPost方法,将数据给到BufferQueue:

代码语言:javascript复制
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;}

3.2 SurfaceTexture

SurfaceTexture作为BufferQueue的消费者,其初始化代码如下:

代码语言:javascript复制
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);    }
    //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);
}

消费的方法是updateTexImage,流程如下:

代码语言:javascript复制
static void SurfaceTexture_updateTexImage(JNIEnv* env, jobject thiz){   // 1. 先获取到初始化时构造的消费者   sp<GLConsumer> surfaceTexture(SurfaceTexture_getSurfaceTexture(env, thiz));   // 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)");    }}

GLConsumer的updateTextImage实现如下:

代码语言:javascript复制
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;    }
}

acquireBufferLocked方法,最终走到了ConsumerBase的acquireBufferLocked方法。

代码语言:javascript复制
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;}

同理,消费者消费数据的方法是releaseTexImage,最终也会走到BufferQueueConsumer的releaseBufferLocked方法,这里不再描述了。

4.BufferQueue的实例

上述介绍了BufferQueue的内部实现,以及常用的封装类。接下来将介绍一个具体的实例。

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

4.1 数据的生产过程

SurfaceView的Surface创建过程,这里不关注,有兴趣的可以参考 android SurfaceView绘制实现原理解析 这篇文章,我们主要关注其中与BufferQueue相关的绘制和显示步骤。

使用SuerfaceView绘制伪码如下:

代码语言:javascript复制
Canvas canvas = null;    try {        canvas = holder.lockCanvas(null);        //实际的draw    }catch (Exception e) {        // TODO: handle exception        e.printStackTrace();    }finally {        if(canvas != null) {            holder.unlockCanvasAndPost(canvas);     }

需要调用lockCanvas和unlockCanvasAndPost方法,这两个方法的作用是什么呢?

先看下lockCanvas,调用流程是:

  1. SurfaceHolder.lockCanvas
  2. SurfaceHolder.internalLockCanvas
  3. Surface.lockCanvas 
  4. Surface.nativeLockCanvas 

nativeLockCanvas实现如下:

代码语言:javascript复制
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);
    //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);    }
    // 3. 将创建的Bitmap设置给Canvas,作为画布    Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvasObj);    nativeCanvas->setBitmap(bitmap);
}

从这里可以看到,nativeLockCanvas的步骤主要如下:

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

在绘制图形完成后,调用unlockCanvasAndPost方法,调用流程是:

  1. SurfaceHolder.unlockCanvasAndPost
  2. Surface.unlockCanvasAndPost
  3. Surface.nativeUnlockCanvasAndPost

nativeUnlockCanvasAndPost 的实现如下:

代码语言:javascript复制
static void nativeUnlockCanvasAndPost(JNIEnv* env, jclass clazz,        jlong nativeObject, jobject canvasObj) {    sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));    if (!isSurfaceValid(surface)) {        return;    }
    // detach the canvas from the surface    Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvasObj);    nativeCanvas->setBitmap(SkBitmap());
    // 直接调用Surface的unlockAndPost方法,有上文可知unlockAndPost内部最终也会调用到qeueBuffer方法    status_t err = surface->unlockAndPost方法,有上文可知unlockAndPost内部最终也会调用到qeueBuffer方法();    if (err < 0) {        doThrowIAE(env);    }}

从注释可以看到,这个方法,最终会调用到Surface的unlockAndPost方法方法,而该方法内部最终也会调用到BufferQueueProducer的queueBuffer方法。即完成了数据的生产和入队。

4.2 数据的消费过程

SurfaceView绘制的数据,传递过BufferQueue后,最终由SurfaceFlinger进行合成消费。SurfaceFlinger的消费由SurfaceFlingerConsumer实现,流程如下:

代码语言:javascript复制
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;    }

    //2. 消费完毕,释放Slot    err = updateAndReleaseLocked(item);    if (err != NO_ERROR) {        return err;    }}

acquireBufferLocked的实现如下:

代码语言:javascript复制
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;}

而updateAndReleaseLocked方法的流程如下:

代码语言:javascript复制
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. 总结

本文对BufferQueue的内部实现做了介绍,结合入队/出对说明了BufferQueue内部Slot的状态扭转过程,并介绍了常用的BufferQueue封装类,最后介绍了一个基于BufferQueue的例子。

var first_sceen__time = ( new Date());if ("" == 1 && document.getElementById('js_content')) { document.getElementById('js_content').addEventListener("selectstart",function(e){ e.preventDefault(); }); } (function(){ if (navigator.userAgent.indexOf("WindowsWechat") != -1){ var link = document.createElement('link'); var head = document.getElementsByTagName('head')[0]; link.rel = 'stylesheet'; link.type = 'text/css'; link.href = "//res.wx.qq.com/mmbizwap/zh_CN/htmledition/style/page/appmsg_new/winwx45ba31.css"; head.appendChild(link); } })();

harveyxia

赞赏

长按二维码向我转账

受苹果公司新规定影响,微信 iOS 版的赞赏功能被关闭,可通过二维码转账支持公众号。

文章转载自公众号

腾讯音乐技术团队

阅读

分享 在看

已同步到看一看

取消 发送

我知道了

朋友会在“发现-看一看”看到你“在看”的内容

确定

已同步到看一看写下你的想法

最多200字,当前共字 发送

已发送

朋友将在看一看看到

确定

写下你的想法...

取消

发布到看一看

确定

最多200字,当前共字

发送中

微信扫一扫 关注该公众号

微信扫一扫 使用小程序

即将打开""小程序

取消 打开

0 人点赞