BufferQueue 學習總結(內附動態圖)

一、前言

《最簡單的BufferQueue測試程序(一)》
《最簡單的BufferQueue測試程序(二)》
《最簡單的BufferQueue測試程序(三)》

本文僅對BufferQueue最基本的操作接口進行講解,不包含 SurfaceFlinger、Surface 等上層封裝的概念介紹。

本文適用對象:Android小白
Android版本:8.1

閱讀完本文後,你將瞭解如下內容:

  • 什麼是BufferQueue
  • BufferQueue內部操作的原理是什麼
  • BufferQueue跨進程操作是怎麼實現的
  • 如何寫一個最簡單的BufferQueue測試程序

二、基本概念

首先來自Google官方的原圖:

圖片來源

但本人更喜歡下面這張圖,摘自林學森的《深入理解Android內核設計思想》,該圖更好的闡述了BufferQueue的基本操作流程:
在這裏插入圖片描述

buffer的5種狀態:FREEDEQUEUEDQUEUEDACQUIREDSHARED

在較新的Android版本中(5.1及以上),buffer的狀態由引用計數來表示,如下表(摘自 frameworks/native/include/gui/BufferSlot.h):

state 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

三、BufferQueue內部結構

BufferQueue核心代碼由如下3部分組成:

  • BufferQueueCore
  • BufferQueueProducer
  • BufferQueueConsumer

BufferQueueCore 負責維護 BufferQueue 的基本數據結構,而 BufferQueueProducer 和 BufferQueueConsumer 則負責提供操作 BufferQueue 的基本接口。

BufferQueueCore在frameworks/native/include/gui/BufferQueueCore.h中定義。
下圖爲BufferQueueCore的內部數據結構圖:
在這裏插入圖片描述
說明:

成員變量 說明
mQueue 存放BufferItem的FIFO隊列
mSlots BufferSlot結構體數組,數組長度爲64
mFreeSlots BufferSlot狀態爲FREE,且沒有GraphicBuffer與之相綁定的slot集合
mFreeBuffers BufferSlot狀態爲FREE,且有GraphicBuffer與之相綁定的slot集合
mActiveBuffers BufferSlot狀態不爲FREE(即DEQUEUED、QUEUED、ACQUIRED、SHARED)的slot集合。既然狀態不是FREE,那麼該BufferSlot必然有一個GraphicBuffer與之相綁定
mUnusedSlots 未參與使用的slot集合,由 mMaxBufferCount 決定

所以就有了如下等式:

mSlots = mFreeSlots + mFreeBuffers + mActiveBuffers + mUnusedSlots

四、基本函數

《最簡單的BufferQueue測試程序(一)》中,演示了一個BufferQueue的基本操作流程。dequeue/queue/acquire/release,這些基本函數內部到底做了什麼操作呢?我們一起來研究一下。

dequeueBuffer()

dequeueBuffer() 默認優先從mFreeBuffers中獲取slot,因爲mFreeBuffers中的slot已經有buffer與之綁定過了,這樣就不用再重新分配buffer了。過程如下:
在這裏插入圖片描述
如果mFreeBuffers爲空,則從mFreeSlots中獲取slot:
在這裏插入圖片描述

  1. dequeueBuffer() 優先從mFreeBuffers中獲取一個slot,同時將其對應的BufferSlot狀態從FREE修改爲DEQUEUED,並將該slot從mFreeBuffers遷移到mActiveBuffers中。
  2. 如果mFreeBuffers爲空,則從mFreeSlots中獲取slot,併爲它分配一塊指定大小的buffer,同時將其對應的BufferSlot狀態從FREE修改爲DEQUEUED,然後將該slot從mFreeSlots遷移到mActiveBuffers中。
  3. 將獲取到的slot作爲出參返回給調用者。如果該slot綁定的buffer是重新分配的,則返回值爲BUFFER_NEEDS_REALLOCATION,否則爲NO_ERROR。

可以看到,與mFreeBuffers相比,從mFreeSlots中獲取slot時,要多一步分配內存的操作。當然,如果mFreeBuffers不爲空,但是裏面沒有我們想要的buffer,比如buffer size不匹配,那麼這時候也會通過觸發Allocate的動作來重新分配buffer。

requestBuffer()

requestBuffer() 可以說是最沒有表現力的一個函數了,因爲它本質上不涉及到slot、state以及buffer的任何修改,它僅僅只是從給定的slot中取出與之綁定的GraphicBuffer指針,然後返回,僅此而已。

一般在dequeueBuffer()重新分配內存後(即函數返回值爲BUFFER_NEEDS_REALLOCATION),才需要調用requestBuffer()來獲取新的GraphicBuffer指針。

queueBuffer()

queueBuffer()的內部操作流程如下(以slot5爲例):

在這裏插入圖片描述

  1. queueBuffer()根據調用者傳入的slot參數,將其對應的BufferSlot狀態從DEQUEUED修改爲QUEUED,並根據該BufferSlot的信息生成一個BufferItem對象,然後添加到mQueue隊列中。
  2. 調用Consumer的Listener監聽函數,通知Consumer可以被acquireBuffer了。

acquireBuffer()

acquireBuffer()內部操作流程如下:

在這裏插入圖片描述

acquireBuffer()從mQueue隊列中取出1個BufferItem,並作爲出參返回給調用者,同時修改該BufferItem對應的slot狀態:QUEUED —> ACQUIRED。

releaseBuffer()

releaseBuffer()內部操作流程如下(以slot0爲例):
在這裏插入圖片描述

  1. releaseBuffer()根據調用者傳入的slot參數,將其對應的BufferSlot狀態從ACQUIRED修改爲FREE,並將該slot從mActiveBuffers中遷移到mFreeBuffers中。注意,這裏並沒有對該slot綁定的buffer進行任何解綁操作。
  2. 調用Producer的Listener監聽函數,通知Producer可以dequeueBuffer了。

五、特殊函數

除了上面提到的dequeue/queue/acquire/release這些基本操作函數外,BufferQueue還爲我們提供了一些特殊函數接口,方便調用者在一些非常規流程中使用。
這些特殊函數包括attach/detach/cancel等操作,在《最簡單的BufferQueue測試程序(二)》中,已經演示瞭如何使用這些特殊函數。但是這些特殊接口內部到底又做了什麼操作呢?我們一起往下看。

producer->attachBuffer()

描述: 給定1個GraphicBuffer指針,優先從mFreeSlots中獲取一個slot,並與該GraphicBuffer綁定。slot從mFreeSlots/mFreeBuffers遷移到mActiveBuffers。
狀態: FREE —> DEQUEUED

producer->detachBuffer()

描述: 給定1個slot,將該slot綁定的buffer解綁。slot從mActiveBuffers遷移到mFreeSlots。
狀態: DEQUEUED —> FREE

producer->cancelBuffer()

描述: 給定1個slot,僅僅將該slot從mActiveBuffers遷移到mFreeBuffers,不對buffer進行任何操作。
狀態: DEQUEUED —> FREE

consumer->attachBuffer()

描述: 給定1個GraphicBuffer指針,優先從mFreeSlots中獲取一個slot,並與該GraphicBuffer綁定。slot從mFreeSlots/mFreeBuffers遷移到mActiveBuffers。
狀態: FREE —> ACQUIRED

consumer->detachBuffer()

描述: 給定1個slot,將該slot綁定的buffer解綁。slot從mActiveBuffers遷移到mFreeSlots。
狀態: ACQUIRED —> FREE

consumer->discardFreeBuffers

描述: 將mFreeBuffers中的slot全部遷移到mFreeSlots中,並釋放所有綁定的buffers。
狀態: FREE —> FREE

光看上面這些描述,可能大家還是不太清楚這些特殊函數的意義和用途。那麼通過下面與基本函數的對比,我想大家應該會更容易理解一些。

Producer:

函數名 區別
attachBuffer 不涉及到buffer的分配動作
dequeueBuffer 可能會涉及到buffer的分配動作
函數名 區別
detachBuffer 釋放buffer,slot —> mFreeSlots
cancelBuffer 不釋放buffer,slot —> mFreeBuffers

Consumer:

函數名 區別
attachBuffer 直接從FREE —> ACQUIRED
acquireBuffer 必須是 QUEUED —> ACQUIRED
函數名 區別
detachBuffer 釋放buffer,slot —> mFreeSlots
releaseBuffer 不釋放buffer,slot —> mFreeBuffers

六、跨進程如何實現

《最簡單的BufferQueue測試程序(三)》中,演示瞭如何跨進程操作BufferQueue。在該示例中我們看到,client.cpp中對BufferQueue的遠程操作代碼,和《(一)》中本地執行的代碼一模一樣。但是前者是通過binder調用來實現跨進程訪問,而後者則爲本地函數直接調用,雖然二者在代碼的表現形式上都是一樣的,但是各自實現的路徑卻是有差別的。當然,殊途同歸,最終BufferQueue的操作結果還是一樣的

那麼client.cpp中同樣的代碼,是如何實現跨進程通信的呢?

Binder 調用

client端:remote()->transact()
server端:onTransact()
在這裏插入圖片描述

  1. client端通過 remote()->transact() 對binder驅動發起操作請求,並等待返回結果。
  2. binder驅動將該請求轉發給對應的server端。
  3. server端通過onTransact()來響應client的操作請求,並將結果返回給client端。

虛函數

如下圖所示:
在這裏插入圖片描述

Class Base 爲基類,Class A 和 Class B 均繼承於 Class Base。由於Class Base內部爲純虛函數,因此不能直接對它進行實例化,只能對它的子類 Class A 或 Class B 進行實例化對象。

有時候因爲軟件設計需要,我們希望使用同一套代碼來實現不同的軟件邏輯。例如,我們希望當實現 Class A 對象時,則調用 Class A 的成員函數。當實現 Class B 對象時,則調用 Class B 的成員函數。那麼這時候,只需要定義一個基類 Class Base 的指針 sp,讓它分別指向不同的子類對象,就可以實現。

Bp 和 Bn

Bp: Binder Proxy,Binder遠程代理,即client端。
Bn: Binder Native,Binder本地實現,即server端。

如果將上面兩幅圖融合在一起,就成了下面這樣:
在這裏插入圖片描述

於是,我們只需要定義一個基類的指針 sp,就可以使用同一套操作接口,來實現不同的操作路徑。比如 sp->func1(),當sp指向 Class BpBase 對象時,它執行的是binder遠程調用;當sp指向 Class BnBase 對象時,它執行的是本地函數func1()的直接調用。

因此,如果僅僅只看下面這段代碼,是無法區分該BufferQueue的操作是在本地進程執行,還是在其它進程中執行的。

void main(void)
{
    sp<IGraphicBufferProducer> producer;
    sp<IGraphicBufferConsumer> consumer;
	...
	producer->dequeueBuffer();
	producer->requestBuffer();
	producer->queueBuffer();
	...
	consumer->acquireBuffer();
	consumer->releaseBuffer();
}

唯一區分的方法是,看producer和consumer所指向的對象,是在本地進程創建的,還是在遠程進程中創建的。

七、總結

  1. buffer狀態切換如下:
    在這裏插入圖片描述

  2. 除了關注代碼的邏輯,還需要確定當前是本地操作,還是遠程調用;

  3. Android 規定,BufferQueue只能在Consumer進程中創建;

八、參考資料

示例代碼下載:GitHub BufferQueue
Android自帶BufferQueue測試程序:BufferQueue_test.cpp
瑪法里奧趙四 CSDN博客:AndroidFramework – Binder中的Bn與Bp

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章