Mobile Development 13 min read

Common Wrapper Classes for Android BufferQueue: Surface and SurfaceTexture

The article explains BufferQueue’s internal design and shows how Android developers typically use its wrapper classes—Surface as the producer and SurfaceTexture as the consumer—detailing their constructors, dequeue/queue workflows, lock/unlock mechanisms, and a complete SurfaceView example that illustrates buffer production and consumption by SurfaceFlinger.

Tencent Music Tech Team
Tencent Music Tech Team
Tencent Music Tech Team
Common Wrapper Classes for Android BufferQueue: Surface and SurfaceTexture

The article introduces the design concept and internal implementation of BufferQueue, then focuses on common wrapper classes used in practice, namely Surface (the producer) and SurfaceTexture (the consumer), and provides concrete usage examples.

3. BufferQueue Common Wrapper Classes

In real applications developers rarely use BufferQueue directly. Instead they work with Surface and SurfaceTexture , which encapsulate BufferQueue for easier production and consumption of graphic buffers.

3.1 Surface

The constructor of Surface is defined as:

Surface::Surface(
        const sp<IGraphicBufferProducer>& bufferProducer,
        bool controlledByApp)
    : mGraphicBufferProducer(bufferProducer),
      mGenerationNumber(0)

The constructor receives a reference to a producer; all interactions with BufferQueue go through this reference. The dequeueBuffer workflow is:

int Surface::dequeueBuffer(android_native_buffer_t** buffer, int* fenceFd) {

    // 1. Call mGraphicBufferProducer->dequeueBuffer to get a slot index
    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. Call requestBuffer if the slot needs reallocation
    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. Return the GraphicBuffer
    *buffer = gbuf.get();
}

The corresponding queueBuffer method simply forwards the buffer to the producer:

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. Directly call the producer's queueBuffer
    status_t err = mGraphicBufferProducer->queueBuffer(i, input, &output);
    if (err != OK)  {
        ALOGE("queueBuffer: error queuing buffer to SurfaceTexture, %d", err);
    }
}

Surface also provides lock (for double‑buffering) and unlockAndPost to deliver the filled buffer back to BufferQueue:

status_t Surface::lock(
        ANativeWindow_Buffer* outBuffer, ARect* inOutDirtyBounds)
{
    ANativeWindowBuffer* out;
    int fenceFd = -1;
    // 1. Get the actual buffer
    status_t err = dequeueBuffer(&out, &fenceFd);

    // 2. Handle double‑buffering if needed
    if (canCopyBack) {
        const Region copyback(mDirtyRegion.subtract(newDirtyRegion));
        if (!copyback.isEmpty())
            copyBlt(backBuffer, frontBuffer, copyback);
    }
}
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. Send the produced data to 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 acts as the consumer. Its initialization creates a BufferQueue and links the producer and consumer objects:

static void SurfaceTexture_init(JNIEnv* env, jobject thiz, jboolean isDetached,
        jint texName, jboolean singleBufferMode, jobject weakThiz)
{
    sp<IGraphicBufferProducer> producer;
    sp<IGraphicBufferConsumer> consumer;
    // 1. Create a BufferQueue
    BufferQueue::createBufferQueue(&producer, &consumer);

    if (singleBufferMode) {
        consumer->disableAsyncBuffer();
        consumer->setDefaultMaxBufferCount(1);
    }

    // 2. Create a GLConsumer instance (the consumer side)
    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. Store the consumer instance and its producer back to the Java layer
    SurfaceTexture_setSurfaceTexture(env, thiz, surfaceTexture);
    SurfaceTexture_setProducer(env, thiz, producer);
}

The consumer method updateTexImage pulls a new frame from the queue:

static void SurfaceTexture_updateTexImage(JNIEnv* env, jobject thiz)
{
   // 1. Get the consumer created during init
   sp<GLConsumer> surfaceTexture(SurfaceTexture_getSurfaceTexture(env, thiz));
   // 2. Call the consumer's 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)");
   }
}

The underlying GLConsumer::updateTexImage acquires a buffer and releases the previous one:

status_t GLConsumer::updateTexImage() {
    BufferItem item;
    // 1. Acquire a buffer from the queue
    err = acquireBufferLocked(&item, 0);
    // Release the previous buffer
    err = updateAndReleaseLocked(item);
    if (err != NO_ERROR) {
        glBindTexture(mTexTarget, mTexName);
        return err;
    }
}

Acquisition eventually reaches ConsumerBase::acquireBufferLocked , which forwards the call to the concrete BufferQueueConsumer implementation.

4. BufferQueue Example

The article then demonstrates a concrete usage scenario with SurfaceView in Android. SurfaceView creates its own Surface , which interacts with BufferQueue without sharing the Activity's surface.

4.1 Production Process

Typical drawing code for a SurfaceView looks like:

Canvas canvas = null;
try {
    canvas = holder.lockCanvas(null);
    // draw here
} catch (Exception e) {
    e.printStackTrace();
} finally {
    if (canvas != null) {
        holder.unlockCanvasAndPost(canvas);
    }
}

The lockCanvas call follows this chain:

SurfaceHolder.lockCanvas

SurfaceHolder.internalLockCanvas

Surface.lockCanvas

Surface.nativeLockCanvas

The native implementation obtains a buffer via Surface::lock , wraps it into a Bitmap , and attaches it to a Canvas so drawing writes directly into a GraphicBuffer that will later be queued.

static jlong nativeLockCanvas(JNIEnv* env, jclass clazz,
        jlong nativeObject, jobject canvasObj, jobject dirtyRectObj) {
    sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));

    ANativeWindow_Buffer outBuffer;
    // 1. Get a suitable buffer via Surface::lock (internally dequeueBuffer & requestBuffer)
    status_t err = surface->lock(&outBuffer, dirtyRectPtr);

    // 2. Build a Bitmap that points to the buffer memory
    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 {
        bitmap.setPixels(NULL);
    }

    // 3. Attach the Bitmap to the Canvas
    Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvasObj);
    nativeCanvas->setBitmap(bitmap);
}

After drawing, unlockCanvasAndPost is called, which eventually invokes Surface::unlockAndPost , and that method forwards the buffer to queueBuffer , completing the production side.

4.2 Consumption Process

The produced buffers are consumed by SurfaceFlinger via its SurfaceFlingerConsumer . The consumer acquires a buffer, processes it, and releases the slot:

status_t SurfaceFlingerConsumer::updateTexImage(BufferRejecter* rejecter,
        const DispSync& dispSync, uint64_t maxFrameNumber) {
    BufferItem item;
    // 1. Acquire a slot
    err = acquireBufferLocked(&item, computeExpectedPresent(dispSync), maxFrameNumber);
    if (err != NO_ERROR) {
        return err;
    }

    // 2. After consumption, release the slot
    err = updateAndReleaseLocked(item);
    if (err != NO_ERROR) {
        return err;
    }
}

The acquisition ultimately calls GLConsumer::acquireBufferLocked , which forwards to BufferQueueConsumer::acquireBuffer . Release follows a similar path through releaseBufferLocked .

5. Summary

The article presented the internal workings of BufferQueue, explained how Surface (producer) and SurfaceTexture (consumer) wrap it, and demonstrated a complete end‑to‑end example using SurfaceView to produce graphics and SurfaceFlinger to consume them.

6. References

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

NativeGraphicsAndroidC++BufferQueueSurfaceSurfaceTexture
Tencent Music Tech Team
Written by

Tencent Music Tech Team

Public account of Tencent Music's development team, focusing on technology sharing and communication.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.