58 | * To avoid allocations, this returns internal state. The caller must not modify it. 59 | */ 60 | public FloatBuffer getVertexArray() { 61 | return mVertexArray; 62 | } 63 | 64 | /** 65 | * Returns the array of texture coordinates. 66 | *
67 | * To avoid allocations, this returns internal state. The caller must not modify it. 68 | */ 69 | public FloatBuffer getTexCoordArray() { 70 | return mTexCoordArray; 71 | } 72 | 73 | /** 74 | * Returns the number of vertices stored in the vertex array. 75 | */ 76 | public int getVertexCount() { 77 | return mVertexCount; 78 | } 79 | 80 | /** 81 | * Returns the width, in bytes, of the data for each vertex. 82 | */ 83 | public int getVertexStride() { 84 | return mVertexStride; 85 | } 86 | 87 | /** 88 | * Returns the width, in bytes, of the data for each texture coordinate. 89 | */ 90 | public int getTexCoordStride() { 91 | return mTexCoordStride; 92 | } 93 | 94 | /** 95 | * Returns the number of position coordinates per vertex. This will be 2 or 3. 96 | */ 97 | public int getCoordsPerVertex() { 98 | return mCoordsPerVertex; 99 | } 100 | 101 | 102 | } 103 | -------------------------------------------------------------------------------- /app/src/main/java/me/relex/camerafilter/gles/DrawableFlipVertical2d.java: -------------------------------------------------------------------------------- 1 | //package me.relex.camerafilter.gles; 2 | ///* 3 | // * Copyright 2014 Google Inc. All rights reserved. 4 | // * 5 | // * Licensed under the Apache License, Version 2.0 (the "License"); 6 | // * you may not use this file except in compliance with the License. 7 | // * You may obtain a copy of the License at 8 | // * 9 | // * http://www.apache.org/licenses/LICENSE-2.0 10 | // * 11 | // * Unless required by applicable law or agreed to in writing, software 12 | // * distributed under the License is distributed on an "AS IS" BASIS, 13 | // * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // * See the License for the specific language governing permissions and 15 | // * limitations under the License. 16 | // */ 17 | // 18 | //import java.nio.FloatBuffer; 19 | // 20 | //public class DrawableFlipVertical2d { 21 | // private static final int SIZEOF_FLOAT = 4; 22 | // private static final float FULL_RECTANGLE_COORDS[] = { 23 | // -1.0f, -1.0f, // 0 bottom left 24 | // 1.0f, -1.0f, // 1 bottom right 25 | // -1.0f, 1.0f, // 2 top left 26 | // 1.0f, 1.0f, // 3 top right 27 | // }; 28 | // private static final float FULL_RECTANGLE_TEX_COORDS[] = { 29 | // 0.0f, 1.0f, // 30 | // 1.0f, 1.0f, // 31 | // 0.0f, 0.0f, // 32 | // 1.0f, 0.0f // 33 | // }; 34 | // private static final FloatBuffer FULL_RECTANGLE_BUF = 35 | // GlUtil.createFloatBuffer(FULL_RECTANGLE_COORDS); 36 | // private static final FloatBuffer FULL_RECTANGLE_TEX_BUF = 37 | // GlUtil.createFloatBuffer(FULL_RECTANGLE_TEX_COORDS); 38 | // 39 | // private FloatBuffer mVertexArray; 40 | // private FloatBuffer mTexCoordArray; 41 | // private int mVertexCount; 42 | // private int mCoordsPerVertex; 43 | // private int mVertexStride; 44 | // private int mTexCoordStride; 45 | // 46 | // public DrawableFlipVertical2d() { 47 | // mVertexArray = FULL_RECTANGLE_BUF; 48 | // mTexCoordArray = FULL_RECTANGLE_TEX_BUF; 49 | // mCoordsPerVertex = 2; 50 | // mVertexStride = mCoordsPerVertex * SIZEOF_FLOAT; 51 | // mVertexCount = FULL_RECTANGLE_COORDS.length / mCoordsPerVertex; 52 | // mTexCoordStride = 2 * SIZEOF_FLOAT; 53 | // } 54 | // 55 | // /** 56 | // * Returns the array of vertices. 57 | // *
58 | // * To avoid allocations, this returns internal state. The caller must not modify it. 59 | // */ 60 | // public FloatBuffer getVertexArray() { 61 | // return mVertexArray; 62 | // } 63 | // 64 | // /** 65 | // * Returns the array of texture coordinates. 66 | // *
67 | // * To avoid allocations, this returns internal state. The caller must not modify it. 68 | // */ 69 | // public FloatBuffer getTexCoordArray() { 70 | // return mTexCoordArray; 71 | // } 72 | // 73 | // /** 74 | // * Returns the number of vertices stored in the vertex array. 75 | // */ 76 | // public int getVertexCount() { 77 | // return mVertexCount; 78 | // } 79 | // 80 | // /** 81 | // * Returns the width, in bytes, of the data for each vertex. 82 | // */ 83 | // public int getVertexStride() { 84 | // return mVertexStride; 85 | // } 86 | // 87 | // /** 88 | // * Returns the width, in bytes, of the data for each texture coordinate. 89 | // */ 90 | // public int getTexCoordStride() { 91 | // return mTexCoordStride; 92 | // } 93 | // 94 | // /** 95 | // * Returns the number of position coordinates per vertex. This will be 2 or 3. 96 | // */ 97 | // public int getCoordsPerVertex() { 98 | // return mCoordsPerVertex; 99 | // } 100 | //} 101 | -------------------------------------------------------------------------------- /app/src/main/java/me/relex/camerafilter/gles/FullFrameRect.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.relex.camerafilter.gles; 18 | 19 | /* 20 | * Copyright 2014 Google Inc. All rights reserved. 21 | * 22 | * Licensed under the Apache License, Version 2.0 (the "License"); 23 | * you may not use this file except in compliance with the License. 24 | * You may obtain a copy of the License at 25 | * 26 | * http://www.apache.org/licenses/LICENSE-2.0 27 | * 28 | * Unless required by applicable law or agreed to in writing, software 29 | * distributed under the License is distributed on an "AS IS" BASIS, 30 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 31 | * See the License for the specific language governing permissions and 32 | * limitations under the License. 33 | */ 34 | 35 | import android.graphics.Bitmap; 36 | import android.opengl.Matrix; 37 | import me.relex.camerafilter.filter.IFilter; 38 | 39 | /** 40 | * This class essentially represents a viewport-sized sprite that will be rendered with 41 | * a texture, usually from an external source like the camera or video decoder. 42 | */ 43 | public class FullFrameRect { 44 | private final Drawable2d mRectDrawable = new Drawable2d(); 45 | private IFilter mFilter; 46 | public final float[] IDENTITY_MATRIX = new float[16]; 47 | 48 | /** 49 | * Prepares the object. 50 | * 51 | * @param program The program to use. FullFrameRect takes ownership, and will release 52 | * the program when no longer needed. 53 | */ 54 | public FullFrameRect(IFilter program) { 55 | mFilter = program; 56 | Matrix.setIdentityM(IDENTITY_MATRIX, 0); 57 | } 58 | 59 | /** 60 | * Releases resources. 61 | *
62 | * This must be called with the appropriate EGL context current (i.e. the one that was 63 | * current when the constructor was called). If we're about to destroy the EGL context, 64 | * there's no value in having the caller make it current just to do this cleanup, so you 65 | * can pass a flag that will tell this function to skip any EGL-context-specific cleanup. 66 | */ 67 | public void release(boolean doEglCleanup) { 68 | if (mFilter != null) { 69 | if (doEglCleanup) { 70 | mFilter.releaseProgram(); 71 | } 72 | mFilter = null; 73 | } 74 | } 75 | 76 | /** 77 | * Returns the program currently in use. 78 | */ 79 | public IFilter getFilter() { 80 | return mFilter; 81 | } 82 | 83 | /** 84 | * Changes the program. The previous program will be released. 85 | *
86 | * The appropriate EGL context must be current. 87 | */ 88 | public void changeProgram(IFilter newFilter) { 89 | mFilter.releaseProgram(); 90 | mFilter = newFilter; 91 | } 92 | 93 | /** 94 | * Creates a texture object suitable for use with drawFrame(). 95 | */ 96 | public int createTexture() { 97 | return GlUtil.createTexture(mFilter.getTextureTarget()); 98 | } 99 | 100 | public int createTexture(Bitmap bitmap) { 101 | return GlUtil.createTexture(mFilter.getTextureTarget(), bitmap); 102 | } 103 | 104 | public void scaleMVPMatrix(float x, float y) { 105 | Matrix.setIdentityM(IDENTITY_MATRIX, 0); 106 | Matrix.scaleM(IDENTITY_MATRIX, 0, x, y, 1f); 107 | } 108 | 109 | /** 110 | * Draws a viewport-filling rect, texturing it with the specified texture object. 111 | */ 112 | 113 | public void drawFrame(int textureId, float[] texMatrix) { 114 | 115 | // Use the identity matrix for MVP so our 2x2 FULL_RECTANGLE covers the viewport. 116 | mFilter.onDraw(IDENTITY_MATRIX, mRectDrawable.getVertexArray(), 0, 117 | mRectDrawable.getVertexCount(), mRectDrawable.getCoordsPerVertex(), 118 | mRectDrawable.getVertexStride(), texMatrix, mRectDrawable.getTexCoordArray(), 119 | textureId, mRectDrawable.getTexCoordStride()); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /app/src/main/java/me/relex/camerafilter/gles/GlUtil.java: -------------------------------------------------------------------------------- 1 | package me.relex.camerafilter.gles; 2 | /* 3 | * Copyright 2014 Google Inc. All rights reserved. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | import android.content.Context; 19 | import android.graphics.Bitmap; 20 | import android.graphics.Canvas; 21 | import android.graphics.Paint; 22 | import android.opengl.GLES20; 23 | import android.opengl.GLUtils; 24 | import android.support.annotation.Nullable; 25 | import android.support.annotation.RawRes; 26 | import android.util.Log; 27 | import java.io.BufferedReader; 28 | import java.io.IOException; 29 | import java.io.InputStream; 30 | import java.io.InputStreamReader; 31 | import java.nio.ByteBuffer; 32 | import java.nio.ByteOrder; 33 | import java.nio.FloatBuffer; 34 | 35 | public class GlUtil { 36 | private static final String TAG = "GlUtil"; 37 | /** Identity matrix for general use. Don't modify or life will get weird. */ 38 | 39 | public static final int NO_TEXTURE = -1; 40 | 41 | private static final int SIZEOF_FLOAT = 4; 42 | 43 | private GlUtil() { // do not instantiate 44 | } 45 | 46 | public static int createProgram(Context applicationContext, @RawRes int vertexSourceRawId, 47 | @RawRes int fragmentSourceRawId) { 48 | 49 | String vertexSource = readTextFromRawResource(applicationContext, vertexSourceRawId); 50 | String fragmentSource = readTextFromRawResource(applicationContext, fragmentSourceRawId); 51 | 52 | return createProgram(vertexSource, fragmentSource); 53 | } 54 | 55 | public static int createProgram(String vertexSource, String fragmentSource) { 56 | int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); 57 | if (vertexShader == 0) { 58 | return 0; 59 | } 60 | int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); 61 | if (pixelShader == 0) { 62 | return 0; 63 | } 64 | int program = GLES20.glCreateProgram(); 65 | checkGlError("glCreateProgram"); 66 | if (program == 0) { 67 | Log.e(TAG, "Could not create program"); 68 | } 69 | GLES20.glAttachShader(program, vertexShader); 70 | checkGlError("glAttachShader"); 71 | GLES20.glAttachShader(program, pixelShader); 72 | checkGlError("glAttachShader"); 73 | GLES20.glLinkProgram(program); 74 | int[] linkStatus = new int[1]; 75 | GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); 76 | if (linkStatus[0] != GLES20.GL_TRUE) { 77 | Log.e(TAG, "Could not link program: "); 78 | Log.e(TAG, GLES20.glGetProgramInfoLog(program)); 79 | GLES20.glDeleteProgram(program); 80 | program = 0; 81 | } 82 | return program; 83 | } 84 | 85 | public static int loadShader(int shaderType, String source) { 86 | int shader = GLES20.glCreateShader(shaderType); 87 | checkGlError("glCreateShader type=" + shaderType); 88 | GLES20.glShaderSource(shader, source); 89 | GLES20.glCompileShader(shader); 90 | int[] compiled = new int[1]; 91 | GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); 92 | if (compiled[0] == 0) { 93 | Log.e(TAG, "Could not compile shader " + shaderType + ":"); 94 | Log.e(TAG, " " + GLES20.glGetShaderInfoLog(shader)); 95 | GLES20.glDeleteShader(shader); 96 | shader = 0; 97 | } 98 | return shader; 99 | } 100 | 101 | /** 102 | * @param textureTarget Texture类型。 103 | * 1. 相机用 GLES11Ext.GL_TEXTURE_EXTERNAL_OES 104 | * 2. 图片用GLES20.GL_TEXTURE_2D 105 | * @param minFilter 缩小过滤类型 (1.GL_NEAREST ; 2.GL_LINEAR) 106 | * @param magFilter 放大过滤类型 107 | * @param wrapS X方向边缘环绕 108 | * @param wrapT Y方向边缘环绕 109 | * @return 返回创建的 Texture ID 110 | */ 111 | public static int createTexture(int textureTarget, @Nullable Bitmap bitmap, int minFilter, 112 | int magFilter, int wrapS, int wrapT) { 113 | int[] textureHandle = new int[1]; 114 | 115 | GLES20.glGenTextures(1, textureHandle, 0); 116 | GlUtil.checkGlError("glGenTextures"); 117 | GLES20.glBindTexture(textureTarget, textureHandle[0]); 118 | GlUtil.checkGlError("glBindTexture " + textureHandle[0]); 119 | GLES20.glTexParameterf(textureTarget, GLES20.GL_TEXTURE_MIN_FILTER, minFilter); 120 | GLES20.glTexParameterf(textureTarget, GLES20.GL_TEXTURE_MAG_FILTER, magFilter); //线性插值 121 | GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_WRAP_S, wrapS); 122 | GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_WRAP_T, wrapT); 123 | 124 | if (bitmap != null) { 125 | GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0); 126 | } 127 | 128 | GlUtil.checkGlError("glTexParameter"); 129 | return textureHandle[0]; 130 | } 131 | 132 | public static int createTexture(int textureTarget) { 133 | return createTexture(textureTarget, null, GLES20.GL_LINEAR, GLES20.GL_LINEAR, 134 | GLES20.GL_CLAMP_TO_EDGE, GLES20.GL_CLAMP_TO_EDGE); 135 | } 136 | 137 | public static int createTexture(int textureTarget, Bitmap bitmap) { 138 | return createTexture(textureTarget, bitmap, GLES20.GL_LINEAR, GLES20.GL_LINEAR, 139 | GLES20.GL_CLAMP_TO_EDGE, GLES20.GL_CLAMP_TO_EDGE); 140 | } 141 | 142 | /** 143 | * Checks to see if a GLES error has been raised. 144 | */ 145 | public static void checkGlError(String op) { 146 | int error = GLES20.glGetError(); 147 | if (error != GLES20.GL_NO_ERROR) { 148 | String msg = op + ": glError 0x" + Integer.toHexString(error); 149 | Log.e(TAG, msg); 150 | throw new RuntimeException(msg); 151 | } 152 | } 153 | 154 | /** 155 | * Checks to see if the location we obtained is valid. GLES returns -1 if a label 156 | * could not be found, but does not set the GL error. 157 | *
158 | * Throws a RuntimeException if the location is invalid. 159 | */ 160 | public static void checkLocation(int location, String label) { 161 | if (location < 0) { 162 | throw new RuntimeException("Unable to locate '" + label + "' in program"); 163 | } 164 | } 165 | 166 | /** 167 | * Allocates a direct float buffer, and populates it with the float array data. 168 | */ 169 | public static FloatBuffer createFloatBuffer(float[] coords) { 170 | // Allocate a direct ByteBuffer, using 4 bytes per float, and copy coords into it. 171 | ByteBuffer bb = ByteBuffer.allocateDirect(coords.length * SIZEOF_FLOAT); 172 | bb.order(ByteOrder.nativeOrder()); 173 | FloatBuffer fb = bb.asFloatBuffer(); 174 | fb.put(coords); 175 | fb.position(0); 176 | return fb; 177 | } 178 | 179 | public static String readTextFromRawResource(final Context applicationContext, 180 | @RawRes final int resourceId) { 181 | final InputStream inputStream = 182 | applicationContext.getResources().openRawResource(resourceId); 183 | final InputStreamReader inputStreamReader = new InputStreamReader(inputStream); 184 | final BufferedReader bufferedReader = new BufferedReader(inputStreamReader); 185 | String nextLine; 186 | final StringBuilder body = new StringBuilder(); 187 | try { 188 | while ((nextLine = bufferedReader.readLine()) != null) { 189 | body.append(nextLine); 190 | body.append('\n'); 191 | } 192 | } catch (IOException e) { 193 | return null; 194 | } 195 | 196 | return body.toString(); 197 | } 198 | 199 | public static int createTextureWithTextContent(String text) { 200 | // Create an empty, mutable bitmap 201 | Bitmap bitmap = Bitmap.createBitmap(256, 256, Bitmap.Config.ARGB_8888); 202 | // get a canvas to paint over the bitmap 203 | Canvas canvas = new Canvas(bitmap); 204 | canvas.drawARGB(0, 0, 255, 0); 205 | // get a background image from resources 206 | // note the image format must match the bitmap format 207 | // Drawable background = context.getResources().getDrawable(R.drawable.background); 208 | // background.setBounds(0, 0, 256, 256); 209 | // background.draw(canvas); // draw the background to our bitmap 210 | // Draw the text 211 | Paint textPaint = new Paint(); 212 | textPaint.setTextSize(32); 213 | textPaint.setAntiAlias(true); 214 | textPaint.setARGB(0xff, 0xff, 0xff, 0xff); 215 | // draw the text centered 216 | canvas.drawText(text, 16, 112, textPaint); 217 | 218 | int[] textures = new int[1]; 219 | 220 | //Generate one texture pointer... 221 | GLES20.glGenTextures(1, textures, 0); 222 | 223 | //...and bind it to our array 224 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]); 225 | 226 | //Create Nearest Filtered Texture 227 | GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, 228 | GLES20.GL_NEAREST); 229 | GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, 230 | GLES20.GL_LINEAR); 231 | 232 | //Different possible texture parameters, e.g. GLES20.GL_CLAMP_TO_EDGE 233 | GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT); 234 | GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT); 235 | 236 | //Alpha blending 237 | //GLES20.glEnable(GLES20.GL_BLEND); 238 | //GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA); 239 | 240 | //Use the Android GLUtils to specify a two-dimensional texture image from our bitmap 241 | GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0); 242 | //Clean up 243 | bitmap.recycle(); 244 | 245 | return textures[0]; 246 | } 247 | } -------------------------------------------------------------------------------- /app/src/main/java/me/relex/camerafilter/image/ImageEglSurface.java: -------------------------------------------------------------------------------- 1 | package me.relex.camerafilter.image; 2 | 3 | import android.graphics.Bitmap; 4 | import android.opengl.EGL14; 5 | import android.opengl.GLES20; 6 | import android.opengl.GLSurfaceView; 7 | import android.util.Log; 8 | import java.nio.ByteBuffer; 9 | import java.nio.ByteOrder; 10 | import javax.microedition.khronos.egl.EGL10; 11 | import javax.microedition.khronos.egl.EGLConfig; 12 | import javax.microedition.khronos.egl.EGLContext; 13 | import javax.microedition.khronos.egl.EGLDisplay; 14 | import javax.microedition.khronos.egl.EGLSurface; 15 | import javax.microedition.khronos.opengles.GL10; 16 | 17 | import static javax.microedition.khronos.egl.EGL10.EGL_NO_CONTEXT; 18 | 19 | // 类似 EglCore 和 EglSurfaceBase,不过EglCore使用EGL14,而这里图片滤镜可以拆处理兼容法低版本,所以重新写了个,使用EGL10 20 | // 需要放到同一个线程中处理 21 | public class ImageEglSurface { 22 | 23 | private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; 24 | 25 | private int mWidth, mHeight; 26 | 27 | private EGL10 mEGL; 28 | private EGLDisplay mEGLDisplay; 29 | private EGLConfig mEGLConfig; 30 | private EGLContext mEGLContext; 31 | private EGLSurface mEGLSurface; 32 | private GL10 mGL; 33 | 34 | private GLSurfaceView.Renderer mRenderer; 35 | 36 | public ImageEglSurface(final int width, final int height) { 37 | mWidth = width; 38 | mHeight = height; 39 | 40 | int[] version = new int[2]; 41 | int[] surfaceAttribList = { 42 | EGL10.EGL_WIDTH, width, EGL10.EGL_HEIGHT, height, EGL10.EGL_NONE 43 | }; 44 | 45 | mEGL = (EGL10) EGLContext.getEGL(); 46 | mEGLDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); 47 | if (mEGLDisplay == EGL10.EGL_NO_DISPLAY) { 48 | throw new RuntimeException("unable to get EGL10 display"); 49 | } 50 | 51 | if (!mEGL.eglInitialize(mEGLDisplay, version)) { 52 | mEGLDisplay = null; 53 | throw new RuntimeException("unable to initialize EGL14"); 54 | } 55 | 56 | mEGLConfig = getConfig(); 57 | if (mEGLConfig == null) { 58 | throw new RuntimeException("Unable to find a suitable EGLConfig"); 59 | } 60 | int[] attribList = { 61 | EGL_CONTEXT_CLIENT_VERSION /*EGL14.EGL_CONTEXT_CLIENT_VERSION*/, 2, EGL14.EGL_NONE 62 | }; 63 | mEGLContext = mEGL.eglCreateContext(mEGLDisplay, mEGLConfig, EGL_NO_CONTEXT, attribList); 64 | mEGLSurface = mEGL.eglCreatePbufferSurface(mEGLDisplay, mEGLConfig, surfaceAttribList); 65 | mEGL.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext); 66 | mGL = (GL10) mEGLContext.getGL(); 67 | } 68 | 69 | private EGLConfig getConfig() { 70 | int renderableType = 4; // EGL14.EGL_OPENGL_ES2_BIT; 71 | int[] attribList = { 72 | EGL10.EGL_DEPTH_SIZE, 0, EGL10.EGL_STENCIL_SIZE, 0, EGL10.EGL_RED_SIZE, 8, 73 | EGL10.EGL_GREEN_SIZE, 8, EGL10.EGL_BLUE_SIZE, 8, EGL10.EGL_ALPHA_SIZE, 8, 74 | EGL10.EGL_RENDERABLE_TYPE, renderableType, EGL10.EGL_NONE 75 | }; 76 | 77 | EGLConfig[] configs = new EGLConfig[1]; 78 | int[] numConfigs = new int[1]; 79 | 80 | if (!mEGL.eglChooseConfig(mEGLDisplay, attribList, configs, configs.length, numConfigs)) { 81 | Log.w("ImageEglSurface", "unable to find RGB8888 EGLConfig"); 82 | return null; 83 | } 84 | return configs[0]; 85 | } 86 | 87 | public void setRenderer(GLSurfaceView.Renderer renderer) { 88 | mRenderer = renderer; 89 | mRenderer.onSurfaceCreated(mGL, mEGLConfig); 90 | mRenderer.onSurfaceChanged(mGL, mWidth, mHeight); 91 | } 92 | 93 | public void drawFrame() { 94 | if (mRenderer == null) { 95 | return; 96 | } 97 | mRenderer.onDrawFrame(mGL); 98 | } 99 | 100 | public Bitmap getBitmap() { 101 | ByteBuffer buf = ByteBuffer.allocateDirect(mWidth * mHeight * 4); 102 | buf.order(ByteOrder.LITTLE_ENDIAN); 103 | GLES20.glReadPixels(0, 0, mWidth, mHeight, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf); 104 | Bitmap bmp = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888); 105 | bmp.copyPixelsFromBuffer(buf); 106 | return bmp; 107 | } 108 | 109 | public void release() { 110 | mEGL.eglDestroySurface(mEGLDisplay, mEGLSurface); 111 | if (mEGLDisplay != EGL10.EGL_NO_DISPLAY) { 112 | mEGL.eglMakeCurrent(mEGLDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, 113 | EGL10.EGL_NO_CONTEXT); 114 | mEGL.eglDestroyContext(mEGLDisplay, mEGLContext); 115 | //EGL14.eglReleaseThread(); 116 | mEGL.eglTerminate(mEGLDisplay); 117 | } 118 | 119 | mEGLDisplay = EGL10.EGL_NO_DISPLAY; 120 | mEGLConfig = null; 121 | mEGLContext = EGL10.EGL_NO_CONTEXT; 122 | mEGLSurface = EGL10.EGL_NO_SURFACE; 123 | 124 | mWidth = mHeight = -1; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /app/src/main/java/me/relex/camerafilter/image/ImageRenderer.java: -------------------------------------------------------------------------------- 1 | package me.relex.camerafilter.image; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.opengl.GLSurfaceView; 6 | import android.opengl.Matrix; 7 | import android.util.Log; 8 | import javax.microedition.khronos.egl.EGLConfig; 9 | import javax.microedition.khronos.opengles.GL10; 10 | import me.relex.camerafilter.filter.FilterManager; 11 | import me.relex.camerafilter.gles.FullFrameRect; 12 | import me.relex.camerafilter.gles.GlUtil; 13 | 14 | public class ImageRenderer implements GLSurfaceView.Renderer { 15 | 16 | private final Context mContext; 17 | private FilterManager.FilterType mCurrentFilterType; 18 | private FilterManager.FilterType mNewFilterType; 19 | 20 | private int mTextureId = GlUtil.NO_TEXTURE; 21 | private final float[] mSTMatrix = new float[16]; 22 | 23 | private int mSurfaceWidth, mSurfaceHeight; 24 | private int mIncomingWidth, mIncomingHeight; 25 | 26 | private FullFrameRect mFullScreen; 27 | 28 | public ImageRenderer(Context context, FilterManager.FilterType filterType) { 29 | mContext = context; 30 | mCurrentFilterType = mNewFilterType = filterType; 31 | } 32 | 33 | @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { 34 | Matrix.setIdentityM(mSTMatrix, 0); 35 | mFullScreen = new FullFrameRect(FilterManager.getImageFilter(mCurrentFilterType, mContext)); 36 | } 37 | 38 | @Override public void onSurfaceChanged(GL10 gl, int width, int height) { 39 | mSurfaceWidth = width; 40 | mSurfaceHeight = height; 41 | if (gl != null) { 42 | gl.glViewport(0, 0, width, height); 43 | } 44 | } 45 | 46 | public void setImageBitmap(Bitmap bitmap) { 47 | if (bitmap == null) { 48 | return; 49 | } 50 | 51 | mIncomingWidth = bitmap.getWidth(); 52 | mIncomingHeight = bitmap.getHeight(); 53 | 54 | float scaleHeight = mSurfaceWidth / (mIncomingWidth * 1f / mIncomingHeight * 1f); 55 | float surfaceHeight = mSurfaceHeight; 56 | 57 | mTextureId = mFullScreen.createTexture(bitmap); 58 | if (mFullScreen != null) { 59 | mFullScreen.scaleMVPMatrix(1f, scaleHeight / surfaceHeight); 60 | } 61 | } 62 | 63 | public void changeFilter(FilterManager.FilterType filterType) { 64 | mNewFilterType = filterType; 65 | } 66 | 67 | @Override public void onDrawFrame(GL10 gl) { 68 | 69 | if (mTextureId == GlUtil.NO_TEXTURE) { 70 | Log.e("ImageRenderer", "need setImageBitmap"); 71 | return; 72 | } 73 | 74 | if (mNewFilterType != mCurrentFilterType) { 75 | mFullScreen.changeProgram(FilterManager.getImageFilter(mNewFilterType, mContext)); 76 | mCurrentFilterType = mNewFilterType; 77 | } 78 | 79 | mFullScreen.getFilter().setTextureSize(mIncomingWidth, mIncomingHeight); 80 | 81 | mFullScreen.drawFrame(mTextureId, mSTMatrix); 82 | } 83 | 84 | public void destroy() { 85 | if (mFullScreen != null) { 86 | mFullScreen.release(false); 87 | mFullScreen = null; 88 | } 89 | 90 | mTextureId = GlUtil.NO_TEXTURE; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/java/me/relex/camerafilter/video/EglCore.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.relex.camerafilter.video; 18 | 19 | import android.graphics.SurfaceTexture; 20 | import android.opengl.EGL14; 21 | import android.opengl.EGLConfig; 22 | import android.opengl.EGLContext; 23 | import android.opengl.EGLDisplay; 24 | import android.opengl.EGLExt; 25 | import android.opengl.EGLSurface; 26 | import android.util.Log; 27 | import android.view.Surface; 28 | 29 | /** 30 | * Core EGL state (display, context, config). 31 | *
32 | * The EGLContext must only be attached to one thread at a time. This class is not thread-safe. 33 | */ 34 | public final class EglCore { 35 | private static final String TAG = "EglCore"; 36 | 37 | /** 38 | * Constructor flag: surface must be recordable. This discourages EGL from using a 39 | * pixel format that cannot be converted efficiently to something usable by the video 40 | * encoder. 41 | */ 42 | public static final int FLAG_RECORDABLE = 0x01; 43 | 44 | /** 45 | * Constructor flag: ask for GLES3, fall back to GLES2 if not available. Without this 46 | * flag, GLES2 is used. 47 | */ 48 | public static final int FLAG_TRY_GLES3 = 0x02; 49 | 50 | // Android-specific extension. 51 | private static final int EGL_RECORDABLE_ANDROID = 0x3142; 52 | 53 | private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY; 54 | private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT; 55 | private EGLConfig mEGLConfig = null; 56 | private int mGlVersion = -1; 57 | 58 | /** 59 | * Prepares EGL display and context. 60 | *
61 | * Equivalent to EglCore(null, 0). 62 | */ 63 | public EglCore() { 64 | this(null, 0); 65 | } 66 | 67 | /** 68 | * Prepares EGL display and context. 69 | *
70 | * 71 | * @param sharedContext The context to share, or null if sharing is not desired. 72 | * @param flags Configuration bit flags, e.g. FLAG_RECORDABLE. 73 | */ 74 | public EglCore(EGLContext sharedContext, int flags) { 75 | if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { 76 | throw new RuntimeException("EGL already set up"); 77 | } 78 | 79 | if (sharedContext == null) { 80 | sharedContext = EGL14.EGL_NO_CONTEXT; 81 | } 82 | 83 | mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); 84 | if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) { 85 | throw new RuntimeException("unable to get EGL14 display"); 86 | } 87 | int[] version = new int[2]; 88 | if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) { 89 | mEGLDisplay = null; 90 | throw new RuntimeException("unable to initialize EGL14"); 91 | } 92 | 93 | // Try to get a GLES3 context, if requested. 94 | if ((flags & FLAG_TRY_GLES3) != 0) { 95 | //Log.d(TAG, "Trying GLES 3"); 96 | EGLConfig config = getConfig(flags, 3); 97 | if (config != null) { 98 | int[] attrib3_list = { 99 | EGL14.EGL_CONTEXT_CLIENT_VERSION, 3, EGL14.EGL_NONE 100 | }; 101 | EGLContext context = 102 | EGL14.eglCreateContext(mEGLDisplay, config, sharedContext, attrib3_list, 0); 103 | 104 | if (EGL14.eglGetError() == EGL14.EGL_SUCCESS) { 105 | //Log.d(TAG, "Got GLES 3 config"); 106 | mEGLConfig = config; 107 | mEGLContext = context; 108 | mGlVersion = 3; 109 | } 110 | } 111 | } 112 | if (mEGLContext == EGL14.EGL_NO_CONTEXT) { // GLES 2 only, or GLES 3 attempt failed 113 | //Log.d(TAG, "Trying GLES 2"); 114 | EGLConfig config = getConfig(flags, 2); 115 | if (config == null) { 116 | throw new RuntimeException("Unable to find a suitable EGLConfig"); 117 | } 118 | int[] attrib2_list = { 119 | EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE 120 | }; 121 | EGLContext context = 122 | EGL14.eglCreateContext(mEGLDisplay, config, sharedContext, attrib2_list, 0); 123 | checkEglError("eglCreateContext"); 124 | mEGLConfig = config; 125 | mEGLContext = context; 126 | mGlVersion = 2; 127 | } 128 | 129 | // Confirm with query. 130 | int[] values = new int[1]; 131 | EGL14.eglQueryContext(mEGLDisplay, mEGLContext, EGL14.EGL_CONTEXT_CLIENT_VERSION, values, 132 | 0); 133 | Log.d(TAG, "EGLContext created, client version " + values[0]); 134 | } 135 | 136 | /** 137 | * Finds a suitable EGLConfig. 138 | * 139 | * @param flags Bit flags from constructor. 140 | * @param version Must be 2 or 3. 141 | */ 142 | private EGLConfig getConfig(int flags, int version) { 143 | int renderableType = EGL14.EGL_OPENGL_ES2_BIT; 144 | if (version >= 3) { 145 | renderableType |= EGLExt.EGL_OPENGL_ES3_BIT_KHR; 146 | } 147 | 148 | // The actual surface is generally RGBA or RGBX, so situationally omitting alpha 149 | // doesn't really help. It can also lead to a huge performance hit on glReadPixels() 150 | // when reading into a GL_RGBA buffer. 151 | int[] attribList = { 152 | EGL14.EGL_RED_SIZE, 8, EGL14.EGL_GREEN_SIZE, 8, EGL14.EGL_BLUE_SIZE, 8, 153 | EGL14.EGL_ALPHA_SIZE, 8, 154 | //EGL14.EGL_DEPTH_SIZE, 16, 155 | //EGL14.EGL_STENCIL_SIZE, 8, 156 | EGL14.EGL_RENDERABLE_TYPE, renderableType, EGL14.EGL_NONE, 0, 157 | // placeholder for recordable [@-3] 158 | EGL14.EGL_NONE 159 | }; 160 | if ((flags & FLAG_RECORDABLE) != 0) { 161 | attribList[attribList.length - 3] = EGL_RECORDABLE_ANDROID; 162 | attribList[attribList.length - 2] = 1; 163 | } 164 | EGLConfig[] configs = new EGLConfig[1]; 165 | int[] numConfigs = new int[1]; 166 | if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length, 167 | numConfigs, 0)) { 168 | Log.w(TAG, "unable to find RGB8888 / " + version + " EGLConfig"); 169 | return null; 170 | } 171 | return configs[0]; 172 | } 173 | 174 | /** 175 | * Discards all resources held by this class, notably the EGL context. This must be 176 | * called from the thread where the context was created. 177 | *
178 | * On completion, no context will be current. 179 | */ 180 | public void release() { 181 | if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { 182 | // Android is unusual in that it uses a reference-counted EGLDisplay. So for 183 | // every eglInitialize() we need an eglTerminate(). 184 | EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, 185 | EGL14.EGL_NO_CONTEXT); 186 | EGL14.eglDestroyContext(mEGLDisplay, mEGLContext); 187 | EGL14.eglReleaseThread(); 188 | EGL14.eglTerminate(mEGLDisplay); 189 | } 190 | 191 | mEGLDisplay = EGL14.EGL_NO_DISPLAY; 192 | mEGLContext = EGL14.EGL_NO_CONTEXT; 193 | mEGLConfig = null; 194 | } 195 | 196 | @Override protected void finalize() throws Throwable { 197 | try { 198 | if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { 199 | // We're limited here -- finalizers don't run on the thread that holds 200 | // the EGL state, so if a surface or context is still current on another 201 | // thread we can't fully release it here. Exceptions thrown from here 202 | // are quietly discarded. Complain in the log file. 203 | Log.w(TAG, "WARNING: EglCore was not explicitly released -- state may be leaked"); 204 | release(); 205 | } 206 | } finally { 207 | super.finalize(); 208 | } 209 | } 210 | 211 | /** 212 | * Destroys the specified surface. Note the EGLSurface won't actually be destroyed if it's 213 | * still current in a context. 214 | */ 215 | public void releaseSurface(EGLSurface eglSurface) { 216 | EGL14.eglDestroySurface(mEGLDisplay, eglSurface); 217 | } 218 | 219 | /** 220 | * Creates an EGL surface associated with a Surface. 221 | *
222 | * If this is destined for MediaCodec, the EGLConfig should have the "recordable" attribute. 223 | */ 224 | public EGLSurface createWindowSurface(Object surface) { 225 | if (!(surface instanceof Surface) && !(surface instanceof SurfaceTexture)) { 226 | throw new RuntimeException("invalid surface: " + surface); 227 | } 228 | 229 | // Create a window surface, and attach it to the Surface we received. 230 | int[] surfaceAttribs = { 231 | EGL14.EGL_NONE 232 | }; 233 | EGLSurface eglSurface = 234 | EGL14.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, surface, surfaceAttribs, 0); 235 | checkEglError("eglCreateWindowSurface"); 236 | if (eglSurface == null) { 237 | throw new RuntimeException("surface was null"); 238 | } 239 | return eglSurface; 240 | } 241 | 242 | /** 243 | * Creates an EGL surface associated with an offscreen buffer. 244 | */ 245 | public EGLSurface createOffscreenSurface(int width, int height) { 246 | int[] surfaceAttribs = { 247 | EGL14.EGL_WIDTH, width, EGL14.EGL_HEIGHT, height, EGL14.EGL_NONE 248 | }; 249 | EGLSurface eglSurface = 250 | EGL14.eglCreatePbufferSurface(mEGLDisplay, mEGLConfig, surfaceAttribs, 0); 251 | checkEglError("eglCreatePbufferSurface"); 252 | if (eglSurface == null) { 253 | throw new RuntimeException("surface was null"); 254 | } 255 | return eglSurface; 256 | } 257 | 258 | /** 259 | * Makes our EGL context current, using the supplied surface for both "draw" and "read". 260 | */ 261 | public void makeCurrent(EGLSurface eglSurface) { 262 | if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) { 263 | // called makeCurrent() before create? 264 | Log.d(TAG, "NOTE: makeCurrent w/o display"); 265 | } 266 | if (!EGL14.eglMakeCurrent(mEGLDisplay, eglSurface, eglSurface, mEGLContext)) { 267 | throw new RuntimeException("eglMakeCurrent failed"); 268 | } 269 | } 270 | 271 | /** 272 | * Makes our EGL context current, using the supplied "draw" and "read" surfaces. 273 | */ 274 | public void makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) { 275 | if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) { 276 | // called makeCurrent() before create? 277 | Log.d(TAG, "NOTE: makeCurrent w/o display"); 278 | } 279 | if (!EGL14.eglMakeCurrent(mEGLDisplay, drawSurface, readSurface, mEGLContext)) { 280 | throw new RuntimeException("eglMakeCurrent(draw,read) failed"); 281 | } 282 | } 283 | 284 | /** 285 | * Makes no context current. 286 | */ 287 | public void makeNothingCurrent() { 288 | if (!EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, 289 | EGL14.EGL_NO_CONTEXT)) { 290 | throw new RuntimeException("eglMakeCurrent failed"); 291 | } 292 | } 293 | 294 | /** 295 | * Calls eglSwapBuffers. Use this to "publish" the current frame. 296 | * 297 | * @return false on failure 298 | */ 299 | public boolean swapBuffers(EGLSurface eglSurface) { 300 | return EGL14.eglSwapBuffers(mEGLDisplay, eglSurface); 301 | } 302 | 303 | /** 304 | * Sends the presentation time stamp to EGL. Time is expressed in nanoseconds. 305 | */ 306 | public void setPresentationTime(EGLSurface eglSurface, long nsecs) { 307 | EGLExt.eglPresentationTimeANDROID(mEGLDisplay, eglSurface, nsecs); 308 | } 309 | 310 | /** 311 | * Returns true if our context and the specified surface are current. 312 | */ 313 | public boolean isCurrent(EGLSurface eglSurface) { 314 | return mEGLContext.equals(EGL14.eglGetCurrentContext()) && eglSurface.equals( 315 | EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW)); 316 | } 317 | 318 | /** 319 | * Performs a simple surface query. 320 | */ 321 | public int querySurface(EGLSurface eglSurface, int what) { 322 | int[] value = new int[1]; 323 | EGL14.eglQuerySurface(mEGLDisplay, eglSurface, what, value, 0); 324 | return value[0]; 325 | } 326 | 327 | /** 328 | * Queries a string value. 329 | */ 330 | public String queryString(int what) { 331 | return EGL14.eglQueryString(mEGLDisplay, what); 332 | } 333 | 334 | /** 335 | * Returns the GLES version this context is configured for (currently 2 or 3). 336 | */ 337 | public int getGlVersion() { 338 | return mGlVersion; 339 | } 340 | 341 | /** 342 | * Writes the current display, context, and surface to the log. 343 | */ 344 | public static void logCurrent(String msg) { 345 | EGLDisplay display; 346 | EGLContext context; 347 | EGLSurface surface; 348 | 349 | display = EGL14.eglGetCurrentDisplay(); 350 | context = EGL14.eglGetCurrentContext(); 351 | surface = EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW); 352 | Log.i(TAG, "Current EGL (" + msg + "): display=" + display + ", context=" + context + 353 | ", surface=" + surface); 354 | } 355 | 356 | /** 357 | * Checks for EGL errors. Throws an exception if an error has been raised. 358 | */ 359 | private void checkEglError(String msg) { 360 | int error; 361 | if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) { 362 | throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error)); 363 | } 364 | } 365 | } 366 | -------------------------------------------------------------------------------- /app/src/main/java/me/relex/camerafilter/video/EglSurfaceBase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.relex.camerafilter.video; 18 | 19 | import android.graphics.Bitmap; 20 | import android.opengl.EGL14; 21 | import android.opengl.EGLSurface; 22 | import android.opengl.GLES20; 23 | import android.util.Log; 24 | import java.io.BufferedOutputStream; 25 | import java.io.File; 26 | import java.io.FileOutputStream; 27 | import java.io.IOException; 28 | import java.nio.ByteBuffer; 29 | import java.nio.ByteOrder; 30 | import me.relex.camerafilter.gles.GlUtil; 31 | 32 | /** 33 | * Common base class for EGL surfaces. 34 | *
35 | * There can be multiple surfaces associated with a single context. 36 | */ 37 | public class EglSurfaceBase { 38 | protected static final String TAG = "EglSurfaceBase"; 39 | 40 | // EglCore object we're associated with. It may be associated with multiple surfaces. 41 | protected EglCore mEglCore; 42 | 43 | private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE; 44 | private int mWidth = -1; 45 | private int mHeight = -1; 46 | 47 | protected EglSurfaceBase(EglCore eglCore) { 48 | mEglCore = eglCore; 49 | } 50 | 51 | /** 52 | * Creates a window surface. 53 | *
54 | * 55 | * @param surface May be a Surface or SurfaceTexture. 56 | */ 57 | public void createWindowSurface(Object surface) { 58 | if (mEGLSurface != EGL14.EGL_NO_SURFACE) { 59 | throw new IllegalStateException("surface already created"); 60 | } 61 | mEGLSurface = mEglCore.createWindowSurface(surface); 62 | 63 | // Don't cache width/height here, because the size of the underlying surface can change 64 | // out from under us (see e.g. HardwareScalerActivity). 65 | //mWidth = mEglCore.querySurface(mEGLSurface, EGL14.EGL_WIDTH); 66 | //mHeight = mEglCore.querySurface(mEGLSurface, EGL14.EGL_HEIGHT); 67 | } 68 | 69 | /** 70 | * Creates an off-screen surface. 71 | */ 72 | public void createOffscreenSurface(int width, int height) { 73 | if (mEGLSurface != EGL14.EGL_NO_SURFACE) { 74 | throw new IllegalStateException("surface already created"); 75 | } 76 | mEGLSurface = mEglCore.createOffscreenSurface(width, height); 77 | mWidth = width; 78 | mHeight = height; 79 | } 80 | 81 | /** 82 | * Returns the surface's width, in pixels. 83 | *
84 | * If this is called on a window surface, and the underlying surface is in the process 85 | * of changing size, we may not see the new size right away (e.g. in the "surfaceChanged" 86 | * callback). The size should match after the next buffer swap. 87 | */ 88 | public int getWidth() { 89 | if (mWidth < 0) { 90 | return mEglCore.querySurface(mEGLSurface, EGL14.EGL_WIDTH); 91 | } else { 92 | return mWidth; 93 | } 94 | } 95 | 96 | /** 97 | * Returns the surface's height, in pixels. 98 | */ 99 | public int getHeight() { 100 | if (mHeight < 0) { 101 | return mEglCore.querySurface(mEGLSurface, EGL14.EGL_HEIGHT); 102 | } else { 103 | return mHeight; 104 | } 105 | } 106 | 107 | /** 108 | * Release the EGL surface. 109 | */ 110 | public void releaseEglSurface() { 111 | mEglCore.releaseSurface(mEGLSurface); 112 | mEGLSurface = EGL14.EGL_NO_SURFACE; 113 | mWidth = mHeight = -1; 114 | } 115 | 116 | /** 117 | * Makes our EGL context and surface current. 118 | */ 119 | public void makeCurrent() { 120 | mEglCore.makeCurrent(mEGLSurface); 121 | } 122 | 123 | /** 124 | * Makes our EGL context and surface current for drawing, using the supplied surface 125 | * for reading. 126 | */ 127 | public void makeCurrentReadFrom(EglSurfaceBase readSurface) { 128 | mEglCore.makeCurrent(mEGLSurface, readSurface.mEGLSurface); 129 | } 130 | 131 | /** 132 | * Calls eglSwapBuffers. Use this to "publish" the current frame. 133 | * 134 | * @return false on failure 135 | */ 136 | public boolean swapBuffers() { 137 | boolean result = mEglCore.swapBuffers(mEGLSurface); 138 | if (!result) { 139 | Log.d(TAG, "WARNING: swapBuffers() failed"); 140 | } 141 | return result; 142 | } 143 | 144 | /** 145 | * Sends the presentation time stamp to EGL. 146 | * 147 | * @param nsecs Timestamp, in nanoseconds. 148 | */ 149 | public void setPresentationTime(long nsecs) { 150 | mEglCore.setPresentationTime(mEGLSurface, nsecs); 151 | } 152 | 153 | /** 154 | * Saves the EGL surface to a file. 155 | *
156 | * Expects that this object's EGL surface is current. 157 | */ 158 | public void saveFrame(File file) throws IOException { 159 | if (!mEglCore.isCurrent(mEGLSurface)) { 160 | throw new RuntimeException("Expected EGL context/surface is not current"); 161 | } 162 | 163 | // glReadPixels fills in a "direct" ByteBuffer with what is essentially big-endian RGBA 164 | // data (i.e. a byte of red, followed by a byte of green...). While the Bitmap 165 | // constructor that takes an int[] wants little-endian ARGB (blue/red swapped), the 166 | // Bitmap "copy pixels" method wants the same format GL provides. 167 | // 168 | // Ideally we'd have some way to re-use the ByteBuffer, especially if we're calling 169 | // here often. 170 | // 171 | // Making this even more interesting is the upside-down nature of GL, which means 172 | // our output will look upside down relative to what appears on screen if the 173 | // typical GL conventions are used. 174 | 175 | String filename = file.toString(); 176 | 177 | int width = getWidth(); 178 | int height = getHeight(); 179 | ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4); 180 | 181 | buf.order(ByteOrder.LITTLE_ENDIAN); 182 | GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf); 183 | GlUtil.checkGlError("glReadPixels"); 184 | buf.rewind(); 185 | 186 | BufferedOutputStream bos = null; 187 | try { 188 | bos = new BufferedOutputStream(new FileOutputStream(filename)); 189 | Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 190 | bmp.copyPixelsFromBuffer(buf); 191 | 192 | bmp.compress(Bitmap.CompressFormat.PNG, 90, bos); 193 | bmp.recycle(); 194 | } finally { 195 | if (bos != null) bos.close(); 196 | } 197 | Log.d(TAG, "Saved " + width + "x" + height + " frame as '" + filename + "'"); 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /app/src/main/java/me/relex/camerafilter/video/EncoderConfig.java: -------------------------------------------------------------------------------- 1 | package me.relex.camerafilter.video; 2 | 3 | /** 4 | * Created by relex on 15/6/2. 5 | */ 6 | 7 | import android.opengl.EGLContext; 8 | import java.io.File; 9 | 10 | /** 11 | * Encoder configuration. 12 | *
13 | * Object is immutable, which means we can safely pass it between threads without 14 | * explicit synchronization (and don't need to worry about it getting tweaked out from 15 | * under us). 16 | *
17 | * TODO: make frame rate and iframe interval configurable? Maybe use builder pattern 18 | * with reasonable defaults for those and bit rate. 19 | */ 20 | public class EncoderConfig { 21 | final File mOutputFile; 22 | final int mWidth; 23 | final int mHeight; 24 | final int mBitRate; 25 | EGLContext mEglContext; 26 | 27 | public EncoderConfig(File outputFile, int width, int height, int bitRate) { 28 | mOutputFile = outputFile; 29 | mWidth = width; 30 | mHeight = height; 31 | mBitRate = bitRate; 32 | } 33 | 34 | public void updateEglContext(EGLContext eglContext) { 35 | mEglContext = eglContext; 36 | } 37 | //@Override public String toString() { 38 | // return "EncoderConfig: " + mWidth + "x" + mHeight + " @" + mBitRate + 39 | // " to '" + mOutputFile.toString() + "' ctxt=" + mEglContext; 40 | //} 41 | } 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/java/me/relex/camerafilter/video/VideoEncoderCore.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.relex.camerafilter.video; 18 | 19 | import android.media.MediaCodec; 20 | import android.media.MediaCodecInfo; 21 | import android.media.MediaFormat; 22 | import android.media.MediaMuxer; 23 | import android.util.Log; 24 | import android.view.Surface; 25 | import java.io.File; 26 | import java.io.IOException; 27 | import java.nio.ByteBuffer; 28 | 29 | /** 30 | * This class wraps up the core components used for surface-input video encoding. 31 | *
32 | * Once created, frames are fed to the input surface. Remember to provide the presentation 33 | * time stamp, and always call drainEncoder() before swapBuffers() to ensure that the 34 | * producer side doesn't get backed up. 35 | *
36 | * This class is not thread-safe, with one exception: it is valid to use the input surface 37 | * on one thread, and drain the output on a different thread. 38 | */ 39 | public class VideoEncoderCore { 40 | private static final String TAG = "VideoEncoderCore"; 41 | private static final boolean VERBOSE = false; 42 | 43 | // TODO: these ought to be configurable as well 44 | private static final String MIME_TYPE = "video/avc"; // H.264 Advanced Video Coding 45 | private static final int FRAME_RATE = 30; // 30fps 46 | private static final int IFRAME_INTERVAL = 5; // 5 seconds between I-frames 47 | 48 | private Surface mInputSurface; 49 | private MediaMuxer mMuxer; 50 | private MediaCodec mEncoder; 51 | private MediaCodec.BufferInfo mBufferInfo; 52 | private int mTrackIndex; 53 | private boolean mMuxerStarted; 54 | 55 | /** 56 | * Configures encoder and muxer state, and prepares the input Surface. 57 | */ 58 | public VideoEncoderCore(int width, int height, int bitRate, File outputFile) 59 | throws IOException { 60 | mBufferInfo = new MediaCodec.BufferInfo(); 61 | 62 | MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, width, height); 63 | 64 | // Set some properties. Failing to specify some of these can cause the MediaCodec 65 | // configure() call to throw an unhelpful exception. 66 | format.setInteger(MediaFormat.KEY_COLOR_FORMAT, 67 | MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); 68 | format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate); 69 | format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE); 70 | format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL); 71 | if (VERBOSE) Log.d(TAG, "format: " + format); 72 | 73 | // Create a MediaCodec encoder, and configure it with our format. Get a Surface 74 | // we can use for input and wrap it with a class that handles the EGL work. 75 | mEncoder = MediaCodec.createEncoderByType(MIME_TYPE); 76 | mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 77 | mInputSurface = mEncoder.createInputSurface(); 78 | mEncoder.start(); 79 | 80 | // Create a MediaMuxer. We can't add the video track and start() the muxer here, 81 | // because our MediaFormat doesn't have the Magic Goodies. These can only be 82 | // obtained from the encoder after it has started processing data. 83 | // 84 | // We're not actually interested in multiplexing audio. We just want to convert 85 | // the raw H.264 elementary stream we get from MediaCodec into a .mp4 file. 86 | mMuxer = new MediaMuxer(outputFile.toString(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); 87 | mTrackIndex = -1; 88 | mMuxerStarted = false; 89 | } 90 | 91 | /** 92 | * Returns the encoder's input surface. 93 | */ 94 | public Surface getInputSurface() { 95 | return mInputSurface; 96 | } 97 | 98 | /** 99 | * Releases encoder resources. 100 | */ 101 | public void release() { 102 | if (VERBOSE) Log.d(TAG, "releasing encoder objects"); 103 | if (mEncoder != null) { 104 | mEncoder.stop(); 105 | mEncoder.release(); 106 | mEncoder = null; 107 | } 108 | if (mMuxer != null) { 109 | // TODO: stop() throws an exception if you haven't fed it any data. Keep track 110 | // of frames submitted, and don't call stop() if we haven't written anything. 111 | mMuxer.stop(); 112 | mMuxer.release(); 113 | mMuxer = null; 114 | } 115 | } 116 | 117 | /** 118 | * Extracts all pending data from the encoder and forwards it to the muxer. 119 | *
120 | * If endOfStream is not set, this returns when there is no more data to drain. If it 121 | * is set, we send EOS to the encoder, and then iterate until we see EOS on the output. 122 | * Calling this with endOfStream set should be done once, right before stopping the muxer. 123 | *
124 | * We're just using the muxer to get a .mp4 file (instead of a raw H.264 stream). We're 125 | * not recording audio. 126 | */ 127 | public void drainEncoder(boolean endOfStream) { 128 | final int TIMEOUT_USEC = 10000; 129 | if (VERBOSE) Log.d(TAG, "drainEncoder(" + endOfStream + ")"); 130 | 131 | if (endOfStream) { 132 | if (VERBOSE) Log.d(TAG, "sending EOS to encoder"); 133 | mEncoder.signalEndOfInputStream(); 134 | } 135 | 136 | ByteBuffer[] encoderOutputBuffers = mEncoder.getOutputBuffers(); 137 | while (true) { 138 | int encoderStatus = mEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC); 139 | if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { 140 | // no output available yet 141 | if (!endOfStream) { 142 | break; // out of while 143 | } else { 144 | if (VERBOSE) Log.d(TAG, "no output available, spinning to await EOS"); 145 | } 146 | } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { 147 | // not expected for an encoder 148 | encoderOutputBuffers = mEncoder.getOutputBuffers(); 149 | } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 150 | // should happen before receiving buffers, and should only happen once 151 | if (mMuxerStarted) { 152 | throw new RuntimeException("format changed twice"); 153 | } 154 | MediaFormat newFormat = mEncoder.getOutputFormat(); 155 | Log.d(TAG, "encoder output format changed: " + newFormat); 156 | 157 | // now that we have the Magic Goodies, start the muxer 158 | mTrackIndex = mMuxer.addTrack(newFormat); 159 | mMuxer.start(); 160 | mMuxerStarted = true; 161 | } else if (encoderStatus < 0) { 162 | Log.w(TAG, "unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus); 163 | // let's ignore it 164 | } else { 165 | ByteBuffer encodedData = encoderOutputBuffers[encoderStatus]; 166 | if (encodedData == null) { 167 | throw new RuntimeException("encoderOutputBuffer " + encoderStatus + 168 | " was null"); 169 | } 170 | 171 | if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { 172 | // The codec config data was pulled out and fed to the muxer when we got 173 | // the INFO_OUTPUT_FORMAT_CHANGED status. Ignore it. 174 | if (VERBOSE) Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG"); 175 | mBufferInfo.size = 0; 176 | } 177 | 178 | if (mBufferInfo.size != 0) { 179 | if (!mMuxerStarted) { 180 | throw new RuntimeException("muxer hasn't started"); 181 | } 182 | 183 | // adjust the ByteBuffer values to match BufferInfo (not needed?) 184 | encodedData.position(mBufferInfo.offset); 185 | encodedData.limit(mBufferInfo.offset + mBufferInfo.size); 186 | 187 | mMuxer.writeSampleData(mTrackIndex, encodedData, mBufferInfo); 188 | if (VERBOSE) { 189 | Log.d(TAG, "sent " + mBufferInfo.size + " bytes to muxer, ts=" + 190 | mBufferInfo.presentationTimeUs); 191 | } 192 | } 193 | 194 | mEncoder.releaseOutputBuffer(encoderStatus, false); 195 | 196 | if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 197 | if (!endOfStream) { 198 | Log.w(TAG, "reached end of stream unexpectedly"); 199 | } else { 200 | if (VERBOSE) Log.d(TAG, "end of stream reached"); 201 | } 202 | break; // out of while 203 | } 204 | } 205 | } 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /app/src/main/java/me/relex/camerafilter/video/WindowSurface.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package me.relex.camerafilter.video; 17 | 18 | import android.graphics.SurfaceTexture; 19 | import android.view.Surface; 20 | 21 | /** 22 | * Recordable EGL window surface. 23 | *
24 | * It's good practice to explicitly release() the surface, preferably from a "finally" block. 25 | */ 26 | public class WindowSurface extends EglSurfaceBase { 27 | private Surface mSurface; 28 | private boolean mReleaseSurface; 29 | 30 | /** 31 | * Associates an EGL surface with the native window surface. 32 | *
33 | * Set releaseSurface to true if you want the Surface to be released when release() is 34 | * called. This is convenient, but can interfere with framework classes that expect to 35 | * manage the Surface themselves (e.g. if you release a SurfaceView's Surface, the 36 | * surfaceDestroyed() callback won't fire). 37 | */ 38 | public WindowSurface(EglCore eglCore, Surface surface, boolean releaseSurface) { 39 | super(eglCore); 40 | createWindowSurface(surface); 41 | mSurface = surface; 42 | mReleaseSurface = releaseSurface; 43 | } 44 | 45 | /** 46 | * Associates an EGL surface with the SurfaceTexture. 47 | */ 48 | public WindowSurface(EglCore eglCore, SurfaceTexture surfaceTexture) { 49 | super(eglCore); 50 | createWindowSurface(surfaceTexture); 51 | } 52 | 53 | /** 54 | * Releases any resources associated with the EGL surface (and, if configured to do so, 55 | * with the Surface as well). 56 | *
57 | * Does not require that the surface's EGL context be current. 58 | */ 59 | public void release() { 60 | releaseEglSurface(); 61 | if (mSurface != null) { 62 | if (mReleaseSurface) { 63 | mSurface.release(); 64 | } 65 | mSurface = null; 66 | } 67 | } 68 | 69 | /** 70 | * Recreate the EGLSurface, using the new EglBase. The caller should have already 71 | * freed the old EGLSurface with releaseEglSurface(). 72 | *
73 | * This is useful when we want to update the EGLSurface associated with a Surface. 74 | * For example, if we want to share with a different EGLContext, which can only 75 | * be done by tearing down and recreating the context. (That's handled by the caller; 76 | * this just creates a new EGLSurface for the Surface we were handed earlier.) 77 | *
78 | * If the previous EGLSurface isn't fully destroyed, e.g. it's still current on a
79 | * context somewhere, the create call will fail with complaints from the Surface
80 | * about already being connected.
81 | */
82 | public void recreate(EglCore newEglCore) {
83 | if (mSurface == null) {
84 | throw new RuntimeException("not yet implemented for SurfaceTexture");
85 | }
86 | mEglCore = newEglCore; // switch to new context
87 | createWindowSurface(mSurface); // create new surface
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/app/src/main/java/me/relex/camerafilter/widget/AutoFitGLSurfaceView.java:
--------------------------------------------------------------------------------
1 | package me.relex.camerafilter.widget;
2 |
3 | import android.content.Context;
4 | import android.opengl.GLSurfaceView;
5 | import android.util.AttributeSet;
6 |
7 | public class AutoFitGLSurfaceView extends GLSurfaceView {
8 |
9 | protected int mRatioWidth = 0;
10 | protected int mRatioHeight = 0;
11 |
12 | public AutoFitGLSurfaceView(Context context) {
13 | super(context);
14 | }
15 |
16 | public AutoFitGLSurfaceView(Context context, AttributeSet attrs) {
17 | super(context, attrs);
18 | }
19 |
20 | public void setAspectRatio(int width, int height) {
21 | if (width < 0 || height < 0) {
22 | throw new IllegalArgumentException("Size cannot be negative.");
23 | }
24 | mRatioWidth = width;
25 | mRatioHeight = height;
26 | requestLayout();
27 | }
28 |
29 | @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
30 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
31 | int width = MeasureSpec.getSize(widthMeasureSpec);
32 | int height = MeasureSpec.getSize(heightMeasureSpec);
33 | if (0 == mRatioWidth || 0 == mRatioHeight) {
34 | setMeasuredDimension(width, height);
35 | } else {
36 | if (width < height * mRatioWidth / mRatioHeight) {
37 | setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
38 | } else {
39 | setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/java/me/relex/camerafilter/widget/CameraSurfaceView.java:
--------------------------------------------------------------------------------
1 | package me.relex.camerafilter.widget;
2 |
3 | import android.content.Context;
4 | import android.graphics.SurfaceTexture;
5 | import android.hardware.Camera;
6 | import android.os.Handler;
7 | import android.os.HandlerThread;
8 | import android.os.Looper;
9 | import android.os.Message;
10 | import android.util.AttributeSet;
11 | import me.relex.camerafilter.camera.CameraController;
12 | import me.relex.camerafilter.camera.CameraHelper;
13 | import me.relex.camerafilter.camera.CameraRecordRenderer;
14 | import me.relex.camerafilter.camera.CommonHandlerListener;
15 | import me.relex.camerafilter.filter.FilterManager.FilterType;
16 |
17 | public class CameraSurfaceView extends AutoFitGLSurfaceView
18 | implements CommonHandlerListener, SurfaceTexture.OnFrameAvailableListener {
19 |
20 | private CameraHandler mBackgroundHandler;
21 | private HandlerThread mHandlerThread;
22 | private CameraRecordRenderer mCameraRenderer;
23 |
24 | public CameraSurfaceView(Context context) {
25 | super(context);
26 | init(context);
27 | }
28 |
29 | public CameraSurfaceView(Context context, AttributeSet attrs) {
30 | super(context, attrs);
31 | init(context);
32 | }
33 |
34 | private void init(Context context) {
35 |
36 | setEGLContextClientVersion(2);
37 |
38 | mHandlerThread = new HandlerThread("CameraHandlerThread");
39 | mHandlerThread.start();
40 |
41 | mBackgroundHandler = new CameraHandler(mHandlerThread.getLooper(), this);
42 | mCameraRenderer =
43 | new CameraRecordRenderer(context.getApplicationContext(), mBackgroundHandler);
44 |
45 | setRenderer(mCameraRenderer);
46 | setRenderMode(RENDERMODE_WHEN_DIRTY);
47 | }
48 |
49 | public CameraRecordRenderer getRenderer() {
50 | return mCameraRenderer;
51 | }
52 |
53 | //public void setEncoderConfig(EncoderConfig encoderConfig) {
54 | // if (mCameraRenderer != null) {
55 | // mCameraRenderer.setEncoderConfig(encoderConfig);
56 | // }
57 | //}
58 |
59 | @Override public void onResume() {
60 | super.onResume();
61 | }
62 |
63 | @Override public void onPause() {
64 | mBackgroundHandler.removeCallbacksAndMessages(null);
65 | CameraController.getInstance().release();
66 | queueEvent(new Runnable() {
67 | @Override public void run() {
68 | // 跨进程 清空 Renderer数据
69 | mCameraRenderer.notifyPausing();
70 | }
71 | });
72 |
73 | super.onPause();
74 | }
75 |
76 | public void onDestroy() {
77 | mBackgroundHandler.removeCallbacksAndMessages(null);
78 | if (!mHandlerThread.isInterrupted()) {
79 | try {
80 | mHandlerThread.quit();
81 | mHandlerThread.interrupt();
82 | } catch (Exception e) {
83 | e.printStackTrace();
84 | }
85 | }
86 | }
87 |
88 | public void changeFilter(FilterType filterType) {
89 | mCameraRenderer.changeFilter(filterType);
90 | }
91 |
92 | @Override public void onFrameAvailable(SurfaceTexture surfaceTexture) {
93 | requestRender();
94 | }
95 |
96 | public static class CameraHandler extends Handler {
97 | public static final int SETUP_CAMERA = 1001;
98 | public static final int CONFIGURE_CAMERA = 1002;
99 | public static final int START_CAMERA_PREVIEW = 1003;
100 | //public static final int STOP_CAMERA_PREVIEW = 1004;
101 | private CommonHandlerListener listener;
102 |
103 | public CameraHandler(Looper looper, CommonHandlerListener listener) {
104 | super(looper);
105 | this.listener = listener;
106 | }
107 |
108 | @Override public void handleMessage(Message msg) {
109 | listener.handleMessage(msg);
110 | }
111 | }
112 |
113 | @Override public void handleMessage(final Message msg) {
114 | switch (msg.what) {
115 | case CameraHandler.SETUP_CAMERA: {
116 | final int width = msg.arg1;
117 | final int height = msg.arg2;
118 | final SurfaceTexture surfaceTexture = (SurfaceTexture) msg.obj;
119 | surfaceTexture.setOnFrameAvailableListener(this);
120 |
121 | mBackgroundHandler.post(new Runnable() {
122 | @Override public void run() {
123 | CameraController.getInstance()
124 | .setupCamera(surfaceTexture, getContext().getApplicationContext(),
125 | width);
126 | mBackgroundHandler.sendMessage(mBackgroundHandler.obtainMessage(
127 | CameraSurfaceView.CameraHandler.CONFIGURE_CAMERA, width, height));
128 | }
129 | });
130 | }
131 | break;
132 | case CameraHandler.CONFIGURE_CAMERA: {
133 | final int width = msg.arg1;
134 | final int height = msg.arg2;
135 | Camera.Size previewSize = CameraHelper.getOptimalPreviewSize(
136 | CameraController.getInstance().getCameraParameters(),
137 | CameraController.getInstance().mCameraPictureSize, width);
138 |
139 | CameraController.getInstance().configureCameraParameters(previewSize);
140 | if (previewSize != null) {
141 | mCameraRenderer.setCameraPreviewSize(previewSize.height, previewSize.width);
142 | }
143 | mBackgroundHandler.sendEmptyMessage(CameraHandler.START_CAMERA_PREVIEW);
144 | }
145 | break;
146 |
147 | case CameraHandler.START_CAMERA_PREVIEW:
148 | mBackgroundHandler.post(new Runnable() {
149 | @Override public void run() {
150 | CameraController.getInstance().startCameraPreview();
151 | }
152 | });
153 |
154 | break;
155 | //case CameraHandler.STOP_CAMERA_PREVIEW:
156 | // mBackgroundHandler.post(new Runnable() {
157 | // @Override public void run() {
158 | // CameraController.getInstance().stopCameraPreview();
159 | // }
160 | // });
161 | // break;
162 |
163 | default:
164 | break;
165 | }
166 | }
167 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ongakuer/CameraFilter/b8fde13626122e57d83c5eaf38b24192691b766e/app/src/main/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ongakuer/CameraFilter/b8fde13626122e57d83c5eaf38b24192691b766e/app/src/main/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/mask.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ongakuer/CameraFilter/b8fde13626122e57d83c5eaf38b24192691b766e/app/src/main/res/drawable-nodpi/mask.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/raw_image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ongakuer/CameraFilter/b8fde13626122e57d83c5eaf38b24192691b766e/app/src/main/res/drawable-nodpi/raw_image.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ongakuer/CameraFilter/b8fde13626122e57d83c5eaf38b24192691b766e/app/src/main/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ongakuer/CameraFilter/b8fde13626122e57d83c5eaf38b24192691b766e/app/src/main/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_image_filter.xml:
--------------------------------------------------------------------------------
1 |