Android音視頻開發筆記(三)--實時相機濾鏡&使用Android自帶硬編碼錄製視頻

原本按照計劃,筆者應該在這篇文章給你們介紹如何使用EGL API建立自定義OpenGL環境,可是在寫demo的過程當中反覆思考,與其作單個的demo功能還不如寫一個app,也方便你們在開發工做中根據使用場景來借鑑代碼和思路。so,在這篇文章中,會向你們介紹如何使用OpenGL給相機預覽畫面添加實時濾鏡以及使用MediaCodec+MediaMuxer錄製咱們的預覽畫面保存成MP4文件。android

OpenGL ES實時濾鏡渲染

簡單濾鏡

在上篇文章中,咱們經過一個使用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);
}
複製代碼

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

相關文章
相關標籤/搜索