前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入浅出Android BufferQueue-下

深入浅出Android BufferQueue-下

作者头像
QQ音乐技术团队
修改2019-05-16 17:34:58
4.3K0
修改2019-05-16 17:34:58
举报
文章被收录于专栏:QQ音乐技术团队的专栏

上文主要介绍了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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-04-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯音乐技术团队 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 3.BufferQueue常用封装类
    • 3.1 Surface
    • 3.2 SurfaceTexture
    • 4.BufferQueue的实例
      • 4.1 数据的生产过程
        • 4.2 数据的消费过程
        • 5. 总结
        • 6. 参考资料
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档