49 | * Triangles are 0-1-2 and 2-1-3 (counter-clockwise winding). 50 | */ 51 | private static final float RECTANGLE_COORDS[] = { 52 | -0.5f, -0.5f, // 0 bottom left 53 | 0.5f, -0.5f, // 1 bottom right 54 | -0.5f, 0.5f, // 2 top left 55 | 0.5f, 0.5f, // 3 top right 56 | }; 57 | private static final float RECTANGLE_TEX_COORDS[] = { 58 | 0.0f, 1.0f, // 0 bottom left 59 | 1.0f, 1.0f, // 1 bottom right 60 | 0.0f, 0.0f, // 2 top left 61 | 1.0f, 0.0f // 3 top right 62 | }; 63 | private static final FloatBuffer RECTANGLE_BUF = 64 | GlUtil.createFloatBuffer(RECTANGLE_COORDS); 65 | private static final FloatBuffer RECTANGLE_TEX_BUF = 66 | GlUtil.createFloatBuffer(RECTANGLE_TEX_COORDS); 67 | 68 | /** 69 | * A "full" square, extending from -1 to +1 in both dimensions. When the model/view/projection 70 | * matrix is identity, this will exactly cover the viewport. 71 | *
72 | * The texture coordinates are Y-inverted relative to RECTANGLE. (This seems to work out 73 | * right with external textures from SurfaceTexture.) 74 | */ 75 | private static final float FULL_RECTANGLE_COORDS[] = { 76 | -1.0f, -1.0f, // 0 bottom left 77 | 1.0f, -1.0f, // 1 bottom right 78 | -1.0f, 1.0f, // 2 top left 79 | 1.0f, 1.0f, // 3 top right 80 | }; 81 | 82 | // private static final float FULL_RECTANGLE_COORDS[] = { 83 | // -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f 84 | // }; 85 | 86 | private static final float FULL_RECTANGLE_TEX_COORDS[] = { 87 | 0.0f, 0.0f, // 0 bottom left 88 | 1.0f, 0.0f, // 1 bottom right 89 | 0.0f, 1.0f, // 2 top left 90 | 1.0f, 1.0f // 3 top right 91 | }; 92 | private static final FloatBuffer FULL_RECTANGLE_BUF = 93 | GlUtil.createFloatBuffer(FULL_RECTANGLE_COORDS); 94 | private static final FloatBuffer FULL_RECTANGLE_TEX_BUF = 95 | GlUtil.createFloatBuffer(FULL_RECTANGLE_TEX_COORDS); 96 | 97 | 98 | private FloatBuffer mVertexArray; 99 | private FloatBuffer mTexCoordArray; 100 | private int mVertexCount; 101 | private int mCoordsPerVertex; 102 | private int mVertexStride; 103 | private int mTexCoordStride; 104 | private Prefab mPrefab; 105 | 106 | /** 107 | * Enum values for constructor. 108 | */ 109 | public enum Prefab { 110 | TRIANGLE, RECTANGLE, FULL_RECTANGLE 111 | } 112 | 113 | /** 114 | * Prepares a drawable from a "pre-fabricated" shape definition. 115 | *
116 | * Does no EGL/GL operations, so this can be done at any time. 117 | */ 118 | public Drawable2d(Prefab shape) { 119 | switch (shape) { 120 | case TRIANGLE: 121 | mVertexArray = TRIANGLE_BUF; 122 | mTexCoordArray = TRIANGLE_TEX_BUF; 123 | mCoordsPerVertex = 2; 124 | mVertexStride = mCoordsPerVertex * SIZEOF_FLOAT; 125 | mVertexCount = TRIANGLE_COORDS.length / mCoordsPerVertex; 126 | break; 127 | case RECTANGLE: 128 | mVertexArray = RECTANGLE_BUF; 129 | mTexCoordArray = RECTANGLE_TEX_BUF; 130 | mCoordsPerVertex = 2; 131 | mVertexStride = mCoordsPerVertex * SIZEOF_FLOAT; 132 | mVertexCount = RECTANGLE_COORDS.length / mCoordsPerVertex; 133 | break; 134 | case FULL_RECTANGLE: 135 | mVertexArray = FULL_RECTANGLE_BUF; 136 | mTexCoordArray = FULL_RECTANGLE_TEX_BUF; 137 | mCoordsPerVertex = 2; 138 | mVertexStride = mCoordsPerVertex * SIZEOF_FLOAT; 139 | mVertexCount = FULL_RECTANGLE_COORDS.length / mCoordsPerVertex; 140 | break; 141 | default: 142 | throw new RuntimeException("Unknown shape " + shape); 143 | } 144 | mTexCoordStride = 2 * SIZEOF_FLOAT; 145 | mPrefab = shape; 146 | } 147 | 148 | /** 149 | * Returns the array of vertices. 150 | *
151 | * To avoid allocations, this returns internal state. The caller must not modify it. 152 | */ 153 | public FloatBuffer getVertexArray() { 154 | return mVertexArray; 155 | } 156 | 157 | /** 158 | * Returns the array of texture coordinates. 159 | *
160 | * To avoid allocations, this returns internal state. The caller must not modify it. 161 | */ 162 | public FloatBuffer getTexCoordArray() { 163 | return mTexCoordArray; 164 | } 165 | 166 | /** 167 | * Returns the number of vertices stored in the vertex array. 168 | */ 169 | public int getVertexCount() { 170 | return mVertexCount; 171 | } 172 | 173 | /** 174 | * Returns the width, in bytes, of the data for each vertex. 175 | */ 176 | public int getVertexStride() { 177 | return mVertexStride; 178 | } 179 | 180 | /** 181 | * Returns the width, in bytes, of the data for each texture coordinate. 182 | */ 183 | public int getTexCoordStride() { 184 | return mTexCoordStride; 185 | } 186 | 187 | /** 188 | * Returns the number of position coordinates per vertex. This will be 2 or 3. 189 | */ 190 | public int getCoordsPerVertex() { 191 | return mCoordsPerVertex; 192 | } 193 | 194 | @Override 195 | public String toString() { 196 | if (mPrefab != null) { 197 | return "[Drawable2d: " + mPrefab + "]"; 198 | } else { 199 | return "[Drawable2d: ...]"; 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /libRecorderEditor/src/main/java/com/pinssible/librecorder/base/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 com.pinssible.librecorder.base; 18 | 19 | import android.graphics.Bitmap; 20 | import android.opengl.EGL14; 21 | import android.opengl.EGLSurface; 22 | import android.opengl.GLES20; 23 | import android.os.Build; 24 | import android.support.annotation.RequiresApi; 25 | import android.util.Log; 26 | 27 | import java.io.BufferedOutputStream; 28 | import java.io.File; 29 | import java.io.FileOutputStream; 30 | import java.io.IOException; 31 | import java.nio.ByteBuffer; 32 | import java.nio.ByteOrder; 33 | 34 | /** 35 | * Common base class for EGL surfaces. 36 | *
37 | * There can be multiple surfaces associated with a single context. 38 | */ 39 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) 40 | public class EglSurfaceBase { 41 | protected static final String TAG = "EglSurfaceBase"; 42 | 43 | // EglCore object we're associated with. It may be associated with multiple surfaces. 44 | protected EglCore mEglCore; 45 | 46 | private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE; 47 | private int mWidth = -1; 48 | private int mHeight = -1; 49 | 50 | protected EglSurfaceBase(EglCore eglCore) { 51 | mEglCore = eglCore; 52 | } 53 | 54 | /** 55 | * Creates a window surface. 56 | *
57 | * @param surface May be a Surface or SurfaceTexture. 58 | */ 59 | public void createWindowSurface(Object surface) { 60 | if (mEGLSurface != EGL14.EGL_NO_SURFACE) { 61 | throw new IllegalStateException("surface already created"); 62 | } 63 | mEGLSurface = mEglCore.createWindowSurface(surface); 64 | 65 | // Don't cache width/height here, because the size of the underlying surface can change 66 | // out from under us (see e.g. HardwareScalerActivity). 67 | //mWidth = mEglCore.querySurface(mEGLSurface, EGL14.EGL_WIDTH); 68 | //mHeight = mEglCore.querySurface(mEGLSurface, EGL14.EGL_HEIGHT); 69 | } 70 | 71 | /** 72 | * Creates an off-screen surface. 73 | */ 74 | public void createOffscreenSurface(int width, int height) { 75 | if (mEGLSurface != EGL14.EGL_NO_SURFACE) { 76 | throw new IllegalStateException("surface already created"); 77 | } 78 | mEGLSurface = mEglCore.createOffscreenSurface(width, height); 79 | mWidth = width; 80 | mHeight = height; 81 | } 82 | 83 | /** 84 | * Returns the surface's width, in pixels. 85 | *
86 | * If this is called on a window surface, and the underlying surface is in the process 87 | * of changing size, we may not see the new size right away (e.g. in the "surfaceChanged" 88 | * callback). The size should match after the next buffer swap. 89 | */ 90 | public int getWidth() { 91 | if (mWidth < 0) { 92 | return mEglCore.querySurface(mEGLSurface, EGL14.EGL_WIDTH); 93 | } else { 94 | return mWidth; 95 | } 96 | } 97 | 98 | /** 99 | * Returns the surface's height, in pixels. 100 | */ 101 | public int getHeight() { 102 | if (mHeight < 0) { 103 | return mEglCore.querySurface(mEGLSurface, EGL14.EGL_HEIGHT); 104 | } else { 105 | return mHeight; 106 | } 107 | } 108 | 109 | /** 110 | * Release the EGL surface. 111 | */ 112 | public void releaseEglSurface() { 113 | mEglCore.releaseSurface(mEGLSurface); 114 | mEGLSurface = EGL14.EGL_NO_SURFACE; 115 | mWidth = mHeight = -1; 116 | } 117 | 118 | /** 119 | * Makes our EGL context and surface current. 120 | */ 121 | public void makeCurrent() { 122 | mEglCore.makeCurrent(mEGLSurface); 123 | } 124 | 125 | /** 126 | * Makes our EGL context and surface current for drawing, using the supplied surface 127 | * for reading. 128 | */ 129 | public void makeCurrentReadFrom(EglSurfaceBase readSurface) { 130 | mEglCore.makeCurrent(mEGLSurface, readSurface.mEGLSurface); 131 | } 132 | 133 | /** 134 | * Calls eglSwapBuffers. Use this to "publish" the current frame. 135 | * 136 | * @return false on failure 137 | */ 138 | public boolean swapBuffers() { 139 | boolean result = mEglCore.swapBuffers(mEGLSurface); 140 | if (!result) { 141 | Log.d(TAG, "WARNING: swapBuffers() failed"); 142 | } 143 | return result; 144 | } 145 | 146 | /** 147 | * Sends the presentation time stamp to EGL. 148 | * 149 | * @param nsecs Timestamp, in nanoseconds. 150 | */ 151 | public void setPresentationTime(long nsecs) { 152 | mEglCore.setPresentationTime(mEGLSurface, nsecs); 153 | } 154 | 155 | /** 156 | * Saves the EGL surface to a file. 157 | *
158 | * Expects that this object's EGL surface is current. 159 | */ 160 | public void saveFrame(File file) throws IOException { 161 | if (!mEglCore.isCurrent(mEGLSurface)) { 162 | throw new RuntimeException("Expected EGL context/surface is not current"); 163 | } 164 | 165 | // glReadPixels fills in a "direct" ByteBuffer with what is essentially big-endian RGBA 166 | // data (i.e. a byte of red, followed by a byte of green...). While the Bitmap 167 | // constructor that takes an int[] wants little-endian ARGB (blue/red swapped), the 168 | // Bitmap "copy pixels" method wants the same format GL provides. 169 | // 170 | // Ideally we'd have some way to re-use the ByteBuffer, especially if we're calling 171 | // here often. 172 | // 173 | // Making this even more interesting is the upside-down nature of GL, which means 174 | // our output will look upside down relative to what appears on screen if the 175 | // typical GL conventions are used. 176 | 177 | String filename = file.toString(); 178 | 179 | int width = getWidth(); 180 | int height = getHeight(); 181 | ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4); 182 | buf.order(ByteOrder.LITTLE_ENDIAN); 183 | GLES20.glReadPixels(0, 0, width, height, 184 | GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf); 185 | GlUtil.checkGlError("glReadPixels"); 186 | buf.rewind(); 187 | 188 | BufferedOutputStream bos = null; 189 | try { 190 | bos = new BufferedOutputStream(new FileOutputStream(filename)); 191 | Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 192 | bmp.copyPixelsFromBuffer(buf); 193 | bmp.compress(Bitmap.CompressFormat.PNG, 90, bos); 194 | bmp.recycle(); 195 | } finally { 196 | if (bos != null) bos.close(); 197 | } 198 | Log.d(TAG, "Saved " + width + "x" + height + " frame as '" + filename + "'"); 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /libRecorderEditor/src/main/java/com/pinssible/librecorder/base/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 com.pinssible.librecorder.base; 18 | 19 | import android.opengl.Matrix; 20 | import android.view.MotionEvent; 21 | 22 | import com.pinssible.librecorder.filter.program.GpuImageProgram; 23 | import com.pinssible.librecorder.filter.program.Program; 24 | import com.pinssible.librecorder.filter.program.Texture2dProgram; 25 | 26 | import java.nio.FloatBuffer; 27 | 28 | /** 29 | * This class essentially represents a viewport-sized sprite that will be rendered with 30 | * a texture, usually from an external source like the camera or video decoder. 31 | * 32 | */ 33 | public class FullFrameRect { 34 | 35 | private final Drawable2d mRectDrawable = new Drawable2d(Drawable2d.Prefab.FULL_RECTANGLE); 36 | private Program mProgram; 37 | private final Object mDrawLock = new Object(); 38 | 39 | private static final int SIZEOF_FLOAT = 4; 40 | 41 | private float[] IDENTITY_MATRIX = new float[16]; 42 | 43 | private static final float TEX_COORDS[] = { //纹理坐标系(和顶点坐标系有所不同) 44 | 0.0f, 0.0f, // 0 bottom left 45 | 1.0f, 0.0f, // 1 bottom right 46 | 0.0f, 1.0f, // 2 top left 47 | 1.0f, 1.0f // 3 top right 48 | }; 49 | private static final FloatBuffer TEX_COORDS_BUF = GlUtil.createFloatBuffer(TEX_COORDS); 50 | private static final int TEX_COORDS_STRIDE = 2 * SIZEOF_FLOAT; 51 | 52 | /** 53 | * Prepares the object. 54 | * 55 | * @param program The program to use. FullFrameRect takes ownership, and will release 56 | * the program when no longer needed. 57 | */ 58 | public FullFrameRect(Program program) { 59 | mProgram = program; 60 | Matrix.setIdentityM(IDENTITY_MATRIX, 0); 61 | } 62 | 63 | 64 | public void release(boolean doEglCleanup) { 65 | if (mProgram != null) { 66 | if (doEglCleanup) { 67 | mProgram.release(); 68 | } 69 | mProgram = null; 70 | } 71 | } 72 | 73 | /** 74 | * Returns the program currently in use. 75 | */ 76 | public Program getProgram() { 77 | return mProgram; 78 | } 79 | 80 | /** 81 | * Changes the program. The previous program will be released. 82 | */ 83 | public void changeProgram(Program program) { 84 | mProgram.release(); 85 | mProgram = program; 86 | } 87 | 88 | /** 89 | * Creates a texture object suitable for use with drawFrame(). 90 | */ 91 | public int createTextureObject() { 92 | return mProgram.createTextureObject(); 93 | } 94 | 95 | /** 96 | * Draws a viewport-filling rect, texturing it with the specified texture object. 97 | */ 98 | public void drawFrame(int textureId, float[] texMatrix) { 99 | // Use the identity matrix for MVP so our 2x2 FULL_RECTANGLE covers the viewport. 100 | synchronized (mDrawLock) { 101 | mProgram.draw(IDENTITY_MATRIX, mRectDrawable.getVertexArray(), 0, 102 | mRectDrawable.getVertexCount(), mRectDrawable.getCoordsPerVertex(), 103 | mRectDrawable.getVertexStride(), 104 | texMatrix, TEX_COORDS_BUF, textureId, TEX_COORDS_STRIDE); 105 | } 106 | } 107 | 108 | /** 109 | * Pass touch event down to the 110 | * texture's shader program 111 | * 112 | * @param ev 113 | */ 114 | public void handleTouchEvent(MotionEvent ev) { 115 | mProgram.handleTouchEvent(ev); 116 | } 117 | } -------------------------------------------------------------------------------- /libRecorderEditor/src/main/java/com/pinssible/librecorder/base/GlUtil.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 com.pinssible.librecorder.base; 18 | 19 | import android.opengl.GLES20; 20 | import android.opengl.GLES30; 21 | import android.opengl.Matrix; 22 | import android.util.Log; 23 | 24 | import java.nio.ByteBuffer; 25 | import java.nio.ByteOrder; 26 | import java.nio.FloatBuffer; 27 | 28 | /** 29 | * Some OpenGL utility functions. 30 | */ 31 | public class GlUtil { 32 | public static final String TAG = "Grafika"; 33 | 34 | /** Identity matrix for general use. Don't modify or life will get weird. */ 35 | public static final float[] IDENTITY_MATRIX; 36 | static { 37 | IDENTITY_MATRIX = new float[16]; 38 | Matrix.setIdentityM(IDENTITY_MATRIX, 0); 39 | } 40 | 41 | private static final int SIZEOF_FLOAT = 4; 42 | 43 | 44 | private GlUtil() {} // do not instantiate 45 | 46 | /** 47 | * Creates a new program from the supplied vertex and fragment shaders. 48 | * 49 | * @return A handle to the program, or 0 on failure. 50 | */ 51 | public static int createProgram(String vertexSource, String fragmentSource) { 52 | int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); 53 | if (vertexShader == 0) { 54 | return 0; 55 | } 56 | int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); 57 | if (pixelShader == 0) { 58 | return 0; 59 | } 60 | 61 | int program = GLES20.glCreateProgram(); 62 | checkGlError("glCreateProgram"); 63 | if (program == 0) { 64 | Log.e(TAG, "Could not create program"); 65 | } 66 | GLES20.glAttachShader(program, vertexShader); 67 | checkGlError("glAttachShader"); 68 | GLES20.glAttachShader(program, pixelShader); 69 | checkGlError("glAttachShader"); 70 | GLES20.glLinkProgram(program); 71 | int[] linkStatus = new int[1]; 72 | GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); 73 | if (linkStatus[0] != GLES20.GL_TRUE) { 74 | Log.e(TAG, "Could not link program: "); 75 | Log.e(TAG, GLES20.glGetProgramInfoLog(program)); 76 | GLES20.glDeleteProgram(program); 77 | program = 0; 78 | } 79 | return program; 80 | } 81 | 82 | /** 83 | * Compiles the provided shader source. 84 | * 85 | * @return A handle to the shader, or 0 on failure. 86 | */ 87 | public static int loadShader(int shaderType, String source) { 88 | int shader = GLES20.glCreateShader(shaderType); 89 | checkGlError("glCreateShader type=" + shaderType); 90 | GLES20.glShaderSource(shader, source); 91 | GLES20.glCompileShader(shader); 92 | int[] compiled = new int[1]; 93 | GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); 94 | if (compiled[0] == 0) { 95 | Log.e(TAG, "Could not compile shader " + shaderType + ":"); 96 | Log.e(TAG, " " + GLES20.glGetShaderInfoLog(shader)); 97 | GLES20.glDeleteShader(shader); 98 | shader = 0; 99 | } 100 | return shader; 101 | } 102 | 103 | /** 104 | * Checks to see if a GLES error has been raised. 105 | */ 106 | public static void checkGlError(String op) { 107 | int error = GLES20.glGetError(); 108 | if (error != GLES20.GL_NO_ERROR) { 109 | String msg = op + ": glError 0x" + Integer.toHexString(error); 110 | Log.e(TAG, msg); 111 | throw new RuntimeException(msg); 112 | } 113 | } 114 | 115 | /** 116 | * Checks to see if the location we obtained is valid. GLES returns -1 if a label 117 | * could not be found, but does not set the GL error. 118 | *
119 | * Throws a RuntimeException if the location is invalid. 120 | */ 121 | public static void checkLocation(int location, String label) { 122 | if (location < 0) { 123 | throw new RuntimeException("Unable to locate '" + label + "' in program"); 124 | } 125 | } 126 | 127 | /** 128 | * Creates a texture from raw data. 129 | * 130 | * @param data Image data, in a "direct" ByteBuffer. 131 | * @param width Texture width, in pixels (not bytes). 132 | * @param height Texture height, in pixels. 133 | * @param format Image data format (use constant appropriate for glTexImage2D(), e.g. GL_RGBA). 134 | * @return Handle to texture. 135 | */ 136 | public static int createImageTexture(ByteBuffer data, int width, int height, int format) { 137 | int[] textureHandles = new int[1]; 138 | int textureHandle; 139 | 140 | GLES20.glGenTextures(1, textureHandles, 0); 141 | textureHandle = textureHandles[0]; 142 | GlUtil.checkGlError("glGenTextures"); 143 | 144 | // Bind the texture handle to the 2D texture target. 145 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle); 146 | 147 | // Configure min/mag filtering, i.e. what scaling method do we use if what we're rendering 148 | // is smaller or larger than the source image. 149 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, 150 | GLES20.GL_LINEAR); 151 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, 152 | GLES20.GL_LINEAR); 153 | GlUtil.checkGlError("loadImageTexture"); 154 | 155 | // Load the data from the buffer into the texture handle. 156 | GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, /*level*/ 0, format, 157 | width, height, /*border*/ 0, format, GLES20.GL_UNSIGNED_BYTE, data); 158 | GlUtil.checkGlError("loadImageTexture"); 159 | 160 | return textureHandle; 161 | } 162 | 163 | /** 164 | * Allocates a direct float buffer, and populates it with the float array data. 165 | */ 166 | public static FloatBuffer createFloatBuffer(float[] coords) { 167 | // Allocate a direct ByteBuffer, using 4 bytes per float, and copy coords into it. 168 | ByteBuffer bb = ByteBuffer.allocateDirect(coords.length * SIZEOF_FLOAT); 169 | bb.order(ByteOrder.nativeOrder()); 170 | FloatBuffer fb = bb.asFloatBuffer(); 171 | fb.put(coords); 172 | fb.position(0); 173 | return fb; 174 | } 175 | 176 | /** 177 | * Writes GL version info to the log. 178 | */ 179 | public static void logVersionInfo() { 180 | Log.i(TAG, "vendor : " + GLES20.glGetString(GLES20.GL_VENDOR)); 181 | Log.i(TAG, "renderer: " + GLES20.glGetString(GLES20.GL_RENDERER)); 182 | Log.i(TAG, "version : " + GLES20.glGetString(GLES20.GL_VERSION)); 183 | 184 | if (false) { 185 | int[] values = new int[1]; 186 | GLES30.glGetIntegerv(GLES30.GL_MAJOR_VERSION, values, 0); 187 | int majorVersion = values[0]; 188 | GLES30.glGetIntegerv(GLES30.GL_MINOR_VERSION, values, 0); 189 | int minorVersion = values[0]; 190 | if (GLES30.glGetError() == GLES30.GL_NO_ERROR) { 191 | Log.i(TAG, "iversion: " + majorVersion + "." + minorVersion); 192 | } 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /libRecorderEditor/src/main/java/com/pinssible/librecorder/base/Viewport.java: -------------------------------------------------------------------------------- 1 | package com.pinssible.librecorder.base; 2 | 3 | /** 4 | * Created by ZhangHaoSong on 2017/10/17. 5 | */ 6 | 7 | public class Viewport { 8 | 9 | public int x, y; 10 | public int width, height; 11 | 12 | public Viewport() { 13 | } 14 | 15 | public Viewport(int _x, int _y, int _width, int _height) { 16 | x = _x; 17 | y = _y; 18 | width = _width; 19 | height = _height; 20 | } 21 | 22 | @Override 23 | public String toString() { 24 | return "Viewport{" + 25 | "x=" + x + 26 | ", y=" + y + 27 | ", width=" + width + 28 | ", height=" + height + 29 | '}'; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /libRecorderEditor/src/main/java/com/pinssible/librecorder/base/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 | 17 | package com.pinssible.librecorder.base; 18 | 19 | import android.graphics.SurfaceTexture; 20 | import android.os.Build; 21 | import android.support.annotation.RequiresApi; 22 | import android.view.Surface; 23 | 24 | /** 25 | * Recordable EGL window surface. 26 | *
27 | * It's good practice to explicitly release() the surface, preferably from a "finally" block. 28 | */ 29 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) 30 | public class WindowSurface extends EglSurfaceBase { 31 | private Surface mSurface; 32 | private boolean mReleaseSurface; 33 | 34 | /** 35 | * Associates an EGL surface with the native window surface. 36 | *
37 | * Set releaseSurface to true if you want the Surface to be released when release() is 38 | * called. This is convenient, but can interfere with framework classes that expect to 39 | * manage the Surface themselves (e.g. if you release a SurfaceView's Surface, the 40 | * surfaceDestroyed() callback won't fire). 41 | */ 42 | public WindowSurface(EglCore eglCore, Surface surface, boolean releaseSurface) { 43 | super(eglCore); 44 | createWindowSurface(surface); 45 | mSurface = surface; 46 | mReleaseSurface = releaseSurface; 47 | } 48 | 49 | /** 50 | * Associates an EGL surface with the SurfaceTexture. 51 | */ 52 | public WindowSurface(EglCore eglCore, SurfaceTexture surfaceTexture) { 53 | super(eglCore); 54 | createWindowSurface(surfaceTexture); 55 | } 56 | 57 | /** 58 | * Releases any resources associated with the EGL surface (and, if configured to do so, 59 | * with the Surface as well). 60 | *
61 | * Does not require that the surface's EGL context be current. 62 | */ 63 | public void release() { 64 | releaseEglSurface(); 65 | if (mSurface != null) { 66 | if (mReleaseSurface) { 67 | mSurface.release(); 68 | } 69 | mSurface = null; 70 | } 71 | } 72 | 73 | /** 74 | * Recreate the EGLSurface, using the new EglBase. The caller should have already 75 | * freed the old EGLSurface with releaseEglSurface(). 76 | *
77 | * This is useful when we want to update the EGLSurface associated with a Surface. 78 | * For example, if we want to share with a different EGLContext, which can only 79 | * be done by tearing down and recreating the context. (That's handled by the caller; 80 | * this just creates a new EGLSurface for the Surface we were handed earlier.) 81 | *
82 | * If the previous EGLSurface isn't fully destroyed, e.g. it's still current on a 83 | * context somewhere, the create call will fail with complaints from the Surface 84 | * about already being connected. 85 | */ 86 | public void recreate(EglCore newEglCore) { 87 | if (mSurface == null) { 88 | throw new RuntimeException("not yet implemented for SurfaceTexture"); 89 | } 90 | mEglCore = newEglCore; // switch to new context 91 | createWindowSurface(mSurface); // create new surface 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /libRecorderEditor/src/main/java/com/pinssible/librecorder/filter/program/GpuImageProgram.java: -------------------------------------------------------------------------------- 1 | package com.pinssible.librecorder.filter.program; 2 | 3 | import android.opengl.GLES11Ext; 4 | import android.opengl.GLES20; 5 | import android.util.Log; 6 | import android.view.MotionEvent; 7 | 8 | import com.pinssible.librecorder.base.GlUtil; 9 | import com.pinssible.librecorder.base.Viewport; 10 | 11 | import java.nio.FloatBuffer; 12 | 13 | /** 14 | * Created by ZhangHaoSong on 2017/10/10. 15 | */ 16 | 17 | public class GpuImageProgram extends Program { 18 | private static final String TAG = "GpuImageProgram"; 19 | 20 | 21 | /*************************************************************************** Program ***********************************************************************************************************************************************************************/ 22 | //origin program used for all origin. 23 | protected static final String vshDrawDefault = "" + 24 | "attribute vec2 vPosition;\n" + // attribute 是将要传入的参数 只读定点数据,只在这里使用,可以是浮点类型的标量向量或者矩阵 25 | "varying vec2 texCoord;\n" + //varying 是传递给fragmentshader的参数,顶点着色器输出, 26 | "uniform mat4 transform;\n" + //uniform 一致变量,执行期间值不会改变,和fragmentshader共享,和matrix相关联 mat4 是个4*4的矩阵 27 | "uniform mat2 rotation;\n" + // 28 | "uniform vec2 flipScale;\n" + 29 | "void main()\n" + 30 | "{\n" + 31 | " gl_Position = vec4(vPosition, 0.0, 1.0);\n" + // glPosition 是一个特殊的变量,用来存储最终的位置,所有的定点着色器必须写这个值 32 | " vec2 coord = flipScale * (vPosition / 2.0 * rotation) + 0.5;\n" + 33 | " texCoord = (transform * vec4(coord, 0.0, 1.0)).xy;\n" + //将这个值重新赋值 34 | "}"; 35 | 36 | //origin fragment program 37 | protected static final String fshDrawOrigin = 38 | "#extension GL_OES_EGL_image_external : require\n" + 39 | "precision mediump float;\n" + 40 | "varying vec2 texCoord;\n" + 41 | "uniform samplerExternalOES inputImageTexture;\n" + 42 | "void main()\n" + 43 | "{\n" + 44 | " gl_FragColor = texture2D(inputImageTexture, texCoord);\n" + 45 | "}"; 46 | 47 | 48 | /*************************************************************************** Init ***********************************************************************************************************************************************************************/ 49 | private ProgramType mProgramType; 50 | 51 | //text viewport 52 | protected Viewport mTexViewport; 53 | protected int mTexWidth; 54 | protected int mTexHeight; 55 | 56 | private int mTextureTarget; 57 | private int mProgramHandle; //programId 58 | 59 | //program 变量 60 | protected int mRotationLoc, mFlipScaleLoc, mTransformLoc, maPositionLoc = 0; 61 | protected static final String POSITION_NAME = "vPosition"; 62 | protected static final String ROTATION_NAME = "rotation"; 63 | protected static final String FLIPSCALE_NAME = "flipScale"; 64 | protected static final String TRANSFORM_NAME = "transform"; 65 | 66 | public GpuImageProgram(ProgramType programType,Viewport previewViewport) { 67 | Log.e(TAG,"GpuImageProgram"); 68 | init(programType,previewViewport); 69 | } 70 | 71 | @Override 72 | protected void init(ProgramType programType,Viewport previewViewport) { 73 | //Viewport 74 | this.previewViewport = previewViewport; 75 | //调整显示区域(重新设置为全屏,预设播放区域) 76 | GLES20.glViewport(previewViewport.x, previewViewport.y, previewViewport.width, previewViewport.height); 77 | 78 | mProgramType = programType; 79 | 80 | //initProgram 81 | mTextureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES; 82 | mProgramHandle = GlUtil.createProgram(vshDrawDefault, fshDrawOrigin); 83 | 84 | if (mProgramHandle == 0) { 85 | throw new RuntimeException("Unable to create program"); 86 | } 87 | Log.d(TAG, "Created program " + mProgramHandle + " (" + programType + ")"); 88 | 89 | //绑定变量 90 | GLES20.glBindAttribLocation(mProgramHandle, maPositionLoc, POSITION_NAME); 91 | 92 | mRotationLoc = GLES20.glGetUniformLocation(mProgramHandle, ROTATION_NAME); 93 | GlUtil.checkLocation(mRotationLoc, ROTATION_NAME); 94 | 95 | mFlipScaleLoc = GLES20.glGetUniformLocation(mProgramHandle, FLIPSCALE_NAME); 96 | GlUtil.checkLocation(mFlipScaleLoc, FLIPSCALE_NAME); 97 | 98 | mTransformLoc = GLES20.glGetUniformLocation(mProgramHandle, TRANSFORM_NAME); 99 | GlUtil.checkLocation(mTransformLoc, TRANSFORM_NAME); 100 | 101 | //初始化变化 102 | setRotation(0.0f); 103 | setFlipscale(1.0f, 1.0f); 104 | setTransform(new float[]{ 105 | 1.0f, 0.0f, 0.0f, 0.0f, 106 | 0.0f, 1.0f, 0.0f, 0.0f, 107 | 0.0f, 0.0f, 1.0f, 0.0f, 108 | 0.0f, 0.0f, 0.0f, 1.0f 109 | }); 110 | } 111 | 112 | @Override 113 | public void release() { 114 | Log.d(TAG, "deleting program " + mProgramHandle); 115 | GLES20.glDeleteProgram(mProgramHandle); 116 | mProgramHandle = -1; 117 | } 118 | 119 | @Override 120 | public ProgramType getProgramType() { 121 | return mProgramType; 122 | } 123 | 124 | @Override 125 | public int createTextureObject() { 126 | int[] textures = new int[1]; 127 | GLES20.glGenTextures(1, textures, 0); 128 | GlUtil.checkGlError("glGenTextures"); 129 | 130 | int texId = textures[0]; 131 | GLES20.glBindTexture(mTextureTarget, texId); 132 | GlUtil.checkGlError("glBindTexture " + texId); 133 | 134 | GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, 135 | GLES20.GL_NEAREST); 136 | GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, 137 | GLES20.GL_LINEAR); 138 | GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, 139 | GLES20.GL_CLAMP_TO_EDGE); 140 | GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, 141 | GLES20.GL_CLAMP_TO_EDGE); 142 | GlUtil.checkGlError("glTexParameter"); 143 | 144 | return texId; 145 | } 146 | 147 | 148 | @Override 149 | public void draw(float[] mvpMatrix, FloatBuffer vertexBuffer, int firstVertex, 150 | int vertexCount, int coordsPerVertex, int vertexStride, 151 | float[] texMatrix, FloatBuffer texBuffer, int textureId, int texStride) { 152 | GlUtil.checkGlError("draw start"); 153 | 154 | // Select the program. 155 | GLES20.glUseProgram(mProgramHandle); 156 | GlUtil.checkGlError("glUseProgram"); 157 | 158 | // Set the texture. 159 | GLES20.glActiveTexture(GLES20.GL_TEXTURE0); //glActiveTexture选择哪一个纹理在后面的纹理状态改变时有效, 160 | GLES20.glBindTexture(mTextureTarget, textureId); //再次绑定OES纹理 161 | 162 | // Enable the "vPosition" vertex attribute. 163 | GLES20.glEnableVertexAttribArray(maPositionLoc); 164 | GlUtil.checkGlError("glEnableVertexAttribArray"); 165 | 166 | // Connect vertexBuffer to "vPosition". 167 | GLES20.glVertexAttribPointer(maPositionLoc, coordsPerVertex, 168 | GLES20.GL_FLOAT, false, vertexStride, vertexBuffer); 169 | GlUtil.checkGlError("glVertexAttribPointer"); 170 | 171 | // Draw the rect.(绘制矩形)(目前就绘制4个点,一个矩形,所有画面) 172 | GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, firstVertex, vertexCount); //绘制,运行Shader,GlProgram 173 | GlUtil.checkGlError("glDrawArrays"); 174 | 175 | // // Done -- disable vertex array, texture, and program. 176 | // GLES20.glDisableVertexAttribArray(maPositionLoc); 177 | // GLES20.glBindTexture(mTextureTarget, 0); 178 | // GLES20.glUseProgram(0); 179 | } 180 | 181 | 182 | @Override 183 | public void setTexSize(int width, int height) { 184 | mTexHeight = height; 185 | mTexWidth = width; 186 | mTexViewport = new Viewport(0, 0, mTexWidth, mTexHeight); 187 | } 188 | 189 | @Override 190 | public void handleTouchEvent(MotionEvent ev) { 191 | //todo 192 | } 193 | 194 | //设置界面旋转弧度 -- 录像时一般是 PI / 2 (也就是 90°) 的整数倍 195 | public void setRotation(float rad) { 196 | Log.e(TAG, "setRotation"); 197 | final float cosRad = (float) Math.cos(rad); 198 | final float sinRad = (float) Math.sin(rad); 199 | 200 | float rot[] = new float[]{ 201 | cosRad, sinRad, 202 | -sinRad, cosRad 203 | }; 204 | 205 | GLES20.glUseProgram(mProgramHandle); 206 | GLES20.glUniformMatrix2fv(mRotationLoc, 1, false, rot, 0); 207 | } 208 | 209 | public void setFlipscale(float x, float y) { 210 | GLES20.glUseProgram(mProgramHandle); 211 | GLES20.glUniform2f(mFlipScaleLoc, x, y); 212 | } 213 | 214 | public void setTransform(float[] matrix) { 215 | GLES20.glUseProgram(mProgramHandle); 216 | GLES20.glUniformMatrix4fv(mTransformLoc, 1, false, matrix, 0); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /libRecorderEditor/src/main/java/com/pinssible/librecorder/filter/program/LerpBlurGpuProgram.java: -------------------------------------------------------------------------------- 1 | package com.pinssible.librecorder.filter.program; 2 | 3 | import android.opengl.GLES20; 4 | import android.util.Log; 5 | 6 | import com.pinssible.librecorder.base.GlUtil; 7 | import com.pinssible.librecorder.base.Viewport; 8 | 9 | import java.nio.FloatBuffer; 10 | 11 | /** 12 | * Created by ZhangHaoSong on 2017/10/11. 13 | */ 14 | 15 | public class LerpBlurGpuProgram extends GpuImageProgram { 16 | private static final String TAG = "LerpBlurGpuProgram"; 17 | 18 | 19 | /*************************************************************************** Program ***********************************************************************************************************************************************************************/ 20 | //origin program used for all origin. 21 | protected static final String vshUpScale = "" + 22 | "attribute vec2 vPosition;\n" + 23 | "varying vec2 texCoord;\n" + 24 | "void main()\n" + 25 | "{\n" + 26 | " gl_Position = vec4(vPosition, 0.0, 1.0);\n" + 27 | " texCoord = vPosition / 2.0 + 0.5;\n" + 28 | "}"; 29 | 30 | //origin fragment program 31 | private static final String fshUpScale = "" + 32 | "precision mediump float;\n" + 33 | "varying vec2 texCoord;\n" + 34 | "uniform sampler2D inputImageTexture;\n" + 35 | 36 | "void main()\n" + 37 | "{\n" + 38 | " gl_FragColor = texture2D(inputImageTexture, texCoord);\n" + 39 | "}"; 40 | 41 | 42 | /*************************************************************************** Init ***********************************************************************************************************************************************************************/ 43 | 44 | 45 | private int[] mTextureDownScale; 46 | 47 | //Scale Program 48 | private int mScaleProgramId; 49 | private final int mLevel = 30; 50 | private int maPositionLoc = 0; 51 | 52 | //frame 53 | private int mFramebufferID; 54 | 55 | //Intensity 56 | private int mIntensity = 10; 57 | 58 | //intensity >= 0 level 越大,模糊程度越高 59 | public void setIntensity(int intensity) { 60 | if(intensity == mIntensity) 61 | return; 62 | 63 | mIntensity = intensity; 64 | if(mIntensity > mLevel) 65 | mIntensity = mLevel; 66 | } 67 | 68 | public LerpBlurGpuProgram(ProgramType programType, Viewport previewViewport) { 69 | super(programType, previewViewport); 70 | initLocal(); 71 | Log.e(TAG,"LerpBlurGpuProgram"); 72 | } 73 | 74 | private void initLocal() { 75 | //texture 76 | genMipmaps(mLevel, 512, 512); 77 | 78 | //frameBuffer 79 | int[] buf = new int[1]; 80 | GLES20.glGenFramebuffers(1, buf, 0); 81 | mFramebufferID = buf[0]; 82 | 83 | //scale program 84 | mScaleProgramId = GlUtil.createProgram(vshUpScale, fshUpScale); 85 | //get vPosition in scale program 86 | GLES20.glBindAttribLocation(mScaleProgramId, maPositionLoc, POSITION_NAME); 87 | } 88 | 89 | @Override 90 | public void draw(float[] mvpMatrix, FloatBuffer vertexBuffer, int firstVertex, int vertexCount, int coordsPerVertex, int vertexStride, float[] texMatrix, FloatBuffer texBuffer, int textureId, int texStride) { 91 | if (mIntensity == 0 || mFramebufferID < 0) { 92 | GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); 93 | super.draw(mvpMatrix, vertexBuffer, firstVertex, vertexCount, coordsPerVertex, vertexStride, texMatrix, texBuffer, textureId, texStride); 94 | return; 95 | } 96 | 97 | GLES20.glActiveTexture(GLES20.GL_TEXTURE0); 98 | 99 | frameBufferBindTexture(mFramebufferID, mTextureDownScale[0]); 100 | 101 | //重新设置第一层viewport 102 | mTexViewport.width = calcMips(512, 1); 103 | mTexViewport.height = calcMips(512, 1); 104 | GLES20.glViewport(mTexViewport.x, mTexViewport.y, mTexViewport.width, mTexViewport.height); 105 | 106 | //绘制原图像 107 | super.draw(mvpMatrix, vertexBuffer, firstVertex, vertexCount, coordsPerVertex, vertexStride, texMatrix, texBuffer, textureId, texStride); 108 | 109 | //切换Program 110 | GLES20.glUseProgram(mScaleProgramId); 111 | 112 | //绘制缩小模糊texture 113 | for (int i = 1; i < mIntensity; ++i) { 114 | frameBufferBindTexture(mFramebufferID, mTextureDownScale[i]); 115 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureDownScale[i - 1]); 116 | GLES20.glViewport(0, 0, calcMips(512, i + 1), calcMips(512, i + 1)); 117 | GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); 118 | } 119 | 120 | for (int i = mIntensity - 1; i > 0; --i) { 121 | frameBufferBindTexture(mFramebufferID, mTextureDownScale[i - 1]); 122 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureDownScale[i]); 123 | GLES20.glViewport(0, 0, calcMips(512, i), calcMips(512, i)); 124 | GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); 125 | } 126 | 127 | //调整显示区域(重新设置为全屏,预设播放区域) 128 | GLES20.glViewport(previewViewport.x, previewViewport.y, previewViewport.width, previewViewport.height); 129 | 130 | GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); 131 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureDownScale[0]); 132 | 133 | GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); 134 | } 135 | 136 | 137 | @Override 138 | public void release() { 139 | super.release(); 140 | 141 | GLES20.glDeleteProgram(mScaleProgramId); 142 | mScaleProgramId = -1; 143 | 144 | GLES20.glDeleteFramebuffers(1, new int[]{mFramebufferID}, 0); 145 | 146 | try { 147 | GLES20.glDeleteTextures(mTextureDownScale.length, mTextureDownScale, 0); 148 | } catch (Exception e) { 149 | } 150 | } 151 | 152 | /** 153 | * 生成多个Texture 154 | */ 155 | private void genMipmaps(int level, int width, int height) { 156 | mTextureDownScale = new int[level]; 157 | GLES20.glGenTextures(level, mTextureDownScale, 0); 158 | 159 | for (int i = 0; i < level; ++i) { 160 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureDownScale[i]); 161 | GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, calcMips(width, i + 1), calcMips(height, i + 1), 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);//指定纹理图像的宽度,必须是2的n次方 162 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); // glTexParameteri 定如何把纹理象素映射成像素. 163 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); 164 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); 165 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); 166 | } 167 | mTexViewport = new Viewport(0, 0, 512, 512); 168 | } 169 | 170 | //计算纹理的宽度,高度,根据设置的level来模糊纹理 171 | private int calcMips(int len, int level) { 172 | return len / (level + 1); 173 | } 174 | 175 | private void frameBufferBindTexture(int frameBufferId, int texID) { 176 | GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBufferId); 177 | GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, texID, 0); //glFramebufferTexture2D把一幅纹理图像关联到一个FBO 178 | if (GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER) != GLES20.GL_FRAMEBUFFER_COMPLETE) { 179 | Log.e("LerpBlurGpuProgram", "CGE::FrameBuffer::bindTexture2D - Frame buffer is not valid!"); 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /libRecorderEditor/src/main/java/com/pinssible/librecorder/filter/program/Program.java: -------------------------------------------------------------------------------- 1 | package com.pinssible.librecorder.filter.program; 2 | 3 | import android.view.MotionEvent; 4 | 5 | import com.pinssible.librecorder.base.Viewport; 6 | 7 | import java.nio.FloatBuffer; 8 | 9 | /** 10 | * Created by ZhangHaoSong on 2017/10/10. 11 | */ 12 | 13 | public abstract class Program { 14 | 15 | public enum ProgramType { 16 | //Texture2dProgram ProgramType 17 | TEXTURE_2D, TEXTURE_EXT, TEXTURE_EXT_BW, TEXTURE_EXT_NIGHT, TEXTURE_EXT_CHROMA_KEY, 18 | TEXTURE_EXT_SQUEEZE, TEXTURE_EXT_TWIRL, TEXTURE_EXT_TUNNEL, TEXTURE_EXT_BULGE, 19 | TEXTURE_EXT_DENT, TEXTURE_EXT_FISHEYE, TEXTURE_EXT_STRETCH, TEXTURE_EXT_MIRROR, 20 | TEXTURE_EXT_FILT, 21 | //GpuImageProgram ProgramType 22 | GPU_IMAGE_ORIGIN, 23 | GPU_IMAGE_LERP_BLUR 24 | } 25 | 26 | /** 27 | * Preview viewport 28 | */ 29 | protected Viewport previewViewport; 30 | 31 | 32 | protected abstract void init(ProgramType programType, Viewport previewViewport); 33 | 34 | 35 | /** 36 | * Releases the program. 37 | */ 38 | public abstract void release(); 39 | 40 | /** 41 | * Returns the program type. 42 | */ 43 | public abstract ProgramType getProgramType(); 44 | 45 | /** 46 | * Creates a texture object suitable for use with this program.(主要用于存储CameraPreview纹理) 47 | *
48 | * On exit, the texture will be bound. 49 | */ 50 | public abstract int createTextureObject(); 51 | 52 | 53 | /** 54 | * Issues the draw call. Does the full setup on every call. 55 | * 56 | * @param mvpMatrix The 4x4 projection matrix. 57 | * @param vertexBuffer Buffer with vertex position data. 58 | * @param firstVertex Index of first vertex to use in vertexBuffer. 59 | * @param vertexCount Number of vertices in vertexBuffer. 60 | * @param coordsPerVertex The number of coordinates per vertex (e.g. x,y is 2). 61 | * @param vertexStride Width, in bytes, of the position data for each vertex (often 62 | * vertexCount * sizeof(float)). 63 | * @param texMatrix A 4x4 transformation matrix for texture coords. 64 | * @param texBuffer Buffer with vertex texture data. 65 | * @param texStride Width, in bytes, of the texture data for each vertex. 66 | */ 67 | public abstract void draw(float[] mvpMatrix, FloatBuffer vertexBuffer, int firstVertex, 68 | int vertexCount, int coordsPerVertex, int vertexStride, 69 | float[] texMatrix, FloatBuffer texBuffer, int textureId, int texStride); 70 | 71 | /** 72 | * Sets the size of the texture. This is used to find adjacent texels when filtering. 73 | */ 74 | public abstract void setTexSize(int width, int height); 75 | 76 | 77 | /** 78 | * Configures the effect offset 79 | *
80 | * This only has an effect for programs that 81 | * use positional effects like SQUEEZE and MIRROR 82 | */ 83 | public abstract void handleTouchEvent(MotionEvent ev); 84 | 85 | /** 86 | * Create a Default Program for show 87 | */ 88 | public static Program getDefaultProgram(Viewport viewport) { 89 | return new Texture2dProgram(ProgramType.TEXTURE_EXT, viewport); 90 | //return new GpuImageProgram(ProgramType.GPU_IMAGE_ORIGIN,viewport); 91 | //return new LerpBlurGpuProgram(ProgramType.GPU_IMAGE_LERP_BLUR, viewport); 92 | } 93 | 94 | /** 95 | * Create a Default Program for show 96 | */ 97 | public static Program.ProgramType getDefaultProgramType() { 98 | return ProgramType.TEXTURE_EXT; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /libRecorderEditor/src/main/java/com/pinssible/librecorder/gles/EglContextWrapper.java: -------------------------------------------------------------------------------- 1 | package com.pinssible.librecorder.gles; 2 | 3 | import android.opengl.EGL14; 4 | import android.os.Build; 5 | 6 | import javax.microedition.khronos.egl.EGL10; 7 | import javax.microedition.khronos.egl.EGLContext; 8 | 9 | /** 10 | * Created by Chilling on 2016/12/29. 11 | */ 12 | 13 | public class EglContextWrapper { 14 | 15 | protected EGLContext eglContextOld; 16 | protected android.opengl.EGLContext eglContext; 17 | public static EglContextWrapper EGL_NO_CONTEXT_WRAPPER = new EGLNoContextWrapper(); 18 | 19 | public EGLContext getEglContextOld() { 20 | return eglContextOld; 21 | } 22 | 23 | public void setEglContextOld(EGLContext eglContextOld) { 24 | this.eglContextOld = eglContextOld; 25 | } 26 | 27 | public android.opengl.EGLContext getEglContext() { 28 | return eglContext; 29 | } 30 | 31 | public void setEglContext(android.opengl.EGLContext eglContext) { 32 | this.eglContext = eglContext; 33 | } 34 | 35 | 36 | public static class EGLNoContextWrapper extends EglContextWrapper { 37 | 38 | public EGLNoContextWrapper() { 39 | eglContextOld = EGL10.EGL_NO_CONTEXT; 40 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 41 | eglContext = EGL14.EGL_NO_CONTEXT; 42 | } 43 | } 44 | 45 | @Override 46 | public void setEglContext(android.opengl.EGLContext eglContext) { 47 | } 48 | 49 | @Override 50 | public void setEglContextOld(EGLContext eglContextOld) { 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /libRecorderEditor/src/main/java/com/pinssible/librecorder/gles/EglHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * * 4 | * * * Copyright (C) 2016 ChillingVan 5 | * * * 6 | * * * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * * * you may not use this file except in compliance with the License. 8 | * * * You may obtain a copy of the License at 9 | * * * 10 | * * * http://www.apache.org/licenses/LICENSE-2.0 11 | * * * 12 | * * * Unless required by applicable law or agreed to in writing, software 13 | * * * distributed under the License is distributed on an "AS IS" BASIS, 14 | * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * * * See the License for the specific language governing permissions and 16 | * * * limitations under the License. 17 | * * 18 | * 19 | */ 20 | 21 | package com.pinssible.librecorder.gles; 22 | 23 | import android.util.Log; 24 | 25 | 26 | import javax.microedition.khronos.egl.EGL10; 27 | import javax.microedition.khronos.egl.EGLConfig; 28 | import javax.microedition.khronos.egl.EGLContext; 29 | import javax.microedition.khronos.egl.EGLDisplay; 30 | import javax.microedition.khronos.egl.EGLSurface; 31 | 32 | /** 33 | * Created by Chilling on 2016/11/2. 34 | */ 35 | public class EglHelper implements IEglHelper { 36 | 37 | 38 | private GLThread.EGLConfigChooser eglConfigChooser; 39 | private GLThread.EGLContextFactory eglContextFactory; 40 | private GLThread.EGLWindowSurfaceFactory eglWindowSurfaceFactory; 41 | private EGL10 mEgl; 42 | private EGLDisplay mEglDisplay; 43 | private EGLSurface mEglSurface; 44 | private EGLConfig mEglConfig; 45 | private EGLContext mEglContext; 46 | 47 | 48 | public EglHelper(GLThread.EGLConfigChooser configChooser, GLThread.EGLContextFactory eglContextFactory 49 | , GLThread.EGLWindowSurfaceFactory eglWindowSurfaceFactory) { 50 | this.eglConfigChooser = configChooser; 51 | this.eglContextFactory = eglContextFactory; 52 | this.eglWindowSurfaceFactory = eglWindowSurfaceFactory; 53 | } 54 | 55 | /** 56 | * Initialize EGL for a given configuration spec. 57 | * 58 | * @param eglContext 59 | */ 60 | @Override 61 | public EglContextWrapper start(EglContextWrapper eglContext) { 62 | if (GLThread.LOG_EGL) { 63 | Log.w("EglHelper", "start() tid=" + Thread.currentThread().getId()); 64 | } 65 | /* 66 | * Get an EGL instance 67 | */ 68 | mEgl = (EGL10) EGLContext.getEGL(); 69 | 70 | /* 71 | * Get to the default display. 72 | */ 73 | mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); 74 | 75 | if (mEglDisplay == EGL10.EGL_NO_DISPLAY) { 76 | throw new RuntimeException("eglGetDisplay failed"); 77 | } 78 | 79 | /* 80 | * We can now initialize EGL for that display 81 | */ 82 | int[] version = new int[2]; 83 | if (!mEgl.eglInitialize(mEglDisplay, version)) { 84 | throw new RuntimeException("eglInitialize failed"); 85 | } 86 | mEglConfig = eglConfigChooser.chooseConfig(mEgl, mEglDisplay); 87 | 88 | /* 89 | * Create an EGL context. We want to do this as rarely as we can, because an 90 | * EGL context is a somewhat heavy object. 91 | */ 92 | mEglContext = eglContextFactory.createContext(mEgl, mEglDisplay, mEglConfig, eglContext.getEglContextOld()); 93 | if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) { 94 | mEglContext = null; 95 | throwEglException("createContext"); 96 | } 97 | if (GLThread.LOG_EGL) { 98 | Log.w("EglHelper", "createContext " + mEglContext + " tid=" + Thread.currentThread().getId()); 99 | } 100 | 101 | mEglSurface = null; 102 | 103 | EglContextWrapper eglContextWrapper = new EglContextWrapper(); 104 | eglContextWrapper.setEglContextOld(mEglContext); 105 | return eglContextWrapper; 106 | } 107 | 108 | /** 109 | * Create an egl surface for the current SurfaceHolder surface. If a surface 110 | * already exists, destroy it before creating the new surface. 111 | * 112 | * @return true if the surface was created successfully. 113 | */ 114 | @Override 115 | public boolean createSurface(Object surface) { 116 | Log.w("EglHelper", "createSurface() tid=" + Thread.currentThread().getId()); 117 | /* 118 | * Check preconditions. 119 | */ 120 | if (mEgl == null) { 121 | throw new RuntimeException("egl not initialized"); 122 | } 123 | if (mEglDisplay == null) { 124 | throw new RuntimeException("eglDisplay not initialized"); 125 | } 126 | if (mEglConfig == null) { 127 | throw new RuntimeException("mEglConfig not initialized"); 128 | } 129 | 130 | /* 131 | * The window size has changed, so we need to create a new 132 | * surface. 133 | */ 134 | destroySurfaceImp(); 135 | 136 | /* 137 | * Create an EGL surface we can render into. 138 | */ 139 | mEglSurface = eglWindowSurfaceFactory.createWindowSurface(mEgl, 140 | mEglDisplay, mEglConfig, surface); 141 | 142 | if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) { 143 | int error = mEgl.eglGetError(); 144 | if (error == EGL10.EGL_BAD_NATIVE_WINDOW) { 145 | Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); 146 | } 147 | return false; 148 | } 149 | 150 | /* 151 | * Before we can issue GL commands, we need to make sure 152 | * the context is current and bound to a surface. 153 | */ 154 | if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { 155 | /* 156 | * Could not make the context current, probably because the underlying 157 | * SurfaceView surface has been destroyed. 158 | */ 159 | logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError()); 160 | return false; 161 | } 162 | 163 | return true; 164 | } 165 | 166 | /** 167 | * Display the current render surface. 168 | * 169 | * @return the EGL error code from eglSwapBuffers. 170 | */ 171 | @Override 172 | public int swap() { 173 | if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) { 174 | return mEgl.eglGetError(); 175 | } 176 | return EGL10.EGL_SUCCESS; 177 | } 178 | 179 | @Override 180 | public void destroySurface() { 181 | if (GLThread.LOG_EGL) { 182 | Log.w("EglHelper", "destroySurface() tid=" + Thread.currentThread().getId()); 183 | } 184 | destroySurfaceImp(); 185 | } 186 | 187 | private void destroySurfaceImp() { 188 | if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) { 189 | mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, 190 | EGL10.EGL_NO_SURFACE, 191 | EGL10.EGL_NO_CONTEXT); 192 | eglWindowSurfaceFactory.destroySurface(mEgl, mEglDisplay, mEglSurface); 193 | mEglSurface = null; 194 | } 195 | } 196 | 197 | @Override 198 | public void finish() { 199 | if (GLThread.LOG_EGL) { 200 | Log.w("EglHelper", "finish() tid=" + Thread.currentThread().getId()); 201 | } 202 | if (mEglContext != null) { 203 | eglContextFactory.destroyContext(mEgl, mEglDisplay, mEglContext); 204 | mEglContext = null; 205 | } 206 | if (mEglDisplay != null) { 207 | mEgl.eglTerminate(mEglDisplay); 208 | mEglDisplay = null; 209 | } 210 | } 211 | 212 | @Override 213 | public void setPresentationTime(long nsecs) { 214 | } 215 | 216 | private void throwEglException(String function) { 217 | throwEglException(function, mEgl.eglGetError()); 218 | } 219 | 220 | public static void throwEglException(String function, int error) { 221 | String message = formatEglError(function, error); 222 | if (GLThread.LOG_THREADS) { 223 | Log.e("EglHelper", "throwEglException tid=" + Thread.currentThread().getId() + " " 224 | + message); 225 | } 226 | throw new RuntimeException(message); 227 | } 228 | 229 | public static void logEglErrorAsWarning(String tag, String function, int error) { 230 | Log.w(tag, formatEglError(function, error)); 231 | } 232 | 233 | public static String formatEglError(String function, int error) { 234 | return function + " failed: " + EGLLogWrapper.getErrorString(error); 235 | } 236 | 237 | } 238 | -------------------------------------------------------------------------------- /libRecorderEditor/src/main/java/com/pinssible/librecorder/gles/EglHelperAPI17.java: -------------------------------------------------------------------------------- 1 | package com.pinssible.librecorder.gles; 2 | 3 | import android.opengl.EGL14; 4 | import android.opengl.EGLConfig; 5 | import android.opengl.EGLContext; 6 | import android.opengl.EGLDisplay; 7 | import android.opengl.EGLExt; 8 | import android.opengl.EGLSurface; 9 | import android.os.Build; 10 | import android.support.annotation.RequiresApi; 11 | import android.util.Log; 12 | import static com.pinssible.librecorder.gles.EglHelper.formatEglError; 13 | 14 | /** 15 | * Created by Chilling on 2016/12/29. 16 | */ 17 | 18 | @RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1) 19 | public class EglHelperAPI17 implements IEglHelper { 20 | 21 | private GLThread.EGLConfigChooser eglConfigChooser; 22 | private GLThread.EGLContextFactory eglContextFactory; 23 | private GLThread.EGLWindowSurfaceFactory eglWindowSurfaceFactory; 24 | private EGLDisplay mEglDisplay; 25 | private EGLConfig mEglConfig; 26 | private EGLContext mEglContext; 27 | private EGLSurface mEglSurface; 28 | 29 | public EglHelperAPI17(GLThread.EGLConfigChooser configChooser, GLThread.EGLContextFactory eglContextFactory 30 | , GLThread.EGLWindowSurfaceFactory eglWindowSurfaceFactory) { 31 | this.eglConfigChooser = configChooser; 32 | this.eglContextFactory = eglContextFactory; 33 | this.eglWindowSurfaceFactory = eglWindowSurfaceFactory; 34 | } 35 | 36 | @Override 37 | public EglContextWrapper start(EglContextWrapper eglContext) { 38 | Log.w("EglHelper", "start() tid=" + Thread.currentThread().getId()); 39 | /* 40 | * Get an EGL instance 41 | */ 42 | 43 | /* 44 | * Get to the default display. 45 | */ 46 | mEglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); 47 | 48 | if (mEglDisplay == EGL14.EGL_NO_DISPLAY) { 49 | throw new RuntimeException("eglGetDisplay failed"); 50 | } 51 | 52 | /* 53 | * We can now initialize EGL for that display 54 | */ 55 | int[] version = new int[2]; 56 | if (!EGL14.eglInitialize(mEglDisplay, version, 0, version, 1)) { 57 | mEglDisplay = null; 58 | throw new RuntimeException("eglInitialize failed"); 59 | } 60 | mEglConfig = eglConfigChooser.chooseConfig(mEglDisplay, false); 61 | 62 | /* 63 | * Create an EGL context. We want to do this as rarely as we can, because an 64 | * EGL context is a somewhat heavy object. 65 | */ 66 | mEglContext = eglContextFactory.createContextAPI17(mEglDisplay, mEglConfig, eglContext.getEglContext()); 67 | if (mEglContext == null || mEglContext == EGL14.EGL_NO_CONTEXT) { 68 | mEglContext = null; 69 | throwEglException("createContext"); 70 | } 71 | Log.w("EglHelper", "createContext " + mEglContext + " tid=" + Thread.currentThread().getId()); 72 | 73 | mEglSurface = null; 74 | 75 | 76 | EglContextWrapper eglContextWrapper = new EglContextWrapper(); 77 | eglContextWrapper.setEglContext(mEglContext); 78 | return eglContextWrapper; 79 | } 80 | 81 | @Override 82 | public boolean createSurface(Object surface) { 83 | Log.w("EglHelper", "createSurface() tid=" + Thread.currentThread().getId()); 84 | 85 | if (mEglDisplay == null) { 86 | throw new RuntimeException("eglDisplay not initialized"); 87 | } 88 | if (mEglConfig == null) { 89 | throw new RuntimeException("mEglConfig not initialized"); 90 | } 91 | 92 | destroySurfaceImp(); 93 | /* 94 | * Create an EGL surface we can render into. 95 | */ 96 | mEglSurface = eglWindowSurfaceFactory.createWindowSurface(mEglDisplay, mEglConfig, surface); 97 | 98 | if (mEglSurface == null || mEglSurface == EGL14.EGL_NO_SURFACE) { 99 | int error = EGL14.eglGetError(); 100 | if (error == EGL14.EGL_BAD_NATIVE_WINDOW) { 101 | Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); 102 | } 103 | return false; 104 | } 105 | 106 | /* 107 | * Before we can issue GL commands, we need to make sure 108 | * the context is current and bound to a surface. 109 | */ 110 | if (!EGL14.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { 111 | /* 112 | * Could not make the context current, probably because the underlying 113 | * SurfaceView surface has been destroyed. 114 | */ 115 | logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", EGL14.eglGetError()); 116 | return false; 117 | } 118 | 119 | return true; 120 | } 121 | 122 | 123 | @Override 124 | public int swap() { 125 | if (!EGL14.eglSwapBuffers(mEglDisplay, mEglSurface)) { 126 | Log.w("EglHelperAPI17", String.format("swap: start get error")); 127 | return EGL14.eglGetError(); 128 | } 129 | return EGL14.EGL_SUCCESS; 130 | } 131 | 132 | @Override 133 | public void destroySurface() { 134 | if (GLThread.LOG_EGL) { 135 | Log.w("EglHelper", "destroySurface() tid=" + Thread.currentThread().getId()); 136 | } 137 | destroySurfaceImp(); 138 | } 139 | 140 | private void destroySurfaceImp() { 141 | if (mEglSurface != null && mEglSurface != EGL14.EGL_NO_SURFACE) { 142 | EGL14.eglMakeCurrent(mEglDisplay, EGL14.EGL_NO_SURFACE, 143 | EGL14.EGL_NO_SURFACE, 144 | EGL14.EGL_NO_CONTEXT); 145 | eglWindowSurfaceFactory.destroySurface(mEglDisplay, mEglSurface); 146 | mEglSurface = null; 147 | } 148 | } 149 | 150 | @Override 151 | public void finish() { 152 | if (GLThread.LOG_EGL) { 153 | Log.w("EglHelper", "finish() tid=" + Thread.currentThread().getId()); 154 | } 155 | if (mEglContext != null) { 156 | eglContextFactory.destroyContext(mEglDisplay, mEglContext); 157 | mEglContext = null; 158 | } 159 | if (mEglDisplay != null) { 160 | EGL14.eglTerminate(mEglDisplay); 161 | mEglDisplay = null; 162 | } 163 | } 164 | 165 | 166 | /** 167 | * Sends the presentation time stamp to EGL. 168 | * 169 | * @param nsecs Timestamp, in nanoseconds. 170 | */ 171 | @Override 172 | public void setPresentationTime(long nsecs) { 173 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && nsecs != 0) { 174 | EGLExt.eglPresentationTimeANDROID(mEglDisplay, mEglSurface, nsecs); 175 | } 176 | } 177 | 178 | public static void logEglErrorAsWarning(String tag, String function, int error) { 179 | Log.w(tag, formatEglError(function, error)); 180 | } 181 | 182 | private void throwEglException(String function) { 183 | throwEglException(function, EGL14.eglGetError()); 184 | } 185 | 186 | public static void throwEglException(String function, int error) { 187 | String message = formatEglError(function, error); 188 | Log.e("EglHelper", "throwEglException tid=" + Thread.currentThread().getId() + " " + message); 189 | throw new RuntimeException(message); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /libRecorderEditor/src/main/java/com/pinssible/librecorder/gles/EglHelperFactory.java: -------------------------------------------------------------------------------- 1 | package com.pinssible.librecorder.gles; 2 | 3 | import android.os.Build; 4 | 5 | /** 6 | * Created by Chilling on 2017/1/2. 7 | */ 8 | 9 | public class EglHelperFactory { 10 | 11 | public static IEglHelper create(GLThread.EGLConfigChooser configChooser, GLThread.EGLContextFactory eglContextFactory 12 | , GLThread.EGLWindowSurfaceFactory eglWindowSurfaceFactory) { 13 | 14 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 15 | return new EglHelperAPI17(configChooser, eglContextFactory, eglWindowSurfaceFactory); 16 | } else { 17 | return new EglHelper(configChooser, eglContextFactory, eglWindowSurfaceFactory); 18 | } 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /libRecorderEditor/src/main/java/com/pinssible/librecorder/gles/IEglHelper.java: -------------------------------------------------------------------------------- 1 | package com.pinssible.librecorder.gles; 2 | 3 | /** 4 | * Created by Chilling on 2016/12/29. 5 | */ 6 | public interface IEglHelper { 7 | EglContextWrapper start(EglContextWrapper eglContext); 8 | 9 | boolean createSurface(Object surface); 10 | 11 | int swap(); 12 | 13 | void destroySurface(); 14 | 15 | void finish(); 16 | 17 | void setPresentationTime(long nsecs); 18 | } 19 | -------------------------------------------------------------------------------- /libRecorderEditor/src/main/java/com/pinssible/librecorder/gles/RenderListener.java: -------------------------------------------------------------------------------- 1 | package com.pinssible.librecorder.gles; 2 | 3 | /** 4 | * Created by ZhangHaoSong on 2017/11/20. 5 | */ 6 | 7 | public interface RenderListener { 8 | 9 | void onSurfaceCreated(); 10 | 11 | void onSurfaceChanged(int width,int height); 12 | 13 | void onDrawFrame(); 14 | } 15 | -------------------------------------------------------------------------------- /libRecorderEditor/src/main/java/com/pinssible/librecorder/listener/OnMuxFinishListener.java: -------------------------------------------------------------------------------- 1 | package com.pinssible.librecorder.listener; 2 | 3 | /** 4 | * not in UI thread ,you should change to ui thread to do something mostTime 5 | * Created by ZhangHaoSong on 2017/9/27. 6 | */ 7 | 8 | public interface OnMuxFinishListener { 9 | /** 10 | * not in UI thread ,you should change to ui thread to do something mostTime 11 | */ 12 | void onMuxFinish(); 13 | 14 | void onMuxFail(Exception e); 15 | } 16 | -------------------------------------------------------------------------------- /libRecorderEditor/src/main/java/com/pinssible/librecorder/listener/PreSettingListener.java: -------------------------------------------------------------------------------- 1 | package com.pinssible.librecorder.listener; 2 | 3 | import com.pinssible.librecorder.camera.CameraInstance; 4 | import com.pinssible.librecorder.recorder.CameraSurfaceRender; 5 | 6 | /** 7 | * Created by ZhangHaoSong on 2017/9/27. 8 | */ 9 | 10 | public interface PreSettingListener { 11 | /** 12 | * 在CameraOpen之前的预加载设置 13 | */ 14 | void preSetting(CameraSurfaceRender render, CameraInstance camera); 15 | } 16 | -------------------------------------------------------------------------------- /libRecorderEditor/src/main/java/com/pinssible/librecorder/player/PinMediaPlayer.java: -------------------------------------------------------------------------------- 1 | package com.pinssible.librecorder.player; 2 | 3 | import android.content.Context; 4 | import android.net.Uri; 5 | import android.opengl.GLSurfaceView; 6 | import android.os.Build; 7 | import android.support.annotation.RequiresApi; 8 | import android.util.Log; 9 | import android.view.Surface; 10 | import android.view.View; 11 | 12 | import com.google.android.exoplayer2.ExoPlaybackException; 13 | import com.google.android.exoplayer2.ExoPlayer; 14 | import com.google.android.exoplayer2.ExoPlayerFactory; 15 | import com.google.android.exoplayer2.Format; 16 | import com.google.android.exoplayer2.PlaybackParameters; 17 | import com.google.android.exoplayer2.SimpleExoPlayer; 18 | import com.google.android.exoplayer2.Timeline; 19 | import com.google.android.exoplayer2.decoder.DecoderCounters; 20 | import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; 21 | import com.google.android.exoplayer2.extractor.ExtractorsFactory; 22 | import com.google.android.exoplayer2.metadata.Metadata; 23 | import com.google.android.exoplayer2.metadata.MetadataRenderer; 24 | import com.google.android.exoplayer2.source.ExtractorMediaSource; 25 | import com.google.android.exoplayer2.source.LoopingMediaSource; 26 | import com.google.android.exoplayer2.source.MediaSource; 27 | import com.google.android.exoplayer2.source.TrackGroupArray; 28 | import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection; 29 | import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; 30 | import com.google.android.exoplayer2.trackselection.TrackSelection; 31 | import com.google.android.exoplayer2.trackselection.TrackSelectionArray; 32 | import com.google.android.exoplayer2.trackselection.TrackSelector; 33 | import com.google.android.exoplayer2.upstream.BandwidthMeter; 34 | import com.google.android.exoplayer2.upstream.DataSource; 35 | import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; 36 | import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; 37 | import com.google.android.exoplayer2.util.Util; 38 | import com.google.android.exoplayer2.video.VideoRendererEventListener; 39 | import com.pinssible.librecorder.view.GLTextureView; 40 | 41 | /** 42 | * Created by ZhangHaoSong on 2017/10/20. 43 | */ 44 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) 45 | public class PinMediaPlayer { 46 | private String TAG = "PinMediaPlayer"; 47 | 48 | private Uri sourceUri; //暂定 本地播放 本地播放地址 49 | private Context context; 50 | private SimpleExoPlayer exoPlayer; 51 | private View preview; 52 | private PlayerRender render; 53 | 54 | public PinMediaPlayer(Context context, Uri sourcePath) throws Exception{ 55 | this(context, sourcePath, null); 56 | } 57 | 58 | public PinMediaPlayer(Context context, Uri sourcePath,boolean isLoop) throws Exception{ 59 | this(context, sourcePath, null,true); 60 | } 61 | 62 | public PinMediaPlayer(Context context, Uri sourcePath, View preview,boolean isLoop) throws Exception{ 63 | this.sourceUri = sourcePath; 64 | this.context = context; 65 | this.preview = preview; 66 | //create exoPlayer 67 | DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); 68 | TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter); 69 | TrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory); 70 | exoPlayer = ExoPlayerFactory.newSimpleInstance(context, trackSelector); 71 | //create MediaSource 72 | // Produces DataSource instances through which media data is loaded. 73 | DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(context, 74 | Util.getUserAgent(context, context.getApplicationInfo().name), bandwidthMeter); 75 | // Produces Extractor instances for parsing the media data. 76 | ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory(); 77 | // This is the MediaSource representing the media to be played. 78 | MediaSource videoSource = new ExtractorMediaSource(sourceUri, 79 | dataSourceFactory, extractorsFactory, null, null); 80 | //create render 81 | if (preview != null) { 82 | if(preview instanceof GLSurfaceView) { 83 | render = new PlayerRender(context, (GLSurfaceView)preview, exoPlayer); 84 | ((GLSurfaceView)preview).setRenderer(render); 85 | }else if(preview instanceof GLTextureView) { 86 | render = new PlayerRender(context, (GLTextureView)preview, exoPlayer); 87 | ((GLTextureView)preview).setRenderer(render); 88 | }else{ 89 | throw new Exception("you should set GlSurfaceView or GlTextureView as preview view"); 90 | } 91 | } 92 | if(isLoop) { 93 | // Prepare the player with the source. 94 | LoopingMediaSource loopMediaSource = new LoopingMediaSource(videoSource); 95 | exoPlayer.prepare(loopMediaSource); 96 | }else{ 97 | exoPlayer.prepare(videoSource); 98 | } 99 | } 100 | 101 | 102 | public PinMediaPlayer(Context context, Uri sourcePath, View preview) throws Exception{ 103 | this(context, sourcePath, preview,false); 104 | } 105 | 106 | 107 | public void setPreview(View preview) throws Exception{ 108 | if (exoPlayer != null) { 109 | this.preview = preview; 110 | if(preview instanceof GLSurfaceView) { 111 | render = new PlayerRender(context, (GLSurfaceView)preview, exoPlayer); 112 | ((GLSurfaceView)preview).setRenderer(render); 113 | }else if(preview instanceof GLTextureView) { 114 | render = new PlayerRender(context, (GLTextureView)preview, exoPlayer); 115 | ((GLTextureView)preview).setRenderer(render); 116 | }else{ 117 | throw new Exception("you should set GlSurfaceView or GlTextureView as preview view"); 118 | } 119 | } 120 | } 121 | 122 | public void start() { 123 | if (exoPlayer != null) { 124 | exoPlayer.setPlayWhenReady(true); 125 | } 126 | } 127 | 128 | 129 | public void stop() { 130 | if (exoPlayer != null) { 131 | exoPlayer.stop(); 132 | } 133 | } 134 | 135 | public void pause() { 136 | if (exoPlayer != null) { 137 | exoPlayer.setPlayWhenReady(false); 138 | } 139 | } 140 | 141 | public void resume() { 142 | if (exoPlayer != null) { 143 | exoPlayer.setPlayWhenReady(true); 144 | } 145 | } 146 | 147 | public void seekTo(long positionMs) { 148 | if (exoPlayer != null) { 149 | exoPlayer.seekTo(positionMs); 150 | } 151 | } 152 | 153 | public long getVideoDuration() { 154 | if (exoPlayer != null) { 155 | return exoPlayer.getDuration(); 156 | } 157 | return 0; 158 | } 159 | 160 | public SimpleExoPlayer getExoPlayer() { 161 | return exoPlayer; 162 | } 163 | 164 | public View getPreview() { 165 | return preview; 166 | } 167 | 168 | /** 169 | * 设置滤镜(部分内置滤镜) 170 | */ 171 | public void setFilter(int filterType) { 172 | render.applyFilter(filterType); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /libRecorderEditor/src/main/java/com/pinssible/librecorder/recorder/AVRecorder.java: -------------------------------------------------------------------------------- 1 | package com.pinssible.librecorder.recorder; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.hardware.Camera; 6 | import android.opengl.EGL14; 7 | import android.opengl.GLSurfaceView; 8 | import android.os.Build; 9 | import android.os.Environment; 10 | import android.support.annotation.RequiresApi; 11 | import android.text.TextUtils; 12 | import android.util.Log; 13 | import android.view.View; 14 | 15 | import com.pinssible.librecorder.view.GLTextureView; 16 | 17 | import java.io.File; 18 | import java.io.IOException; 19 | 20 | /** 21 | * Created by ZhangHaoSong on 2017/9/26. 22 | */ 23 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) 24 | public class AVRecorder { 25 | private String LOG_TAG = "AvRecorder"; 26 | //config 27 | private RecorderConfig recorderConfig; 28 | private String outputPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/NeonRecordTest.mp4"; 29 | 30 | //Camera 31 | private CameraSurfaceRender cameraRender; 32 | 33 | //Audio 34 | private MicrophoneEncoder microphoneEncoder; 35 | 36 | //state 37 | public boolean isRecording = false; 38 | 39 | //display rotation 40 | private int displayRotation = 0; 41 | 42 | private View previewView; 43 | 44 | public AVRecorder(PreviewConfig previewConfig, RecorderConfig config, GLSurfaceView cameraRecordGLSurfaceView2) throws Exception { 45 | this.recorderConfig = config; 46 | this.previewView = cameraRecordGLSurfaceView2; 47 | displayRotation = getDisplayRotation(cameraRecordGLSurfaceView2.getContext()); 48 | this.cameraRender = new CameraSurfaceRender(cameraRecordGLSurfaceView2.getContext(), cameraRecordGLSurfaceView2, previewConfig, displayRotation); 49 | cameraRecordGLSurfaceView2.setRenderer(cameraRender); 50 | this.microphoneEncoder = new MicrophoneEncoder(config); 51 | Log.e(LOG_TAG, "displayRotation = " + displayRotation); 52 | //size 53 | if (previewConfig.getPreviewSize().getWidth() != config.videoEncoderConfig.mWidth || previewConfig.getPreviewSize().getHeight() != config.videoEncoderConfig.mHeight) { 54 | throw new Exception("preview Size should equal record size"); 55 | } 56 | } 57 | 58 | 59 | public AVRecorder(PreviewConfig previewConfig, RecorderConfig config, GLTextureView glTextureView) throws Exception { 60 | this.recorderConfig = config; 61 | this.previewView = glTextureView; 62 | displayRotation = getDisplayRotation(glTextureView.getContext()); 63 | this.cameraRender = new CameraSurfaceRender(glTextureView.getContext(), glTextureView, previewConfig, displayRotation); 64 | glTextureView.setRenderer(cameraRender); 65 | this.microphoneEncoder = new MicrophoneEncoder(config); 66 | Log.e(LOG_TAG, "displayRotation = " + displayRotation); 67 | //size 68 | if (previewConfig.getPreviewSize().getWidth() != config.videoEncoderConfig.mWidth || previewConfig.getPreviewSize().getHeight() != config.videoEncoderConfig.mHeight) { 69 | throw new Exception("preview Size should equal record size"); 70 | } 71 | } 72 | 73 | /** 74 | * Prepare for a subsequent recording. Must be called after {@link #stopRecording()}(用于录制第二段视频之前) 75 | * and before {@link #release()} 76 | * 77 | * @param config 78 | */ 79 | public void reset(RecorderConfig config) throws IOException { 80 | cameraRender.setRecorderConfig(config); 81 | microphoneEncoder.reset(config); 82 | recorderConfig = config; 83 | isRecording = false; 84 | } 85 | 86 | public void release() { 87 | cameraRender.release(); 88 | // MicrophoneEncoder releases all it's resources when stopRecording is called 89 | // because it doesn't have any meaningful state 90 | // between recordings. It might someday if we decide to present 91 | // persistent audio volume meters etc. 92 | // Until then, we don't need to write MicrophoneEncoder.release() 93 | if (isRecording) { 94 | recorderConfig.muxer.forceStop(); 95 | } 96 | recorderConfig.muxer.release(); 97 | } 98 | 99 | public void startRecording() { 100 | //create file 101 | if (!recorderConfig.mOutputFile.exists()) { 102 | try { 103 | recorderConfig.mOutputFile.createNewFile(); 104 | } catch (IOException e) { 105 | e.printStackTrace(); 106 | } 107 | } 108 | isRecording = true; 109 | cameraRender.startRecording(recorderConfig); 110 | microphoneEncoder.startRecording(); 111 | } 112 | 113 | public void stopRecording() { 114 | isRecording = false; 115 | cameraRender.stopRecording(); 116 | microphoneEncoder.stopRecording(); 117 | } 118 | 119 | /** 120 | * 截屏 121 | */ 122 | public void takeShot(CameraSurfaceRender.TakePictureCallback callback) { 123 | cameraRender.takeShot(callback); 124 | } 125 | 126 | /** 127 | * 获取缩略图 128 | * 129 | * @param callback 130 | * @param destWidth 131 | * @param destHeight 132 | */ 133 | public void getThumbnail(CameraSurfaceRender.TakePictureCallback callback, int destWidth, int destHeight) { 134 | cameraRender.takeShot(callback, destWidth, destHeight); 135 | } 136 | 137 | /** 138 | * 设置滤镜(部分内置滤镜) 139 | */ 140 | public void setFilter(int filterType) { 141 | cameraRender.applyFilter(filterType); 142 | } 143 | 144 | /** 145 | * 切换摄像头 146 | * 147 | * @param isCameraBackForward 148 | */ 149 | public void changeCameraFacing(boolean isCameraBackForward) { 150 | cameraRender.changeCameraFacing(isCameraBackForward); 151 | } 152 | 153 | public boolean isBackgroundCamera() { 154 | return cameraRender.getCameraFacing() == Camera.CameraInfo.CAMERA_FACING_BACK; 155 | } 156 | 157 | public void openFlash() { 158 | cameraRender.setFlashLightMode(Camera.Parameters.FLASH_MODE_TORCH); 159 | } 160 | 161 | public void closeFlash() { 162 | cameraRender.setFlashLightMode(Camera.Parameters.FLASH_MODE_OFF); 163 | } 164 | 165 | public boolean isFlashOpen() { 166 | if (!TextUtils.isEmpty(cameraRender.getFlashMode())) { 167 | return !(cameraRender.getFlashMode().equals(Camera.Parameters.FLASH_MODE_OFF)); 168 | } else { 169 | return true; 170 | } 171 | } 172 | 173 | /** 174 | * 开启模糊 175 | * 176 | * @param isBlur 177 | * @param blurIntensity 178 | */ 179 | public void setBlurMode(boolean isBlur, int blurIntensity) { 180 | cameraRender.changeBlurMode(isBlur, blurIntensity); 181 | } 182 | 183 | /** 184 | * 设置模糊程度,越大越模糊 185 | * 186 | * @param blurIntensity 0~30 187 | */ 188 | public void setBlurIntensity(int blurIntensity) { 189 | cameraRender.setBlurIntensity(blurIntensity); 190 | } 191 | 192 | /** 193 | * 聚焦 194 | */ 195 | public void focusAtPoint(float x, float y, final Camera.AutoFocusCallback callback) { 196 | cameraRender.focusAtPoint(x, y, callback); 197 | } 198 | 199 | public synchronized void autoFocus() { 200 | cameraRender.autoFocus(); 201 | } 202 | 203 | public void stopPreview() { 204 | cameraRender.stopPreview(); 205 | } 206 | 207 | public void resumePreview() { 208 | 209 | } 210 | 211 | private int getDisplayRotation(Context context) { 212 | if (context instanceof Activity) { 213 | return ((Activity) context).getWindowManager().getDefaultDisplay().getRotation(); 214 | } 215 | return 0; 216 | } 217 | 218 | private RecorderConfig createDefaultRecordConfig() { 219 | //File 220 | File mOutputFile = new File(outputPath); 221 | if (!mOutputFile.exists()) { 222 | try { 223 | mOutputFile.createNewFile(); 224 | } catch (IOException e) { 225 | e.printStackTrace(); 226 | Log.e(LOG_TAG, "create New file fail"); 227 | } 228 | } 229 | 230 | //码率计算 231 | //宽*高*帧率*像素数据量,如果隔行扫描再减半,得到的就是比特率,单位bps 232 | //720*480*29.97*24=248583168bps=248583.168Kbps≈248.6Mbps 233 | RecorderConfig.VideoEncoderConfig videoConfig = new RecorderConfig.VideoEncoderConfig(480, 640, 234 | 5 * 1000 * 1000, EGL14.eglGetCurrentContext()); 235 | RecorderConfig.AudioEncoderConfig audioConfig = new RecorderConfig.AudioEncoderConfig(1, 96 * 1000, 44100); 236 | RecorderConfig recorderConfig = new RecorderConfig(videoConfig, audioConfig, 237 | mOutputFile, RecorderConfig.SCREEN_ROTATION.VERTICAL); 238 | return recorderConfig; 239 | } 240 | 241 | 242 | } 243 | -------------------------------------------------------------------------------- /libRecorderEditor/src/main/java/com/pinssible/librecorder/recorder/AndroidEncoder.java: -------------------------------------------------------------------------------- 1 | package com.pinssible.librecorder.recorder; 2 | 3 | import android.annotation.TargetApi; 4 | import android.media.MediaCodec; 5 | import android.media.MediaFormat; 6 | import android.os.Build; 7 | import android.os.Bundle; 8 | import android.util.Log; 9 | 10 | import java.nio.ByteBuffer; 11 | 12 | /** 13 | */ 14 | public abstract class AndroidEncoder { 15 | private final static String TAG = "AndroidEncoder"; 16 | private final static boolean VERBOSE = false; 17 | 18 | protected Muxer mMuxer; 19 | protected MediaCodec mEncoder; 20 | protected MediaCodec.BufferInfo mBufferInfo; 21 | protected int mTrackIndex; 22 | protected volatile boolean mForceEos = false; 23 | int mEosSpinCount = 0; 24 | final int MAX_EOS_SPINS = 10; 25 | 26 | /** 27 | * This method should be called before the last input packet is queued 28 | * Some devices don't honor MediaCodec#signalEndOfInputStream 29 | * e.g: Google Glass 30 | */ 31 | public void signalEndOfStream() { 32 | mForceEos = true; 33 | } 34 | 35 | public void release() { 36 | if (mMuxer != null) 37 | mMuxer.onEncoderReleased(mTrackIndex); 38 | if (mEncoder != null) { 39 | mEncoder.stop(); 40 | mEncoder.release(); 41 | mEncoder = null; 42 | if (VERBOSE) Log.i(TAG, "Released encoder"); 43 | } 44 | } 45 | 46 | public static boolean isKitKat() { 47 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; 48 | } 49 | 50 | @TargetApi(Build.VERSION_CODES.KITKAT) 51 | public void adjustBitrate(int targetBitrate) { 52 | if (isKitKat() && mEncoder != null) { 53 | Bundle bitrate = new Bundle(); 54 | bitrate.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, targetBitrate); 55 | mEncoder.setParameters(bitrate); 56 | } else if (!isKitKat()) { 57 | Log.w(TAG, "Ignoring adjustVideoBitrate call. This functionality is only available on Android API 19+"); 58 | } 59 | } 60 | 61 | public void drainEncoder(boolean endOfStream) { 62 | if (endOfStream && VERBOSE) { 63 | if (isSurfaceInputEncoder()) { 64 | Log.i(TAG, "final video drain"); 65 | } else { 66 | Log.i(TAG, "final audio drain"); 67 | } 68 | } 69 | synchronized (mMuxer) { 70 | final int TIMEOUT_USEC = 1000; 71 | if (VERBOSE) Log.d(TAG, "drainEncoder(" + endOfStream + ") track: " + mTrackIndex); 72 | 73 | if (endOfStream) { 74 | if (VERBOSE) Log.d(TAG, "sending EOS to encoder for track " + mTrackIndex); 75 | // When all target devices honor MediaCodec#signalEndOfInputStream, return to this method 76 | // if(isSurfaceInputEncoder()){ 77 | // if (VERBOSE) Log.i(TAG, "signalEndOfInputStream for track " + mTrackIndex); 78 | // mEncoder.signalEndOfInputStream(); 79 | // // Note: This method isn't honored on certain devices including Google Glass 80 | // } 81 | } 82 | 83 | ByteBuffer[] encoderOutputBuffers = mEncoder.getOutputBuffers(); 84 | while (true) { 85 | int encoderStatus = mEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC); 86 | if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { 87 | // no output available yet 88 | if (!endOfStream) { 89 | break; // out of while 90 | } else { 91 | mEosSpinCount++; 92 | if (mEosSpinCount > MAX_EOS_SPINS) { 93 | if (VERBOSE) Log.i(TAG, "Force shutting down Muxer"); 94 | mMuxer.forceStop(); 95 | break; 96 | } 97 | if (VERBOSE) Log.d(TAG, "no output available, spinning to await EOS"); 98 | } 99 | } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { 100 | // not expected for an encoder 101 | encoderOutputBuffers = mEncoder.getOutputBuffers(); 102 | } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 103 | // should happen before receiving buffers, and should only happen once 104 | MediaFormat newFormat = mEncoder.getOutputFormat(); 105 | if (VERBOSE) Log.d(TAG, "encoder output format changed: " + newFormat); 106 | 107 | // now that we have the Magic Goodies, start the muxer 108 | mTrackIndex = mMuxer.addTrack(newFormat); 109 | // Muxer is responsible for starting/stopping itself 110 | // based on knowledge of expected # tracks 111 | } else if (encoderStatus < 0) { 112 | Log.w(TAG, "unexpected result from encoder.dequeueOutputBuffer: " + 113 | encoderStatus); 114 | // let's ignore it 115 | } else { 116 | ByteBuffer encodedData = encoderOutputBuffers[encoderStatus]; 117 | if (encodedData == null) { 118 | throw new RuntimeException("encoderOutputBuffer " + encoderStatus + 119 | " was null"); 120 | } 121 | 122 | if (mBufferInfo.size >= 0) { // Allow zero length buffer for purpose of sending 0 size video EOS Flag 123 | // adjust the ByteBuffer values to match BufferInfo (not needed?) 124 | encodedData.position(mBufferInfo.offset); 125 | encodedData.limit(mBufferInfo.offset + mBufferInfo.size); 126 | if (mForceEos) { 127 | mBufferInfo.flags = mBufferInfo.flags | MediaCodec.BUFFER_FLAG_END_OF_STREAM; 128 | Log.i(TAG, "Forcing EOS"); 129 | } 130 | // It is the muxer's responsibility to release encodedData 131 | mMuxer.writeSampleData(mEncoder, mTrackIndex, encoderStatus, encodedData, mBufferInfo); 132 | if (VERBOSE) { 133 | Log.e(TAG, "sent " + mBufferInfo.size + " bytes to muxer, \t ts=" + 134 | mBufferInfo.presentationTimeUs + "track " + mTrackIndex); 135 | } 136 | } 137 | 138 | if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 139 | if (!endOfStream) { 140 | Log.w(TAG, "reached end of stream unexpectedly"); 141 | } else { 142 | if (VERBOSE) 143 | Log.d(TAG, "end of stream reached for track " + mTrackIndex); 144 | } 145 | break; // out of while 146 | } 147 | } 148 | } 149 | if (endOfStream && VERBOSE) { 150 | if (isSurfaceInputEncoder()) { 151 | Log.i(TAG, "final video drain complete"); 152 | } else { 153 | Log.i(TAG, "final audio drain complete"); 154 | } 155 | } 156 | } 157 | } 158 | 159 | protected abstract boolean isSurfaceInputEncoder(); 160 | } 161 | -------------------------------------------------------------------------------- /libRecorderEditor/src/main/java/com/pinssible/librecorder/recorder/AndroidMuxer.java: -------------------------------------------------------------------------------- 1 | package com.pinssible.librecorder.recorder; 2 | 3 | import android.media.MediaCodec; 4 | import android.media.MediaFormat; 5 | import android.media.MediaMuxer; 6 | import android.os.Build; 7 | import android.support.annotation.RequiresApi; 8 | import android.util.Log; 9 | 10 | import java.io.IOException; 11 | import java.nio.ByteBuffer; 12 | 13 | /** 14 | */ 15 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) 16 | public class AndroidMuxer extends Muxer { 17 | private static final String TAG = "AndroidMuxer"; 18 | private static final boolean VERBOSE = false; 19 | 20 | private MediaMuxer mMuxer; 21 | private boolean mStarted; 22 | 23 | private AndroidMuxer(String outputFile, FORMAT format) { 24 | super(outputFile, format); 25 | Log.e(TAG, "AndroidMuxer create path = " + outputFile); 26 | try { 27 | switch (format) { 28 | case MPEG4: 29 | mMuxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); 30 | break; 31 | default: 32 | throw new IllegalArgumentException("Unrecognized format!"); 33 | } 34 | } catch (IOException e) { 35 | throw new RuntimeException("MediaMuxer creation failed", e); 36 | } 37 | mStarted = false; 38 | } 39 | 40 | public static AndroidMuxer create(String outputFile, FORMAT format) { 41 | return new AndroidMuxer(outputFile, format); 42 | } 43 | 44 | @Override 45 | public int addTrack(MediaFormat trackFormat) { 46 | super.addTrack(trackFormat); 47 | Log.e(TAG, "addTrack trackFormat = " + trackFormat.toString()); 48 | if (mStarted) 49 | throw new RuntimeException("format changed twice"); 50 | int track = mMuxer.addTrack(trackFormat); 51 | 52 | if (allTracksAdded()) { 53 | start(); 54 | } 55 | return track; 56 | } 57 | 58 | protected void start() { 59 | mMuxer.start(); 60 | mStarted = true; 61 | } 62 | 63 | 64 | @Override 65 | public void stop() { 66 | Log.e(TAG, "stop muxer is Started = " + mStarted); 67 | if (mMuxer != null) { 68 | mMuxer.stop(); 69 | super.stop(); 70 | } 71 | mStarted = false; 72 | } 73 | 74 | @Override 75 | public void release() { 76 | super.release(); 77 | mMuxer.release(); 78 | } 79 | 80 | @Override 81 | public boolean isStarted() { 82 | return mStarted; 83 | } 84 | 85 | @Override 86 | public void writeSampleData(MediaCodec encoder, int trackIndex, int bufferIndex, ByteBuffer encodedData, MediaCodec.BufferInfo bufferInfo) { 87 | super.writeSampleData(encoder, trackIndex, bufferIndex, encodedData, bufferInfo); 88 | if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { 89 | // MediaMuxer gets the codec config info via the addTrack command 90 | if (VERBOSE) Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG"); 91 | encoder.releaseOutputBuffer(bufferIndex, false); 92 | return; 93 | } 94 | 95 | if (bufferInfo.size == 0) { 96 | if (VERBOSE) Log.d(TAG, "ignoring zero size buffer"); 97 | encoder.releaseOutputBuffer(bufferIndex, false); 98 | return; 99 | } 100 | 101 | if (!mStarted) { 102 | Log.e(TAG, "writeSampleData called before muxer started. Ignoring packet. Track index: " + trackIndex + " tracks added: " + mNumTracks); 103 | encoder.releaseOutputBuffer(bufferIndex, false); 104 | return; 105 | } 106 | 107 | Log.e(TAG, "writeSampleData presentationTimeUs = " + bufferInfo.presentationTimeUs); 108 | 109 | bufferInfo.presentationTimeUs = getNextRelativePts(bufferInfo.presentationTimeUs, trackIndex); 110 | 111 | mMuxer.writeSampleData(trackIndex, encodedData, bufferInfo); 112 | 113 | encoder.releaseOutputBuffer(bufferIndex, false); 114 | 115 | if (allTracksFinished()) { 116 | stop(); 117 | release(); //停止释放; 118 | } 119 | } 120 | 121 | @Override 122 | public void forceStop() { 123 | Log.e(TAG, "forceStop"); 124 | try { 125 | stop(); 126 | release(); 127 | } catch (Exception e) { 128 | if (muxFinishListener != null) { 129 | muxFinishListener.onMuxFail(e); 130 | } 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /libRecorderEditor/src/main/java/com/pinssible/librecorder/recorder/AudioEncoderCore.java: -------------------------------------------------------------------------------- 1 | package com.pinssible.librecorder.recorder; 2 | 3 | import android.media.AudioFormat; 4 | import android.media.MediaCodec; 5 | import android.media.MediaCodecInfo; 6 | import android.media.MediaFormat; 7 | 8 | import java.io.IOException; 9 | 10 | /** 11 | */ 12 | public class AudioEncoderCore extends AndroidEncoder { 13 | 14 | private static final String TAG = "AudioEncoderCore"; 15 | private static final boolean VERBOSE = false; 16 | 17 | protected static final String MIME_TYPE = "audio/mp4a-latm"; // AAC Low Overhead Audio Transport Multiplex 18 | 19 | // Configurable options 20 | protected int mChannelConfig; 21 | protected int mSampleRate; 22 | 23 | /** 24 | * Configures encoder and muxer state, and prepares the input Surface. 25 | */ 26 | public AudioEncoderCore(int numChannels, int bitRate, int sampleRate, Muxer muxer) throws IOException { 27 | switch (numChannels) { 28 | case 1: 29 | mChannelConfig = AudioFormat.CHANNEL_IN_MONO; 30 | break; 31 | case 2: 32 | mChannelConfig = AudioFormat.CHANNEL_IN_STEREO; 33 | break; 34 | default: 35 | throw new IllegalArgumentException("Invalid channel count. Must be 1 or 2"); 36 | } 37 | mSampleRate = sampleRate; 38 | mMuxer = muxer; 39 | mBufferInfo = new MediaCodec.BufferInfo(); 40 | 41 | MediaFormat format = MediaFormat.createAudioFormat(MIME_TYPE, mSampleRate, mChannelConfig); 42 | 43 | // Set some properties. Failing to specify some of these can cause the MediaCodec 44 | // configure() call to throw an unhelpful exception. 45 | format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); 46 | format.setInteger(MediaFormat.KEY_SAMPLE_RATE, mSampleRate); 47 | format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, numChannels); 48 | format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate); 49 | format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 16384); 50 | 51 | // Create a MediaCodec encoder, and configure it with our format. Get a Surface 52 | // we can use for input and wrap it with a class that handles the EGL work. 53 | mEncoder = MediaCodec.createEncoderByType(MIME_TYPE); 54 | mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 55 | mEncoder.start(); 56 | 57 | mTrackIndex = -1; 58 | } 59 | 60 | /** 61 | * Depending on this method ties AudioEncoderCore 62 | * to a MediaCodec-based implementation. 63 | * However, when reading AudioRecord samples directly 64 | * to MediaCode's input ByteBuffer we can avoid a memory copy 65 | * TODO: Measure performance gain and remove if negligible 66 | * @return 67 | */ 68 | public MediaCodec getMediaCodec(){ 69 | return mEncoder; 70 | } 71 | 72 | @Override 73 | protected boolean isSurfaceInputEncoder() { 74 | return false; 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /libRecorderEditor/src/main/java/com/pinssible/librecorder/recorder/Muxer.java: -------------------------------------------------------------------------------- 1 | package com.pinssible.librecorder.recorder; 2 | 3 | import android.media.MediaCodec; 4 | import android.media.MediaFormat; 5 | import android.os.Build; 6 | import android.util.Log; 7 | 8 | import com.pinssible.librecorder.listener.OnMuxFinishListener; 9 | 10 | import java.nio.ByteBuffer; 11 | 12 | 13 | /** 14 | * Base Muxer class for interaction with MediaCodec based 15 | * encoders 16 | * 17 | */ 18 | public abstract class Muxer { 19 | private static final String TAG = "Muxer"; 20 | 21 | public static enum FORMAT {MPEG4, HLS} 22 | 23 | private final int mExpectedNumTracks = 1; // TODO: Make this configurable? yes,wait to do 24 | 25 | protected FORMAT mFormat; 26 | protected String mOutputPath; 27 | protected int mNumTracks; 28 | protected int mNumTracksFinished; 29 | protected long mFirstPts; 30 | protected long mLastPts[]; 31 | 32 | protected OnMuxFinishListener muxFinishListener; 33 | 34 | protected Muxer(String outputPath, FORMAT format) { 35 | Log.i(TAG, "Created muxer for output: " + outputPath); 36 | mOutputPath = outputPath; 37 | mFormat = format; 38 | mNumTracks = 0; 39 | mNumTracksFinished = 0; 40 | mFirstPts = 0; 41 | mLastPts = new long[mExpectedNumTracks]; 42 | for (int i = 0; i < mLastPts.length; i++) { 43 | mLastPts[i] = 0; 44 | } 45 | } 46 | 47 | 48 | public void setMuxFinishListener(OnMuxFinishListener muxFinishListener) { 49 | this.muxFinishListener = muxFinishListener; 50 | } 51 | 52 | /** 53 | * Returns the absolute output path. 54 | *
55 | * e.g /sdcard/app/uuid/index.m3u8 56 | * 57 | * @return 58 | */ 59 | public String getOutputPath() { 60 | return mOutputPath; 61 | } 62 | 63 | /** 64 | * Adds the specified track and returns the track index 65 | * 66 | * @param trackFormat MediaFormat of the track to add. Gotten from MediaCodec#dequeueOutputBuffer 67 | * when returned status is INFO_OUTPUT_FORMAT_CHANGED 68 | * @return index of track in output file 69 | */ 70 | public int addTrack(MediaFormat trackFormat) { 71 | mNumTracks++; 72 | return mNumTracks - 1; 73 | } 74 | 75 | /** 76 | * Called by the hosting Encoder 77 | * to notify the Muxer that it should no 78 | * longer assume the Encoder resources are available. 79 | */ 80 | public void onEncoderReleased(int trackIndex) { 81 | } 82 | 83 | public void release() { 84 | 85 | } 86 | 87 | public void stop(){ 88 | if (muxFinishListener != null) 89 | muxFinishListener.onMuxFinish(); 90 | } 91 | 92 | public boolean isStarted() { 93 | return false; 94 | } 95 | 96 | /** 97 | * Write the MediaCodec output buffer. This method must 98 | * be overridden by subclasses to release encodedData, transferring 99 | * ownership back to encoder, by calling encoder.releaseOutputBuffer(bufferIndex, false); 100 | * 101 | * @param trackIndex 102 | * @param encodedData 103 | * @param bufferInfo 104 | */ 105 | public void writeSampleData(MediaCodec encoder, int trackIndex, int bufferIndex, ByteBuffer encodedData, MediaCodec.BufferInfo bufferInfo) { 106 | if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 107 | signalEndOfTrack(); 108 | } 109 | } 110 | 111 | public abstract void forceStop(); 112 | 113 | protected boolean allTracksFinished() { 114 | return (mNumTracks == mNumTracksFinished); 115 | } 116 | 117 | protected boolean allTracksAdded() { 118 | return (mNumTracks == mExpectedNumTracks); 119 | } 120 | 121 | /** 122 | * Muxer will call this itself if it detects BUFFER_FLAG_END_OF_STREAM 123 | * in writeSampleData. 124 | */ 125 | protected void signalEndOfTrack() { 126 | mNumTracksFinished++; 127 | } 128 | 129 | /** 130 | * Does this Muxer's format require AAC ADTS headers? 131 | * see http://wiki.multimedia.cx/index.php?title=ADTS 132 | * 133 | * @return 134 | */ 135 | protected boolean formatRequiresADTS() { 136 | switch (mFormat) { 137 | case HLS: 138 | return true; 139 | default: 140 | return false; 141 | } 142 | } 143 | 144 | /** 145 | * Does this Muxer's format require 146 | * copying and buffering encoder output buffers. 147 | * Generally speaking, is the output a Socket or File? 148 | * 149 | * @return 150 | */ 151 | protected boolean formatRequiresBuffering() { 152 | if (Build.VERSION.SDK_INT >= 21) return true; 153 | 154 | switch (mFormat) { 155 | case HLS: 156 | return false; 157 | default: 158 | return false; 159 | } 160 | } 161 | 162 | /** 163 | * Return a relative pts given an absolute pts and trackIndex. 164 | *
165 | * This method advances the state of the Muxer, and must only 166 | * be called once per call to {@link #writeSampleData(MediaCodec, int, int, ByteBuffer, MediaCodec.BufferInfo)}. 167 | */ 168 | protected long getNextRelativePts(long absPts, int trackIndex) { 169 | if (mFirstPts == 0) { 170 | mFirstPts = absPts; 171 | return 0; 172 | } 173 | return getSafePts(absPts - mFirstPts, trackIndex); 174 | } 175 | 176 | /** 177 | * Sometimes packets with non-increasing pts are dequeued from the MediaCodec output buffer. 178 | * This method ensures that a crash won't occur due to non monotonically increasing packet timestamp. 179 | */ 180 | private long getSafePts(long pts, int trackIndex) { 181 | if (mLastPts[trackIndex] >= pts) { 182 | // Enforce a non-zero minimum spacing 183 | // between pts 184 | mLastPts[trackIndex] += 9643; 185 | return mLastPts[trackIndex]; 186 | } 187 | mLastPts[trackIndex] = pts; 188 | return pts; 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /libRecorderEditor/src/main/java/com/pinssible/librecorder/recorder/PreviewConfig.java: -------------------------------------------------------------------------------- 1 | package com.pinssible.librecorder.recorder; 2 | 3 | /** 4 | * Created by ZhangHaoSong on 2017/10/30. 5 | */ 6 | 7 | public class PreviewConfig { 8 | public class Size { 9 | int width; 10 | int height; 11 | 12 | public int getWidth() { 13 | return width; 14 | } 15 | 16 | public void setWidth(int width) { 17 | this.width = width; 18 | } 19 | 20 | public int getHeight() { 21 | return height; 22 | } 23 | 24 | public void setHeight(int height) { 25 | this.height = height; 26 | } 27 | 28 | public Size(int width, int height) { 29 | this.width = width; 30 | this.height = height; 31 | } 32 | } 33 | 34 | private PreviewState initState; 35 | private Size previewSize; 36 | private boolean previewFitView; 37 | 38 | 39 | public PreviewConfig(int width, int height){ 40 | this.previewFitView = true; 41 | //size 42 | previewSize = new Size(width, height); 43 | //state 44 | initState = new PreviewState(); 45 | } 46 | 47 | public PreviewConfig(int width, int height, boolean previewFitView, boolean isBackCamera){ 48 | this.previewFitView = previewFitView; 49 | //size 50 | previewSize = new Size(width, height); 51 | //state 52 | initState = new PreviewState(); 53 | PreviewState.Facing face = PreviewState.Facing.BACKGROUND; 54 | if (!isBackCamera) face = PreviewState.Facing.FRONT; 55 | initState.setFacing(face); 56 | } 57 | 58 | public PreviewConfig(int width, int height, boolean previewFitView, boolean isBackCamera, int filter, boolean isBlur) { 59 | this.previewFitView = previewFitView; 60 | //size 61 | previewSize = new Size(width, height); 62 | //state 63 | initState = new PreviewState(); 64 | PreviewState.Facing face = PreviewState.Facing.BACKGROUND; 65 | if (!isBackCamera) face = PreviewState.Facing.FRONT; 66 | initState.setBlur(isBlur) 67 | .setFacing(face) 68 | .setFilter(filter); 69 | } 70 | 71 | public PreviewState getInitState() { 72 | return initState; 73 | } 74 | 75 | public void setInitState(PreviewState initState) { 76 | this.initState = initState; 77 | } 78 | 79 | public Size getPreviewSize() { 80 | return previewSize; 81 | } 82 | 83 | public void setPreviewSize(Size previewSize) { 84 | this.previewSize = previewSize; 85 | } 86 | 87 | public boolean isPreviewFitView() { 88 | return previewFitView; 89 | } 90 | 91 | public void setPreviewFitView(boolean previewFitView) { 92 | this.previewFitView = previewFitView; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /libRecorderEditor/src/main/java/com/pinssible/librecorder/recorder/PreviewState.java: -------------------------------------------------------------------------------- 1 | package com.pinssible.librecorder.recorder; 2 | 3 | import com.pinssible.librecorder.filter.Filters; 4 | 5 | /** 6 | * Created by ZhangHaoSong on 2017/10/30. 7 | */ 8 | 9 | public class PreviewState { 10 | public enum Facing {BACKGROUND, FRONT} 11 | 12 | /** 13 | * flash 14 | */ 15 | private boolean isFlashOpen = false; 16 | /** 17 | * face 18 | */ 19 | private Facing facing = Facing.BACKGROUND; 20 | /** 21 | * {@see Filters} 22 | */ 23 | private int filter = Filters.FILTER_NONE; 24 | /** 25 | * 模糊 26 | */ 27 | private boolean isBlur = false; 28 | /** 29 | * 模糊程度,0~30 30 | */ 31 | private int blurIntensity = 20; 32 | 33 | public PreviewState() { 34 | } 35 | 36 | public boolean isFlashOpen() { 37 | return isFlashOpen; 38 | } 39 | 40 | public PreviewState setFlashOpen(boolean flashOpen) { 41 | isFlashOpen = flashOpen; 42 | return this; 43 | } 44 | 45 | public Facing getFacing() { 46 | return facing; 47 | } 48 | 49 | public PreviewState setFacing(Facing facing) { 50 | this.facing = facing; 51 | return this; 52 | } 53 | 54 | public int getFilter() { 55 | return filter; 56 | } 57 | 58 | public PreviewState setFilter(int filter) { 59 | this.filter = filter; 60 | return this; 61 | } 62 | 63 | public boolean isBlur() { 64 | return isBlur; 65 | } 66 | 67 | public PreviewState setBlur(boolean blur) { 68 | isBlur = blur; 69 | return this; 70 | } 71 | 72 | public int getBlurIntensity() { 73 | return blurIntensity; 74 | } 75 | 76 | public PreviewState setBlurIntensity(int blurIntensity) { 77 | this.blurIntensity = blurIntensity; 78 | return this; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /libRecorderEditor/src/main/java/com/pinssible/librecorder/recorder/RecorderConfig.java: -------------------------------------------------------------------------------- 1 | package com.pinssible.librecorder.recorder; 2 | 3 | import android.opengl.EGLContext; 4 | import android.os.Build; 5 | import android.support.annotation.RequiresApi; 6 | 7 | import com.pinssible.librecorder.listener.OnMuxFinishListener; 8 | 9 | import java.io.File; 10 | 11 | /** 12 | * Created by ZhangHaoSong on 2017/9/26. 13 | */ 14 | 15 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) 16 | public class RecorderConfig { 17 | public enum SCREEN_ROTATION {LANDSCAPE, VERTICAL} 18 | 19 | public VideoEncoderConfig videoEncoderConfig; 20 | public AudioEncoderConfig audioEncoderConfig; 21 | public File mOutputFile; 22 | public Muxer muxer; 23 | public SCREEN_ROTATION screenRotation = SCREEN_ROTATION.VERTICAL; 24 | 25 | public RecorderConfig(VideoEncoderConfig videoEncoderConfig, AudioEncoderConfig audioEncoderConfig, File outputFile) { 26 | this.videoEncoderConfig = videoEncoderConfig; 27 | this.audioEncoderConfig = audioEncoderConfig; 28 | this.mOutputFile = outputFile; 29 | this.muxer = AndroidMuxer.create(outputFile.toString(), Muxer.FORMAT.MPEG4); 30 | } 31 | 32 | public RecorderConfig(VideoEncoderConfig videoEncoderConfig, AudioEncoderConfig audioEncoderConfig, File outputFile, SCREEN_ROTATION screenRotation) { 33 | this.videoEncoderConfig = videoEncoderConfig; 34 | this.audioEncoderConfig = audioEncoderConfig; 35 | this.mOutputFile = outputFile; 36 | this.muxer = AndroidMuxer.create(outputFile.toString(), Muxer.FORMAT.MPEG4); 37 | this.screenRotation = screenRotation; 38 | } 39 | 40 | public RecorderConfig(VideoEncoderConfig videoEncoderConfig, AudioEncoderConfig audioEncoderConfig, File outputFile, SCREEN_ROTATION screenRotation, OnMuxFinishListener muxFinishListener) { 41 | this.videoEncoderConfig = videoEncoderConfig; 42 | this.audioEncoderConfig = audioEncoderConfig; 43 | this.mOutputFile = outputFile; 44 | this.muxer = AndroidMuxer.create(outputFile.toString(), Muxer.FORMAT.MPEG4); 45 | this.muxer.setMuxFinishListener(muxFinishListener); 46 | this.screenRotation = screenRotation; 47 | } 48 | 49 | public void setMuxFinishListener(OnMuxFinishListener muxFinishListener) { 50 | if (muxer != null) { 51 | muxer.setMuxFinishListener(muxFinishListener); 52 | } 53 | } 54 | 55 | public static class VideoEncoderConfig { 56 | public int mWidth; 57 | public int mHeight; 58 | public int mBitRate; 59 | public EGLContext mEglContext; 60 | 61 | public VideoEncoderConfig(int width, int height, int bitRate, 62 | EGLContext sharedEglContext) { 63 | mWidth = width; 64 | mHeight = height; 65 | mBitRate = bitRate; 66 | mEglContext = sharedEglContext; 67 | } 68 | 69 | @Override 70 | public String toString() { 71 | return "VideoEncoderConfig{" + 72 | "mWidth=" + mWidth + 73 | ", mHeight=" + mHeight + 74 | ", mBitRate=" + mBitRate + 75 | ", mEglContext=" + mEglContext + 76 | '}'; 77 | } 78 | } 79 | 80 | public static class AudioEncoderConfig { 81 | public int numChannels; 82 | public int bitRate; 83 | public int sampleRate; 84 | 85 | public AudioEncoderConfig(int numChannels, int bitRate, int sampleRate) { 86 | this.numChannels = numChannels; 87 | this.bitRate = bitRate; 88 | this.sampleRate = sampleRate; 89 | } 90 | 91 | @Override 92 | public String toString() { 93 | return "AudioEncoderConfig{" + 94 | "numChannels=" + numChannels + 95 | ", bitRate=" + bitRate + 96 | ", sampleRate=" + sampleRate + 97 | '}'; 98 | } 99 | } 100 | 101 | @Override 102 | public String toString() { 103 | return "RecorderConfig{" + 104 | "videoEncoderConfig=" + videoEncoderConfig + 105 | ", audioEncoderConfig=" + audioEncoderConfig + 106 | ", mOutputFile=" + mOutputFile + 107 | ", muxer=" + muxer + 108 | '}'; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /libRecorderEditor/src/main/java/com/pinssible/librecorder/recorder/VideoEncoderCore2.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 com.pinssible.librecorder.recorder; 18 | 19 | import android.media.MediaCodec; 20 | import android.media.MediaCodecInfo; 21 | import android.media.MediaFormat; 22 | import android.os.Build; 23 | import android.support.annotation.RequiresApi; 24 | import android.util.Log; 25 | import android.view.Surface; 26 | 27 | import java.io.IOException; 28 | 29 | 30 | /** 31 | * This class wraps up the core components used for surface-input video encoding. 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 | * This class is not thread-safe, with one exception: it is valid to use the input surface 36 | * on one thread, and drain the output on a different thread. 37 | */ 38 | public class VideoEncoderCore2 extends AndroidEncoder{ 39 | private static final String TAG = "VideoEncoderCore"; 40 | private static final boolean VERBOSE = false; 41 | 42 | // TODO: these ought to be configurable as well 43 | private static final String MIME_TYPE = "video/avc"; // H.264 Advanced Video Coding 44 | private static final int FRAME_RATE = 30; // 30fps 45 | private static final int IFRAME_INTERVAL = 3; // 5 seconds between I-frames 46 | 47 | private Surface mInputSurface; 48 | 49 | 50 | /** 51 | * Configures encoder and muxer state, and prepares the input Surface. 52 | */ 53 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) 54 | public VideoEncoderCore2(int width, int height, int bitRate, Muxer muxer) throws IOException { 55 | mMuxer = muxer; 56 | mBufferInfo = new MediaCodec.BufferInfo(); 57 | 58 | MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, width, height); 59 | 60 | // Set some properties. Failing to specify some of these can cause the MediaCodec 61 | // configure() call to throw an unhelpful exception. 62 | format.setInteger(MediaFormat.KEY_COLOR_FORMAT, 63 | MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); 64 | format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate); 65 | format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE); 66 | format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL); 67 | if (VERBOSE) Log.d(TAG, "format: " + format); 68 | 69 | // Create a MediaCodec encoder, and configure it with our format. Get a Surface 70 | // we can use for input and wrap it with a class that handles the EGL work. 71 | mEncoder = MediaCodec.createEncoderByType(MIME_TYPE); 72 | mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 73 | mInputSurface = mEncoder.createInputSurface(); 74 | mEncoder.start(); 75 | 76 | mTrackIndex = -1; 77 | } 78 | 79 | /** 80 | * Returns the encoder's input surface. 81 | */ 82 | public Surface getInputSurface() { 83 | return mInputSurface; 84 | } 85 | 86 | @Override 87 | protected boolean isSurfaceInputEncoder() { 88 | return true; 89 | } 90 | } -------------------------------------------------------------------------------- /libRecorderEditor/src/main/java/com/pinssible/librecorder/remux/InputSurface.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 The Android Open Source Project 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 com.pinssible.librecorder.remux; 18 | 19 | import android.opengl.EGL14; 20 | import android.opengl.EGLConfig; 21 | import android.opengl.EGLContext; 22 | import android.opengl.EGLDisplay; 23 | import android.opengl.EGLExt; 24 | import android.opengl.EGLSurface; 25 | import android.view.Surface; 26 | 27 | 28 | /** 29 | * Holds state associated with a Surface used for MediaCodec encoder input. 30 | *
31 | * The constructor takes a Surface obtained from MediaCodec.createInputSurface(), and uses that
32 | * to create an EGL window surface. Calls to eglSwapBuffers() cause a frame of data to be sent
33 | * to the video encoder.
34 | */
35 | class InputSurface {
36 | private static final String TAG = "InputSurface";
37 | private static final boolean VERBOSE = false;
38 |
39 | private static final int EGL_RECORDABLE_ANDROID = 0x3142;
40 |
41 | private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY;
42 | private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT;
43 | private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE;
44 |
45 | private Surface mSurface;
46 |
47 | /**
48 | * Creates an InputSurface from a Surface.
49 | */
50 | public InputSurface(Surface surface) {
51 | if (surface == null) {
52 | throw new NullPointerException();
53 | }
54 | mSurface = surface;
55 |
56 | eglSetup();
57 | }
58 |
59 | /**
60 | * Prepares EGL. We want a GLES 2.0 context and a surface that supports recording.
61 | */
62 | private void eglSetup() {
63 | mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
64 | if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
65 | throw new RuntimeException("unable to get EGL14 display");
66 | }
67 | int[] version = new int[2];
68 | if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
69 | mEGLDisplay = null;
70 | throw new RuntimeException("unable to initialize EGL14");
71 | }
72 |
73 | // Configure EGL for recordable and OpenGL ES 2.0. We want enough RGB bits
74 | // to minimize artifacts from possible YUV conversion.
75 | int[] attribList = {
76 | EGL14.EGL_RED_SIZE, 8,
77 | EGL14.EGL_GREEN_SIZE, 8,
78 | EGL14.EGL_BLUE_SIZE, 8,
79 | EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
80 | EGL_RECORDABLE_ANDROID, 1,
81 | EGL14.EGL_NONE
82 | };
83 | EGLConfig[] configs = new EGLConfig[1];
84 | int[] numConfigs = new int[1];
85 | if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length,
86 | numConfigs, 0)) {
87 | throw new RuntimeException("unable to find RGB888+recordable ES2 EGL config");
88 | }
89 |
90 | // Configure context for OpenGL ES 2.0.
91 | int[] attrib_list = {
92 | EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
93 | EGL14.EGL_NONE
94 | };
95 | mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT,
96 | attrib_list, 0);
97 | checkEglError("eglCreateContext");
98 | if (mEGLContext == null) {
99 | throw new RuntimeException("null context");
100 | }
101 |
102 | // Create a window surface, and attach it to the Surface we received.
103 | int[] surfaceAttribs = {
104 | EGL14.EGL_NONE
105 | };
106 | mEGLSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, configs[0], mSurface,
107 | surfaceAttribs, 0);
108 | checkEglError("eglCreateWindowSurface");
109 | if (mEGLSurface == null) {
110 | throw new RuntimeException("surface was null");
111 | }
112 | }
113 |
114 | /**
115 | * Discard all resources held by this class, notably the EGL context. Also releases the
116 | * Surface that was passed to our constructor.
117 | */
118 | public void release() {
119 | if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
120 | EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);
121 | EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
122 | EGL14.eglReleaseThread();
123 | EGL14.eglTerminate(mEGLDisplay);
124 | }
125 |
126 | mSurface.release();
127 |
128 | mEGLDisplay = EGL14.EGL_NO_DISPLAY;
129 | mEGLContext = EGL14.EGL_NO_CONTEXT;
130 | mEGLSurface = EGL14.EGL_NO_SURFACE;
131 |
132 | mSurface = null;
133 | }
134 |
135 | /**
136 | * Makes our EGL context and surface current.
137 | */
138 | public void makeCurrent() {
139 | if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
140 | throw new RuntimeException("eglMakeCurrent failed");
141 | }
142 | }
143 |
144 | public void makeUnCurrent() {
145 | if (!EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
146 | EGL14.EGL_NO_CONTEXT)) {
147 | throw new RuntimeException("eglMakeCurrent failed");
148 | }
149 | }
150 |
151 | /**
152 | * Calls eglSwapBuffers. Use this to "publish" the current frame.
153 | */
154 | public boolean swapBuffers() {
155 | return EGL14.eglSwapBuffers(mEGLDisplay, mEGLSurface);
156 | }
157 |
158 | /**
159 | * Returns the Surface that the MediaCodec receives buffers from.
160 | */
161 | public Surface getSurface() {
162 | return mSurface;
163 | }
164 |
165 | /**
166 | * Queries the surface's width.
167 | */
168 | public int getWidth() {
169 | int[] value = new int[1];
170 | EGL14.eglQuerySurface(mEGLDisplay, mEGLSurface, EGL14.EGL_WIDTH, value, 0);
171 | return value[0];
172 | }
173 |
174 | /**
175 | * Queries the surface's height.
176 | */
177 | public int getHeight() {
178 | int[] value = new int[1];
179 | EGL14.eglQuerySurface(mEGLDisplay, mEGLSurface, EGL14.EGL_HEIGHT, value, 0);
180 | return value[0];
181 | }
182 |
183 | /**
184 | * Sends the presentation time stamp to EGL. Time is expressed in nanoseconds.
185 | */
186 | public void setPresentationTime(long nsecs) {
187 | EGLExt.eglPresentationTimeANDROID(mEGLDisplay, mEGLSurface, nsecs);
188 | }
189 |
190 | /**
191 | * Checks for EGL errors.
192 | */
193 | private void checkEglError(String msg) {
194 | int error;
195 | if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) {
196 | throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error));
197 | }
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/libRecorderEditor/src/main/java/com/pinssible/librecorder/remux/PinRemuxer.java:
--------------------------------------------------------------------------------
1 | package com.pinssible.librecorder.remux;
2 |
3 | import android.content.res.AssetFileDescriptor;
4 | import android.util.Log;
5 |
6 | import com.pinssible.librecorder.filter.Filters;
7 |
8 | import java.io.File;
9 | import java.io.FileDescriptor;
10 | import java.io.IOException;
11 |
12 | /**
13 | * Created by ZhangHaoSong on 2017/11/14.
14 | */
15 |
16 | public class PinRemuxer {
17 | private final String TAG = "PinRemuxer";
18 |
19 | //src
20 | private String srcPath;
21 | private FileDescriptor srcFd;
22 | private AssetFileDescriptor srcAfd;
23 |
24 | //dst
25 | private String dstPath;
26 |
27 | private RemuxerFactory remuxer;
28 | private RemuxerFactory.OnRemuxListener remuxListener;
29 |
30 | /**
31 | * {@link Filters}
32 | */
33 | private int filter = Filters.FILTER_NONE;
34 |
35 |
36 | public PinRemuxer(String srcPath, String dstPath, RemuxerFactory.OnRemuxListener remuxListener) {
37 | this.srcPath = srcPath;
38 | this.dstPath = dstPath;
39 | this.remuxListener = remuxListener;
40 | remuxer = new RemuxerFactory();;
41 | }
42 |
43 | public PinRemuxer(FileDescriptor fd, String dstPath, RemuxerFactory.OnRemuxListener remuxListener) {
44 | this.srcFd = fd;
45 | this.dstPath = dstPath;
46 | this.remuxListener = remuxListener;
47 | remuxer = new RemuxerFactory();
48 | }
49 |
50 | public PinRemuxer(AssetFileDescriptor afd, String dstPath, RemuxerFactory.OnRemuxListener remuxListener) {
51 | this.srcAfd = afd;
52 | this.dstPath = dstPath;
53 | this.remuxListener = remuxListener;
54 | remuxer = new RemuxerFactory();
55 | }
56 |
57 | public void start(int filter) {
58 | this.filter = filter;
59 | start();
60 | }
61 |
62 | public void start() {
63 | File dstFile = new File(dstPath);
64 | if (!dstFile.exists()) {
65 | try {
66 | dstFile.createNewFile();
67 | } catch (IOException e) {
68 | e.printStackTrace();
69 | }
70 | }
71 |
72 | //launch thread
73 | Thread myThread = new Thread(new RemuxRunnable());
74 | myThread.start();
75 | }
76 |
77 | public void stop() {
78 | remuxer.stopRemux();
79 | }
80 |
81 | private class RemuxRunnable implements Runnable {
82 | @Override
83 | public void run() {
84 | //start
85 | try {
86 | if (srcAfd != null) {
87 | remuxer.startRemux(srcAfd, dstPath, remuxListener, filter);
88 | } else if (srcFd != null) {
89 | remuxer.startRemux(srcFd, dstPath, remuxListener, filter);
90 | } else {
91 | remuxer.startRemux(srcPath, dstPath, remuxListener, filter);
92 | }
93 | } catch (Throwable throwable) {
94 | throwable.printStackTrace();
95 | Log.e(TAG, "test throwable = " + throwable.toString());
96 | }
97 | }
98 | }
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/libRecorderEditor/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |