【移动应用开发技术】BufferQueue的设计思想和内部实现方法是什么_第1页
【移动应用开发技术】BufferQueue的设计思想和内部实现方法是什么_第2页
【移动应用开发技术】BufferQueue的设计思想和内部实现方法是什么_第3页
【移动应用开发技术】BufferQueue的设计思想和内部实现方法是什么_第4页
【移动应用开发技术】BufferQueue的设计思想和内部实现方法是什么_第5页
已阅读5页,还剩5页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

【移动应用开发技术】BufferQueue的设计思想和内部实现方法是什么

这篇文章主要介绍“BufferQueue的设计思想和内部实现方法是什么”的相关知识,在下通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“BufferQueue的设计思想和内部实现方法是什么”文章能帮助大家解决问题。1.背景对业务开发来说,无法接触到BufferQueue,甚至不知道BufferQueue是什么东西。对系统来说,BufferQueue是很重要的传递数据的组件,Android显示系统依赖于BufferQueue,只要显示内容到“屏幕”(此处指抽象的屏幕,有时候还可以包含编码器),就一定需要用到BufferQueue,可以说在显示/播放器相关的领域中,BufferQueue无处不在。即使直接调用Opengl

ES来绘制,底层依然需要BufferQueue才能显示到屏幕上。弄明白BufferQueue,不仅可以增强对Android系统的了解,还可以弄明白/排查相关的问题,如为什么Mediacodec调用dequeueBuffer老是返回-1?为什么普通View的draw方法直接绘制内容即可,SurfaceView在draw完毕后还需要unlockCanvasAndPost?注:本文分析的代码来自于Android6.0.1。2.BufferQueue内部运作方式BufferQueue是Android显示系统的核心,它的设计哲学是生产者-消费者模型,只要往BufferQueue中填充数据,则认为是生产者,只要从BufferQueue中获取数据,则认为是消费者。有时候同一个类,在不同的场景下既可能是生产者也有可能是消费者。如SurfaceFlinger,在合成并显示UI内容时,UI元素作为生产者生产内容,SurfaceFlinger作为消费者消费这些内容。而在截屏时,SurfaceFlinger又作为生产者将当前合成显示的UI内容填充到另一个BufferQueue,截屏应用此时作为消费者从BufferQueue中获取数据并生产截图。以下是常见的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:用来存储GraphicBufferBufferState:表示GraphicBuffer的状态IGraphicBufferProducer:BufferQueue的生产者接口,实现类是BufferQueueProducerIGraphicBufferConsumer:BufferQueue的消费者接口,实现类是BufferQueueConsumerGraphicBuffer:表示一个Buffer,可以填充图像数据ANativeWindow_Buffer:GraphicBuffer的父类ConsumerBase:实现了ConsumerListener接口,在数据入队列时会被调用到,用来通知消费者BufferQueue中用BufferSlot来存储GraphicBuffer,使用数组来存储一系列BufferSlot,数组默认大小为64。GraphicBuffer用BufferState来表示其状态,有以下状态:FREE:表示该Buffer没有被生产者-消费者所使用,该Buffer的所有权属于BufferQueueDEQUEUED:表示该Buffer被生产者获取了,该Buffer的所有权属于生产者QUEUED:表示该Buffer被生产者填充了数据,并且入队到BufferQueue了,该Buffer的所有权属于BufferQueueACQUIRED:表示该Buffer被消费者获取了,该Buffer的所有权属于消费者为什么需要这些状态呢?

假设不需要这些状态,实现一个简单的BufferQueue,假设是如下实现:BufferQueue{

vector<GraphicBuffer>slots;

voidpush(GraphicBufferslot){

slots.push(slot);

}

GraphicBufferpull(){

returnslots.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.1BufferQueueCore内部数据结构核心数据结构如下: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:一个先进先出队列,保存了生产者生产的数据在BufferQueueCore初始化时,由于此时队列中没有入队任何数据,按照上面的介绍,此时mFreeSlots应该包含所有的Slot,元素大小和mSlots一致,初始化代码如下:for

(int

slot

=

0;

slot

<

BufferQueueDefs::NUM_BUFFER_SLOTS;

++slot)

{

mFreeSlots.insert(slot);

}当生产者可以生产图形数据时,首先向BufferQueue中申请一块GraphicBuffer。调用函数BufferQueueProducer.dequeueBuffer,如果当前BufferQueue中有可用的GraphicBuffer,则返回其对用的索引;如果不存在,则返回-1,代码在BufferQueueProducer,流程如下:status_t

BufferQueueProducer::dequeueBuffer(int

*outSlot,

sp<android::Fence>

*outFence,

bool

async,

uint32_t

width,

uint32_t

height,

PixelFormat

format,

uint32_t

usage)

{

//1.

寻找可用的Slot,可用指Buffer状态为FREE

status_t

status

=

waitForFreeSlotThenRelock("dequeueBuffer",

async,

&found,

&returnFlags);

if

(status

!=

NO_ERROR)

{

return

status;

}

//2.找到可用的Slot,将Buffer状态设置为DEQUEUED,由于步骤1找到的Slot状态为FREE,因此这一步完成了FREE到DEQUEUED的状态切换

*outSlot

=

found;

ATRACE_BUFFER_INDEX(found);

attachedByConsumer

=

mSlots[found].mAttachedByConsumer;

mSlots[found].mBufferState

=

BufferSlot::DEQUEUED;

//3.

找到的Slot如果需要申请GraphicBuffer,则申请GraphicBuffer,这里采用了懒加载机制,如果内存没有申请,申请内存放在生产者来处理

if

(returnFlags

&

BUFFER_NEEDS_REALLOCATION)

{

status_t

error;

sp<GraphicBuffer>

graphicBuffer(mCore->mAllocator->createGraphicBuffer(width,

height,

format,

usage,

&error));

graphicBuffer->setGenerationNumber(mCore->mGenerationNumber);

mSlots[*outSlot].mGraphicBuffer

=

graphicBuffer;

}}关键在于寻找可用Slot,waitForFreeSlotThenRelock的流程如下:status_t

BufferQueueProducer::waitForFreeSlotThenRelock(const

char*

caller,

bool

async,

int*

found,

status_t*

returnFlags)

const

{

//1.

mQueue

是否太多

bool

tooManyBuffers

=

mCore->mQueue.size()>

static_cast<size_t>(maxBufferCount);

if

(tooManyBuffers)

{

}

else

{

//

2.

先查找mFreeBuffers中是否有可用的,由2.1介绍可知,mFreeBuffers中的元素关联了GraphicBuffer,直接可用

if

(!mCore->mFreeBuffers.empty())

{

auto

slot

=

mCore->mFreeBuffers.begin();

*found

=

*slot;

mCore->mFreeBuffers.erase(slot);

}

else

if

(mCore->mAllowAllocation

&&

!mCore->mFreeSlots.empty())

{

//

3.

再查找mFreeSlots中是否有可用的,由2.1可知,初始化时会填充满这个列表,因此第一次调用一定不会为空。同时用这个列表中的元素需要关联上GraphicBuffer才可以直接使用,关联的过程由外层函数来实现

auto

slot

=

mCore->mFreeSlots.begin();

//

Only

return

free

slots

up

to

the

max

buffer

count

if

(*slot

<

maxBufferCount)

{

*found

=

*slot;

mCore->mFreeSlots.erase(slot);

}

}

}

tryAgain

=

(*found

==

BufferQueueCore::INVALID_BUFFER_SLOT)

||

tooManyBuffers;

//4.

如果找不到可用的Slot或者Buffer太多(同步阻塞模式下),则可能需要等

if

(tryAgain)

{

if

(mCore->mDequeueBufferCannotBlock

&&

(acquiredCount

<=

mCore->mMaxAcquiredBufferCount))

{

return

WOULD_BLOCK;

}

mCore->mDequeueCondition.wait(mCore->mMutex);

}}waitForFreeSlotThenRelock函数会尝试寻找一个可用的Slot,可用的Slot状态一定是FREE(因为是从两个FREE状态的列表中获取的),然后dequeueBuffer将状态改变为DEQUEUED,即完成了状态的扭转。waitForFreeSlotThenRelock返回可用的Slot分为两种:从mFreeBuffers中获取到的,mFreeBuffers中的元素关联了GraphicBuffer,直接可用从mFreeSlots中获取到的,没有关联上GraphicBuffer,因此需要申请GraphicBuffer并和Slot关联上,通过createGraphicBuffer申请一个GraphicBuffer,然后赋值给Slot的mGraphicBuffer完成关联小结dequeueBuffer:尝试找到一个Slot,并完成Slot与GraphicBuffer的关联(如果需要),然后将Slot的状态由FREE扭转成DEQUEUED,返回Slot在BufferQueueCore中mSlots对应的索引。dequeueBuffer函数获取到了可用Slot的索引后,通过requestBuffer获取到对应的GraphicBuffer。流程如下:status_t

BufferQueueProducer::requestBuffer(int

slot,

sp<GraphicBuffer>*

buf)

{

//

1.

判断slot参数是否合法

if

(slot

<

0

||

slot

>=

BufferQueueDefs::NUM_BUFFER_SLOTS)

{

BQ_LOGE("requestBuffer:

slot

index

%d

out

of

range

[0,

%d)",

slot,

BufferQueueDefs::NUM_BUFFER_SLOTS);

return

BAD_VALUE;

}

else

if

(mSlots[slot].mBufferState

!=

BufferSlot::DEQUEUED)

{

BQ_LOGE("requestBuffer:

slot

%d

is

not

owned

by

the

producer

"

"(state

=

%d)",

slot,

mSlots[slot].mBufferState);

return

BAD_VALUE;

}

//2.

将mRequestBufferCalled置为true

mSlots[slot].mRequestBufferCalled

=

true;

*buf

=

mSlots[slot].mGraphicBuffer;

return

NO_ERROR;}这一步不是必须的,业务层可以直接通过Slot的索引获取到对应的GraphicBuffer。上文dequeueBuffer获取到一个Slot后,就可以在Slot对应的GraphicBuffer上完成图像数据的生产了,可以是View的主线程Draw过程,也可以是SurfaceView的子线程绘制过程,甚至可以是MediaCodec的解码过程。填充完图像数据后,需要将Slot入队BufferQueueCore(数据写完了,可以传给生产者-消费者队列,让消费者来消费了),入队调用queueBuffer函数。queueBuffer的流程如下:status_t

BufferQueueProducer::queueBuffer(int

slot,

const

QueueBufferInput

&input,

QueueBufferOutput

*output)

{

//

1.

先判断传入的Slot是否合法

if

(slot

<

0

||

slot

>=

maxBufferCount)

{

BQ_LOGE("queueBuffer:

slot

index

%d

out

of

range

[0,

%d)",

slot,

maxBufferCount);

return

BAD_VALUE;

}

//2.

将Buffer状态扭转成QUEUED,此步完成了Buffer的状态由DEQUEUED到QUEUED的过程

mSlots[slot].mFence

=

fence;

mSlots[slot].mBufferState

=

BufferSlot::QUEUED;

++mCore->mFrameCounter;

mSlots[slot].mFrameNumber

=

mCore->mFrameCounter;

//3.

入队mQueue

if

(mCore->mQueue.empty())

{

mCore->mQueue.push_back(item);

frameAvailableListener

=

mCore->mConsumerListener;

}

//

4.

回调frameAvailableListener,告知消费者有数据入队了

if

(frameAvailableListener

!=

NULL)

{

frameAvailableListener->onFrameAvailable(item);

}

else

if

(frameReplacedListener

!=

NULL)

{

frameReplacedListener->onFrameReplaced(item);

}}从上面的注释可以看到,queueBuffer的主要步骤如下:将Buffer状态扭转成QUEUED,此步完成了Buffer的状态由DEQUEUED到QUEUED的过程将Buffer入队到BufferQueueCore的mQueue队列中回调frameAvailableListener,告知消费者有数据入队,可以来消费数据了,frameAvailableListener是消费者注册的回调小结queueBuffer:将Slot的状态扭转成QUEUED,并添加到mQueue中,最后通知消费者有数据入队。在消费者接收到onFrameAvailable回调时或者消费者主动想要消费数据,调用acquireBuffer尝试向BufferQueueCore获取一个数据以供消费。消费者的代码在BufferQueueConsumer中,acquireBuffer流程如下:status_t

BufferQueueConsumer::acquireBuffer(BufferItem*

outBuffer,

nsecs_t

expectedPresent,

uint64_t

maxFrameNumber)

{

//1.

如果队列为空,则直接返回

if

(mCore->mQueue.empty())

{

return

NO_BUFFER_AVAILABLE;

}

//2.

取出mQueue队列的第一个元素,并从队列中移除

BufferQueueCore::Fifo::iterator

front(mCore->mQueue.begin());

int

slot

=

front->mSlot;

*outBuffer

=

*front;

mCore->mQueue.erase(front);

//3.

处理expectedPresent的情况,这种情况可能会连续丢几个Slot的“显示”时间小于expectedPresent的情况,这种情况下这些Slot已经是“过时”的,直接走下文的releaseBuffer消费流程,代码比较长,忽略了

//4.

更新Slot的状态为ACQUIRED

if

(mCore->stillTracking(front))

{

mSlots[slot].mAcquireCalled

=

true;

mSlots[slot].mNeedsCleanupOnRelease

=

false;

mSlots[slot].mBufferState

=

BufferSlot::ACQUIRED;

mSlots[slot].mFence

=

Fence::NO_FENCE;

}

//5.

如果步骤3有直接releaseBuffer的过程,则回调生产者,有数据被消费了

if

(listener

!=

NULL)

{

for

(int

i

=

0;

i

<

numDroppedBuffers;

++i)

{

listener->onBufferReleased();

}

}}从上面的注释可以看到,acquireBuffer的主要步骤如下:从mQueue队列中取出并移除一个元素改变Slot对应的状态为ACQUIRED如果有丢帧逻辑,回调告知生产者有数据被消费,生产者可以准备生产数据了小结acquireBuffer:将Slot的状态扭转成ACQUIRED,并从mQueue中移除,最后通知生产者有数据出队。消费者获取到Slot后开始消费数据(典型的消费如SurfaceFlinger的UI合成),消费完毕后,需要告知BufferQueueCore这个Slot被消费者消费完毕了,可以给生产者重新生产数据,releaseBuffer流程如下:status_t

BufferQueueConsumer::releaseBuffer(int

slot,

uint64_t

frameNumber,

const

sp<Fence>&

releaseFence,

EGLDisplay

eglDisplay,EGLSyncKHR

eglFence)

{

//1.

检查Slot是否合法

if

(slot

<

0

||

slot

>=

BufferQueueDefs::NUM_BUFFER_SLOTS

||

return

BAD_VALUE;

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论