Android音视频开发笔记(三)--实时相机滤镜&使用Android自带硬编码录制视频

本来按照计划,笔者应该在这篇文章给大家介绍如何使用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。

转载于:https://juejin.im/post/5c8609755188257deb5c8919

你可能感兴趣的:(Android音视频开发笔记(三)--实时相机滤镜&使用Android自带硬编码录制视频)