背景
在客戶端中存在一種應(yīng)用場(chǎng)景:需要將 MediaCodec 或者 Camera 產(chǎn)生的圖像,通過 OpenGL 交給算法做特效假勿,由于算法可能是基于普通的 Texture2D 紋理實(shí)現(xiàn)的借嗽,而 Android 上更常用的則是 GL_TEXTURE_EXTERNAL_OES 紋理,算法一般都是基于 OpenGL 而不是 OpenGLES 環(huán)境實(shí)現(xiàn)的转培,所以就需要客戶端這邊做一個(gè)轉(zhuǎn)換工作恶导。
這個(gè)轉(zhuǎn)換工作當(dāng)然最好是在 GPU 中能完成的,因?yàn)槿绻ㄟ^ CPU 從 OES 紋理中讀出圖像數(shù)據(jù)浸须,再提交到 2D 紋理中惨寿,這一來一回,即浪費(fèi) CPU 頁(yè)占有了內(nèi)存删窒,很不劃算裂垦。所以就出現(xiàn)了這篇文章,如何利用 OpenGL 將 OES 紋理渲染到普通 2D 紋理上肌索。
GL_TEXTURE_EXTERNAL_OES 紋理
外部 GLES 紋理 (GL_TEXTURE_EXTERNAL_OES) 與傳統(tǒng) GLES 紋理 (GL_TEXTURE_2D) 的區(qū)別如下:
- 外部紋理直接在從 BufferQueue 接收的數(shù)據(jù)中渲染紋理多邊形蕉拢。
- 外部紋理渲染程序的配置與傳統(tǒng)的 GLES 紋理渲染程序不同。
- 外部紋理不一定可以執(zhí)行所有傳統(tǒng)的 GLES 紋理活動(dòng)。
外部紋理的主要優(yōu)勢(shì)是它們能夠直接從 BufferQueue 數(shù)據(jù)進(jìn)行渲染晕换。在 Android 平臺(tái)上午乓,BufferQueue 是連接圖形數(shù)據(jù)生產(chǎn)方和消費(fèi)方的隊(duì)列,也就表示 OES 紋理能直接拿到某些生產(chǎn)方產(chǎn)生的圖形數(shù)據(jù)進(jìn)行渲染届巩。
OES Texture 渲染到 TEXTURE_2D
比如現(xiàn)在有個(gè)需求:使用 MediaCodec 解碼視頻硅瞧,最終需要將解碼的每一幀渲染到外部設(shè)置的一個(gè) TEXTURE_2D 紋理上。
實(shí)現(xiàn)方案:MediaCodec 支持將解碼結(jié)果輸出到 Surface 中恕汇,我們可以通過構(gòu)造一個(gè)綁定了 OES 紋理的 SurfaceTexture 來為 MediaCodec 構(gòu)造一個(gè)輸出 Surface腕唧。當(dāng)解碼結(jié)果寫入到 Surface 的 BufferQueue 之后,再利用 SurfaceTexture 將結(jié)果從 BufferQueue 渲染到 OES 紋理上瘾英,然后再通過 OpegGL 管道流水線操作將 OES 紋理上的內(nèi)容渲染到 TEXTURE_2D 紋理:
MediaCodec 解碼到 Surface 偽代碼如下:
oesTextureId = x
sTexture = SurfaceTexture(oesTextureId)
outputSurface = Surface(sTexture)
decoder.setOutputSurface(outputSurface)
復(fù)制代碼
這里可以借鑒 grafika 中 Buffer 的生成和消費(fèi)流程:
然后在參考了 grafika 的流程后設(shè)計(jì)的流程:
正如上圖所示枣接,從 TextureOES 到 Texture2D 的關(guān)鍵是利用 FBO(幀緩沖)。在執(zhí)行 OpenGL 渲染之前缺谴,開始 FBO但惶,渲染完成之后關(guān)閉 FBO。
幀緩沖實(shí)現(xiàn)
如果我們不額外設(shè)置 OpenGL 的幀緩沖湿蛔,OpenGL 所有操作都將在默認(rèn)幀緩沖的渲染緩沖上進(jìn)行膀曾;如果我們激活了自己的幀緩沖,也就是在綁定到 GL_FRAMEBUFFER
目標(biāo)之后阳啥,所有的讀取和寫入幀緩沖的操作將會(huì)影響當(dāng)前綁定的幀緩沖添谊。
所以這里的操作是:創(chuàng)建一個(gè)幀緩沖,將 Texture2D 紋理作為它的顏色緩沖察迟,然后在利用 Shader 從 TextureOES 紋理上采樣之前將這個(gè)幀緩沖設(shè)置為 OpenGL 上下文當(dāng)前激活的幀緩沖斩狱。這樣設(shè)置之后就相當(dāng)于,將 TextureOES 采樣到幀緩沖中扎瓶,而幀緩沖背后又是 Texture2D所踊,就間接的將 TextureOES 采樣到了 Texture2D 上。
class DecodeFBO {
private var mFrameBuffer = -1
init {
val tmp = IntArray(1)
GLES30.glGenFramebuffers(1, tmp, 0)
SLGLUtils.checkGlError("glGenFrameBuffer")
mFrameBuffer = tmp[0]
}
/**
* 綁定 FBO 到 Texture2D 紋理
*/
fun begin(texture2D: Int) {
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFrameBuffer)
SLGLUtils.checkGlError("glBindFrameBuffer")
//將紋理作為幀緩沖對(duì)象的顏色緩沖
GLES30.glFramebufferTexture2D(
GLES30.GL_FRAMEBUFFER,
GLES30.GL_COLOR_ATTACHMENT0,
GLES30.GL_TEXTURE_2D,
texture2D,
0
)
checkGlError("glFramebufferTexture2D")
val status = GLES30.glCheckFramebufferStatus(GLES30.GL_FRAMEBUFFER)
if (status != GLES30.GL_FRAMEBUFFER_COMPLETE) {
Log.e(TAG, "bind FBO failed概荷!")
return
}
}
fun end() {
GLES30.glFramebufferTexture2D(
GLES30.GL_FRAMEBUFFER,
GLES30.GL_COLOR_ATTACHMENT0,
GLES30.GL_TEXTURE_2D,
0,
0
)
checkGlError("detach texture from FBO")
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)
checkGlError("deactivate FBO")
}
fun release() {
GLES30.glDeleteFramebuffers(1, IntArray(1) { mFrameBuffer }, 0)
checkGlError("glDeleteFramebuffers")
}
}
復(fù)制代碼
著色器實(shí)現(xiàn)
這里的著色器就不復(fù)雜了秕岛,就是從一個(gè)紋理上采樣,然后設(shè)置給 gl_FragColor
误证。
頂點(diǎn)著色器:
private static final String VERTEX_SHADER =
"uniform mat4 uMVPMatrix;\n" +
"attribute vec4 aPosition;\n" +
"attribute vec4 aTextureCoord;\n" +
"varying vec2 vTextureCoord;\n" +
"void main() {\n" +
" gl_Position = uMVPMatrix * aPosition;\n" +
" vTextureCoord = aTextureCoord.xy;\n" +
"}\n";
復(fù)制代碼
片段著色器:
private static final String FRAGMENT_SHADER =
"#extension GL_OES_EGL_image_external : require\n" +
"precision mediump float;\n" +
"varying vec2 vTextureCoord;\n" +
"uniform sampler2D sTexture;\n" +
"void main() {\n" +
" gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
"}\n";
作者:StefanJi
鏈接:https://juejin.cn/post/7012517274768179236
來源:稀土掘金
著作權(quán)歸作者所有继薛。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處雷厂。
更多Android技術(shù)分享可以關(guān)注@我,也可以加入QQ群號(hào):Android進(jìn)階學(xué)習(xí)群:345659112惋增,一起學(xué)習(xí)交流叠殷。