最近遇到一個需求,要求將一個GL_TEXTURE_2D型別的紋理ID寫入到ImageReader生成的Surface中。
其實這個需求與我之前寫過的一篇文章 一文學會MediaCodeC與OpenGL錄製mp4視訊需求比較接近,只需要對該案例原始碼進行一些改造即可。
在正式介紹實現之前,需先明確:
android.view.Surface
?Surface
中寫入資料?什麼是android.view.Surface
?
android.view.Surface
是 Android 系統中一個重要的圖形渲染類,它用於與硬體顯示層進行通訊,將圖形資料渲染到螢幕上,可以用於實現多種功能,如視訊播放、相機預覽、螢幕錄製等。Surface
物件代表了一個畫布,可以在其上繪製圖形,這些圖形將通過硬體顯示層呈現在螢幕上。可以在 SurfaceView
、TextureView
、WindowManager
等控制元件中使用 Surface
進行圖形渲染。此外,Surface
還可以用於視訊播放、相機預覽、螢幕錄製等功能。Surface是一個Java層類,其持有一個Native (C層) 管理的影象緩衝區控制程式碼。由影象緩衝區的消費者建立(如MediaRecorder),通過Surface將控制程式碼傳遞給影象的生產者 (如MediaPlayer) 進行渲染。
關於android.view.Surface
官方描述如下:
翻譯過來就是:
Surface
持有一個由螢幕合成器管理(Native層管理)的原始緩衝區的控制程式碼
。通常是由影象緩衝區的消費者
(如 SurfaceTexture、MediaRecorder、Allocation等)建立或生成,並被傳遞給生產者
(如 OpenGL、MediaPlayer、CameraDevice等)進行繪製。
因為,Surface
只是持有Native層緩衝區的控制程式碼,若Native層的指標被釋放後,則該Surface
不再有效。
檢視Surface
原始碼,可以看到Surface
通過持有Native層的控制程式碼mNativeObject
來管理原始資料緩衝區。
檢視Surface
官方檔案,檢視其所有的公有方法:
看到上述公有方法後,發現除了通過lockCanvas()
方法可以獲取一個Canvas
物件,然後使用drawBitmap()
等API寫入圖形資料外,並無其他有用的方法,幫助我們實現TextureID
紋理ID的寫入。
要向 Surface
持有的 mNativeObject
控制程式碼中寫入影象資料,我現在已知有兩種方式:
Canvas
寫入;OpenGL
寫入,也就是本文的要介紹的重點;上文介紹到,在檢視Surface
的公有方法後,發現通過lockCanvas()
方法可以獲取一個Canvas
物件,然後使用drawBitmap()
等API寫入圖形資料,操作步驟如下:
SurfaceHolder
獲取 Surface
物件;Surface
物件的 lockCanvas()
方法獲取 Canvas
物件;Canvas
上進行繪製操作,例如呼叫 drawBitmap()
方法繪製點陣圖;Canvas
物件的 unlockCanvasAndPost()
方法將繪製結果提交到 Surface
中,從而實現在螢幕上渲染資料。以下為簡單的程式碼舉例:
SurfaceHolder holder = surfaceView.getHolder();
Surface surface = holder.getSurface();
Canvas canvas = surface.lockCanvas(null);
// 在 canvas 上進行繪製操作
canvas.drawBitmap(bitmap, 0, 0, null);
surface.unlockCanvasAndPost(canvas);
通過OpenGL向Surface中寫入資料,其根本原理是:
將Surface繫結到一個EGLSurface上,然後通過OpenGL向EGLSurface渲染資料,最終將結果渲染到關聯的Surface上。
具體實現是使用 EGL 提供的 eglCreateWindowSurface()
函數,將 EGLSurface
與 Surface
物件關聯起來。然後就可以通過 OpenGL ES 將渲染結果繪製到 EGLSurface 中,最終渲染到Surface上。
其程式碼實現舉例如下所示:
// 獲取 EGLDisplay 物件
EGLDisplay mEglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
// 初始化 EGL 環境
int[] version = new int[2];
EGL14.eglInitialize(mEglDisplay, version, 0, version, 1)
// 設定 attribList
int[] attribList = {
EGL14.EGL_RED_SIZE, 8,
EGL14.EGL_GREEN_SIZE, 8,
EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_ALPHA_SIZE, 8,
//
EGL14.EGL_RENDERABLE_TYPE,
EGL14.EGL_OPENGL_ES2_BIT,
0x3142,
1,
EGL14.EGL_NONE
};
EGLConfig[] configs = new EGLConfig[1];
int[] numConfigs = new int[1];
EGL14.eglChooseConfig(mEglDisplay, attribList, 0, configs, 0, configs.length, numConfigs, 0);
// 獲取 EGLContext 上下文
EGLContext shareEglContext = inEglContext;
// 設定 EGLContext 屬性
final int[] attrib_list = {
EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
EGL14.EGL_NONE
};
// 獲取 EGLDisplay 物件
EGLDisplay eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
// 建立一個 EGLSurface 物件(繫結surface)
EGLSurface eglSurface = EGL14.eglCreateWindowSurface(eglDisplay, configs[0], surface, surfaceAttribs, 0);
// 將 EGLSurface 和 EGLContext 繫結到 EGLDisplay 上
EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
// 渲染圖形 ...
// 交換前後緩衝(將egl渲染結果交換到surface上)
EGL14.eglSwapBuffers(eglDisplay, eglSurface);
將一個GL_TEXTURE_2D型別的紋理ID寫入到ImageReader生成的Surface中,原始碼案例工程下載地址如下:
https://download.csdn.net/download/aiwusheng/87959680
案例程式碼實現流程流程如下:
案例原始碼效果圖如下圖所示: