本来按照计划,笔者应该在这篇文章给大家介绍如何使用EGL API创建自定义OpenGL环境,但是在写demo的过程中反复思考,与其做单个的demo功能还不如写一个app,也方便大家在开发工作中根据使用场景来借鉴代码和思路。so,在这篇文章中,会向大家介绍如何使用OpenGL给相机预览画面添加实时滤镜以及使用MediaCodec+MediaMuxer录制我们的预览画面保存成MP4文件。
OpenGL ES实时滤镜渲染
简单滤镜
在上篇文章中,我们通过一个使用samplerExternalOES采样器的shader来渲染相机的实时预览数据,这样完全体现不出我们使用OpenGL ES的优势,所以我们在这里就可以利用OpenGL ES这个图形渲染库来给相机的实时预览数据添加一些实时滤镜,我们的shader程序是运行在GPU的,原因是GPU和CPU比较起来,更擅长一些浮点运算。不过在此之前,我们需要先了解一下OpenGL中FrameBuffer的概念。
在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可以快速的切换缓冲对象。
OK,说了这么多FBO(FrameBufferObject)的概念,那它究竟在我们做实时相机预览数据渲染中起到一个什么作用呢? FBO在使用时,可以绑定一个输出纹理,我们可以利用这个特性来做几个渲染结果的衔接。
创建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;
所以我们创建一个着色器程序:
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衔接起来。
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);
}
复制代码
Color Lookup Table
网上有很多介绍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是不能使用的,而且做像素算法效率也不高。
上面是一张没有颜色偏差的lut,如果我们想做一些想要的滤镜颜色,可以让设计同学使用PS来调色,我们把调色后的lut图片用来做查表法,就可以做出想要的效果了。
MediaCodec + OpenGL ES 视频编码
网上介绍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。