原本按照計劃,筆者應該在這篇文章給你們介紹如何使用EGL API建立自定義OpenGL環境,可是在寫demo的過程當中反覆思考,與其作單個的demo功能還不如寫一個app,也方便你們在開發工做中根據使用場景來借鑑代碼和思路。so,在這篇文章中,會向你們介紹如何使用OpenGL給相機預覽畫面添加實時濾鏡以及使用MediaCodec+MediaMuxer錄製咱們的預覽畫面保存成MP4文件。android
在上篇文章中,咱們經過一個使用samplerExternalOES採樣器的shader來渲染相機的實時預覽數據,這樣徹底體現不出咱們使用OpenGL ES的優點,因此咱們在這裏就能夠利用OpenGL ES這個圖形渲染庫來給相機的實時預覽數據添加一些實時濾鏡,咱們的shader程序是運行在GPU的,緣由是GPU和CPU比較起來,更擅長一些浮點運算。不過在此以前,咱們須要先了解一下OpenGL中FrameBuffer的概念。git
在OpenGL中,渲染管線中的頂點、紋理等通過一系列的處理後,最終顯示在2D屏幕上,渲染管線的最終目的地就是幀緩衝區。幀緩衝區包括OpenGL使用的顏色緩衝區(Color buffer)、深度緩衝區(Depth buffer)、模板緩衝區(Stencil buffer)等緩衝區。默認的幀緩衝區由窗口系統建立。這個默認的緩衝區,就是目前咱們一直使用的繪圖命令的做用對象,稱之爲窗口系統提供的幀緩衝區。OpenGL也容許咱們手動建立一個幀緩衝區,並將渲染結果重定向到這個緩衝區。在建立時容許咱們自定義幀緩衝區的一些特性,這個自定義緩衝區,稱之爲應用程序幀緩衝區。同默認的幀緩衝區同樣,自定義的幀緩衝區也包含顏色緩衝區、深度和模板緩衝區,這些邏輯上的緩衝區(logical buffers)在FBO中稱之爲可附加的圖像(framebuffer-attachable images),他們是能夠附加到FBO的二維像素數組(2D arrays of pixels )。FBO中包含兩種類型的附加圖像(framebuffer-attachable): 紋理圖像和RenderBuffer圖像(texture images and renderbuffer images)。附加紋理時OpenGL渲染到這個紋理圖像,在着色器中能夠訪問到這個紋理對象;附加RenderBuffer時,OpenGL執行離屏渲染(offscreen rendering)。之因此用附加這個詞,表達的是FBO能夠附加多個緩衝區,並且能夠靈活地在緩衝區中切換,一個重要的概念是附加點(attachment points)。一個FBO能夠有 (GL_COLOR_ATTACHMENT0,…, GL_COLOR_ATTACHMENTn) 多個附加點,最多的附加點能夠經過查詢GL_MAX_COLOR_ATTACHMENTS變量獲取。值得注意的是:FBO自己並不包含任何緩衝對象,其實是經過附加點來指向實際的緩衝對象的。這樣FBO能夠快速的切換緩衝對象。github
OK,說了這麼多FBO(FrameBufferObject)的概念,那它究竟在咱們作實時相機預覽數據渲染中起到一個什麼做用呢? FBO在使用時,能夠綁定一個輸出紋理,咱們能夠利用這個特性來作幾個渲染結果的銜接。web
建立FBO和銷燬的步驟很簡單:算法
void GLES20.glGenFramebuffers(int n, int[] framebuffers, int offset);
void GLES20.glDeleteFramebuffers(int n, int[] framebuffers, int offset);
複製代碼
RGB轉爲單色[0~256]之間的灰度,最經常使用的轉換公式以下:數組
Gray = 0.299 * red + 0.587 * green + 0.114 * blue;app
因此咱們建立一個着色器程序:函數
precision mediump float;
varying highp vec2 vTexCoord;
uniform sampler2D sTexture;
void main() {
lowp vec4 textureColor = texture2D(sTexture, vTexCoord);
float gray = textureColor.r * 0.299 + textureColor.g * 0.587 + textureColor.b * 0.114;
gl_FragColor = vec4(gray, gray, gray, textureColor.w);
}
複製代碼
注意,這裏咱們使用的採樣器是sampler2D而不是samplerExternalOES!性能
按照正常的OpenGL program建立流程建立出咱們的灰度處理效果的程序後,咱們還須要將它和咱們渲染相機數據的program銜接起來。ui
1.建立FrameBuffer
private int createFramebuffer() {
int[] framebuffers = new int[1];
GLES20.glGenFramebuffers(1, framebuffers, 0);
return framebuffers[0];
}
複製代碼
2.建立與之綁定的輸出紋理
private int createTexture(int width, int height) {
int[] textures = new int[1];
GLES20.glGenTextures(1, textures, 0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
// 邊緣重複像素
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
// 縮小和放大時使用臨近插值過濾
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
return textures[0];
}
複製代碼
3.銜接。在咱們繪製OES紋理前,綁定一個FBO並綁定輸出紋理。 CameraFilter類中onDraw函數
{
MGLUtils.bindFrameTexture(mFrameBufId, mOutputTexId);
mOESFilter.draw();
MGLUtils.unBindFrameBuffer();
}
複製代碼
在咱們繪製灰度程序前,將CameraFilter的輸出紋理設置給GrayFilter,一樣的,在繪製前咱們也要綁定一個FBO並綁定輸出紋理。最後,使用咱們最終輸出的shader向窗口系統提供的緩衝區繪製咱們最終處理後的數據,就能夠將咱們實時的灰度濾鏡添加到相機預覽數據上了。shader以下:
precision mediump float;
varying vec2 vTexCoord;
uniform sampler2D sTexture;
void main() {
gl_FragColor = texture2D(sTexture, vTexCoord);
}
複製代碼
網上有不少介紹OpenGL Color Lookup Table顏色查找表的文章,可是能說清楚的很少。記得以前看過一篇文章,裏面介紹瞭如何用代碼生成Color Lookup Table圖片,再對照shader讀起來豁然開朗。
實際上Color Lookup Table(一下簡稱lut)就是一張顏色查找表,它看起來長這樣:
網上有介紹說這是一個64x64x64的三維立方體,固然這樣說也沒錯,只是對於新手來講,不方便理解。
上圖中的lut圖片是一張寬512像素、高512像素的正方形圖,它的x軸和y軸分別有8個大格子。那麼就能夠算出,每一個大格子的寬和高分別有64個像素。
512 / 8 = 64
複製代碼
每一個大格子的x軸,是RGB中的R紅色從0~255的分佈,根據這個能夠算出每一個像素的紅色色度差值是4。
256 / 64 = 4
複製代碼
每一個大格子的y軸,是RGB中的G綠色從0~255的分佈,一樣的,綠色色度的差值也是4。
那麼RGB中的B藍色,在這張lut表中是如何分佈的呢?答案是B藍色從0~255是分佈於每一個大格子裏面的,從左上角的第1個大格子到右下角第64個大格子。差值一樣是4。
瞭解了這些,咱們就能夠對照lut的shader來分析它的原理。
precision mediump float;
varying highp vec2 vTexCoord;
uniform sampler2D sTexture;
uniform sampler2D maskTexture;
const highp float intensity = 1.0;
void main() {
highp vec4 textureColor = texture2D(sTexture, vTexCoord);
highp float blueColor = textureColor.b * 63.0;
highp vec2 quad1;
quad1.y = floor(floor(blueColor) / 8.0);
quad1.x = floor(blueColor) - (quad1.y * 8.0);
highp vec2 quad2;
quad2.y = floor(ceil(blueColor) / 8.0);
quad2.x = ceil(blueColor) - (quad2.y * 8.0);
highp vec2 texPos1;
texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);
texPos1.y = (quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g);
highp vec2 texPos2;
texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);
texPos2.y = (quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g);
lowp vec4 newColor1 = texture2D(maskTexture, texPos1);
lowp vec4 newColor2 = texture2D(maskTexture, texPos2);
lowp vec4 newColor = mix(newColor1, newColor2, fract(blueColor));
gl_FragColor = mix(textureColor, vec4(newColor.rgb, textureColor.w), intensity);
}
複製代碼
這個shader的源碼摘自webRTC.
從上面的glsl代碼中能夠看出,它首先是用須要查表的原圖紋理中的B藍色來定位是在哪個大格子中,好比藍色、紅色和綠色色值均爲128,在shader中由於顏色值是從0~1的,因此歸一化能夠知道,128對應的是0.5。
// blueColor = 31.5 = 0.5 * 63.0;
highp float blueColor = textureColor.b * 63.0;
複製代碼
根據這一句代碼,咱們能夠知道定位到的大格子是第32個附近。接下來須要將其轉換爲笛卡爾座標:
highp vec2 quad1;
// quad1.y = 3.875 = 31 / 8
quad1.y = floor(floor(blueColor) / 8.0);
// quad1.x = 0 = 31 - (3.875 * 8)
quad1.x = floor(blueColor) - (quad1.y * 8.0);
highp vec2 quad2;
// quad2.y = 4 = 32 / 8
quad2.y = floor(ceil(blueColor) / 8.0);
// quad2.x = 32 - (4 * 8)
quad2.x = ceil(blueColor) - (quad2.y * 8.0);
複製代碼
爲了不偏差,這裏向下取整和向上取整獲得兩組座標值。
這樣,咱們就能夠獲得查找的大格子在這張lut表紋理中的座標了。
大概是這個位置,但此時仍是以x、y軸均是0~7的座標值來表示的。知道了大格子的具體座標後,也就知道了藍色的色值,接下來咱們就要查找R和G的色值了,前面說過,每一個大格子的x軸和y軸分別是R和G從0~255,那麼咱們就能夠知道,其實每一個大格子里根據色值差值4,又分爲64個小格子。 接下來的幾句是這樣的:
highp vec2 texPos1;
// 0.125是由於OpenGL裏的座標值是從0~1表示的,0.125表示1/8
// 這裏乘1/8是爲了將0~7的座標值歸一化爲0~1
// 1 / 512是爲了將0~512的座標值小格子寬度歸一化爲0~1的座標值
// 能夠獲得1個像素寬度在歸一化座標後爲0.001953125 再用原圖紋理紅色乘歸一化後的像素寬度
// 就能夠獲得咱們要查的那個小格子的座標 爲了更好的定位具體的像素顏色
// 這裏取小格子x軸和y軸的中心點 + 0.5 / 512
// texPos1.x = 0.0625 = 0 + 0.0009765625 + 0.0615234375
texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);
// 綠色通道也是同樣的
texPos1.y = (quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g);
// 這裏是爲了防止偏差
highp vec2 texPos2;
texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);
texPos2.y = (quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g);
複製代碼
最後,將向下取整的獲得的座標和向上取整獲得的紋理座標利用內建函數texture2D,就能夠獲得咱們查表後用來和原圖混合的片元了。
// 向下取整獲得的新顏色
lowp vec4 newColor1 = texture2D(maskTexture, texPos1);
// 向上取整獲得的新顏色
lowp vec4 newColor2 = texture2D(maskTexture, texPos2);
// 這裏使用glsl的內建函數將獲得的兩個新顏色線性混合
lowp vec4 newColor = mix(newColor1, newColor2, fract(blueColor));
// 最後的輸出 textureColor.w表示原圖的透明度
// 這裏能夠用intensity來控制濾鏡的濃度
gl_FragColor = mix(textureColor, vec4(newColor.rgb, textureColor.w), intensity);
複製代碼
這裏按照正常的GLES API來建立program正常使用就能夠了,和上面同樣要使用FBO作銜接。由於相機使用的紋理是Android的一種特殊紋理,它有不少GLES API是不能使用的,並且作像素算法效率也不高。
網上介紹MediaCodec的文章有不少,官方文檔對此API也介紹的蠻好的,這裏就不作過多的介紹了。不少人都說Android MediaCodec系列的API是最難用的API之一。
在早期的時候,筆者作視頻編碼是沒有使用OpenGL的(相關API也是在API 18之後纔有的)。當時的作法是將Camera的previewCallback獲得的原始數據處理事後交給MediaCodec編碼。通常來講Camera preview返回的數據是NV21,這個時候咱們就須要使用API查詢一下MediaCodec支持什麼樣的ColorFormat,若是不支持NV21咱們還須要將NV21數據轉換爲其支持的格式之一,這樣就會有性能問題和兼容問題。
在API 18,MediaCodec有了一個新的API
Surface createInputSurface()
複製代碼
咱們能夠獲得一個Surface對象,這樣咱們就可使用EGL提供的API構造一個本地窗口,咱們往這個本地窗口上渲染圖像時(離屏渲染),就能夠通知MediaCodec進行視頻編碼了,免除了咱們獲取數據和處理數據的過程,一樣的,咱們能夠在離屏渲染時渲染一些咱們想要添加的一些附加效果。 由於要離屏渲染,咱們須要手動建立一個單獨的線程並綁定EGLContext來初始化OpenGL環境,這條OpenGL線程是獨立於渲染的OpenGL線程(也就是咱們借的GLSurfaceView的OpenGL線程)的,那麼如何傳遞數據呢?
EGL支持共享上下文。廢話少說,放代碼:
import android.graphics.SurfaceTexture;
import android.opengl.EGL14;
import android.opengl.EGLConfig;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.EGLSurface;
import android.os.Build;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class EGLBase {
private static final String TAG = EGLBase.class.getSimpleName();
private static final int EGL_RECORDABLE_ANDROID = 0x3142;
private EGLConfig mEglConfig;
private EGLContext mEglContext = EGL14.EGL_NO_CONTEXT;
private EGLDisplay mEglDisplay = EGL14.EGL_NO_DISPLAY;
private EGLContext mDefaultContext = EGL14.EGL_NO_CONTEXT;
public EGLBase(EGLContext sharedContext, boolean withDepthBuffer, boolean isRecordable) {
init(sharedContext, withDepthBuffer, isRecordable);
}
private void init(EGLContext sharedContext, boolean withDepthBuffer, boolean isRecordable) {
Log.i(TAG, "init");
if (mEglDisplay != EGL14.EGL_NO_DISPLAY) {
throw new IllegalStateException("EGL already set up!");
}
mEglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
if (mEglDisplay == EGL14.EGL_NO_DISPLAY) {
throw new RuntimeException("eglGetDisplay failed!");
}
final int[] version = new int[2];
if (!EGL14.eglInitialize(mEglDisplay, version, 0, version, 1)) {
mEglDisplay = null;
throw new RuntimeException("eglInitialize failed");
}
sharedContext = sharedContext != null ? sharedContext : EGL14.EGL_NO_CONTEXT;
if (mEglContext == EGL14.EGL_NO_CONTEXT) {
mEglConfig = getConfig(withDepthBuffer, isRecordable);
if (mEglConfig == null) {
throw new RuntimeException("chooseConfig failed!");
}
mEglContext = createContext(sharedContext);
}
final int[] values = new int[1];
EGL14.eglQueryContext(mEglDisplay, mEglContext, EGL14.EGL_CONTEXT_CLIENT_VERSION, values, 0);
makeDefault();
}
private EGLConfig getConfig(boolean withDepthBuffer, boolean isRecordable) {
final int[] attribList = {
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
EGL14.EGL_RED_SIZE, 8,
EGL14.EGL_GREEN_SIZE, 8,
EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_ALPHA_SIZE, 8,
EGL14.EGL_NONE, EGL14.EGL_NONE, //EGL14.EGL_STENCIL_SIZE, 8,
EGL14.EGL_NONE, EGL14.EGL_NONE, //EGL_RECORDABLE_ANDROID, 1, // this flag need to recording of MediaCodec
EGL14.EGL_NONE, EGL14.EGL_NONE, // with_depth_buffer ? EGL14.EGL_DEPTH_SIZE : EGL14.EGL_NONE,
// with_depth_buffer ? 16 : 0,
EGL14.EGL_NONE
};
int offset = 10;
if (withDepthBuffer) {
attribList[offset++] = EGL14.EGL_DEPTH_SIZE;
attribList[offset++] = 16;
}
if (isRecordable && (Build.VERSION.SDK_INT >= 18)) { // 配合MediaCodec InputSurface
attribList[offset++] = EGL_RECORDABLE_ANDROID;
attribList[offset++] = 1;
}
for (int i = attribList.length - 1; i >= offset; i--) {
attribList[i] = EGL14.EGL_NONE;
}
final EGLConfig[] configs = new EGLConfig[1];
final int[] numConfigs = new int[1];
if (!EGL14.eglChooseConfig(mEglDisplay, attribList, 0, configs, 0, configs.length,
numConfigs, 0)) {
// XXX it will be better to fallback to RGB565
Log.w(TAG, "unable to find RGBA8888 / " + " EGLConfig");
return null;
}
return configs[0];
}
private EGLContext createContext(EGLContext sharedContext) {
final int[] attributeList = {
EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
EGL14.EGL_NONE
};
EGLContext context = EGL14.eglCreateContext(mEglDisplay, mEglConfig, sharedContext,
attributeList, 0);
return context;
}
private void makeDefault() {
if (!EGL14.eglMakeCurrent(mEglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
EGL14.EGL_NO_CONTEXT)) {
Log.w("TAG", "makeDefault" + EGL14.eglGetError());
}
}
private void destroyContext() {
Log.i(TAG, "destroyContext");
if (!EGL14.eglDestroyContext(mEglDisplay, mEglContext)) {
Log.e("destroyContext", "display:" + mEglDisplay + " context: "
+ mEglContext);
Log.e(TAG, "eglDestroyContex:" + EGL14.eglGetError());
}
mEglContext = EGL14.EGL_NO_CONTEXT;
if (mDefaultContext != EGL14.EGL_NO_CONTEXT) {
if (!EGL14.eglDestroyContext(mEglDisplay, mDefaultContext)) {
Log.e("destroyContext", "display:" + mEglDisplay + " context: "
+ mDefaultContext);
Log.e(TAG, "eglDestroyContex:" + EGL14.eglGetError());
}
mDefaultContext = EGL14.EGL_NO_CONTEXT;
}
}
private EGLSurface createWindowSurface(Object nativeWindow) {
final int[] surfaceAttribs = {
EGL14.EGL_NONE
};
EGLSurface result = null;
try {
result = EGL14.eglCreateWindowSurface(mEglDisplay, mEglConfig, nativeWindow,
surfaceAttribs, 0);
} catch (final IllegalArgumentException e) {
Log.e(TAG, "eglCreateWindowSurface", e);
}
return result;
}
private EGLSurface createOffscreenSurface(final int width, final int height) {
Log.v(TAG, "createOffscreenSurface:");
final int[] surfaceAttribs = {
EGL14.EGL_WIDTH, width,
EGL14.EGL_HEIGHT, height,
EGL14.EGL_NONE
};
EGLSurface result = null;
try {
result = EGL14.eglCreatePbufferSurface(mEglDisplay, mEglConfig, surfaceAttribs, 0);
if (result == null) {
throw new RuntimeException("surface was null");
}
} catch (final IllegalArgumentException e) {
Log.e(TAG, "createOffscreenSurface", e);
} catch (final RuntimeException e) {
Log.e(TAG, "createOffscreenSurface", e);
}
return result;
}
private boolean makeCurrent(final EGLSurface surface) {
if (mEglDisplay == null) {
Log.w(TAG, "makeCurrent: eglDisplay not initialized");
}
if (surface == null || surface == EGL14.EGL_NO_SURFACE) {
final int error = EGL14.eglGetError();
if (error == EGL14.EGL_BAD_NATIVE_WINDOW) {
Log.e(TAG, "makeCurrent:returned EGL_BAD_NATIVE_WINDOW.");
}
return false;
}
// attach EGL renderring context to specific EGL window surface
if (!EGL14.eglMakeCurrent(mEglDisplay, surface, surface, mEglContext)) {
Log.w(TAG, "eglMakeCurrent:" + EGL14.eglGetError());
return false;
}
return true;
}
private void destroyWindowSurface(EGLSurface surface) {
Log.v(TAG, "destroySurface:");
if (surface != EGL14.EGL_NO_SURFACE) {
EGL14.eglMakeCurrent(mEglDisplay,
EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
EGL14.eglDestroySurface(mEglDisplay, surface);
}
surface = EGL14.EGL_NO_SURFACE;
Log.v(TAG, "destroySurface: finished");
}
private int swap(final EGLSurface surface) {
if (!EGL14.eglSwapBuffers(mEglDisplay, surface)) {
final int err = EGL14.eglGetError();
Log.w(TAG, "swap: err=" + err);
return err;
}
return EGL14.EGL_SUCCESS;
}
public EglSurface createFromSurface(final Object surface) {
final EglSurface eglSurface = new EglSurface(this, surface);
eglSurface.makeCurrent();
return eglSurface;
}
public EglSurface createOffScreen(int width, int height) {
final EglSurface eglSurface = new EglSurface(this, width, height);
eglSurface.makeCurrent();
return eglSurface;
}
public EGLContext getContext() {
return mEglContext;
}
public int querySurface(final EGLSurface eglSurface, final int what) {
final int[] value = new int[1];
EGL14.eglQuerySurface(mEglDisplay, eglSurface, what, value, 0);
return value[0];
}
public void release() {
Log.i(TAG, "release");
if (mEglDisplay != EGL14.EGL_NO_DISPLAY) {
destroyContext();
EGL14.eglTerminate(mEglDisplay);
EGL14.eglReleaseThread();
}
mEglDisplay = EGL14.EGL_NO_DISPLAY;
mEglContext = EGL14.EGL_NO_CONTEXT;
}
public static class EglSurface {
private final EGLBase mEgl;
private final int mWidth, mHeight;
private EGLSurface mEglSurface = EGL14.EGL_NO_SURFACE;
EglSurface(EGLBase egl, final Object surface) {
if (!(surface instanceof SurfaceView)
&& !(surface instanceof Surface)
&& !(surface instanceof SurfaceHolder)
&& !(surface instanceof SurfaceTexture))
throw new IllegalArgumentException("unsupported surface");
mEgl = egl;
mEglSurface = mEgl.createWindowSurface(surface);
mWidth = mEgl.querySurface(mEglSurface, EGL14.EGL_WIDTH);
mHeight = mEgl.querySurface(mEglSurface, EGL14.EGL_HEIGHT);
Log.v(TAG, String.format("EglSurface:size(%d,%d)", mWidth, mHeight));
}
EglSurface(final EGLBase egl, final int width, final int height) {
Log.v(TAG, "EglSurface:");
mEgl = egl;
mEglSurface = mEgl.createOffscreenSurface(width, height);
mWidth = width;
mHeight = height;
}
public void makeCurrent() {
mEgl.makeCurrent(mEglSurface);
}
public void swap() {
mEgl.swap(mEglSurface);
}
public EGLContext getContext() {
return mEgl.getContext();
}
public void release() {
Log.v(TAG, "EglSurface:release:");
mEgl.makeDefault();
mEgl.destroyWindowSurface(mEglSurface);
mEglSurface = EGL14.EGL_NO_SURFACE;
}
public int getWidth() {
return mWidth;
}
public int getHeight() {
return mHeight;
}
}
}
複製代碼
使用共享上下文後,咱們這個本身使用EGL建立的OpenGL線程就能夠共享用來渲染的OpenGL線程的資源了,咱們就能夠直接將最終輸出的渲染紋理ID傳遞到這邊來,向用MediaCodec生成的Surface構建的本地窗口渲染內容,MediaCodec就有數據可用了!
本篇文章篇幅較長,就不貼完整代碼了,完整代碼已經上傳到github。