眾所周知,有一個程序screencap可以截屏,這個程序十分簡單,只是使用了surfaceflinger服務的截屏功能。
所以要了解截屏,看surfaceflinger服務的代碼是不二首選。但是surfaceflinger也隨android系統顯示子系統的變更而變更,網上最容易搜到的android資料都在11-14年的文章,都是4.x時代甚至2.x時代的技術,而android代代變化,有不少文章已經不再適用。
2.3.6之前,surfaceflinger放於base目錄。這時候的surfaceflinger使用FramebufferNativeWindow。
4.0.x開始,suffaceflinger放於native目錄。但仍舊使用FramebufferNativeWindow。
4.2 開始,DisplayHardware類廢除(注意DisplayHardware目錄或者說組件沒有廢除,因為顯示系統變更了,不再只有一個Display,取而代之的是DisplayDevice類,這個類不在與DisplayHardware目錄同級。換句話說,以前surfaceflinger擁有一個DisplayHardware,現在開始就擁有一組DisplayDevice,這些DisplayDevice只代表某一GL上下文,並且不直接訪問Hardware層的fbDev,fbDev由HWComposer管理,DisplayDevice只能通過FramebufferSurface去訪問Hardware層),不再使用FramebufferNativeWindow,替而引入了FramebufferSurface。
4.3 開始,DisplaySurface,VirutalDisplaySurface。並且廢除了libgui中的SurfaceTextureClient。
在4.1.x之前,我稱為FramebufferNativeWindow時代,surface是直接從FramebufferNativeWindow創建的。
可以看到這是很常規的egl初始化。
在4.2 開始 FramebufferNativeWindow不再使用,因為在以前單一Display的時代,直接將fbDev和ANativeWindow偶合在一起。現在ANativeWindow和fbDev分離開。SurfaceTextureClient替代了FramebufferNativeWindow,並且不與fbDev偶合(,fbDev由HWComposer接管)。這里必須區分好,android顯示系統有一個Surface,而egl也有一個EGLSurface,它們的連結點就是ANavtiveWindow,其定義路徑在/system/core/include/system/window.h 。
SurfaceTexture之前是直接繼承於BnSurfaceTexture,4.2 開始它與BnSurfaceTexture解偶,轉而繼承ComsumerBase。ComsumerBase擁有的BufferQueue才是繼承於BnSurfaceTexture(,定義在/frameworks/native/include/gui/ISurfaceTexture.h)。SurfaceTexture 與 BnSurfaceTexture生產消費者模式,並只作為消費者。
與此同時,在SurfaceTexture.h同一目錄添加了Surface(,定義在/frameworks/native/include/gui ,也就是 libgui.so), 相應地在surfaceflinger目錄有另一個對應的FramebufferSurface,是surfaceflinger服務私有的類,它是面向DisplayDevice的,是SurfaceTextureClient與HWComposer的結合點(Framebuffer就是指HWComposer,Surface則是SurfaceTextureClient一側,以及用於它的ConsumerBase)。這里要注意,Surface繼承於(,isa)ANativeWindow,而FramebufferSurface卻繼承於(,isa)ConsumerBase。SurfaceTextureClient 作為ANativeWindow 依賴 ISurfaceTexture 服務器,去實現 ANativeWindow的hook接口queueBuffer以及dequeueBuffer。
下面是surfaceflinger初始化DisplayDevice數組:
以及DisplayDevice初始化:
每個DisplayDevice使用SurfaceTextureClient這個ANativeWIndow去創建EGLSurface,而且這里的SurfaceTextureClient使用的是FramebufferSurface的BufferQueue。
在 FramebufferNativeWindow時代,截屏直接使用FBO(,Frame Buffer Object)。
然后每一個layer都繪制一次到framebuffer:
最后GL讀出像素:
在4.2開始,發生這些變化,首先實現的函數參數變化了
可以看到display不再以整型來作為標識,而是通過binder來標識。回看上面surfaceflinger在初始化DisplayDevice過程,只是簡單地 new BBinder() 來作為一個新的DisplayDevice的token,這個token只在binder驅動設備上建立了一個唯一的路徑,作用就是用來系統范圍內唯一標識。
所以在4.1.x及以前,screencap的代碼是:
從4.2開始,screencap的代碼是:
雖然上面說了一大堆的4.2變化,但是在截屏實現沒有變化,然而接下來的4.3就完全變化了。
4.2,Surface繼承於SurfaceTextureClient,但是4.3后,Surface脫離SurfaceTextureClient繼承樹,實現成另一個ANativeWindow,依賴 producer。記得上面嗎,4.2時,SurfaceTextureClient是一個依賴 FramebufferSurface這個consumer的ANativeWinodw。
下面是surfaceflinger對DisplayDevice的初始化:
跟着是DisplayDevice的初始化:
我們來回顧一下,4.1.x以前使用FramebufferNativeWindow來創建EGLSurface,4.2時候使用SurfaceTextureClient(它使用FramebufferSurface),4.3開始,使用Surface。FramebufferNativeWindow, SurfaceTextureClient, Surface都是ANativeWindow。
4.3開始Surface也另外繼承DisplaySurface。(...有時間再補充寫)。
最重要是captureScreen接口改了
實現上也就使用了BufferQueue,而不是IMemory來接收數據。
最最重要是沒有放出screencap的最新代碼,只停留在4.2時候的版本,也就是你不可能通過參照screencap.cpp去使用這個功能 。
可參考Transaction_test.cpp
這里勘誤一下,screencap的代碼路徑在 /frameworks/base/cmds/screencap 目錄下,雖然這份代碼使用的是ScreenshotClient::update,但是其實質還是在使用ISurfaceComposer::captureScreen,然后使用CpuConsumer去lockNextBuffer(,原理就是SurfaceFlinger作為生產者從BufferQueue填充了一幀Buffer,我們用消費者去訪問BufferQueue的幀Buffer),以上面Transaction_test.cpp為例過程是一樣的。
我們可以直接將這兩份代碼搬到native項目去嘗試一下,當然是在解決了權限和selinux的環境下。不要高興得太早,ScreenshotClient::update總是返回-22(BAD_VALUE)。而用Transaction_test.cpp的例子代碼,則可以清楚SurfaceFlinger服務執行captureScreen沒有報錯,卻是CpuConsumer::lockNextBuffer返回了-22。
現在我們以4.3(分水嶺)后的6.0.1系統為例。CpuConsumer執行在本進程,是libgui.so的一部分,可以反匯編結合源代碼去跟蹤調試。
下面是CpuConsumer::lockNextBuffer的反匯編:
<+68>調用了pthread_mutex_lock,<+84>調用了一個函數,然后<+88>將結果與0比較,<+90>將結果與2比較。
這可以分析對應的源代碼
執行的結果是從<+90>跳到了<+574>
也就是說代碼從acquireBufferLocked返回結果是NO_BUFFER_AVAIABLE,接着就是退出調用,先將AutoLock析構。
?! captureScreen返回0真的就意味成功了,不是的,logcat日志上報錯了,這真讓人郁悶一陣子為何不返回直接錯誤呢。
看到這uid就知道我是在app進程中使用SurfaceFlinger服務,雖然我將selinux設置可訪問SurfaceFlinger的binder,並且訪問FrameBuffer設備的進程是SurfaceFlinger,但還是逃不出權限的檢測,因為作為binder的client同時表明了自己的進程id。
這里就只能去破解(,修改二進制文件分支跳轉指令)surfaceflinger。
從整個過程來看,截屏最終還是在訪問FrameBuffer設備,並且對它封鎖得更嚴。即使你是超級用戶也不可能對FrameBuffer進行讀操作。必須經由SurfaceFlinger服務接受有權限的進程的請求,才可以讓SurfaceFlinger服務去訪問FrameBuffer設備。這樣一來,使用ffmpeg通過輸入/dev/fb0的錄屏方法也就給抹殺了。當然了,所有幀在遞交到SurfaceFlinger之前,也就獲取當前Window(當前Activity也就對應Window),自己將所有的buffer進行合成render也達到截屏的效果。這一思路分支沒有去研究,因為獲取當前活動Activity的api早就被禁用了,要么你就只能截自己應用的屏。
下面我們來看Screenshot::getPixels返回的是什么,當然環境是root權限進程,代碼是screencap片段。用gdb遠程調試:
返回了像素空間地址在 0xf5ccf000,我們來看這地內存的映射:
(暫時不清楚是什么設備的,dma也就是直接內存訪問相關,以后再補充,這塊內存存在在某個匿名inode)
(暫時不清楚為什么訪問不了)
上面不能訪問,只限於gdb調試,程序里面是可以訪問的。
我們來看兩組數據,第一組數據是兩部低端機啟用screencap進程截屏的性能: (測試項目分別寫/dev/null,共享內存,以及sdcard)
手機A
手機B
再來第二組數據,同樣是上面兩部低端手機,測試項目為ScreenshotClient,以及SurfaceComposerClient,CpuConsumer截屏后寫堆內存。
手機A
手機B
這里說明一下,手機A屏是800M像,手機B屏是300M像,8核。
從上面的數據看,進程創建和切換消耗很大。截屏服務實際消耗在0.02到0.04秒之間,但是spawn進程損耗就增加到100ms~200ms上下。手機B的圖形處理能力低於手機A,手機B的sdcard好於手機A的sdcard,但內存訪問則是手機A更優,多進程卻是手機B損耗更高。
下面是SurfaceComposerClient::captureScreen,然后CpuConsumer::lockNextBuffer,其實就是ScreenshotClient::update的步驟展開。
這里要提一下BufferQueue的生產者-消費者模式,IGraphicsBufferProducer生產者接口,IGraphicsBufferConsumer消費者接口。生產者-(enqueue)->BufferQueue-(dequeu)->消費者,一般來說是這樣的,但是它的接口有些特別,生產者同時有接口方法enqueueBuffer和dequeueBuffer,還有一個requestBuffer。如果不仔細參考一下頭文件的注釋,就會被搞頭暈。dequeueBuffer是生產者從BufferQueue取出一塊空閑Buffer,然后操作這塊空閑Buffer,enqueueBuffer則是入隊讓消費者可見。而消費者讀消費隊列的接口方法不是dequeue而是acquireBuffer。
下是一幀 Buffer 的流向圖。
生產者 <-(dequeueBuffer)- BufferQueue
生產者在buffer上進行讀寫操作
生產者 -(enqueueBuffer)-> BufferQueue -(acquireBuffer)-> 消費者
20180309 補充:
這里的buffer,並非大小為 width * height * bytesPerPixel, 而是 stride * height * bytesPerPixel 且 stride >= width。在做處理時掃描行就要注意了。
下面是SuffaceFlinger服務處理captureScreen過程,必然進行BufferQueue的連接。
只要 native_window_api_connect 不返回 NO_ERROR,都不會執行並且返回 BAD_VALUE,所有原因都被這個值抹去,無從分析。
native_window_api_connect函數的作用是將producer與native_window連接起來。
這里的native_window是SurfaceFlinger進程在執行函數時創建的Surface(從4.3之后繼承於ANativeWindow)。