解密视频魔法:将ExternalOES纹理转化为TEXTURE_2D纹理
在使用OpenGL ES進行圖形圖像開發時,我們常使用
GL_TEXTURE_2D紋理類型,它提供了對標準2D圖像的處理能力。這種紋理類型適用于大多數場景,可以用于展示靜態貼圖、渲染2D圖形和進行圖像處理等操作。
另外,有時我們需要從Camera或外部視頻源讀取數據幀并進行處理。這時,我們會使用GL_TEXTURE_EXTERNAL_OES紋理類型。其專門用于對外部圖像或實時視頻流進行處理,可以直接從 BufferQueue?中接收的數據渲染紋理多邊形,從而提供更高效的視頻處理和渲染性能。
在實際應用中,我們通常將GL_TEXTURE_2D和GL_TEXTURE_EXTERNAL_OES這兩種紋理類型分開使用,并且它們互不干擾。實際上,這種情況占據了80%的使用場景。我們可以根據具體需求選擇合適的紋理類型進行處理和渲染。
然而,有時候我們也會遇到一些特殊情況,需要將GL_TEXTURE_EXTERNAL_OES紋理轉化為GL_TEXTURE_2D紋理進行視頻處理或計算。這種情況可能出現在需要對視頻數據進行特殊的圖像處理或者與GL_TEXTURE_2D紋理類型的其他渲染操作進行交互時。
當以上情況出現時,我們該如何處理呢?難道是直接將GL_TEXTURE_EXTERNAL_OES紋理賦值給GL_TEXTURE_2D紋理使用(經過實驗這種方式是不可用的)?
這里對此情況,先給出解決方案,一般我們可以通過一些技術手段,如離屏渲染或FrameBuffer幀緩沖區對象,將GL_TEXTURE_EXTERNAL_OES紋理轉換為GL_TEXTURE_2D紋理,并進行后續的處理和計算。
而此篇文章主要記錄,我是如何通過FrameBuffer幀緩沖區對象,將GL_TEXTURE_EXTERNAL_OES紋理數據轉化為GL_TEXTURE_2D紋理數據的!
- 首先 回顧一下GL_TEXTURE_2D紋理與GL_TEXTURE_EXTERNAL_OES紋理;
-
GL_TEXTURE_EXTERNAL_OES紋理數據通過FrameBuffer轉化為GL_TEXTURE_2D紋理數據;
一、TEXTURE_2D 與 EXTERNAL_OES
在正式研究 “GL_TEXTURE_EXTERNAL_OES紋理數據轉化為GL_TEXTURE_2D紋理數據” 之前,先要搞清楚:
什么是GL_TEXTURE_2D紋理?什么又是GL_TEXTURE_EXTERNAL_OES紋理?GL_TEXTURE_2D紋理與GL_TEXTURE_EXTERNAL_OES紋理有什么樣的區別?
1.1 GL_TEXTURE_2D紋理
GL_TEXTURE_2D 提供了對標準2D圖像的處理能力,可以存儲靜態的貼圖、圖像或者幀緩沖區的渲染結果。
其使用二維的紋理坐標系,通過將紋理坐標映射到紋理圖像上的對應位置,可以實現紋理貼圖、紋理過濾、紋理環繞等操作。
GL_TEXTURE_2D紋理的特點包括:
- 使用
二維紋理坐標系進行操作; - 使用
glTexImage2D函數加載紋理數據; - 通過紋理過濾和紋理環繞等方式進行紋理的采樣和處理;
GL_TEXTURE_2D紋理:創建、綁定、采樣、加載紋理圖像
public static int createDrawableTexture2D(Context context, int drawableId) {
// 生成紋理ID
int[] textures = new int[1];
GLES30.glGenTextures(1, textures, 0);
// 綁定紋理
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[0]);
// 紋理采樣方式
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
// texImage2D加載圖像數據
InputStream is = context.getResources().openRawResource(drawableId);
Bitmap bitmapTmp;
try {
bitmapTmp = BitmapFactory.decodeStream(is);
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
GLUtils.texImage2D(GLES30.GL_TEXTURE_2D,0, GLUtils.getInternalFormat(bitmapTmp), bitmapTmp, GLUtils.getType(bitmapTmp), 0 );
bitmapTmp.recycle();
return textures[0];
}
GL_TEXTURE_2D紋理:Shader處理階段(片元著色器)
precision mediump float;
varying vec2 v_texture_coord;
uniform sampler2D MAIN;
void main() {
vec4 color=texture2D(MAIN, v_texture_coord);
gl_FragColor=color;
}
GL_TEXTURE_2D紋理:紋理渲染
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, texId);
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mVertexCount);
1.2 GL_TEXTURE_EXTERNAL_OES紋理類型
根據AOSP: SurfaceTexture 文檔描述,GL_TEXTURE_EXTERNAL_OES 是一種特殊的紋理類型,主要用于處理外部圖像或視頻數據,如從攝像頭捕捉的實時圖像和外部視頻流。
GL_TEXTURE_EXTERNAL_OES 相對于 GL_TEXTURE_2D 最大的特點就是 GL_TEXTURE_EXTERNAL_OES可直接從 BufferQueue?中接收的數據渲染紋理多邊形。
GL_TEXTURE_EXTERNAL_OES紋理類型的特點包括:
- 需采用
特殊的采樣器類型和紋理著色器擴展。 - 使用二維紋理坐標系進行操作,與GL_TEXTURE_2D相似。
- 專門用于
處理外部圖像或視頻數據,可直接從 BufferQueue?中接收的數據渲染紋理多邊形,從而提供更高效的視頻處理和渲染性能。
對于此,官方文檔中提供了一個 Grafika 的連續拍攝案例工程,并給出了如下參考流程圖。
通過閱讀 Grafika 的連續拍攝案例,我們得知:
- 首先,需創建一個
OES紋理ID,用于接收Camera圖像數據;
// GL_TEXTURE_EXTERNAL_OES: 紋理創建、綁定、采樣
public static int createTextureOES() {
// 創建OES紋理ID
int[] textures = new int[1];
GLES30.glGenTextures(1, textures, 0);
TextureUtil.checkGlError("glGenTextures");
// 綁定OES紋理ID
int texId = textures[0];
GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texId);
TextureUtil.checkGlError("glBindTexture " + texId);
// OES紋理采樣
GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
GLES30.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
GLES30.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
TextureUtil.checkGlError("glTexParameter");
return texId;
}
- 完成
OES紋理ID創建后,通過oesTextureId創建一個圖像消費者SurfaceTexture,將SurfaceTexture設定為預覽的PreviewTexture;
// 傳入一個OES紋理ID
SurfaceTexture mSurfaceTexture = new SurfaceTexture(oesTextureId);
// 將 SurfaceTexture 設置為預覽的 PreviewTexture
Camera.setPreviewTexture(mSurfaceTexture);
- 或者通過
SurfaceTexture創建Surface,將Surface對象傳遞給MediaPlayer或MediaCodec進行視頻幀數據獲取;
// 傳入一個OES紋理ID
SurfaceTexture mSurfaceTexture = new SurfaceTexture(oesTextureId);
// 創建 Surface
Surface mSurface = new Surface(mSurfaceTexture);
// 將 Surface 設置給 MediaPlayer 外部視頻播放器,獲取視頻幀數據
MediaPlayer.setSurface(surface);
- 前文已經提到
GL_TEXTURE_EXTERNAL_OES紋理類型 可直接從Surface對應的BufferQueue中獲取視頻流數據; - 在獲取到視頻幀數據后:
一方面,可通過OpenGL的渲染管線,將GL_TEXTURE_EXTERNAL_OES紋理渲染到GLSurfaceView上,完成圖像數據的預覽;
另一方面,可將GL_TEXTURE_EXTERNAL_OES紋理,通過離屏渲染的形式,寫入到 MediaCodeC,硬編碼生成MP4視頻。
// GL_TEXTURE_EXTERNAL_OES紋理:Shader處理階段(片元著色器)
#extension GL_OES_EGL_image_external : require
precision mediump float;
varying vec2 v_texture_coord;
uniform samplerExternalOES MAIN;
void main() {
vec4 color=texture2D(MAIN, v_texture_coord);
gl_FragColor=color;
}
GL_TEXTURE_EXTERNAL_OES紋理:紋理渲染
// 紋理渲染階段:GL_TEXTURE_EXTERNAL_OES紋理
GLES30.glBindTexture(GLES30.GL_TEXTURE_EXTERNAL_OES, texId);
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mVertexCount);
1.3 關于兩者的區別 我的個人理解
關于兩者的區別,我的個人理解:
GL_TEXTURE_2D紋理類型與GL_TEXTURE_EXTERNAL_OES紋理類型,在數據來源與紋理數據的存儲格式上存在差異。
-
數據來源方面:
一個來源于glTexImage2D加載的二維圖像數據;
一個來源與圖像消費者Surface對應的BufferQueue; -
紋理存儲格式:
GL_TEXTURE_EXTERNAL_OES數據來源于外部視頻源或Camera,其數據格式可能為YUV或RGB;
GL_TEXTURE_2D的數據格式則依賴于開發中setEGLConfigChooser(int redSize, int greenSize, int blueSize, int alphaSize, int depthSize, int stencilSize)的配置,可能是RGBA8888,也可能是RGBA4444等等。
由于兩者數據來源和紋理存儲格式的差異,兩種紋理類型是不能直接進行轉化的。
- 首先,其在
紋理采樣階段、Shader處理階段和紋理渲染階段均不同程度的存在差異(這一點在上一節的兩者對比的代碼舉例中可以證明)。 - 其次,如果需要在處理和計算階段將
GL_TEXTURE_EXTERNAL_OES紋理轉換為GL_TEXTURE_2D紋理,通常需要使用離屏渲染或幀緩沖區對象等技術手段。
二、EXTERNAL_OES轉化為TEXTURE_2D紋理數據
這里直接介紹轉化過程:
- 首先,需創建一個
OES紋理ID(相關代碼舉例在前文已經給出); - 完成
OES紋理ID創建后,通過oesTextureId創建一個圖像消費者SurfaceTexture(相關代碼舉例在前文已經給出); - 通過
SurfaceTexture創建Surface,將Surface對象傳遞給MediaPlayer,獲取Sdcard中對應路徑的視頻幀數據獲取(相關代碼舉例在前文已經給出); - 創建
FRAMEBUFFER幀緩沖區,并綁定GL_TEXTURE_2D空白紋理對象;
public static int createEmptyTexture2DBindFrameBuffer(int[] frameBuffer, int texPixWidth, int texPixHeight) {
// 創建紋理ID
int[] textures = new int[1];
GLES30.glGenTextures(1, textures, 0);
// 綁定GL_TEXTURE_2D紋理
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[0]);
// 紋理采樣
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
// 創建一個空的2D紋理對象,指定其基本參數,并綁定到對應的紋理ID上
GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, texPixWidth, texPixHeight,0, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, null);
// 取消綁定紋理
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, GLES30.GL_NONE);
/**
* 幀緩沖區
*/
// 創建幀緩沖區
GLES30.glGenFramebuffers(1, frameBuffer, 0);
// 將幀緩沖對象綁定到OpenGL ES上下文的幀緩沖目標上
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, frameBuffer[0]);
// 使用GLES30.GL_COLOR_ATTACHMENT0將紋理作為顏色附著點附加到幀緩沖對象上
GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, textures[0], 0);
// 取消綁定緩沖區
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, GLES30.GL_NONE);
return textures[0];
}
- 將
GL_TEXTURE_EXTERNAL_OES紋理渲染到FRAMEBUFFER幀緩沖區中;
// 激活紋理
GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
// 將所需的紋理對象綁定到Shader中紋理單元0上
GLES30.glUniform1i(mOesTextureIdHandle, 0);
// 綁定紋理
GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texId);
// 綁定FRAMEBUFFER緩沖區
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, tex2DFrameBufferId);
// 繪制矩形
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mVertexCount);
// 取消FRAMEBUFFER的綁定
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, GLES30.GL_NONE);
- 最后,繪制
渲染GL_TEXTURE_2D紋理,完成紋理圖像的顯示。
// 激活紋理
GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
// 將所需的紋理對象綁定到Shader中紋理單元0上
GLES30.glUniform1i(mTex2DIdHandle, 0);
// 綁定紋理
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, tex2DId);
// 繪制矩形
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mVertexCount);
三、源碼下載
ExternalOES紋理數據 轉換為 TEXTURE-2D紋理數據:
https://download.csdn.net/download/aiwusheng/88650498
參考
AOSP:SurfaceTexture
https://source.android.google.cn/docs/core/graphics/arch-st?hl=zh-c
Github:Google Grafika
https://github.com/google/grafika/blob/master/app/src/main/java/com/android/grafika/ContinuousCaptureActivity.java
OpenGL渲染管線:
https://xiaxl.blog.csdn.net/article/details/121467207
紋理ID 離屏渲染 寫入到Surface中:
https://xiaxl.blog.csdn.net/article/details/131682521
MediaCodeC與OpenGL硬編碼錄制mp4:
https://xiaxl.blog.csdn.net/article/details/72530314
總結
以上是生活随笔為你收集整理的解密视频魔法:将ExternalOES纹理转化为TEXTURE_2D纹理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 海盗船发布 2000D AIRFLOW
- 下一篇: 语义化的HTML结构有哪些对于对网站的优