深入浅出Android BufferQueue-下

2019-05-16 17:34:58 浏览数 (1)

上文主要介绍了BufferQueue的设计思想和内部实现,本文将介绍对于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的例子。

6. 参考资料

https://cloud.tencent.com/developer/article/1033903

0 人点赞