Mobile Development 16 min read

Understanding Android BufferQueue: Architecture and Internal Operations

Android’s BufferQueue, the core producer‑consumer mechanism behind the display pipeline, manages GraphicBuffer slots through explicit FREE‑DEQUEUED‑QUEUED‑ACQUIRED transitions, using shared memory and Binder to let producers (e.g., Views, MediaCodec) enqueue frames and consumers (e.g., SurfaceFlinger) acquire and release them efficiently.

Tencent Music Tech Team
Tencent Music Tech Team
Tencent Music Tech Team
Understanding Android BufferQueue: Architecture and Internal Operations

Background: In Android development, many developers are unaware of BufferQueue, yet it is a crucial component for data transmission in the display system. The Android display pipeline relies on BufferQueue to render content to the screen (or other abstract outputs such as encoders). Understanding BufferQueue helps diagnose issues like MediaCodec dequeueBuffer returning -1 or why SurfaceView requires unlockCanvasAndPost after drawing.

The analysis is based on Android 6.0.1 source code.

2. How BufferQueue Works Internally

BufferQueue follows a producer‑consumer model. A producer (e.g., a View, MediaCodec, or SurfaceFlinger) fills a GraphicBuffer and enqueues it; a consumer (e.g., SurfaceFlinger, a screenshot app) dequeues the buffer for rendering or processing. The same class can act as both producer and consumer in different scenarios.

Typical usage steps:

Initialize a BufferQueue.

The producer calls dequeueBuffer to obtain a free GraphicBuffer .

After obtaining the buffer, the producer calls requestBuffer to get the actual GraphicBuffer object.

The producer fills the buffer with graphic data and calls queueBuffer to enqueue it.

The BufferQueue notifies the consumer via a callback that new data is available.

The consumer calls acquireBuffer to dequeue a GraphicBuffer .

After processing, the consumer calls releaseBuffer to return the buffer to the free pool.

The BufferQueue notifies the producer that a free buffer is available, and the cycle repeats.

Producers may produce continuously without waiting for callbacks, while consumers may poll the queue, though polling is less efficient.

BufferQueue operates across process boundaries using shared memory and Binder, minimizing data copies.

Key Classes

BufferQueueCore – actual implementation.

BufferSlot – stores a GraphicBuffer .

BufferState – represents the state of a GraphicBuffer .

IGraphicBufferProducer – producer interface (implemented by BufferQueueProducer).

IGraphicBufferConsumer – consumer interface (implemented by BufferQueueConsumer).

GraphicBuffer – the buffer that holds image data.

ANativeWindow_Buffer – parent class of GraphicBuffer .

ConsumerBase – implements ConsumerListener to receive callbacks.

2.1 BufferQueueCore Data Structures

Core members:

BufferQueueDefs::SlotsType mSlots; // array of BufferSlot, default size 64
std::set
mFreeSlots; // slots whose state is FREE (no GraphicBuffer attached)
std::list
mFreeBuffers; // slots whose state is FREE but already have a GraphicBuffer
Fifo mQueue; // FIFO queue storing produced items

During initialization, all slots are placed into mFreeSlots :

for (int slot = 0; slot < BufferQueueDefs::NUM_BUFFER_SLOTS; ++slot) {
    mFreeSlots.insert(slot);
}

2.2 Producer: dequeueBuffer

The producer obtains a free slot. If a slot is available, its index is returned; otherwise -1 is returned.

status_t BufferQueueProducer::dequeueBuffer(int *outSlot,
        sp
*outFence, bool async,
        uint32_t width, uint32_t height, PixelFormat format, uint32_t usage) {
    // 1. Find a FREE slot
    status_t status = waitForFreeSlotThenRelock("dequeueBuffer", async,
            &found, &returnFlags);
    if (status != NO_ERROR) {
        return status;
    }
    // 2. Mark slot as DEQUEUED
    *outSlot = found;
    mSlots[found].mBufferState = BufferSlot::DEQUEUED;
    // 3. Lazily allocate GraphicBuffer if needed
    if (returnFlags & BUFFER_NEEDS_REALLOCATION) {
        sp
graphicBuffer = mCore->mAllocator->createGraphicBuffer(
                width, height, format, usage, &error);
        graphicBuffer->setGenerationNumber(mCore->mGenerationNumber);
        mSlots[*outSlot].mGraphicBuffer = graphicBuffer;
    }
    return NO_ERROR;
}

waitForFreeSlotThenRelock first checks mFreeBuffers , then mFreeSlots , and finally may block if no slot is available.

2.3 Producer: requestBuffer

status_t BufferQueueProducer::requestBuffer(int slot, sp
* buf) {
    if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
        return BAD_VALUE;
    } else if (mSlots[slot].mBufferState != BufferSlot::DEQUEUED) {
        return BAD_VALUE;
    }
    mSlots[slot].mRequestBufferCalled = true;
    *buf = mSlots[slot].mGraphicBuffer;
    return NO_ERROR;
}

2.4 Producer: queueBuffer

status_t BufferQueueProducer::queueBuffer(int slot,
        const QueueBufferInput &input, QueueBufferOutput *output) {
    if (slot < 0 || slot >= maxBufferCount) {
        return BAD_VALUE;
    }
    // 2. Change state to QUEUED
    mSlots[slot].mFence = fence;
    mSlots[slot].mBufferState = BufferSlot::QUEUED;
    ++mCore->mFrameCounter;
    mSlots[slot].mFrameNumber = mCore->mFrameCounter;
    // 3. Push into FIFO
    if (mCore->mQueue.empty()) {
        mCore->mQueue.push_back(item);
        frameAvailableListener = mCore->mConsumerListener;
    }
    // 4. Notify consumer
    if (frameAvailableListener != NULL) {
        frameAvailableListener->onFrameAvailable(item);
    } else if (frameReplacedListener != NULL) {
        frameReplacedListener->onFrameReplaced(item);
    }
    return NO_ERROR;
}

2.5 Consumer: acquireBuffer

status_t BufferQueueConsumer::acquireBuffer(BufferItem* outBuffer,
        nsecs_t expectedPresent, uint64_t maxFrameNumber) {
    if (mCore->mQueue.empty()) {
        return NO_BUFFER_AVAILABLE;
    }
    // 2. Take first item from FIFO
    BufferQueueCore::Fifo::iterator front(mCore->mQueue.begin());
    int slot = front->mSlot;
    *outBuffer = *front;
    mCore->mQueue.erase(front);
    // 4. Update state to ACQUIRED
    if (mCore->stillTracking(front)) {
        mSlots[slot].mAcquireCalled = true;
        mSlots[slot].mBufferState = BufferSlot::ACQUIRED;
    }
    // 5. Notify producer about dropped frames if any
    if (listener != NULL) {
        for (int i = 0; i < numDroppedBuffers; ++i) {
            listener->onBufferReleased();
        }
    }
    return NO_ERROR;
}

2.6 Consumer: releaseBuffer

status_t BufferQueueConsumer::releaseBuffer(int slot, uint64_t frameNumber,
        const sp
& releaseFence, EGLDisplay eglDisplay, EGLSyncKHR eglFence) {
    if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
        return BAD_VALUE;
    }
    // 3. Change state to FREE and add to free list
    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);
        if (listener != NULL) {
            listener->onBufferReleased();
        }
    }
    return NO_ERROR;
}

The state transition diagram (FREE → DEQUEUED → QUEUED → ACQUIRED → FREE) ensures efficient reuse of GraphicBuffer objects and minimizes costly shared‑memory allocations.

In summary, BufferQueue implements a robust producer‑consumer pipeline with explicit buffer states, shared‑memory buffers, and cross‑process synchronization, which is fundamental for Android UI rendering, video playback, and screen capture.

Mobile developmentGraphicsAndroidBufferQueueProducer ConsumerSurfaceFlinger
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.