Android BufferQueue

  1. 背景
    https://cloud.tencent.com/developer/article/1427585?areaSource=102001.1&traceId=DBY6dq2oGfxQ_ETpyQ2zA
    对业务开发来说,无法接触到BufferQueue,甚至不知道BufferQueue是什么东西。对系统来说,BufferQueue是很重要的传递数据的组件,Android显示系统依赖于BufferQueue,只要显示内容到“屏幕”(此处指抽象的屏幕,有时候还可以包含编码器),就一定需要用到BufferQueue,可以说在显示/播放器相关的领悟中,BufferQueue无处不在。即使直接调用Opengl ES来绘制,底层依然需要BufferQueue才能显示到屏幕上。

弄明白BufferQueue,不仅可以增强对Android系统的了解,还可以弄明白/排查相关的问题,如为什么Mediacodec调用dequeueBuffer老是返回-1?为什么普通View的draw方法直接绘制内容即可,SurfaceView在draw完毕后还需要unlockCanvasAndPost?

注:本文分析的代码来自于Android6.0.1。

  1. BufferQueue内部运作方式

BufferQueue是Android显示系统的核心,它的设计哲学是生产者-消费者模型,只要往BufferQueue中填充数据,则认为是生产者,只要从BufferQueue中获取数据,则认为是消费者。有时候同一个类,在不同的场景下既可能是生产者也有可能是消费者。如SurfaceFlinger,在合成并显示UI内容时,UI元素作为生产者生产内容,SurfaceFlinger作为消费者消费这些内容。而在截屏时,SurfaceFlinger又作为生产者将当前合成显示的UI内容填充到另一个BufferQueue,截屏应用此时作为消费者从BufferQueue中获取数据并生产截图。

以下是Android官网对其的介绍:

以下是常见的BufferQueue使用步骤:

初始化一个BufferQueue
图形数据的生产者通过BufferQueue申请一块GraphicBuffer,对应图中的dequeueBuffer方法
申请到GraphicBuffer后,获取GraphicBuffer,通过函数requestBuffer获取
获取到GraphicBuffer后,通过各种形式往GraphicBuffer中填充图形数据后,然后将GraphicBuffer入队到BufferQueue中,对应上图中的queueBuffer方法
在新的GraphicBuffer入队BufferQueue时,BufferQueue会通过回调通知图形数据的消费者,有新的图形数据被生产出来了
然后消费者从BufferQueue中出队一个GraphicBuffer,对应图中的acquireBuffer方法
待消费者消费完图形数据后,将空的GraphicBuffer还给BufferQueue以便重复利用,此时对应上图中的releaseBuffer方法
此时BufferQueue再通过回调通知图形数据的生产者有空的GraphicBuffer了,图形数据的生产者又可以从BufferQueue中获取一个空的GraphicBuffer来填充数据
一直循环2-8步骤,这样就有条不紊的完成了图形数据的生产-消费
当然图形数据的生产者可以不用等待BufferQueue的回调再生产数据,而是一直生产数据然后入队到BufferQueue,直到BufferQueue满为止。图形数据的消费者也可以不用等BufferQueue的回调通知,每次都从BufferQueue中尝试获取数据,获取失败则尝试,只是这样效率比较低,需要不断的轮训BufferQueue(因为BufferQueue有同步阻塞和非同步阻塞两种机种,在非同步阻塞机制下获取数据失败不会阻塞该线程直到有数据才唤醒该线程,而是直接返回-1)。

同时使用BufferQueue的生产者和消费者往往处在不同的进程,BufferQueue内部使用共享内存和Binder在不同的进程传递数据,减少数据拷贝提高效率。

和BufferQueue有关的几个类分别是:

BufferBufferCore:BufferQueue的实际实现
BufferSlot:用来存储GraphicBuffer
BufferState:表示GraphicBuffer的状态
IGraphicBufferProducer:BufferQueue的生产者接口,实现类是BufferQueueProducer
IGraphicBufferConsumer:BufferQueue的消费者接口,实现类是BufferQueueConsumer
GraphicBuffer:表示一个Buffer,可以填充图像数据
ANativeWindow_Buffer:GraphicBuffer的父类
ConsumerBase:实现了ConsumerListener接口,在数据入队列时会被调用到,用来通知消费者
BufferQueue中用BufferSlot来存储GraphicBuffer,使用数组来存储一系列BufferSlot,数组默认大小为64。

GraphicBuffer用BufferState来表示其状态,有以下状态:

FREE:表示该Buffer没有被生产者-消费者所使用,该Buffer的所有权属于BufferQueue
DEQUEUED:表示该Buffer被生产者获取了,该Buffer的所有权属于生产者
QUEUED:表示该Buffer被生产者填充了数据,并且入队到BufferQueue了,该Buffer的所有权属于BufferQueue
ACQUIRED:表示该Buffer被消费者获取了,该Buffer的所有权属于消费者
为什么需要这些状态呢? 假设不需要这些状态,实现一个简单的BufferQueue,假设是如下实现:

BufferQueue{    vector<GraphicBuffer> slots;    void push(GraphicBuffer slot){        slots.push(slot);    }
    GraphicBuffer pull(){        return slots.pull();    }}

生产者生产完数据后,通过调用BufferQueue的push函数将数据插入到vector中。消费者调用BufferQueue的pull函数出队一个Buffer数据。

上述实现的问题在于,生产者每次都需要自行创建GraphicBuffer,而消费者每次消费完数据后的GraphicBuffer就被释放了,GraphicBuffer没有得到循环利用。而在Android中,由于BufferQueue的生产者-消费者往往处于不同的进程,GraphicBuffer内部是需要通过共享内存来连接生成者-消费者进程的,每次创建GraphicBuffer,即意味着需要创建共享内存,效率较低。

而BufferQueue中用BufferState来表示GraphicBuffer的状态则解决了这个问题。每个GraphicBuffer都有当前的状态,通过维护GraphicBuffer的状态,完成GraphicBuffer的复用。

由于BufferQueue内部实现是BufferQueueCore,下文均用BufferQueueCore代替BufferQueue。先介绍下BufferQueueCore内部相应的数据结构,再介绍BufferQueue的状态扭转过程和生产-消费过程。

以下是Buffer的入队/出队操作和BufferState的状态扭转的过程,这里只介绍非同步阻塞模式。

2.1 BufferQueueCore内部数据结构

核心数据结构如下:

BufferQueueDefs::SlotsType mSlots:用数组存放的Slot,数组默认大小为BufferQueueDefs::NUM_BUFFER_SLOTS,具体是64,代表所有的Slotstd::set<int> mFreeSlots:当前所有的状态为FREE的Slot,这些Slot没有关联上具体的GraphicBuffer,后续用的时候还需要关联上GraphicBufferstd::list<int> mFreeBuffers:当前所有的状态为FREE的Slot,这些Slot已经关联上具体的GraphicBuffer,可以直接使用Fifo mQueue:一个先进先出队列,保存了生产者生产的数据
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值