序
打算用rust重写BufferQueue,千头万绪的BufferQueue,当用另外一种语言去抄作业时,该如何迈出第一步呢?本文基于众多网友的贡献,融合个人学习和认知习惯,逐步展开对BufferQueue的认知和rust实现过程。
抓住根因
千头万绪,怎么抓住最关键的因素呢。有个很直观的思路:页面内容绘制到哪里了,以及怎么显示到屏幕上的。这里的第一要义就是"保存绘制结果"这个东西,即GraphicBuffer。
不管封装多层,核心就是把View Camera等绘制到GraphicBuffer,然后把GraphicBuffer送到显示器显示出来。
这时我们来看安卓官网这个图:
source.android.com/docs/core/g...

具体的应用,如StatusBar,壁纸,APP等持有页面内容(View Camera流等),通过Skia(OpenGL ES或Vulkan)即GPU完成页面内容绘制,绘制结果保存在GraphicBuffer里;有些复杂的GraphicBuffer,显示硬件不能直接 上屏,需要SurfaceFlinger再次利用GPU进行合成,生成新的GraphicBuffer;这些GraphicBuffer被送到显示器的DPU,进行最终合成,然后显示出来。
到这里,主角基本都浮现了:
GraphicBuffer
前面提到过,所有内容到被绘制到这里,其实就是图形缓存,这里面也涉及到非常多的复杂技术,如Gralloc、ION显存分配,跨进程、跨设备高效共享和传输等。个人觉得虽然技术复杂,但逻辑相对简单。这里有个极容易被忽略的点:显存的分配和IPC是"非常耗时的",所以在dequeueBuffer, allocateBuffers时,会用到多线程处理。
BufferQueue
本文讨论的主角。直接把各APP的内容绘制到GraphicBuffer,然后送显不行吗, 为什么要引入BufferQueue呢。引入它当然是有原因的,这里牵涉到两个非常耗时的环节:(1)Skia GPU绘制,即生产者Producer;(2)合成或送显,即消费者Consumer。由于生产者很耗时,消费者也很耗时,如果不用多线程管理,页面一定会卡顿、不流畅。这肯定是不能接受的。
Fence
跨进程、跨设备同步。比如Skia GPU往GraphicBuffer绘制内容(首先等待该GraphicBuffer被上屏模块(HWC)释放)这个过程是非常耗时的,且不会阻塞CPU。所以CPU往GPU提交绘制命令时,会创建一个GPU的fence,即acquireFence(drawFence),通过queueBuffer传递给显示上屏模块(HWC)。同样的,上屏流程也非常耗时,当向上屏模块提交GraphicBuffer和参数后,HWC先等待acquireFence(drawFence)被Skia GPU唤醒,然后创建fence,即releaseFence,通过releaseBuffer流转给给Skia GPU逻辑,并立即返回,不阻塞CPU继续工作。由于安卓源码经过多层封装,找出这两种fence的源头,是非常繁琐的。后续会专门写一篇HWC上屏博客,详细讨论这个过程。
概要设计层面
由于生产者(如skia gpu绘制、相机流等)填充GraphicBuffer非常耗时,消费者(SurfaceFlinger合成、上屏模块HWC)也非常耗时,且GPU和CPU可以并行工作,因此引入了生产者-消费者多线程模型。进一步,借助安卓binder机制,实现跨进程P-C模型。
这时看下安卓官网BufferQueue生产者-消费者模型:
source.android.com/docs/core/g...

从时序角度看,如下:

以上描述还是非常概要和抽象的,先建立一个大致概念,后面逐步拆解。
几个基本概念
GraphicBuffer
前面已经提到过,这是整个BufferQueue管理的核心对象。
Fence
前文也提到过,BufferQueue涉及到两种Fence,即代表GPU是否完成对GraphicBuffer填充的AcquireFence (也叫drawFence),当GraphicBuffer流转到消费者(如上屏服务HWC)时,会wait这个fence,直到被唤醒,消费者才会读取该Buffer内容。另外一类fence代表消费者是否使用完GraphicBuffer(如上屏服务HWC完成该Buffer上屏),称之为ReleaseFence。GPU在往GraphicBuffer填充内容前,会wait release fence,直到被唤醒,才才可以往该Buffer填充内容。在阅读SurfaceFlinger源码时,还有一个PresentFence(也叫RetireFence),当有多个GraphicBuffer直接提交到上屏服务HWC进行显示器硬件合成,然后上屏时,单个GraphicBuffer完成读取后,唤醒ReleaseFence,所有Buffer完成读取,并且显存完成交换后,唤醒RetireFence(PresentFence),以后在上屏服务HWC博客里详细介绍。
BufferState
GraphicBuffer在生产者、消费者之前流转,需要标识当前处于什么状态,早期版本用枚举表示:FREE
、 DEQUEUED
、 QUEUED
、 ACQUIRED
、 SHARED
。
新版本稍微复杂一点,用count标识,但功能是一致的,表示GraphicBuffer处于什么状态。
android.googlesource.com/platform/fr...
sql
// A buffer can be in one of five states, represented as below:
//
// | mShared | mDequeueCount | mQueueCount | mAcquireCount |
// --------|---------|---------------|-------------|---------------|
// FREE | false | 0 | 0 | 0 |
// DEQUEUED| false | 1 | 0 | 0 |
// QUEUED | false | 0 | 1 | 0 |
// ACQUIRED| false | 0 | 0 | 1 |
// SHARED | true | any | any | any |
BufferSlot
GraphicBuffer, BufferState Fence NeedsReallocation等字段,统一封装在BufferSlot里。对于一个BufferQueue,BufferSlot是个数组,固定为64,我称之为"静态原始"数据,它的大小和位置不会改变,并且不IPC流转;与之不同的是BufferItem,我称之为"动态"数据,在queueBuffer时创建并入队列,在acquireBuffer出队列,用完后释放,会进行IPC流转。
BufferItem
GraphicBuffer Fence Crop区域 slot(标识哪个BufferSlot)等其它字段,主要用于Buffer IPC动态流转。
Vector mQueue
我理解这是BufferQueue命名由来,生产者异步填充GraphicBuffer,携带AcquireFence(DrawFence),并构建BufferItem添加到mQueue,消费者取出BufferItem进行读取等其它处理,这个流程会跨进程。
slot索引
BufferQueue持有的所有GraphicBuffer,用BufferSlot mSlots[64]表示,怎么区分哪些buffer被使用、哪些是空闲的呢,可以通过另外一个队列来管理,但安卓采用了一个巧妙的方法来管理,即int型索引。
- set mFreeSlots:初始化,同步模式为[0, 1],异步模式下为[0, 1, 2],同步/异步概念后面会介绍。
- list mFreeBuffers:已经完成GraphicBuffer内存分配的slot,释放时,slot放入该变量,核心是复用已经分配好的GraphicBuffer,以为显存分配很耗时,能复用尽量复用。
- set mActiveBuffers:该slot有有效GraphicBuffer与之绑定,为了复用GraphicBuffer。
- list mUnusedSlots:64 - mFreeSlots - mFreeBuffers - mActiveBuffers剩下所有未用的slot。
BufferQueueCore
所有上述对象,都封装在BufferQueueCore里,并通过IGraphicBufferProducer IGraphicBufferConsumer分别向生产者、消费者提供接口。
状态切换和接口关系
Producer两个核心接口:dequeueBuffer、queueBuffer。
Consumer两个核心接口:acquireBuffer、releaseBuffer。
上述四个接口构成运行BufferQueue最小内核。
attach / detach / cancel先忽略,后文单独介绍。

最小运行内核
抛开其他复杂逻辑,最简化的BufferQueue只需要实现5个接口,代码初期,如果不考虑画面撕裂问题,Fence可以先不关注(空Fence的id是-1,不是0,切记!),其它逻辑都是在此基础上叠加而来,会在后文逐一介绍。
dequeueBuffer
理解这个接口的关键是:
(1)获取可用slot,取不到在wait;
(2)放入mActiveBuffers
(3)egl wait,或者把ReleaseFence传递给消费者,在适当时机wait。
(4)其它逻辑在后文介绍
waitForFreeSlotThenRelock
因为取slot非常复杂,因此封装在waitForFreeSlotThenRelock函数里,包含以下关键点:
1 取slot
int slot = *(mCore->mFreeSlots.begin())
mCore->mFreeSlots.erase(slot)
int slot = mCore->mFreeBuffers.front();
mCore->mFreeBuffers.pop_front();
2 取不到,则wait
mCore->mDequeueCondition.wait(lock)
3 五种return不wait情况(would_block time_out在后文介绍)
kotlin
return NO_INIT;
return INVALID_OPERATION;
return WOULD_BLOCK;
return TIMED_OUT;
return NO_ERROR;
4 mDequeueTimeout mAsyncMode mDequeueBufferCannotBlock等,后文介绍。
slot迁移及fence唤醒
1 取slot
status_t status = waitForFreeSlotThenRelock(FreeSlotCaller::Dequeue, lock, &found);
2 放入mActiveBuffers
mCore->mActiveBuffers.insert(found);
3 BUFFER_NEEDS_REALLOCATION(已经分配过,预分配,或者被释放等)
returnFlags |= BUFFER_NEEDS_REALLOCATION;
sp graphicBuffer = new GraphicBuffer()
4 eglClientWaitSyncKHR / eglDestroySyncKHR
5 ReleaseFence返回给生产者,并在适当时机wait
requestBuffer
mSlots[slot].mRequestBufferCalled = true;
binder返回buffer:mSlots[slot].mGraphicBuffer;
queueBuffer
1 fence赋值
mSlots[slot].mFence = acquireFence;
如Skia GPU正在填充buffer,此时CPU可以继续执行,巧妙地提升性能,通过fence实现同步。
2 切换到QUEUE状态
mSlots[slot].mBufferState.queue();
3 填充BufferItem
BufferItem item,从BufferSlot[slot]处copy,并赋值给BufferItem,等待binder流转。
4 入列(可丢弃覆盖后文介绍)
- slot迁移
mCore->mActiveBuffers.erase(last.mSlot);
mCore->mFreeBuffers.push_back(last.mSlot);
- 直接放入
mCore->mQueue.push_back(item);
5 mCore->mDequeueCondition.notify_all(); 唤醒dequeueBuffer获取不到slot时的阻塞。
acquireBuffer
1 状态切换成ACQUIRE
mSlots[slot].mBufferState.acquire();
2 从队列移除
mCore->mQueue.erase(front);
3 返回BufferItem供消费者使用
BufferItem* outBuffer
*outBuffer = *front;
acquire fence被流转给消费者,如上屏服务HWC,上屏读GraphicBuffer时,会等待acquire fence被唤醒;SurfaceFlinger进行layer合成时,也会等待acquire fence被唤醒,才真正合成该layer。
4 mCore->mDequeueCondition.notify_all();唤醒dequeueBuffer获取不到slot时的阻塞。
5 expectedPresent用于帧率控制,本文不讨论
6 allow extra acquire, shared buffer mode后文讨论
releaseBuffeer
1 状态切换到FREE
mSlots[slot].mBufferState.release();
2 slot从activeBuffers转移到freeBuffers
mCore->mActiveBuffers.erase(slot);
mCore->mFreeBuffers.push_back(slot);
3 ReleaseFence被记录,流转给生产者,在dequeueBuffer时被获取到。
mSlots[slot].mFence = releaseFence;
4 mCore->mDequeueCondition.notify_all();唤醒dequeueBuffer获取不到slot时的阻塞。
再次强调Fence
最小化运行内核不关注fence,会带来画面撕裂问题(即使vsync运作正常),这里再次讨论一下fence,因为acquire fence, release fence藏在复杂代码逻辑之中,对于初次接触Fence和BufferQueue新手来说,太让人恶心了。
AcquireFence:GPU侧创建,被上屏服务HWC侧等待
这是skia创建的gpu fence,drawFence即AcquireFence,最终流转给上屏服务HWC,在上屏读取GraphicBuffer时,会先等待该fence被唤醒。
这里有一个GPU图形绘制的"坑",我们正常调用skia api提交各种绘制指令后,这些命令并没有立即被GPU执行,GPU自己有个command buffer,会缓存这些指令,CPU在提交skia指令后,立即返回,这时候GraphicBuffer还未填充,或正在填充,因此GPU必须要提供一个状态用来表示所有绘制指令执行完成,这就是fence。下述代码是gpu fence直观认知。
scss
// frameworks/native/libs/renderengine_qcom/skia/SkiaGLRenderEngine.cpp
base::unique_fd SkiaGLRenderEngine::flushGL() {
EGLSyncKHR sync = eglCreateSyncKHR(mEGLDisplay, EGL_NATIVE_FENCE_ANDROID, nullptr);
glFlush();
base::unique_fd fenceId(eglDupNativeFenceFDANDROID(mEGLDisplay, sync));
eglDestroySyncKHR(mEGLDisplay, sync);
return fenceFd;
}
base::unique_fd SkiaGLRenderEngine::flushAndSubmit(SkiaGpuContext*) {
base::unique_fd drawFence = flushGL();
context->grDirectContext()->submit(GrSync::kYes or GrSync::kNo);
return drawFence; // drawFence即AcquireFence,queueBuffer()入参
}
ReleaseFence:上屏服务HWC侧创建,被生产者等待(如skia gpu)
ReleaseFence是上屏服务HWC侧创建的,当GraphicBuffer被传递到HWC上屏时,会先wait gpu fence,即AcquireFence,等待Skia GPU完成所有绘制,并唤醒AcquireFence,然后HWC把GraphicBuffer内容copy到帧缓存framebuffer,这个copy过程是异步的,为了不阻塞CPU运行,会创建一个ReleaseFence,立即返回,在SurfaceFlinger中,通过HWComposer模块层层包装,非常繁琐,如果没有理清最本质的几个API,在实现BufferQueue时,会心乱如麻,这块细节会在以后的上屏服务HWC文章里详述。这里给出大致流程和关键代码,先有个大致认知。
在高通平台,SurfaceFlinger提交渲染后,拿到qcom侧创建的fence,赋值给slot,在dequeueBuffer时wait。
调用顺序SurfaceFlinger composer -> qcom composer->sdm->sde-drm->external/libdrm{drmModeAtomicAddProperty}->driver(驱动)
SurfaceFlinger composer
frameworks/native/services/surfaceflinger_qcom/DisplayHardware/AidlComposerHal.cpp
1 提交渲染
scss
Error AidlComposer::validateDisplay(Display display) {
}
Error AidlComposer::presentOrValidateDisplay(Display display, int* outPresentFence) {
writer->get().presentOrValidate(display);
execute();
auto fence = reader->get().takePresentFence(display);
}
Error AidlComposer::presentDisplay(Display display, int* outPresentFence) {
auto writer = getWriter(display);
auto reader = getReader(display);
writer->get().presentDisplay(display);
execute(display);
auto fence = reader->get().takePresentFence(display);
}
2 拿到qcom侧fence
arduino
Error AidlComposer::getReleaseFences(Display display, vector<int>* outReleaseFences) {
vector<ReleaseFences::Layer> fences;
fences = reader->get().takeReleaseFences(display);
}
上屏服务HWC sdm模块:libs/dal/hw_device_drm.cpp
vendor/hardware/qcom/display/sdm/libs/dal/hw_device_drm.cpp
SetupAtomic 配置fence请求
Commit触发内核创建fence
原理类似vk和gl命令提交,异步执行,fence作为同步信号
release & retire fence
ini
DisplayError HWDeviceDRM::Commit(HWLayersInfo* hw_layers_info) {
if (default_mode_) this->DefaultCommit(hw_layers_info);
else this->AtomicCommit(hw_layers_info);
}
DisplayError HWDeviceDRM::AtomicCommit(HWLayersInfo *hw_layers_info) {
int64_t release_fence_fd = -1;
int64_t retire_fence_fd = -1;
Fence::ScopedRef scoped_ref;
this->SetupAtomic(scoped_ref, hw_layers_info, false, &release_fence_fd, &retire_fence_fd) ;
int ret = drm_atomic_intf->Commit(sync_commit, false); // 触发内核创建fence
shared_ptr<Fence> release_fence = Fence::Create(INT(release_fence_fd), "release");
shared_ptr<Fence> retire_fence = Fence::Create(INT(retire_fence_fd), "retire");
hw_layers_info->config[i].hw_rotator_session->output_buffer.release_fence = release_fence;
hw_layers_info->hw_layers.at(i).input_buffer.release_fence = release_fence;
hw_layers_info->sync_handle = release_fence;
}
驱动模块sde
output_fence(即release fence, releaseBuffer()入参)
vendor/qcom/opensource/display-drivers/msm/sde/sde_crtc.c
c
struct drm_crtc *sde_crtc_init(struct drm_device *dev, struct drm_plane *plane) {
sde_crtc_install_properties(crtc, kms->catalog);
}
static void sde_crtc_install_properties(struct drm_crtc *crtc, struct sde_mdss_cfg *catalog) {
msm_property_install_volatile_range(&sde_crtc->property_info,
"output_fence", 0x0, 0, ~0, 0, CRTC_PROP_OUTPUT_FENCE);
}
retire_fence(即present fence)
vendor/qcom/opensource/display-drivers/msm/sde/sde_connector.c
arduino
struct drm_connector *sde_connector_init(struct drm_device *dev,
struct drm_encoder *encoder, struct drm_panel *panel,
void *display, const struct sde_connector_ops *ops,
int connector_poll, int connector_type) {
rc = _sde_connector_install_properties(dev, sde_kms, c_conn,
connector_type, display, &display_info);
}
static int _sde_connector_install_properties(struct drm_device *dev,
struct sde_kms *sde_kms, struct sde_connector *c_conn,
int connector_type, void *display, struct msm_display_info *display_info) {
msm_property_install_volatile_range(&c_conn->property_info,
"RETIRE_FENCE", 0x0, 0, ~0, 0, CONNECTOR_PROP_RETIRE_FENCE);
}
基于最小内核BufferQueue实现简化SurfaceFlinger服务
这里以伪代码表示,重在表达核心逻辑,便于理解纷繁复杂的SurfaceFlinger源码,抓住源码分析主线。
1 生产者线程:填充GraphicBuffer内容
scss
void producer_thread() {
// get igraphic_buffer_producer,获取BufferQueue生产者IPC接口
IGraphicBufferProducer* p = binder_get_service('producer_test01')
// fill graphic buffer
while(true) {
int slot;
Fence* release_fence;
p->dequeueBuffer(&slot, &releaseFence);
release_fence.wait(); // 等待上屏服务完成GraphicBuffer内容拷贝到帧缓存
GraphicBuffer* buffer = p->requestBuffer(slot);
Skia s(buffer); // skia 通过 gpu填充 GraphicBuffer
s.drawRect();
s.drawImage();
s.drawText();
...
Fence* drawFence = flushGL();
// 见上节,创建drawFencce(AcquireFence),作为releaseFence()入参
p->queueBuffer(slot, drawFence); // 在上屏服务HWC时,等待GraphicBuffer填充完成
}
}
2 消费者线程(简化SurfaceFlinger)
ini
void consumer_thread() {
IGraphicBufferProducer *p;
IGraphicBufferConsumer *c;
BufferQueue::createQueue(&p, &c);
binder_register_service('producer_test01', p);
// 上屏服务
// frameworks/native/services/surfaceflinger_qcom/DisplayHardware/ComposerHal.h
std::unique_ptr<android::hwc2::Composer> hwc = android::hwc2::Composer::create('default');
Fence* lastFence = null_ptr;
while (true) {
int slot;
GraphicBuffer buffer;
Fence* acquireFence;
c.acquireBuffer(&slot, &buffer, &acquireFence);
{ // 设置layer各种属性,核心是GraphicBuffer
hwc.setLayerDisplayFrame(layerId, x, y, width ,height);
hwc.setLayerBuffer(layerId, buffer, acquireFence);
hwc.other_apis();
}
Fence* presentFence; // 即retireFence,表示所有layer上屏完成
hwc.presentOrValidateDisplay(&presentFence);
Fence* releaseFence; // 当前layer完成GraphicBuffer拷贝
hwc.getReleaseFences(layerId, releaseFence);
c.releaseFence(slot, lastFence); // Skia GPU填充前 ,会wait阻塞
lastFence = releaseFence; // 体会一下为什么要用lastFence
}
}
至此,已经完成最小化BufferQueue和SurfaceFlinger,在实际用rust实现过程中,我基本是按照这个思路,完成最简内核,然后研读BufferQueue源码,逐步叠加其它功能。每个功能都有各自逻辑,都是对最小内核不断补充和完善,只有充分理解最小内核源码,才可以清晰把握整个BufferQueue,才不会迷失在汪洋代码中。后面根据个人经验,逐步介绍各叠加功能,不断添砖加瓦,最终形成一个复杂怪兽。
mDequeueBufferCannotBlock
注意事项
默认为false,注意的点:
- mDequeueBufferCannotBlock(false), mAsyncMode(false)这俩简单理解,是否阻塞dequeueBuffer,默认都是会阻塞的,对buffer count没影响,后文会介绍。
- adjustAvailableSlotsLock(delta)当buffer数量变化时(增加或减少),需要释放/新分配buffer,挪动slot,比如从free到active之类的,把握这个本质,这个函数一点不难理解。
- 与dequeue timeout有直接关系
代码分布

动态扩容:buffer_count
三个变量
最小内核里,buffer总量,producer数量、consumer数量都是写死的,这很不友好,需要更精准控制producer/consumer数量,因此,引入三个变量:
- mMaxDequeueBufferCount(1) 最大可以dequeue的buffer数量,默认为1
- mMaxAcquireBufferCount(1) 最大可以acquire的buffer数量,默认为1
- mMaxBufferCount(NUM_BUFFER_COUNT) 默认是64,实际maxBufferCount=mMaxDequeueBufferCount+mMaxAcquireBufferCount+(可能加1,后续会介绍)
两个值得注意的点
- mDequeueBufferCannotBlock(false), mAsyncMode(false)这俩简单理解,是否阻塞dequeueBuffer,默认都是会阻塞的,对buffer count没影响,后文会介绍。
- adjustAvailableSlotsLock(delta)当buffer数量变化时(增加或减少),需要释放/新分配buffer,挪动slot,比如从free到active之类的,把握这个本质,这个函数一点不难理解。
代码分布

异步模式mAsyncMode
异步模式特点
多一个AcquiredBuffer
dequeueBuffer不阻塞,直接丢帧
不触发backpresure(背压),如果下游满了,不阻塞上游,直接drop
SF采用异步模式,否则可能引起SF stall,导致UI卡顿
Camera/Video采用异步模式,避免因GL thread延迟造成帧drop或ANR
不是线程"异步",是buffer管理策略层面的异步:
-
提高吞吐
-
避免pipeline stall
-
减少producer阻塞等待时间
异步模式使用场景
SurfaceFlinger 中 Layer 默认都使用 async 模式。
模块 | 是否用异步 | 原因 |
---|---|---|
SurfaceFlinger → app Surface(UI) | ✔ | 避免因 app lag 导致 SF 被阻塞 |
Camera → GLConsumer | ✔ | 保证帧率流畅,避免 drop |
MediaCodec video decoder → Surface | ✔ | video 解码是持续输出,不等 UI |
从代码角度看,异步模式带来的影响
不阻塞
在waitForFreeSlotThenRelock()里,当取不到可用slot时,producer不会阻塞,会立即返回。
rust
status_t BufferQueueProducer::waitForFreeSlotThenRelock(int* found) {
if ((mCore->mDequeueBufferCannotBlock || mCore->mAsyncMode) &&
(acquiredCount <= mCore->mMaxAcquiredBufferCount)) {
return WOULD_BLOCK;
}
}
buffer count数量+1
c
int BufferQueueCore::getMaxBufferCountLocked() const {
int maxBufferCount = mMaxAcquiredBufferCount + mMaxDequeuedBufferCount +
((mAsyncMode || mDequeueBufferCannotBlock) ? 1 : 0);
// limit maxBufferCount by mMaxBufferCount always
maxBufferCount = std::min(mMaxBufferCount, maxBufferCount);
return maxBufferCount;
}
可丢弃mIsDroppable
异步模式下,会导致mIsDroppable为true,会覆盖最后一帧,当消费端来不及时,保证生产者最新内容尽可能被用于展示。
rust
status_t BufferQueueProducer::queueBuffer(int slot) {
BufferItem item;
item.mIsDroppable = mCore->mAsyncMode || ...;
const BufferItem& last = mCore->mQueue.itemAt(mCore->mQueue.size() - 1);
if (last.mIsDroppable) {
mCore->mQueue.editItemAt(mCore->mQueue.size() - 1) = item;
}
}
status_t BufferQueueConsumer::acquireBuffer() {
BufferQueueCore::Fifo::iterator front(mCore->mQueue.begin());
if (acquireNonDroppableBuffer && front->mIsDroppable) {
return NO_BUFFER_AVAILABLE;
}
}
从日志看,真实使用场景
-
SurfaceTexture与Launcher同线程
mAsyncMode 1 mSharedBufferMode 1
-
Camera3StreamSplitter
mAsyncMode 1 mSharedBufferMode 0
-
SurfaceTexture(相册->播放视频)
mAsyncMode 1 mSharedBufferMode 1
mAsyncMode相关代码分布

可丢弃帧mIsDroppable
为什么引入
mIsDroppable 是 BufferItem 中的一个重要标志,主要用于异步模式下的 BufferQueue,控制某帧是否可以被后来的帧替换掉,即 "丢帧优化"。
mIsDroppable == true:表示该帧是"可以被替代的",如果有新的 buffer 到来,且 queue 满了,那么这帧可以直接 被替换掉(不会继续传给消费者)。
这正是异步模式下允许"drop old frame"以换取最新显示内容的机制。
使用场景
- 异步模式下的 SurfaceFlinger 或 Camera 生产数据流
- 比如你在播放 60fps 视频,消费者(比如 GL 渲染线程)可能处理不过来;
- 异步模式 + droppable 帧可以让后续的生产者帧替代老帧,从而避免帧积压;
- 系统会自动合并 SurfaceDamage(你上面代码也有),避免视觉撕裂。
- SurfaceTexture 使用 GLConsumer 时
当 GLConsumer 使用 updateTexImage() 太慢时,BufferQueue 为了不阻塞 producer,会标记帧为 mIsDroppable 并尝试替换队尾帧。
- 缓解 pipeline backlog
比如:
- App 卡顿;
- SurfaceFlinger stall;
- 高速相机预览或流式解码。
可以通过设置 buffer droppable 减轻系统延迟。
过期帧mIsStale(未发现为true场景)
为什么引入
mIsStale 是 BufferItem 的一个标志,表示该 Buffer 在当前上下文中已经过期(Stale),即本应被处理但被错过了,不应该再被消费了。
mIsStale == true: 表示这个 buffer 已经不再适合传递给消费者,虽然还在 queue 里,但已经失去消费意义。
触发场景
- Buffer 被跳过处理(比如在异步模式下被替换)
例如:
- Producer 使用 async 模式;
- 消费者处理不过来;
- 某帧虽然进入了 queue,但后续有更新 buffer 到来;
- 前一帧未被消费,被新帧替换了;
- 那前一帧就会被标记为 stale。
在丢弃队尾的 droppable buffer 前要检查是否 stale,已经 stale 的就不处理释放逻辑了。
- 在 acquireBuffer() 中,skip 掉 stale buffer
在 acquireBuffer() 的部分逻辑中,如果当前 buffer 是 stale,则直接跳过,不返回给消费者:
使用场景举例
- SurfaceFlinger 合成跳过帧,前帧未合成
- Camera Preview 异步模式切帧太快,前帧直接标 stale
- 动画帧率过高,渲染线程落后太多,队列中帧 stale
代码分布

mQueueBufferCanDrop
为什么引入
- 防止队列堆积导致延迟
- 在同步(非 async)模式下,Producer 允许提交多个 buffer(即一帧一帧往里塞)。
- 如果 Producer 速度远远快于 Consumer 消费速度,就会导致 队列积压。
为了避免这种情况,就可以启用 drop 最老的一帧 的机制(只保留最新),也就是所谓的 "丢帧"。
- 某些应用需要强实时性(prefer latest frame)
举个例子:
- Camera 预览、游戏渲染、XR/AR 等场景中,最新帧更重要。
- 如果 Producer 连续 queue 多帧,就算第一帧还没消费,也可以丢掉旧帧,让用户看到更"实时"的画面。
此时必须开启 mQueueBufferCanDrop,以防止过时的帧滞留。
从日志看,真实使用场景
SurfaceTexture
mIsDroppable 1 mAsyncMode 0 mQueueBufferCanDrop 1 mLegacyBufferDrop 1 mSharedBufferMode 0
代码分布

mSharedBufferMode
为什么引入
引入 mSharedBufferMode(单缓冲模式,Single Buffer Mode)是为了支持Producer 与 Consumer 共享同一个 buffer 的场景,从而提高效率、降低延迟,满足某些特殊场景的需求。
某些场景只需要一个 buffer(共享模式)
比如:
场景一:轻量级 UI(如物理仪表盘、嵌入式控件)
- Producer 更新频率低,Consumer 也能立即处理;
- 双缓冲是浪费;
- 使用一个共享 buffer 就够了。
场景二:虚拟显示 / 镜像场景
- 显示内容几乎是静态的,只更新一次;
- Producer 和 Consumer 同时"看"一个 buffer,更高效。
场景三:SurfaceControl 中的 mirror() 特性
- 允许多个 SurfaceConsumer 看到同一个 buffer(用 StreamSplitter 拆分);
- 此时使用共享模式可减少内存复制。
从日志看,真实使用场景
- SurfaceTexture与Launcher同线程
mAsyncMode 1 mSharedBufferMode 1 mAllowExtraAcquire 0
- SurfaceTexture(相册->播放视频)
mAsyncMode 1 mSharedBufferMode 1 mAllowExtraAcquire 0
代码分布

mAllowExtraAcquire
问题背景
BlastBufferQueue 想尽可能 快地把 buffer 送到 SurfaceFlinger(服务端),以支持更智能的帧调度,比如:
- 丢弃旧 buffer(比如时间戳太早的帧)
- 提高帧输送效率
为此需要:额外 acquire 一个 buffer
默认情况下,consumer 最多只能 acquire maxAcquiredBufferCount 个 buffer。
为了支持上述优化,BlastBufferQueue consumer 必须提前拿到额外一个 buffer,从而尽快做判断和决策(如是否 drop)。
背景扩展
- BlastBufferQueue 是 Google 为提升 UI 合成效率设计的新一代 buffer 管理机制(用于 SurfaceView、SurfaceControl 等)。
- 它改进了旧版 BufferQueue 的限制,主打更低延迟、更高吞吐。
为什么这是安全的?
commit 中解释了两个前提:
- Producer 是同步模式(sync mode)时,buffer 不可丢弃(not droppable)
- 此时一旦 queueBuffer() 完成,buffer 由 queue 拥有,producer 不再拥有它。
- 如果 consumer 提前 acquire(),其实是提前"接手"了这个 buffer,安全无误。
- Producer 是异步模式(async mode)时,buffer 是可丢弃的(droppable)
- 如果 consumer 额外 acquire 这个 droppable buffer,可能违反协议。
- 所以:只在 buffer not droppable 时才允许额外 acquire。
从日志看,真实使用场景
- mConsumerName VRI[Launcher]
mAllowExtraAcquire 1 acquireNonDroppableBuffer 0|1mIsDroppable 0 mAsyncMode 0 mConsumerIsSurfaceFlinger 0 mLegacyBufferDrop 1 mQueueBufferCanDrop 0 mSharedBufferMode 0 mQueue.size 0
- SurfaceView
SurfaceView\[com.android.camera/com.android.camera.CameraActivity\]
mAllowExtraAcquire 1 acquireNonDroppableBuffer 1 numAcquiredBuffers 3 mMaxAcquiredBufferCount+1 3
代码分布

mAllowAllocation
为什么引入
在dequeueBuffer时,可以分配GraphicBuffer,为什么还需要allocateBuffers,什么场景使用它?核心区别总结:
特性 | dequeueBuffer() | allocateBuffers() |
---|---|---|
调用时机 | 渲染流程中动态请求 buffer | 提前预分配 |
分配数量 | 一次只可能分配一个 | 一次性尽量分配所有可能的 |
是否可阻塞 | 可能因为无空闲 slot 阻塞 | 不阻塞,失败就跳过 |
触发路径 | 正常生产者流程调用 | 显式性能优化手段 |
典型调用者 | App 渲染线程 / SurfaceFlinger | Camera HAL / Launcher / UI Framework |
举个例子来说明区别
- 正常应用:dequeueBuffer 触发分配
ANativeWindow_dequeueBuffer(...) -> dequeueBuffer() -> 需要分配 -> requestBuffer()
这是 App 正常渲染的一部分,每一帧都可能请求新 buffer,但这是懒惰分配(lazy allocation),只有 buffer 不够时才分配。而第一次使用时,可能卡顿(因为分配代价高)。
- Camera 应用:allocateBuffers 提前全部分配
IGraphicBufferProducer::allocateBuffers(...)
在 Camera preview 启动前,分辨率/格式已知,直接调用 allocateBuffers() 提前分配 3~5 个 buffer,后续渲染时就不会触发任何分配,帧间延迟更低。
使用动机
- 避免首次使用时卡顿
- 分配 GraphicBuffer 可能涉及 binder + gralloc + ion/pmem 分配,代价很大。
- 特别是 Camera、Launcher、游戏启动页,非常敏感。
- 系统对 buffer 管理更可控
- 明确知道预期最大 buffer 数,系统可以提前准备,防止突发内存爆炸。
- 避免在高内存压力时动态分配失败。
- 配合异步模式使用
- 异步模式(mAsyncMode = true)下允许多一个 buffer,App 可以提前分配,防止丢帧。
- 比如 Launcher 滚动动画中提前 prepare buffer,帧率更稳。
代码分布

attachBuffer/detachBuffer
为什么引入
- 支持外部分配的 GraphicBuffer 注入
- 有时 buffer 并不是由 BufferQueue dequeueBuffer() 出来的,而是通过其他方式(比如由应用层 / HAL 层 / 显卡直分配)获得的;
- 我们希望能直接用这个 buffer,绕开 dequeue。
典型场景:
- 视频解码器直接分配 buffer(硬解输出的 buffer);
- 共享内存 buffer(比如多线程/多进程共享一个渲染目标);
- SurfaceControl.mirror() 等镜像 buffer。
attachBuffer(buffer) 让外部分配的 buffer 注册到 BufferQueue,获得一个 slot 号后就可以正常使用。
- 支持 buffer 动态迁移(跨组件/进程共享)
- 应用或中间件可能将 buffer 从一个 BQ"迁移"到另一个 BQ;
- 如果我们只是 dequeue/queue 是没法完成迁移的;
- detachBuffer(slot) 让某个 buffer 脱离当前 BQ 管理;
- 然后可以在另一个 BQ 用 attachBuffer 接手。
示例:
- SurfaceControl 中 mirror() 将一个 Surface 中 buffer 镜像到另一个 Surface;
- StreamSplitter 中将一个 buffer 分发给多个 consumer;
- Camera/HWComposer 中的 buffer 生命周期交叉复杂时。
- 精细控制 slot <-> buffer 的绑定
- 默认 BQ 是把 slot 和 buffer 固定绑定的(某 slot 对应某个 buffer);
- 使用 attach/detach 后,可以动态绑定/解绑,提高灵活性。
接口关系
/frameworks/av/media/codec2/hal/client/output.cpp
/frameworks/av/media/libstagefright/MediaCodec.cpp
/frameworks/av/services/camera/libcameraservice/device3/Camera3OutputStream.cpp

ConsumerListener
IConsumerListener
IConsumerListener本身不复杂,可以仿照ProducerListener写个StubConsumerListener,但安卓默认提供了ConsumerBase,这个非常绕。

ConsumerBase
这里给出类持有关系,核心问题就一个:非常绕。但本质比较简单,就是解耦,这里有好几个listener,本质就是不同模块的解耦。逻辑比较干净,直接读源码就行。

ProducerListener
示例
-
Camera HAL 使用 SurfaceTexture
mSurfaceTexture = new SurfaceTexture(mBufferQueue);
sp window = mSurfaceTexture;
native_window_api_connect(window.get(), NATIVE_WINDOW_API_CAMERA);
-
app 使用 Surface
connect(int api, bool reportBufferRemoval, const sp&)
代码分布

使用时序
scss
void main() {
IGraphicBuferProducer* p;
IGraphicBuferProducer* c;
BufferQueue::createQueue(&p, &c);
c->consumer_connect();
{ // producer listener可以多次connect/disconnect
p->connect();
p->disconnect();
}
c->disconnect(); // 一旦disconnect,此BufferQueue无法再次使用
}
mIsAandoned
这个变量对应的逻辑非常简单,看源码即可。

总结
剖析源码,我认为最关键的是先明白作者的"设计意图",这是"道"的问题,其它的都是术,不管代码怎么绕,它一定是为了解决什么问题的。对于BufferQueue,我认为先抓住几个根因,然后搞懂最小化内核,在此基础上,逐步叠加不同逻辑。
叠加每个逻辑前,先搞懂这个逻辑引入的目的,为了解决什么问题,然后读代码就很容易了。
剩下就是用rust抄作业了~
refs
最简单的BufferQueue测试程序(一)
BufferQueue 学习总结(内附动态图)
www.cnblogs.com/roger-yu/p/...
努比亚技术团队