一、前言
《最簡單的BufferQueue測試程序(一)》
《最簡單的BufferQueue測試程序(二)》
《最簡單的BufferQueue測試程序(三)》
本文僅對BufferQueue最基本的操作接口進行講解,不包含 SurfaceFlinger、Surface 等上層封裝的概念介紹。
本文適用對象:Android小白
Android版本:8.1
閱讀完本文後,你將瞭解如下內容:
- 什麼是BufferQueue
- BufferQueue內部操作的原理是什麼
- BufferQueue跨進程操作是怎麼實現的
- 如何寫一個最簡單的BufferQueue測試程序
二、基本概念
首先來自Google官方的原圖:
但本人更喜歡下面這張圖,摘自林學森的《深入理解Android內核設計思想》,該圖更好的闡述了BufferQueue的基本操作流程:
buffer的5種狀態:FREE
、 DEQUEUED
、 QUEUED
、 ACQUIRED
、 SHARED
。
在較新的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:
- dequeueBuffer() 優先從mFreeBuffers中獲取一個slot,同時將其對應的BufferSlot狀態從FREE修改爲DEQUEUED,並將該slot從mFreeBuffers遷移到mActiveBuffers中。
- 如果mFreeBuffers爲空,則從mFreeSlots中獲取slot,併爲它分配一塊指定大小的buffer,同時將其對應的BufferSlot狀態從FREE修改爲DEQUEUED,然後將該slot從mFreeSlots遷移到mActiveBuffers中。
- 將獲取到的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爲例):
- queueBuffer()根據調用者傳入的slot參數,將其對應的BufferSlot狀態從DEQUEUED修改爲QUEUED,並根據該BufferSlot的信息生成一個BufferItem對象,然後添加到mQueue隊列中。
- 調用Consumer的Listener監聽函數,通知Consumer可以被acquireBuffer了。
acquireBuffer()
acquireBuffer()內部操作流程如下:
acquireBuffer()從mQueue隊列中取出1個BufferItem,並作爲出參返回給調用者,同時修改該BufferItem對應的slot狀態:QUEUED —> ACQUIRED。
releaseBuffer()
releaseBuffer()內部操作流程如下(以slot0爲例):
- releaseBuffer()根據調用者傳入的slot參數,將其對應的BufferSlot狀態從ACQUIRED修改爲FREE,並將該slot從mActiveBuffers中遷移到mFreeBuffers中。注意,這裏並沒有對該slot綁定的buffer進行任何解綁操作。
- 調用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()
- client端通過 remote()->transact() 對binder驅動發起操作請求,並等待返回結果。
- binder驅動將該請求轉發給對應的server端。
- 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所指向的對象,是在本地進程創建的,還是在遠程進程中創建的。
七、總結
-
buffer狀態切換如下:
-
除了關注代碼的邏輯,還需要確定當前是本地操作,還是遠程調用;
-
Android 規定,BufferQueue只能在Consumer進程中創建;
八、參考資料
示例代碼下載:GitHub BufferQueue
Android自帶BufferQueue測試程序:BufferQueue_test.cpp
瑪法里奧趙四 CSDN博客:AndroidFramework – Binder中的Bn與Bp