BufferQueue详解 原理
一、BufferQueue 简介
在工作中,我们经常会和Surface,ImageReader,ImageWriter BufferQueue打交道,它们之间是什么关系呢。
实际上Surface,ImageReader,ImageWriter是对BufferQueue的生产者消费者的封装,其核心实现还是BufferQueue。
而BufferQueue是一个生产者消费者模型又是GraphicBuffer管理者,它和显示系统以及Camera流媒体紧密关系着。
所以要了解显示系统,绕不开BufferQueue,要了解BufferQueue 就要先了解生产者消费者模型。
下面就从生产者消费者模型开始,深入浅出的讲解一下BufferQueue。

二、生产者消费者模型
1.生产者消费者模型基本原理
在生产者消费者模型中,存在生产者和消费者两种角色,它们通过内存缓冲区进行通信,生产者生产消费者需要的数据,消费者获取生产者生产的数据进行消费。
如果没有生产者消费者模型,那么生产者和消费者是直接调用关系,生产速度的和消费速度相互拖累,必须在消费完成之后才能进行下一次生产,如下图:

有了生产者和消费者模型之后,生产者生产的数据存到缓冲队列,消费者从队列中取数据去消费,互不影响,互不拖累,耦合性低。如下图

2.生产者消费者模型的组成
总结起来生产者消费者模型由一个场所,和两个角色组成:
. 一个场所:数据缓冲区,实现内存共享和轮转。
. 两个角色: 生产者(生产线程)生产数据,消费者(消费线程)消费数据。也可以是进程间使用。
生产者和消费着之间又需要遵循三种关系和四个基本的原则:
. 三个关系:生产者和生产者互斥关系,消费者和消费者互斥关系,生产者和消费者互斥同步关系。
. 四个原则:生产者生产的时候消费者不能消费,消费者消费的时候生产者不能生产,缓冲区空时消费者不能消费,缓冲区满时生产者不能生产。

以上就可以很清晰,简单明了的解释了生产者消费者模型。那么BufferQueue是什么,怎么应用生产者-消费者模型思想的,下面详细说明。

三、BufferQueue基本原理
1.BufferQueue的生产消费框架
BufferQueue的核心逻辑是生产者消费者逻辑,在BufferQueue这个生产者消费者框架中, BufferQueuecore可以理解为数据缓冲区的管理者,代码逻辑在BufferQueue.cpp和BufferQueuecore.cpp中。
它的原始消费者是BufferQueueConsumer,它的原始生产者是BufferQueueProducer。
那么BufferQueue.cpp 和BufferQueuecore.cpp是什么样的关系呢,可以理解为BufferQueue 是创建者,BufferQueuecore是由BufferQueue调用createBufferQueue创建的。
总的来讲 BufferQueue.cpp 主要定义了createBufferQueue接口和ProxyConsumerListener的onFrameAvailable等通知接口。
使用的时候,封装的消费者调用createBufferQueue创建BufferQueuecore,然后根据创建好的BufferQueuecore,去创建原始生产者消费者BufferQueueConsumer和BufferQueueProducer。
而消费者BufferQueueConsumer在调用connect的时候把ConsumerListener相关的回调接口注册进BufferQueue供ProxyConsumerListener回调使用。
生产者BufferQueueProducer也有一个connect接口,生产者的connect接口会注册IProducerListener到BufferQueuecore中,在消费者使用完GraphicBuffer释放的时候通过这个Listener通知生产者。
同时这个IProducerListener会注册Binder死亡通知函数,在死亡的时候回调BufferQueueProducer的binderDied,取消链接。
至此由BufferQueue BufferQueuecore,BufferQueueConsumer, BufferQueueProducer,组成的核心的生产者消费者模型就建立起来了。

这里说的是封装的消费者去创建BufferQueuecore有两点需要解释一下:
一个是消费者的封装关系,这个后面会详细说明。
另外一个就是为什么是消费者创建BufferQueuecore 而不是生产者去创建呢。
其实也是可以放到生产者中创建的,但是主要由于两点原因,正常会放在消费者中去创建:
1.出于消费者准备好消费了在去生产的思想考虑。
2.以消费者端作为核心端去管理,在消费者端创建方便统一管理。

2.BufferQueueCore概述
从BufferQueue的生产消费框架建立过程看,BufferQueueCore是核心缓冲区管理者,是由BufferQueue创建的,那么BufferQueueCore具体管理哪些事呢。
代码上看主要包含了下面这些关键参数。
可以大概划分一下:
slots相关,用于关联数据核心GraphicBuffer。(包括mSlots mQueue mFreeSlots mfreeBuffers mUnusedSlots,mActiveBuffers ),
listener相关,用于通知和回调(包括mConsumerListener mLinkedToDeath mConnectedProducerListener),
Buffercount相关,用于定BufferQuue中的Buffer数量。(包括mMaxBufferCount mMaxAcquiredBufferCount mMaxDequeuedBufferCount),
和一些设置项(包括 mConsumerName mDefaultWidth mDefaultHeight mDefaultBufferFormat mDefaultBufferDataSpace)
设置相关主要是name 宽高信息,format信息,dataspace信息等,

3.BufferSlot和BufferItem详解
BufferQueueCore中管理着数据缓冲区,而数据的核心GraphicBuffer关联在BufferSlot中。
从源码中分析BufferSlot 和 GraphicBuffer的关联关系:
BufferQueueCore 定义了 BufferQueueDefs::SlotsType mSlots;
SlotsType的定义如下(代码路径:BufferQueueDefs.h)
namespace BufferQueuDefs {
typedef BufferSlot SlotsType[NUM_BUFFER_SLOTS];
}
由此可以看出mSlots实际上是一个BufferSolt的数组。
BufferSolt中定义了一个GraphicBuffer的強指针用于关联mGraphicBuffer。
struct BufferSlot {
sp mGraphicBuffer;

}

这样BufferSlot 和 GraphicBuffer 就关联上了。BufferQueue框架中,消费者和生产者对缓冲区数据操作的单元核心就是一个BufferSlot,也就是说所有取GraphicBuffer,放GraphicBuffer的操作都是针对BufferSlot来完成的。
具体的BufferSlot是怎么取和放的呢。可以看到BufferQueueCore 中还定义了 Fifo mQueue;字面上看,定义了一个先进先出的对列,那么这个队列里存放的是什么呢。
可以看到这样的定义typedef Vector Fifo; 也就是说Fifo是的向量集,里面存的是BufferItem。而 BufferItem中又定义了mslots的索引值Class BufferItem { int mSlot; … }
这样就可以和BufferSlot关联上了。
总结一下可以简单理解成生产者从mQueue上获取BufferItem从而找到了对应的BufferSlot,并对它完成一系列的操作之后,放回到mQueue中供消费者使用,消费者也是从mQueue上获取BufferItem从而找到对应的BufferSlot来消费,消费完成之后放回mQueue。不过需要注意实际上不是真正的把BufferSlot取出放回mQueue,而是mSlots索引值的传递过程。

BufferQueueCore 中定义的mSlots是BufferSlot数组,默认数组长度为NUM_BUFFER_SLOTS =64(在/frameworks/native/libs//ui/include/ui/BufferQueueDefs.h中定义)。
但是实际使用的时候64个Slots不一定全用,这里就引入了一个mUnusedSlots,mUnusedSlots只的是不被使用的BufferSlot集合,那么可用的BufferSlot 就是NUM_BUFFER_SLOTS - num(mUnusedSlots) 这么多个。
在可用的这些BufferSlot中,又会根据BufferSlot当前的状态即BufferState做区分。分为FREE状态下的BufferSlot和非FREE状态下的BufferSlot,非FREE状态下的BufferSlot集合在mActiveBuffers中。
FREE状态下的BufferSlot又会根据有没有和GraphicBuffer关联做区分。没有GraphicBuffer与之相关联的BufferSlot集合在mFreeSlots中,有GraphicBuffer与之关联的BufferSlot 集合在mFreeBuffers中。

在BufferQueueProducer和BufferQueueConsumer的生产消费的过程中,mslots中的BufferSlot会动态的在 mFreeSlots mFreeBuffers mActiveBuffers mUnusedSlots之间进行流转。

需要注意的是mSlots 是个数据结构数组,但是mFreeSlots mFreeBuffers mActiveBuffers mUnusedSlots 都不是结构数组,都是mSlots的 index值的集合(也就是mSlots的下标的集合)。

刚刚提到的BufferSlot会根据当前的状态即BufferState做区分为FREE状态的slot和非Free状态的slot。这个BufferState 就是BufferSlot流转过程中的状态。主要包含FREE DEQUEUED QUEUED ACQUIRED 四种状态。

FREE:FREE状态下的BufferSlot指的是可以被生产者dequeue或attach出来使用的BufferSlot,以及消费者消费完毕release或者detach的BufferSlot。
DEQUEUED:DEQUEUED状态的BufferSlot 是已经被生产者dequeue或attach出来供生产使用的BufferSlot。
QUEUED:QUEUED状态的BufferSlot 是生产者生产完成放回队列供消费者使用的BufferSlot。
ACQUIRED :消费者从 BufferQueue获取准备消费的BufferSlot。

BufferState 用isFree isDequeued isQueued isAcquired 接口来判断状态,用attachProducer,detachProducer,dequeue,queue,cancel,freeQueued,acquire,release等接口来完成状态转换。

可以用下图形象的描述一下mFreeSlots mFreeBuffers mActiveBuffers mUnusedSlots 之间的关系。其中 TotalSlots 最大支持NUM_BUFFER_SLOTS = 64个,调用getMaxBufferCountLocked 获取的是可用的BufferSlot数量后面会详细介绍。

4.BufferCount详解
BufferSlot中介绍中讲到getMaxBufferCountLocked,可以获取可用的BufferSlot数量maxbuffercount,那么这个可用的数量是哪里来的呢。

消费者BufferQueueConsumer 提供了setMaxBufferCount接口来设置可用的BufferSlot数量maxbuffercount,默认是数量是NUM_BUFFER_SLOTS=64。

同时消费者BufferQueueConsumer还提供了setMaxAcquiredBufferCount 接口来设置mMaxAcquiredBufferCount,

mMaxAcquiredBufferCount 是指一次 同时能被消费者使用的最大BufferCount。

另外,生产者还提供了一个setMaxDequeuedBufferCount接口来设置mMaxDequeuedBufferCount,

mMaxDequeuedBufferCount是指一次同时能被生产者用来生产的最大BufferCount

设定完成之后BufferQueueCore 中就会有 maxBufferCount个mlots, NUM_BUFFER_SLOTS- maxBufferCount个mUnusedSlots

maxBufferCount,mMaxDequeuedBufferCount ,mMaxAcquiredBufferCount三者之间的关系如下:maxbuffercount = mMaxDequeuedBufferCount + mMaxAcquiredBufferCount

四、BufferQueue中的生产者和消费者详解
1.BufferQueue中的生产者和消费者概述
上面讲了BufferQueue 创建了BufferQueueCore,然后根据BufferQueueCore创建了消费者BufferQueueConsumer和生产者BufferQueueProducer,并详细介绍了数据缓存管理区BufferQueueCore,下面重点介绍一下消费者BufferQueueConsumer和生产者BufferQueueProducer。先介绍一下BufferQueueConsumer和BufferQueueProducer的继承关系和关键方法,然后针对关键函数展开介绍一下BufferQueueConsumer和BufferQueueProducer的工作原理,和流转过程。

BufferQueueConsumer 和 BufferQueueProducer 分别对应文件BufferQueueConsumer.cpp 和 BufferQueueProducer.cpp 和 BufferQueueCore.cpp 在同级目录都在/frameworks/native/libs/gui目录下。

下面是这两个类的继承关系和重要函数:
生产者继承关系:
(BufferQueueProducer.cpp) BufferQueueProducer:BnGraphicBufferProducer
:private IBinder::DeathRecipient
BnGraphicBufferProducer: IGraphicBufferProducer
可以看到BufferQueueProducer继承了BnGraphicBufferProducer而BnGraphicBufferProducer又继承了IGraphicBufferProducer,来完成主要的BufferSlot的流转操作,也提供了远程代理接口,实现跨进程binder调用。
同时BufferQueueProducer还继承了DeathRecipient 用来处理Binder死亡通知。

生产者关键方法:
requestBuffer 获取对应BufferSlot的GraphicBuffer地址。
setMaxDequeuedBufferCount 设置最大同时可以dequeue出来的的BufferSlot数量。
dequeueBuffer 从FREE状态下的BufferSlots中队列中获取空闲的BufferSlot做生产使用,优先从mFreeBuffers中获取,如果没有则从mFreeSlots中获取。
attachBuffer 绑定已经分配好的GraphicBuffer到FREE状态下的BufferSlot中,优先从mFreeSlots中查找BufferSlot,如果没有则从mFreeBuffers中查找并绑定。
queueBuffer 生产者把生产好的BufferSlot放到队列中供消费者使用。
detachBuffer 把attachBuffer了GraphicBuffer的 Active状态下的BufferSlot的放到mFreeBuffers之后直接取消GraphicBuffer的绑定。
detachNextBuffer 把需要释放的BufferSlot中的GraphicBuffer指针赋值到outBuffer输出之后,把BufferSlot的放到mFreeBuffers并解绑定GraphicBuffer。
cancelBuffer 把BufferSlot放回到mfreeBuffers中,不会释放graphicbuffer。
connect 生产者通过该接口把IProducerListener注册到BufferQueueCore中供消费者回调,同时建立了Binder死亡通知通路。
disconnect断开BufferQueueProducer和BufferQueueCore之间建立的链接关系。

消费者继承关系:
(BufferQueueConsumer.cpp) BufferQueueConsumer:BnGraphicBufferConsumer
BnGraphicBufferConsumer:IGraphicBufferConsumer
可以看到BufferQueueConsumer的实现结构和BufferQueueProducer很相似继承了BnGraphicBufferConsumer 而BnGraphicBufferConsumer又最终继承了IGraphicBufferConsumer,来完成主要的BufferSlot的流转操作,也提供了远程代理接口,实现跨进程binder调用。

消费者关键方法:
acquireBuffer 获取QUEUE状态下的BufferSlot进行消费。
releaseBuffer消费完成之后把BufferSlot放回mFreeBuffers队列中。
attachBuffer 把消费者的GraphicBuffer绑定到BufferSlot上使用。
detachBuffer把消费者的GraphicBuffer从BufferSlot上解绑。
setMaxBufferCount 设置最大可用BufferSlot数量。
setMaxAcquiredBufferCount 设置最大同时可以acquire的BufferSlot数量。
connect 建立消费者和BufferQueueCore之间的链接,注册IConsumerListener回调。
disconnect 销毁消费者和BufferQueue之间的链接。

2.BufferQueueConsumer关键流程分析
(1). acquirebuffer的流程
主要流程是首先判断AcquireBuffers是否已经超过最大一次能够Acquire的数量,mQueue是否为空,然后从mQueue中获取第一个迭代器赋值给outBuffer输出,同时把mBufferState状态改为Acquired 并从mQueue中移除

status_t BufferQueueConsumer::acquireBuffer(BufferItem* outBuffer,
nsecs_t expectedPresent, uint64_t maxFrameNumber) {
...
{


//判断AcquireBuffers是否已经超过最大一次能够Acquire的数量。
if (numAcquiredBuffers >= mCore->mMaxAcquiredBufferCount + 1) {
return INVALID_OPERATION;
}


//判断mQueue队列是否为空
if (mCore->mQueue.empty() && !sharedBufferAvailable) {
return NO_BUFFER_AVAILABLE;
}

//从mQueue的第一个迭代器付给front
BufferQueueCore::Fifo::iterator front(mCore->mQueue.begin());


if (sharedBufferAvailable && mCore->mQueue.empty()) {//共享buffer的处理逻辑
...
} else {//正常非共享模式下的逻辑
slot = front->mSlot; //从front获取对应的slot,front是一个BufferItem指针
*outBuffer = *front; //把front指向的BufferItem赋值给outBuffer
}

if (!outBuffer->mIsStale) {
mSlots[slot].mAcquireCalled = true;
if (mCore->mQueue.empty()) {
mSlots[slot].mBufferState.acquireNotInQueue();
} else {
mSlots[slot].mBufferState.acquire(); //把BufferState修改成acquired状态
}
}

mCore->mQueue.erase(front); //把acquired slot 对应的BufferItem从mQueue中移除。

mCore->mDequeueCondition.notify_all();
}

return NO_ERROR;
}

(2). releasebuffer的流程
releasebuffer主要流程是先做slot frameNumber 以及 BufferState有效性检查,修改mBufferState状态成FREE状态。然后把对应的slot从mActiveBuffers中移除并放回mFreeBuffers的过程。
这个过程中不做GraphicBuffer和BufferSlot的解绑定操作,也就是说GraphicBuffer不会被释放。详细流程如下面的代码。

status_t BufferQueueConsumer::releaseBuffer(int slot, uint64_t frameNumber,
const sp<Fence>& releaseFence, EGLDisplay eglDisplay,
EGLSyncKHR eglFence) {

//slot的合法性判断。
if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS ||
releaseFence == nullptr) {
BQ_LOGE("releaseBuffer: slot %d out of range or fence %p NULL", slot,
releaseFence.get());
return BAD_VALUE;
}

sp<IProducerListener> listener;
{
//判断frameNumber是否有效,如果做过reallocated frameNumber将会改变。
if (frameNumber != mSlots[slot].mFrameNumber &&
!mSlots[slot].mBufferState.isShared()) {
return STALE_BUFFER_SLOT;
}

//判断BufferState是否是Acquired状态
if (!mSlots[slot].mBufferState.isAcquired()) {
return BAD_VALUE;
}

mSlots[slot].mBufferState.release(); //调用mBufferState.release()将acqiure状态释放。

if (!mSlots[slot].mBufferState.isShared()) {
mCore->mActiveBuffers.erase(slot); //将slot从mActiveBuffers中移除
mCore->mFreeBuffers.push_back(slot); //将slot放入到mFreeBuffers队列中
}

listener = mCore->mConnectedProducerListener; //获取ProducerListener

mCore->mDequeueCondition.notify_all();

}

if (listener != nullptr) {
listener->onBufferReleased(); //调用Producer的onBufferReleased回调通知Producer完成释放。
}

return NO_ERROR;
}

3.BufferQueueProducer关键流程分析
(1). dequeuebuffer的流程
dequeuebuffer是生产者端从BufferQueueCore上获取一个GraphicBuffer进行生产的过程,生产者BufferQueueProducer 会在去获取一个FREE状态的的BufferSlot。
同时把mBufferState状态修改成Dequeue状态,把BufferSlot放到mActiveBuffers中管理。也由此可见GraphicBuffer是在这里实际创建的。
下面是dequeueBuffer的具体流程:

status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp<android::Fence>* outFence,
uint32_t width, uint32_t height, PixelFormat format,
uint64_t usage, uint64_t* outBufferAge,
FrameEventHistoryDelta* outTimestamps) {

int found = BufferItem::INVALID_BUFFER_SLOT;
//调用waitForFreeSlotThenRelock 找到可以dequeue的 FREE状态下的BufferSlot
while (found == BufferItem::INVALID_BUFFER_SLOT) {
status_t status = waitForFreeSlotThenRelock(FreeSlotCaller::Dequeue, lock, &found);
}

const sp<GraphicBuffer>& buffer(mSlots[found].mGraphicBuffer); //把获取到的mGraphicBuffer地址赋值给buffer。

if (mCore->mSharedBufferSlot != found) {
mCore->mActiveBuffers.insert(found); //把找到的slot放到mActiveBuffers中管理
}
*outSlot = found; //赋值给outSlot输出

mSlots[found].mBufferState.dequeue(); //修改BufferState 状态成dequeue状态。

}

return returnFlags;
}
FREE状态的BufferSlot又包含了mFreeSlots和mFreebuffers两组slots,dequeue的时候会先从mFreebuffers查找如果有可用的就使用,如果没有就从mFreeSlots获取BufferSlot并分配GraphicBuffer。
这个过程在waitForFreeSlotThenRelock中实现
下面是waitForFreeSlotThenRelock的流程:

status_t BufferQueueProducer::waitForFreeSlotThenRelock(FreeSlotCaller caller,
std::unique_lock<std::mutex>& lock, int* found) const {
auto callerString = (caller == FreeSlotCaller::Dequeue) ?
"dequeueBuffer" : "attachBuffer";
bool tryAgain = true;
while (tryAgain) {

if (){
} else {
if (caller == FreeSlotCaller::Dequeue) { //Dequeuebuffer调用这段代码,先调用getFreeBufferLocked从mFreeBuffers中获取,如果找到了就返回。
// If we're calling this from dequeue, prefer free buffers
int slot = getFreeBufferLocked();
if (slot != BufferQueueCore::INVALID_BUFFER_SLOT) {
*found = slot;
} else if (mCore->mAllowAllocation) { // 如果没找到,在调用getFreeSlotLocked从mFreeSlots中获取。
*found = getFreeSlotLocked();
}
} else {
// If we're calling this from attach, prefer free slots
int slot = getFreeSlotLocked(); //attachbuffer调用这段代码,优先先调用getFreeSlotLocked从mFreeSlots中获取,如果找到了就返回。
if (slot != BufferQueueCore::INVALID_BUFFER_SLOT) {
*found = slot;
} else {
*found = getFreeBufferLocked(); 如果没找到,在调用getFreeBufferLocked从mFreeBuffers中获取。
}
}
}
}
}

return NO_ERROR;
}

(2). attachbuffer的流程
正如上面的waitForFreeSlotThenRelock流程,attachBuffer也是从FREE状态的slots上获取BufferSlot,但是和dequeueBuffer不同attachBuffer是优先从mfreeslots上获取,如果mfreeslots没有,在从mfreebuffers上获取。
waitForFreeSlotThenRelock获取到BufferSlot之后,再把已有的申请好的GraphicBuffer绑定到这个BufferSlot上。同时把mBufferState状态修改成Dequeued状态。把BufferSlot放到mActiveBuffers中管理。
具体流程如下:

status_t BufferQueueProducer::attachBuffer(int* outSlot,
const sp<android::GraphicBuffer>& buffer) {

//调用waitForFreeSlotThenRelock 找到可以FREE状态下的BufferSlot
status_t status = waitForFreeSlotThenRelock(FreeSlotCaller::Attach, lock, &found);

*outSlot = found; //把找到的slot赋值给outSlot

mSlots[*outSlot].mGraphicBuffer = buffer; //把准备好的buffer关联到slot的mGraphicBuffer上
mSlots[*outSlot].mBufferState.attachProducer(); // 修改BufferState成Dequued状态

mCore->mActiveBuffers.insert(found); // 把slot放到mActiveBuffers中管理。

return returnFlags;
}
(3). queuebuffer的流程
queuebuffer是生产者完成对GraphicBuffer的处理之后调用queuebuffer把GraphicBuffer放回mQueue的操作,同时把mBufferState修改成QUEUE状态。
具体queuebuffer流程如下:

status_t BufferQueueProducer::queueBuffer(int slot,
const QueueBufferInput &input, QueueBufferOutput *output) {

sp<IConsumerListener> frameAvailableListener;
sp<IConsumerListener> frameReplacedListener;

BufferItem item;
{ // Autolock scope

mSlots[slot].mBufferState.queue(); //修改mBufferState状态为QUEUE状态。

//增加mFrameCounter
++mCore->mFrameCounter;
currentFrameNumber = mCore->mFrameCounter;
mSlots[slot].mFrameNumber = currentFrameNumber;

//给BufferItem赋值
item.mGraphicBuffer = mSlots[slot].mGraphicBuffer;
item.mSlot = slot;

output->bufferReplaced = false;
if (mCore->mQueue.empty()) { //如果mQueue为空,就直接把BufferItem push到mQueue尾部。
mCore->mQueue.push_back(item);
frameAvailableListener = mCore->mConsumerListener;
} else { //如mQueue不为空,需要判断一下last BufferItem是否被替换,如果可以替换就替换,如果不可以替换就直接把BufferItem放到mQueue尾部。
const BufferItem& last = mCore->mQueue.itemAt(
mCore->mQueue.size() - 1);
if (last.mIsDroppable) {

if (!last.mIsStale) {
mSlots[last.mSlot].mBufferState.freeQueued();

// Don't put the shared buffer on the free list.
if (!mSlots[last.mSlot].mBufferState.isShared()) {
mCore->mActiveBuffers.erase(last.mSlot);
mCore->mFreeBuffers.push_back(last.mSlot);
output->bufferReplaced = true;
}
}

mCore->mQueue.editItemAt(mCore->mQueue.size() - 1) = item;
frameReplacedListener = mCore->mConsumerListener;
} else {
mCore->mQueue.push_back(item);
frameAvailableListener = mCore->mConsumerListener;
}
}
if (frameAvailableListener != nullptr) {
frameAvailableListener->onFrameAvailable(item); //调用消费者的onFrameAvailable通知消费者,有queue状态的BufferSlot可以使用。
} else if (frameReplacedListener != nullptr) {
frameReplacedListener->onFrameReplaced(item); //调用消费者的onFrameReplaced通知消费者,有queue状态的BufferSlot可以被替换。
}
} // Autolock scope

return NO_ERROR;
}

(4). detachBuffer的流程
detachBuffer主要是对应生产者端的attachbuffer操作,将attachbuffer之后的BufferSlot,放回到mFreeSlots中,并解除对GraphicBuffer的绑定,并通知消费者Buffer释放。

status_t BufferQueueProducer::detachBuffer(int slot) {

sp<IConsumerListener> listener;

mSlots[slot].mBufferState.detachProducer(); //修改BufferState的Dequeued状态成FREE状态
mCore->mActiveBuffers.erase(slot); //把slot从mActiveBuffers中移除
mCore->mFreeSlots.insert(slot); //把slot加到mFreeSlots中。
mCore->clearBufferSlotLocked(slot); //清除slot和Graphicbuffer的绑定关系。
mCore->mDequeueCondition.notify_all();
listener = mCore->mConsumerListener; //把消费者回调的listener赋值给listener
}

if (listener != nullptr) {
listener->onBuffersReleased();//调用消费者的listener接口通知消费者Buffer释放
}

return NO_ERROR;
}
五、BufferQueue的完整生产消费体系
1.BufferQueue的生产消费和BufferSlot状态关系
上面分别详细介绍了BufferQueueCore BufferSlot BufferState BufferQueueConsumer BufferQueueProducer,也涉及到了部分流转关系。
这一节将用图表和文字说明详细的介绍一下 BufferQueueConsumer BufferQueueProducer 和 BufferSlot BufferState 之间的流转关系。

BufferSlot 包含了FREE DEQUEUED QUEUED ACQUIRED 这几种BufferState对应基本操作方法dequeue/queue/acquire/release的关系如下:

首先初始状态下,所有可用BufferSlot全是FREE状态,在mFreeSlots中管理。
BufferQueueProducer 发出dequeueBuffer请求会优先在mFreeSlots找的一个FREE状态的BufferSlot,后面在做dequeueBuffer的时候优先从mFreeBuffers中获取,发现没有对应的GraphicBuffer,就去申请,申请完成之后把GraphicBuffer与Slot绑定,然后把BufferSlot状态修改程Dequeue状态交由生产者生产。
生产完成之后,BufferQueueProducer 调用queueBuffer,把 BufferSlot放入队列mQueue中供消费者使用,并把BufferSlot的状态设置成QUEUE状态,并调用消费者的onFrameAvailable回调通知消费者,有可消费的BufferSlot可以消费。
消费者接到通知之后,调用acquireBuffer到mQueue队列中取出BufferSlot消费,并将BufferSlot状态修改程Acquired状态。消费完成之后调用releaseBuffer把BufferSlot放到mFreeBuffers中管理,这时不回去解绑GraphicBuffer和BufferSlot,所以GraphicBuffer不会被释放。
同时把BufferSlot状态设置为FREE状态,并调用BufferQueueProducer的回调函数通知生产者BufferSlot释放。这样就完成了一次流转。对应下图。

BufferQueue的生产消费关系还有另外一种使用方法。下面是这种操作方法attachbuffer/detachbuffer/cancelbuffer和状态的关系图:

同样首先初始状态下,所有可用BufferSlot全是FREE状态,在mFreeSlots中管理。
BufferQueueProducer调用attachBuffer 首先优先从mFreeSlots中FREE状态BufferSlot,如果mFreeSlots中没有在从mFreeBuffers中获取,然后把已经分配好的GraphicBuffer关联到该BufferSlot上。
并将BufferSlot状态修改成DEQUEUED 这里和dequeueBuffer方式不同,dequeueBuffer是BufferQueueCore负责申请管理GraphicBuffer,而attachBuffer是把申请好的GraphicBuffer关联到BufferQueueCore上。
然后BufferQueueProducer 调用queueBuffer,把 BufferSlot放入队列mQueue中供消费者使用,并把BufferSlot的状态设置成QUEUE状态,并调用消费者的onFrameAvailable回调通知消费者消费。
消费者接到通知之后,调用acquireBuffer到mQueue队列中取出BufferSlot消费,并将BufferSlot状态修改程Acquired状态。消费完成之后调用releaseBuffer把BufferSlot放到mFreeBuffers中管理,并调用BufferQueueProducer的回调函数通知生产者调用detachBuffer释放GrphicBuffer。这里也可以调用消费者的dettachBuffer完成释放。

2.BufferQueue的封装关系
BufferQueue的代码在/frameworks/native/libs/gui中,主要有BufferQueue.cpp BufferQueueCore.cpp BufferQueueProducer.cpp BufferQueueConsumer.cpp 这几个文件前面已经详细介绍过了,BufferQueue框架中的核心实现文件。
IConsumerListener.cpp IProducerListener.cpp 是生产者消费者相关的回调接口文件。
IGraphicBufferProducer.cpp IGraphicBufferConsumer.cpp 为BufferQueueProducer 和BufferQueueConsumer 提供接口,实现跨进程访问。
BufferItemConsumer.cpp CpuConsumer.cpp GLConsumer.cpp 等都继承ConsumerBase.cpp 来完成对BufferQueueConsumer的封装。
Surface.cpp SurfaceComposerClient.cpp SurfaceControl.cpp surface相关的文件是对BufferQueueProducer的封装。

我们正常使用的时候实际上是接触不到BufferQueue的,都是使用的层层封装之后的类,由此可以总结出:
BufferQueue的消费者是层层封装的,核心实现是BufferQueueConsumer。
BufferItemConsumer是对BufferQueueConsumer的封装,ImageReader又是对BufferItemConsumer的封装,BufferQueueConsumer BufferItemConsumer ImageReader都是消费者。

BufferQueue的生产者也是层层封装的,核心实现是BufferQueueProducer。
Surface 是对BufferQueueProducer的封装,ImageWriter又是对Surface的封装。BufferQueueProducer,Surface ,BufferQueueProducer 都是生产者。
每次封装都是有目的,都是为了实现某种特殊的功能。

3.BufferQueue的使用模型
BufferQueue 有两种使用方式:
一种是生产者dequeue buffer用来生产,生产完成之后调用queueBuffer把GraphicBuffer放回BufferQueue并通过回调通知消费者使用,消费者调用acquireBuffer 获取GraphicBuffer进行消费,消费完成之后调用releaseBuffer 把GraphicBuffer放回BufferQueue,并通知生产者detachBuffer。

另一种是生产者调用attachBuffer,获取BufferQueue上的一个freeSlot,并将已经分配好的GraphicBuffer与之连接,然后调用queueBuffer放回到BufferQueue上,并通知消费者消费,消费者调用acquireBuffer获取GraphicBuffer进行消费,消费完成之后调用releaseBuffer 把GraphicBuffer放回BufferQueue,并通知生产者detachBuffer。消费完成之后,消费者也可以调用detachBuffer进行释放GraphicBuffer。

六 、BufferQueue在java层的应用
1.ImageReader消费者关系
(1). ImageReader基本概念
ImageReader是消费者,消费者承担创建BufferQueue的责任。所以在ImageReader初始化的时候创建了BufferQueue以及最原始的生产者BufferQueueProducer和消费者BufferItemConsumer。

ImageReader工作的时候调用acquireNextImage 经过层层调用获取到BufferQueue中的GraphicBuffer来使用,并将状态改成aquired状态。使用完成之后调用releaseBuffer接口放回到mFreeBuffers队列中并通知生产者释放GraphicBuffer。

(2). ImageReader的初始化
ImageReader的初始化是调用nativeInit来完成的,nativeinit调用JNI接口ImageReader_init来实现,
在ImageReader_init中会调用BufferQueue::createBufferQueue(&gbProducer, &gbConsumer);创建最原始的生产者消费者和BufferQueueCore,之后会讲原始消费者封装到BufferItemConsumer中,然后调用bufferConsumer→setFrameAvailableListener(ctx);把JNIImageReaderContext实现的回调注册到ConsumerBase中去。生产完成之后发现有可以消费的Buffer,会先触发ConsumerBase::onFrameAvailable,在ConsumerBase::onFrameAvailable中会调用setFrameAvailableListener设置下来的回调,通知ImageReader进行消费。
然后会调用ctx->setProducer(gbProducer);保存原始生产者,供后面获取并封装使用。

(3). ImageReader的acquirebuffer
ImageReader的acquireBuffer主要根据mFormat创建SurfaceImage,然后用这个SurfaceImage做为参数继续调用acquireNextSurfaceImage完成acquire操作,
acquireNextSurfaceImage调用nativeImageSetup进而调用JNI接口ImageReader_imageSetup完成acquire操作,之后把SurfaceImage存储到mAcquiredImages中保存,在Release的时候释放。
ImageReader_imageSetup 中首先调用getBufferConsumer获取BufferItemConsumer,然后获取BufferItem,接着调用bufferConsumer->acquireBuffer(buffer, 0);继续完成acquireBuffer操作。
并把获取到的BufferItem通过Image_setBufferItem接口设置到image的mNativeBuffer属性中去
acquireBuffer调用ConsumerBase::acquireBufferLocked来完成item的填充,然后用mSlots[item->mSlot].mGraphicBuffer给item→mGraphicBuffer赋值。
acquireBufferLocked则又是调用mConsumer->acquireBuffer完成的,acquireBuffer流程上面已经分析过了,负责获取对应的BufferItem输出。

frameworks/base/media/java/android/media/ImageReader.java
public Image acquireNextImage() {
SurfaceImage si = new SurfaceImage(mFormat); //创建一个SurfaceImage
int status = acquireNextSurfaceImage(si); //调用acquireNextSurfaceImage继续acquire操作
}


private int acquireNextSurfaceImage(SurfaceImage si) {
synchronized (mCloseLock) {
if (mIsReaderValid) {
status = nativeImageSetup(si);
}

if (status == ACQUIRE_SUCCESS) {
mAcquiredImages.add(si);
}
return status;
}
}
frameworks/base/media/jni/android_media_ImageReader.cpp
static jint ImageReader_imageSetup(JNIEnv* env, jobject thiz, jobject image) {

JNIImageReaderContext* ctx = ImageReader_getContext(env, thiz);

BufferItemConsumer* bufferConsumer = ctx->getBufferConsumer();
BufferItem* buffer = ctx->getBufferItem();

status_t res = bufferConsumer->acquireBuffer(buffer, 0);

Image_setBufferItem(env, image, buffer);

return ACQUIRE_SUCCESS;
}

static void Image_setBufferItem(JNIEnv* env, jobject thiz,
const BufferItem* buffer)
{
env->SetLongField(thiz, gSurfaceImageClassInfo.mNativeBuffer, reinterpret_cast<jlong>(buffer));
}

frameworks/native/libs/gui/BufferItemConsumer.cpp
status_t BufferItemConsumer::acquireBuffer(BufferItem *item,
nsecs_t presentWhen, bool waitForFence) {

err = acquireBufferLocked(item, presentWhen);

item->mGraphicBuffer = mSlots[item->mSlot].mGraphicBuffer;

return OK;
}

frameworks/native/libs/gui/ConsumerBase.cpp
status_t ConsumerBase::acquireBufferLocked(BufferItem *item,
nsecs_t presentWhen, uint64_t maxFrameNumber) {

status_t err = mConsumer->acquireBuffer(item, presentWhen, maxFrameNumber); //调用acquireBuffer完成acquireBuffer操作
if (item->mGraphicBuffer != nullptr) {
if (mSlots[item->mSlot].mGraphicBuffer != nullptr) {
freeBufferLocked(item->mSlot);
}
mSlots[item->mSlot].mGraphicBuffer = item->mGraphicBuffer; //如果item->mGraphicBuffer不为空,就用item->mGraphicBuffer覆盖mSlots[item->mSlot].mGraphicBuffer
}

return OK;
}

(4). ImageReader的release
使用graphicbuffer完成之后的释放需要注意一下:

消费者的最原始的buffer释放流程(BufferItemConsumer中的流程)应该是使用完成之后先调用releasebuffer将mlots 置位成FREE状态,link到mFreeBuffers中,
此时GraphicBuffer还是link在mSlots中,但是处于可用状态。在这个时候需要调用discardFreeBuffers来解除GraphicBuffer和mSlots link关系,
然后把mSlots标志成mFreeSlots状态完成释放。但是实际释放过程没有这么简单,ImageReader封装了releaseBuffer接口成releaseImage,但是这个接口是私有的,不能被公开调用。
ImageReader还封装了一个close接口,close接口完成了release操作,但是同时也清理掉了ImageReader消费者本身。

那么如何去释放呢,发现ImageReader的创建的SurfaceImage可以通过acquireNextImage获取,而SurfaceImage中有close方法,这个方法调用了 releaseImage完成SurfaceImage自身的释放。
到此为止看起来,该release的都release了,在调用一下discardFreeBuffers就能解决战斗,其实并没有。在生产者进程中Surface自身还维护了一个BufferSlot mSlots[NUM_BUFFER_SLOTS],
attachBuffer的时候 GraphicBuffer link上,在收到消费者onBufferReleased回调之后调用detachNextBuffer取消关联。
但是实际上 mFormat != HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED 的时候是走不到detachNextBuffer的所以还是不能即时释放。

void JNIImageWriterContext::onBufferReleased() {
// Detach the buffer every time when a buffer consumption is done,
// need let this callback give a BufferItem, then only detach if it was attached to this
// Writer. Do the detach unconditionally for opaque format now. see b/19977520
if (mFormat == HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED) {
mProducer->detachNextBuffer(&buffer, &fence);
}
env->CallStaticVoidMethod(mClazz, gImageWriterClassInfo.postEventFromNative, mWeakThiz);
}

2.imageWriter的生产者关系
(1). ImageWriter基本概念
ImageWriter是生产者,根据消费者的创建的原始生产者封装并创建自己,然后link到消费者进程。
然后通过dequeueInputImage最终调用BufferQueueProducer:dequeueBuffer拿到GraphicBuffer进行处理。
处理完成之后调用queueInputImage 最终调用BufferQueueProducer:queueBuffer 把 buffer放回队列供消费者使用。

(2). ImageWriter的初始化
与ImageReader类似,ImageWiter也是调用nativeInit来完成创建工作,nativeInit会调用JNI接口ImageWriter_init,在ImageWriter_init中首先通过surface->getIGraphicBufferProducer调用获取原始的Producer
然后调用new Surface进行封装,然后调用ctx->setProducer(producer)保存封装好的生产者,封装完成之后调用 producer->connect链接BufferQueueCOre并注册回调。

(3). ImageWriter的dequeue
ImageWriter提供dequeueInputImage接口来完成dequeue操作,dequeueInputImage主要通过nativeDequeueInputImage来实现,获取image之后会放到到mDequeuedImages保存。
nativeDequeueInputImage调用JNI接口ImageWriter_dequeueImage实现,ImageWriter_dequeueImage的主要操作是 通过ctx→getProducer()获取Surface,之后用获取到的Surface调用Surface的dequeueBuffer完成dequeue操作。
Surface的dequeueBuffer函数则是调用BufferQueueProducer的dequeueBuffer函数最终实现。

(4). ImageWriter的queue
dequeBuffer主要就是调用nativeQueueInputImage来完成,nativeQueueInputImage调用JNI接口ImageWriter_queueImage来实现,在ImageWriter_queueImage中通过ctx→getProducer获取生产者
然后根据获取到的生产者继续调用anw->queueBuffer完成queueBuffer的操作,最终调用BufferQueuProducer的queueBuffer函数完成queue操作。

下面是imagewriter 初始化,dequebuffer,queuebuffer的详细的操作流程图:

(5).ImageWriter的attachandqueue
如果这个imageBuffer是不属于ImageWriter自己的,就会走attach流程,attach之后调用queue放到BufferQueue中供消费者使用。
具体的是先调用ownedByMe判断是否属于自己的,如果不是则先找到之前的Owner,然后调用detachImage断开和之前owner的关系。在然后就是调用attachAndQueueInputImage完成attach和queue操作。

public void queueInputImage(Image image) {
boolean ownedByMe = isImageOwnedByMe(image);

// For images from other components, need to detach first, then attach.
if (!ownedByMe) {
ImageReader prevOwner = (ImageReader) image.getOwner(); //获取之前的owner
prevOwner.detachImage(image); //调用detachImage释放和之前owner的关联。
attachAndQueueInputImage(image); //调用attachAndQueueInputImage完成attach和queue操作。
image.close(); //关闭image
return; //直接返回
}

nativeQueueInputImage(mNativeContext, image, image.getTimestamp(), crop.left, crop.top,
crop.right, crop.bottom, image.getTransform(), image.getScalingMode()); //如果ownedByMe则调用这里做queue操作。
}

七、总结
综上介绍了消费者模型,BufferQueue的缓存区管理以及关键概念BufferSlot BufferState BufferItem BufferQueue。
然后介绍了生产者消费者BufferQueueProducer和 BufferQueueConsumer 的关键方法dequeue/queue/acquire/release。
再之后然后介绍了将生产者消费者的生产消费关系和BufferQueue完整的串起来,状态变化,接口回调,Buffer流转。
最后介绍了实际应用中BufferQueue的封装层imagereader,imagewriter。
BufferQueue的生产消费关系终结如下图,消费者和生产者层层封装,消费者可以跨进程也可以不跨进程,一般不跨。
生产者可以跨进程也可以不跨进程一般是跨进程的。

Buffer Queue原理的更多相关文章

  1. Protocol Buffer 序列化原理大揭秘 - 为什么Protocol Buffer性能这么好?

    前言 习惯用 Json.XML 数据存储格式的你们,相信大多都没听过Protocol Buffer Protocol Buffer 其实 是 Google出品的一种轻量 & 高效的结构化数据存 ...

  2. Buffer Cache 原理

    在将数据块读入到SGA中,他们的缓冲区被放置在悬挂散列存储桶的链表中(散列链),这种内存结构由大量 子cache buffers chains锁存器(也称为散列锁存器或CBC锁存器)保护. Buffe ...

  3. RabbitMQ的Vhost,Exchange,Queue原理分析

    Vhost分析 RabbitMQ的Vhost主要是用来划分不同业务模块.不同业务模块之间没有信息交互. Vhost之间相互完全隔离,不同Vhost之间无法共享Exchange和Queue.因此Vhos ...

  4. google protocol buffer的原理和使用(二)

    本文主要会介绍怎么使用Google Protocol的Lib来序列化我们的数据,方法非常多种,本文仅仅介绍当中的三种.其它的方法读者能够通过自行研究摸索.但总的来说,序列化数据总的来说分为下面俩步: ...

  5. google protocol buffer的原理和使用(三)

    介绍下怎么反序列化GoogleBuffer数据.并在最后提供本系列文章中所用到的代码整理供下载. 上一篇文章介绍了如何将数据序列化到了addressbook.data中.那么对于接受方而言该怎么解析出 ...

  6. google protocol buffer的原理和使用(一)

    一.简单的介绍      Protocol buffers是一个用来序列化结构化数据的技术,支持多种语言诸如C++.Java以及Python语言.能够使用该技术来持久化数据或者序列化成网络传输的数据. ...

  7. PolarDB PostgreSQL Buffer Management 原理

    背景介绍 传统数据库的主备架构,主备有各自的存储,备节点回放WAL日志并读写自己的存储,主备节点在存储层没有耦合.PolarDB的实现是基于共享存储的一写多读架构,主备使用共享存储中的一份数据.读写节 ...

  8. ORACLE CACHE BUFFER CHAINS原理

    原理图如下: 一个cache buffer chains 管理多个hash bucket,受隐含参数:_db_block_hash_buckets(控制管理几个hash bucket)

  9. google protocol buffer的原理和使用(四)

    有个电子商务的系统(如果用C++实现).当中的模块A须要发送大量的订单信息给模块B.通讯的方式使用socket. 如果订单包含例如以下属性: ----------------------------- ...

  10. ffmpeg的内部Video Buffer管理和传送机制

    ffmpeg的内部Video Buffer管理和传送机制 本文主要介绍ffmpeg解码器内部管理Video Buffer的原理和过程,ffmpeg的Videobuffer为内部管理,其流程大致为:注册 ...

随机推荐

  1. Linux telnet安装及端口测试联通性

    安装步骤: 可使用该文中的步骤进行安装,已经过本人验证,是可以安装成功的: https://blog.csdn.net/doubleqinyan/article/details/80492421 安装 ...

  2. SV 字符串类型

    概述 常见使用方式 string b; string b=""; // 拼接字符串 string a = {"hi",b}; // 将字符串a赋值给[15:0] ...

  3. 【Git】常用 Git 命令清单

    [来源]https://blog.csdn.net/hj7jay/article/details/53431717

  4. Vue - 父子级的相互调用

    父级调用子级 父级: <script> this.$refs.child.load(); 或 this.$refs.one.load(); </script> 子级: < ...

  5. 左值,右值,引用,指针,常量,auto如何组合?

    左值,右值,引用,指针,常量,auto如何组合? 左值引用:int &a = b; 左值引用是通过使用&符号来声明的,例如int &a. 左值引用用于绑定到左值(可标识的.持久 ...

  6. 百度网盘(百度云)SVIP超级会员共享账号每日更新(2023.11.26)

    一.百度网盘SVIP超级会员共享账号 可能很多人不懂这个共享账号是什么意思,小编在这里给大家做一下解答. 我们多知道百度网盘很大的用处就是类似U盘,不同的人把文件上传到百度网盘,别人可以直接下载,避免 ...

  7. [转帖]使用 Crash 工具分析 Linux dump 文件

    前言 Linux 内核(以下简称内核)是一个不与特定进程相关的功能集合,内核的代码很难轻易的在调试器中执行和跟踪.开发者认为,内核如果发生了错误,就不应该继续运行.因此内核发生错误时,它的行为通常被设 ...

  8. [转帖]/etc/profile和/etc/environment的区别

    时间  2019-11-07 标签 profile environment 区别 繁體版 原文   https://my.oschina.net/u/2885925/blog/2989579 /etc ...

  9. Sysbench的简单学习-编译与安装

    sysbench的简单学习-编译与安装 摘要 github上面获取一下最新版本 https://github.com/akopytov/sysbench 注意现在 2023.2.17 最新版是 sys ...

  10. killall 以及 pkill 等命令

    https://zhidao.baidu.com/question/1500084252693125099.html // 通过 killall 命令killall nginx// 通过 pkill ...