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.
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 itemsDuring 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.
Tencent Music Tech Team
Public account of Tencent Music's development team, focusing on technology sharing and communication.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.