36 | * 37 | * @param recorder video encoder object 38 | */ 39 | public CameraSurfaceRenderer(CameraEncoder recorder) { 40 | mCameraEncoder = recorder; 41 | 42 | mCameraTextureId = -1; 43 | mFrameCount = -1; 44 | 45 | SessionConfig config = recorder.getConfig(); 46 | mIncomingWidth = config.getVideoWidth(); 47 | mIncomingHeight = config.getVideoHeight(); 48 | mIncomingSizeUpdated = true; // Force texture size update on next onDrawFrame 49 | 50 | mCurrentFilter = -1; 51 | mNewFilter = Filters.FILTER_NONE; 52 | 53 | mRecordingEnabled = false; 54 | } 55 | 56 | 57 | /** 58 | * Notifies the renderer that we want to stop or start recording. 59 | */ 60 | public void changeRecordingState(boolean isRecording) { 61 | Log.d(TAG, "changeRecordingState: was " + mRecordingEnabled + " now " + isRecording); 62 | mRecordingEnabled = isRecording; 63 | } 64 | 65 | @Override 66 | public void onSurfaceCreated(GL10 unused, EGLConfig config) { 67 | Log.d(TAG, "onSurfaceCreated"); 68 | // Set up the texture blitter that will be used for on-screen display. This 69 | // is *not* applied to the recording, because that uses a separate shader. 70 | mFullScreenCamera = new FullFrameRect( 71 | new Texture2dProgram(Texture2dProgram.ProgramType.TEXTURE_EXT)); 72 | // For texture overlay: 73 | //GLES20.glEnable(GLES20.GL_BLEND); 74 | //GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA); 75 | mFullScreenOverlay = new FullFrameRect( 76 | new Texture2dProgram(Texture2dProgram.ProgramType.TEXTURE_2D)); 77 | //mOverlayTextureId = GlUtil.createTextureWithTextContent("hello!"); 78 | //mOverlayTextureId = GlUtil.createTextureFromImage(mCameraView.getContext(), R.drawable.red_dot); 79 | mCameraTextureId = mFullScreenCamera.createTextureObject(); 80 | 81 | mCameraEncoder.onSurfaceCreated(mCameraTextureId); 82 | mFrameCount = 0; 83 | } 84 | 85 | @Override 86 | public void onSurfaceChanged(GL10 unused, int width, int height) { 87 | Log.d(TAG, "onSurfaceChanged " + width + "x" + height); 88 | } 89 | 90 | @Override 91 | public void onDrawFrame(GL10 unused) { 92 | if (VERBOSE) { 93 | if (mFrameCount % 30 == 0) { 94 | Log.d(TAG, "onDrawFrame tex=" + mCameraTextureId); 95 | mCameraEncoder.logSavedEglState(); 96 | } 97 | } 98 | 99 | if (mCurrentFilter != mNewFilter) { 100 | Filters.updateFilter(mFullScreenCamera, mNewFilter); 101 | mCurrentFilter = mNewFilter; 102 | mIncomingSizeUpdated = true; 103 | } 104 | 105 | if (mIncomingSizeUpdated) { 106 | mFullScreenCamera.getProgram().setTexSize(mIncomingWidth, mIncomingHeight); 107 | mFullScreenOverlay.getProgram().setTexSize(mIncomingWidth, mIncomingHeight); 108 | mIncomingSizeUpdated = false; 109 | Log.i(TAG, "setTexSize on display Texture"); 110 | } 111 | 112 | // Draw the video frame. 113 | if (mCameraEncoder.isSurfaceTextureReadyForDisplay()) { 114 | mCameraEncoder.getSurfaceTextureForDisplay().updateTexImage(); 115 | mCameraEncoder.getSurfaceTextureForDisplay().getTransformMatrix(mSTMatrix); 116 | //Drawing texture overlay: 117 | mFullScreenOverlay.drawFrame(mOverlayTextureId, mSTMatrix); 118 | mFullScreenCamera.drawFrame(mCameraTextureId, mSTMatrix); 119 | } 120 | mFrameCount++; 121 | } 122 | 123 | public void signalVertialVideo(FullFrameRect.SCREEN_ROTATION isVertical) { 124 | if (mFullScreenCamera != null) mFullScreenCamera.adjustForVerticalVideo(isVertical, false); 125 | } 126 | 127 | /** 128 | * Changes the filter that we're applying to the camera preview. 129 | */ 130 | public void changeFilterMode(int filter) { 131 | mNewFilter = filter; 132 | } 133 | 134 | public void handleTouchEvent(MotionEvent ev) { 135 | mFullScreenCamera.handleTouchEvent(ev); 136 | } 137 | 138 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mcxiaoke/media/av/Drawable2d.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.mcxiaoke.media.av; 18 | 19 | import java.nio.FloatBuffer; 20 | 21 | /** 22 | * Base class for stuff we like to draw. 23 | */ 24 | public class Drawable2d { 25 | /** 26 | * Simple triangle (roughly equilateral, 1.0 per side). 27 | */ 28 | private static final float TRIANGLE_COORDS[] = { 29 | 0.0f, 0.622008459f, // top 30 | -0.5f, -0.311004243f, // bottom left 31 | 0.5f, -0.311004243f // bottom right 32 | }; 33 | private static final FloatBuffer TRIANGLE_BUF = GlUtil.createFloatBuffer(TRIANGLE_COORDS); 34 | 35 | /** 36 | * Simple square, specified as a triangle strip. The square is centered on (0,0) and has 37 | * a size of 1x1. 38 | *
39 | * Triangles are 0-1-2 and 2-1-3 (counter-clockwise winding). 40 | */ 41 | private static final float RECTANGLE_COORDS[] = { 42 | -0.5f, -0.5f, // 0 bottom left 43 | 0.5f, -0.5f, // 1 bottom right 44 | -0.5f, 0.5f, // 2 top left 45 | 0.5f, 0.5f, // 3 top right 46 | }; 47 | private static final FloatBuffer RECTANGLE_BUF = GlUtil.createFloatBuffer(RECTANGLE_COORDS); 48 | 49 | /** 50 | * A "full" square, extending from -1 to +1 in both dimensions. When the model/view/projection 51 | * matrix is identity, this will exactly cover the viewport. 52 | *
53 | * This has texture coordinates as well. 54 | */ 55 | private static final float FULL_RECTANGLE_COORDS[] = { 56 | -1.0f, -1.0f, // 0 bottom left 57 | 1.0f, -1.0f, // 1 bottom right 58 | -1.0f, 1.0f, // 2 top left 59 | 1.0f, 1.0f, // 3 top right 60 | }; 61 | private static final FloatBuffer FULL_RECTANGLE_BUF = 62 | GlUtil.createFloatBuffer(FULL_RECTANGLE_COORDS); 63 | 64 | private static final int SIZEOF_FLOAT = 4; 65 | 66 | private FloatBuffer mVertexArray; 67 | private int mVertexCount; 68 | private int mCoordsPerVertex; 69 | private int mVertexStride; 70 | private Prefab mPrefab; 71 | 72 | /** 73 | * Prepares a drawable from a "pre-fabricated" shape definition. 74 | *
75 | * Does no EGL/GL operations, so this can be done at any time. 76 | */ 77 | public Drawable2d(Prefab shape) { 78 | switch (shape) { 79 | case TRIANGLE: 80 | mVertexArray = TRIANGLE_BUF; 81 | mCoordsPerVertex = 2; 82 | mVertexStride = mCoordsPerVertex * SIZEOF_FLOAT; 83 | mVertexCount = TRIANGLE_COORDS.length / mCoordsPerVertex; 84 | break; 85 | case RECTANGLE: 86 | mVertexArray = RECTANGLE_BUF; 87 | mCoordsPerVertex = 2; 88 | mVertexStride = mCoordsPerVertex * SIZEOF_FLOAT; 89 | mVertexCount = RECTANGLE_COORDS.length / mCoordsPerVertex; 90 | break; 91 | case FULL_RECTANGLE: 92 | mVertexArray = FULL_RECTANGLE_BUF; 93 | mCoordsPerVertex = 2; 94 | mVertexStride = mCoordsPerVertex * SIZEOF_FLOAT; 95 | mVertexCount = FULL_RECTANGLE_COORDS.length / mCoordsPerVertex; 96 | break; 97 | default: 98 | throw new RuntimeException("Unknown shape " + shape); 99 | } 100 | mPrefab = shape; 101 | } 102 | 103 | /** 104 | * Returns the array of vertices. 105 | *
106 | * To avoid allocations, this returns internal state. The caller must not modify it. 107 | */ 108 | public FloatBuffer getVertexArray() { 109 | return mVertexArray; 110 | } 111 | 112 | /** 113 | * Returns the number of vertices stored in the vertex array. 114 | */ 115 | public int getVertexCount() { 116 | return mVertexCount; 117 | } 118 | 119 | /** 120 | * Returns the width, in bytes, of the data for each vertex. 121 | */ 122 | public int getVertexStride() { 123 | return mVertexStride; 124 | } 125 | 126 | /** 127 | * Returns the number of position coordinates per vertex. This will be 2 or 3. 128 | */ 129 | public int getCoordsPerVertex() { 130 | return mCoordsPerVertex; 131 | } 132 | 133 | @Override 134 | public String toString() { 135 | if (mPrefab != null) { 136 | return "[Drawable2d: " + mPrefab + "]"; 137 | } else { 138 | return "[Drawable2d: ...]"; 139 | } 140 | } 141 | 142 | /** 143 | * Enum values for constructor. 144 | */ 145 | public enum Prefab { 146 | TRIANGLE, RECTANGLE, FULL_RECTANGLE 147 | } 148 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mcxiaoke/media/av/EglStateSaver.java: -------------------------------------------------------------------------------- 1 | package com.mcxiaoke.media.av; 2 | 3 | import android.opengl.EGL14; 4 | import android.opengl.EGLContext; 5 | import android.opengl.EGLDisplay; 6 | import android.opengl.EGLSurface; 7 | import android.util.Log; 8 | 9 | 10 | /** 11 | * @hide 12 | */ 13 | public class EglStateSaver { 14 | private static final String TAG = "EglStateSaver"; 15 | private static final boolean DEBUG = true; 16 | 17 | private EGLContext mSavedContext = EGL14.EGL_NO_CONTEXT; 18 | private EGLSurface mSavedReadSurface = EGL14.EGL_NO_SURFACE; 19 | private EGLSurface mSavedDrawSurface = EGL14.EGL_NO_SURFACE; 20 | private EGLDisplay mSavedDisplay = EGL14.EGL_NO_DISPLAY; 21 | 22 | public void saveEGLState() { 23 | mSavedContext = EGL14.eglGetCurrentContext(); 24 | if (DEBUG && mSavedContext.equals(EGL14.EGL_NO_CONTEXT)) Log.e(TAG, "Saved EGL_NO_CONTEXT"); 25 | mSavedReadSurface = EGL14.eglGetCurrentSurface(EGL14.EGL_READ); 26 | if (DEBUG && mSavedReadSurface.equals(EGL14.EGL_NO_CONTEXT)) Log.e(TAG, "Saved EGL_NO_SURFACE"); 27 | mSavedDrawSurface = EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW); 28 | if (DEBUG && mSavedDrawSurface.equals(EGL14.EGL_NO_CONTEXT)) Log.e(TAG, "Saved EGL_NO_SURFACE"); 29 | mSavedDisplay = EGL14.eglGetCurrentDisplay(); 30 | if (DEBUG && mSavedDisplay.equals(EGL14.EGL_NO_DISPLAY)) Log.e(TAG, "Saved EGL_NO_DISPLAY"); 31 | 32 | } 33 | 34 | public EGLContext getSavedEGLContext() { 35 | return mSavedContext; 36 | } 37 | 38 | public void makeSavedStateCurrent() { 39 | EGL14.eglMakeCurrent(mSavedDisplay, mSavedReadSurface, mSavedDrawSurface, mSavedContext); 40 | } 41 | 42 | public void makeNothingCurrent() { 43 | EGL14.eglMakeCurrent(mSavedDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT); 44 | } 45 | 46 | public void logState() { 47 | if (!mSavedContext.equals(EGL14.eglGetCurrentContext())) 48 | Log.i(TAG, "Saved context DOES NOT equal current."); 49 | else 50 | Log.i(TAG, "Saved context DOES equal current."); 51 | 52 | if (!mSavedReadSurface.equals(EGL14.eglGetCurrentSurface(EGL14.EGL_READ))) { 53 | if (mSavedReadSurface.equals(EGL14.EGL_NO_SURFACE)) 54 | Log.i(TAG, "Saved read surface is EGL_NO_SURFACE"); 55 | else 56 | Log.i(TAG, "Saved read surface DOES NOT equal current."); 57 | } else 58 | Log.i(TAG, "Saved read surface DOES equal current."); 59 | 60 | if (!mSavedDrawSurface.equals(EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW))) { 61 | if (mSavedDrawSurface.equals(EGL14.EGL_NO_SURFACE)) 62 | Log.i(TAG, "Saved draw surface is EGL_NO_SURFACE"); 63 | else 64 | Log.i(TAG, "Saved draw surface DOES NOT equal current."); 65 | } else 66 | Log.i(TAG, "Saved draw surface DOES equal current."); 67 | 68 | if (!mSavedDisplay.equals(EGL14.eglGetCurrentDisplay())) 69 | Log.i(TAG, "Saved display DOES NOT equal current."); 70 | else 71 | Log.i(TAG, "Saved display DOES equal current."); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/com/mcxiaoke/media/av/Filters.java: -------------------------------------------------------------------------------- 1 | package com.mcxiaoke.media.av; 2 | 3 | import android.util.Log; 4 | import com.mcxiaoke.media.ocodec.CodecUtils; 5 | 6 | /** 7 | * This class matches descriptive final int 8 | * variables to Texture2dProgram.ProgramType 9 | * 10 | * @hide 11 | */ 12 | public class Filters { 13 | // Camera filters; must match up with camera_filter_names in strings.xml 14 | static final int FILTER_NONE = 0; 15 | static final int FILTER_BLACK_WHITE = 1; 16 | static final int FILTER_NIGHT = 2; 17 | static final int FILTER_CHROMA_KEY = 3; 18 | static final int FILTER_BLUR = 4; 19 | static final int FILTER_SHARPEN = 5; 20 | static final int FILTER_EDGE_DETECT = 6; 21 | static final int FILTER_EMBOSS = 7; 22 | static final int FILTER_SQUEEZE = 8; 23 | static final int FILTER_TWIRL = 9; 24 | static final int FILTER_TUNNEL = 10; 25 | static final int FILTER_BULGE = 11; 26 | static final int FILTER_DENT = 12; 27 | static final int FILTER_FISHEYE = 13; 28 | static final int FILTER_STRETCH = 14; 29 | static final int FILTER_MIRROR = 15; 30 | private static final String TAG = "Filters"; 31 | private static final boolean VERBOSE = false; 32 | 33 | /** 34 | * Ensure a filter int code is valid. Update this function as 35 | * more filters are defined 36 | * 37 | * @param filter 38 | */ 39 | public static void checkFilterArgument(int filter) { 40 | CodecUtils.checkArgument(filter >= 0 && filter <= 15); 41 | } 42 | 43 | /** 44 | * Updates the filter on the provided FullFrameRect 45 | * 46 | * @return the int code of the new filter 47 | */ 48 | public static void updateFilter(FullFrameRect rect, int newFilter) { 49 | Texture2dProgram.ProgramType programType; 50 | float[] kernel = null; 51 | float colorAdj = 0.0f; 52 | 53 | if (VERBOSE) Log.d(TAG, "Updating filter to " + newFilter); 54 | switch (newFilter) { 55 | case FILTER_NONE: 56 | programType = Texture2dProgram.ProgramType.TEXTURE_EXT; 57 | break; 58 | case FILTER_BLACK_WHITE: 59 | // (In a previous version the TEXTURE_EXT_BW variant was enabled by a flag called 60 | // ROSE_COLORED_GLASSES, because the shader set the red channel to the B&W color 61 | // and green/blue to zero.) 62 | programType = Texture2dProgram.ProgramType.TEXTURE_EXT_BW; 63 | break; 64 | case FILTER_NIGHT: 65 | programType = Texture2dProgram.ProgramType.TEXTURE_EXT_NIGHT; 66 | break; 67 | case FILTER_CHROMA_KEY: 68 | programType = Texture2dProgram.ProgramType.TEXTURE_EXT_CHROMA_KEY; 69 | break; 70 | case FILTER_SQUEEZE: 71 | programType = Texture2dProgram.ProgramType.TEXTURE_EXT_SQUEEZE; 72 | break; 73 | case FILTER_TWIRL: 74 | programType = Texture2dProgram.ProgramType.TEXTURE_EXT_TWIRL; 75 | break; 76 | case FILTER_TUNNEL: 77 | programType = Texture2dProgram.ProgramType.TEXTURE_EXT_TUNNEL; 78 | break; 79 | case FILTER_BULGE: 80 | programType = Texture2dProgram.ProgramType.TEXTURE_EXT_BULGE; 81 | break; 82 | case FILTER_DENT: 83 | programType = Texture2dProgram.ProgramType.TEXTURE_EXT_DENT; 84 | break; 85 | case FILTER_FISHEYE: 86 | programType = Texture2dProgram.ProgramType.TEXTURE_EXT_FISHEYE; 87 | break; 88 | case FILTER_STRETCH: 89 | programType = Texture2dProgram.ProgramType.TEXTURE_EXT_STRETCH; 90 | break; 91 | case FILTER_MIRROR: 92 | programType = Texture2dProgram.ProgramType.TEXTURE_EXT_MIRROR; 93 | break; 94 | case FILTER_BLUR: 95 | programType = Texture2dProgram.ProgramType.TEXTURE_EXT_FILT; 96 | kernel = new float[]{ 97 | 1f / 16f, 2f / 16f, 1f / 16f, 98 | 2f / 16f, 4f / 16f, 2f / 16f, 99 | 1f / 16f, 2f / 16f, 1f / 16f}; 100 | break; 101 | case FILTER_SHARPEN: 102 | programType = Texture2dProgram.ProgramType.TEXTURE_EXT_FILT; 103 | kernel = new float[]{ 104 | 0f, -1f, 0f, 105 | -1f, 5f, -1f, 106 | 0f, -1f, 0f}; 107 | break; 108 | case FILTER_EDGE_DETECT: 109 | programType = Texture2dProgram.ProgramType.TEXTURE_EXT_FILT; 110 | kernel = new float[]{ 111 | -1f, -1f, -1f, 112 | -1f, 8f, -1f, 113 | -1f, -1f, -1f}; 114 | break; 115 | case FILTER_EMBOSS: 116 | programType = Texture2dProgram.ProgramType.TEXTURE_EXT_FILT; 117 | kernel = new float[]{ 118 | 2f, 0f, 0f, 119 | 0f, -1f, 0f, 120 | 0f, 0f, -1f}; 121 | colorAdj = 0.5f; 122 | break; 123 | default: 124 | throw new RuntimeException("Unknown filter mode " + newFilter); 125 | } 126 | 127 | // Do we need a whole new program? (We want to avoid doing this if we don't have 128 | // too -- compiling a program could be expensive.) 129 | if (programType != rect.getProgram().getProgramType()) { 130 | rect.changeProgram(new Texture2dProgram(programType)); 131 | } 132 | 133 | // Update the filter kernel (if any). 134 | if (kernel != null) { 135 | rect.getProgram().setKernel(kernel, colorAdj); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /app/src/main/java/com/mcxiaoke/media/av/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.mcxiaoke.media.av; 18 | 19 | import android.opengl.Matrix; 20 | import android.view.MotionEvent; 21 | 22 | import java.nio.FloatBuffer; 23 | 24 | /** 25 | * This class essentially represents a viewport-sized sprite that will be rendered with 26 | * a texture, usually from an external source like the camera or video decoder. 27 | * 28 | * @hide 29 | */ 30 | public class FullFrameRect { 31 | private static final int SIZEOF_FLOAT = 4; 32 | private static final float TEX_COORDS[] = { 33 | 0.0f, 0.0f, // 0 bottom left 34 | 1.0f, 0.0f, // 1 bottom right 35 | 0.0f, 1.0f, // 2 top left 36 | 1.0f, 1.0f // 3 top right 37 | }; 38 | private static final FloatBuffer TEX_COORDS_BUF = GlUtil.createFloatBuffer(TEX_COORDS); 39 | private static final int TEX_COORDS_STRIDE = 2 * SIZEOF_FLOAT; 40 | private final Drawable2d mRectDrawable = new Drawable2d(Drawable2d.Prefab.FULL_RECTANGLE); 41 | private final Object mDrawLock = new Object(); 42 | private Texture2dProgram mProgram; 43 | private float[] IDENTITY_MATRIX = new float[16]; 44 | private boolean mCorrectVerticalVideo = false; 45 | private boolean mScaleToFit; 46 | private SCREEN_ROTATION requestedOrientation = SCREEN_ROTATION.LANDSCAPE; 47 | /** 48 | * Prepares the object. 49 | * 50 | * @param program The program to use. FullFrameRect takes ownership, and will release 51 | * the program when no longer needed. 52 | */ 53 | public FullFrameRect(Texture2dProgram program) { 54 | mProgram = program; 55 | Matrix.setIdentityM(IDENTITY_MATRIX, 0); 56 | } 57 | 58 | /** 59 | * Adjust the MVP Matrix to rotate and crop the texture 60 | * to make vertical video appear upright 61 | */ 62 | public void adjustForVerticalVideo(SCREEN_ROTATION orientation, boolean scaleToFit) { 63 | synchronized (mDrawLock) { 64 | mCorrectVerticalVideo = true; 65 | mScaleToFit = scaleToFit; 66 | requestedOrientation = orientation; 67 | Matrix.setIdentityM(IDENTITY_MATRIX, 0); 68 | switch (orientation) { 69 | case VERTICAL: 70 | if (scaleToFit) { 71 | Matrix.rotateM(IDENTITY_MATRIX, 0, -90, 0f, 0f, 1f); 72 | Matrix.scaleM(IDENTITY_MATRIX, 0, 3.16f, 1.0f, 1f); 73 | } else { 74 | Matrix.scaleM(IDENTITY_MATRIX, 0, 0.316f, 1f, 1f); 75 | } 76 | break; 77 | case UPSIDEDOWN_LANDSCAPE: 78 | if (scaleToFit) { 79 | Matrix.rotateM(IDENTITY_MATRIX, 0, -180, 0f, 0f, 1f); 80 | } 81 | break; 82 | case UPSIDEDOWN_VERTICAL: 83 | if (scaleToFit) { 84 | Matrix.rotateM(IDENTITY_MATRIX, 0, 90, 0f, 0f, 1f); 85 | Matrix.scaleM(IDENTITY_MATRIX, 0, 3.16f, 1.0f, 1f); 86 | } else { 87 | Matrix.scaleM(IDENTITY_MATRIX, 0, 0.316f, 1f, 1f); 88 | } 89 | break; 90 | } 91 | } 92 | } 93 | 94 | /** 95 | * Releases resources. 96 | */ 97 | public void release() { 98 | if (mProgram != null) { 99 | mProgram.release(); 100 | mProgram = null; 101 | } 102 | } 103 | 104 | /** 105 | * Returns the program currently in use. 106 | */ 107 | public Texture2dProgram getProgram() { 108 | return mProgram; 109 | } 110 | 111 | /** 112 | * Changes the program. The previous program will be released. 113 | */ 114 | public void changeProgram(Texture2dProgram program) { 115 | mProgram.release(); 116 | mProgram = program; 117 | } 118 | 119 | /** 120 | * Creates a texture object suitable for use with drawFrame(). 121 | */ 122 | public int createTextureObject() { 123 | return mProgram.createTextureObject(); 124 | } 125 | 126 | /** 127 | * Draws a viewport-filling rect, texturing it with the specified texture object. 128 | */ 129 | public void drawFrame(int textureId, float[] texMatrix) { 130 | // Use the identity matrix for MVP so our 2x2 FULL_RECTANGLE covers the viewport. 131 | synchronized (mDrawLock) { 132 | if (mCorrectVerticalVideo && !mScaleToFit && (requestedOrientation == SCREEN_ROTATION.VERTICAL || requestedOrientation == SCREEN_ROTATION.UPSIDEDOWN_VERTICAL)) { 133 | Matrix.scaleM(texMatrix, 0, 0.316f, 1.0f, 1f); 134 | } 135 | mProgram.draw(IDENTITY_MATRIX, mRectDrawable.getVertexArray(), 0, 136 | mRectDrawable.getVertexCount(), mRectDrawable.getCoordsPerVertex(), 137 | mRectDrawable.getVertexStride(), 138 | texMatrix, TEX_COORDS_BUF, textureId, TEX_COORDS_STRIDE); 139 | } 140 | } 141 | 142 | /** 143 | * Pass touch event down to the 144 | * texture's shader program 145 | * 146 | * @param ev 147 | */ 148 | public void handleTouchEvent(MotionEvent ev) { 149 | mProgram.handleTouchEvent(ev); 150 | } 151 | 152 | public static enum SCREEN_ROTATION {LANDSCAPE, VERTICAL, UPSIDEDOWN_LANDSCAPE, UPSIDEDOWN_VERTICAL} 153 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mcxiaoke/media/av/Muxer.java: -------------------------------------------------------------------------------- 1 | package com.mcxiaoke.media.av; 2 | 3 | import android.media.MediaCodec; 4 | import android.media.MediaFormat; 5 | import android.os.Build; 6 | import android.util.Log; 7 | import io.kickflip.sdk.Util; 8 | 9 | import java.nio.ByteBuffer; 10 | 11 | /** 12 | * Base Muxer class for interaction with MediaCodec based 13 | * encoders 14 | * 15 | * @hide 16 | */ 17 | public abstract class Muxer { 18 | private static final String TAG = "Muxer"; 19 | private final int mExpectedNumTracks = 2; // TODO: Make this configurable? 20 | protected FORMAT mFormat; 21 | protected String mOutputPath; 22 | protected int mNumTracks; 23 | protected int mNumTracksFinished; 24 | protected long mFirstPts; 25 | protected long mLastPts[]; 26 | protected Muxer(String outputPath, FORMAT format) { 27 | Log.i(TAG, "Created muxer for output: " + outputPath); 28 | mOutputPath = Util.checkNotNull(outputPath); 29 | mFormat = format; 30 | mNumTracks = 0; 31 | mNumTracksFinished = 0; 32 | mFirstPts = 0; 33 | mLastPts = new long[mExpectedNumTracks]; 34 | for (int i = 0; i < mLastPts.length; i++) { 35 | mLastPts[i] = 0; 36 | } 37 | } 38 | 39 | /** 40 | * Returns the absolute output path. 41 | * 42 | * e.g /sdcard/app/uuid/index.m3u8 43 | * 44 | * @return 45 | */ 46 | public String getOutputPath() { 47 | return mOutputPath; 48 | } 49 | 50 | /** 51 | * Adds the specified track and returns the track index 52 | * 53 | * @param trackFormat MediaFormat of the track to add. Gotten from MediaCodec#dequeueOutputBuffer 54 | * when returned status is INFO_OUTPUT_FORMAT_CHANGED 55 | * @return index of track in output file 56 | */ 57 | public int addTrack(MediaFormat trackFormat) { 58 | mNumTracks++; 59 | return mNumTracks - 1; 60 | } 61 | 62 | /** 63 | * Called by the hosting Encoder 64 | * to notify the Muxer that it should no 65 | * longer assume the Encoder resources are available. 66 | */ 67 | public void onEncoderReleased(int trackIndex) { 68 | } 69 | 70 | public void release() { 71 | // finished event 72 | } 73 | 74 | public boolean isStarted() { 75 | return false; 76 | } 77 | 78 | /** 79 | * Write the MediaCodec output buffer. This method must 80 | * be overridden by subclasses to release encodedData, transferring 81 | * ownership back to encoder, by calling encoder.releaseOutputBuffer(bufferIndex, false); 82 | * 83 | * @param trackIndex 84 | * @param encodedData 85 | * @param bufferInfo 86 | */ 87 | public void writeSampleData(MediaCodec encoder, int trackIndex, int bufferIndex, ByteBuffer encodedData, MediaCodec.BufferInfo bufferInfo) { 88 | if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 89 | signalEndOfTrack(); 90 | } 91 | } 92 | 93 | public abstract void forceStop(); 94 | 95 | protected boolean allTracksFinished() { 96 | return (mNumTracks == mNumTracksFinished); 97 | } 98 | 99 | protected boolean allTracksAdded() { 100 | return (mNumTracks == mExpectedNumTracks); 101 | } 102 | 103 | /** 104 | * Muxer will call this itself if it detects BUFFER_FLAG_END_OF_STREAM 105 | * in writeSampleData. 106 | */ 107 | protected void signalEndOfTrack() { 108 | mNumTracksFinished++; 109 | } 110 | 111 | /** 112 | * Does this Muxer's format require AAC ADTS headers? 113 | * see http://wiki.multimedia.cx/index.php?title=ADTS 114 | * 115 | * @return 116 | */ 117 | protected boolean formatRequiresADTS() { 118 | switch (mFormat) { 119 | case HLS: 120 | return true; 121 | default: 122 | return false; 123 | } 124 | } 125 | 126 | /** 127 | * Does this Muxer's format require 128 | * copying and buffering encoder output buffers. 129 | * Generally speaking, is the output a Socket or File? 130 | * 131 | * @return 132 | */ 133 | protected boolean formatRequiresBuffering() { 134 | if (Build.VERSION.SDK_INT >= 21) return true; 135 | 136 | switch (mFormat) { 137 | case HLS: 138 | return false; 139 | default: 140 | return false; 141 | } 142 | } 143 | 144 | /** 145 | * Return a relative pts given an absolute pts and trackIndex. 146 | * 147 | * This method advances the state of the Muxer, and must only 148 | * be called once per call to {@link #writeSampleData(MediaCodec, int, int, ByteBuffer, MediaCodec.BufferInfo)}. 149 | */ 150 | protected long getNextRelativePts(long absPts, int trackIndex) { 151 | if (mFirstPts == 0) { 152 | mFirstPts = absPts; 153 | return 0; 154 | } 155 | return getSafePts(absPts - mFirstPts, trackIndex); 156 | } 157 | 158 | /** 159 | * Sometimes packets with non-increasing pts are dequeued from the MediaCodec output buffer. 160 | * This method ensures that a crash won't occur due to non monotonically increasing packet timestamp. 161 | */ 162 | private long getSafePts(long pts, int trackIndex) { 163 | if (mLastPts[trackIndex] >= pts) { 164 | // Enforce a non-zero minimum spacing 165 | // between pts 166 | mLastPts[trackIndex] += 9643; 167 | return mLastPts[trackIndex]; 168 | } 169 | mLastPts[trackIndex] = pts; 170 | return pts; 171 | } 172 | 173 | public static enum FORMAT {MPEG4, HLS} 174 | } 175 | -------------------------------------------------------------------------------- /app/src/main/java/com/mcxiaoke/media/av/SizeableFrameRect.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.mcxiaoke.media.av; 18 | 19 | import android.opengl.Matrix; 20 | import android.view.MotionEvent; 21 | 22 | import java.nio.FloatBuffer; 23 | 24 | /** 25 | * This class essentially represents a sizezble sprite that will be rendered with 26 | * a texture, usually from an external source like the camera or video decoder. 27 | * 28 | * Placeholder. Not yet implemented. 29 | * 30 | * @hide 31 | */ 32 | public class SizeableFrameRect { 33 | private static final int SIZEOF_FLOAT = 4; 34 | private static final float[] IDENTITY_MATRIX = new float[16]; 35 | private static final int TEX_COORDS_STRIDE = 2 * SIZEOF_FLOAT; 36 | private static float TEX_COORDS[] = { 37 | 0.0f, 0.0f, // 0 bottom left 38 | 1.0f, 0.0f, // 1 bottom right 39 | 0.0f, 1.0f, // 2 top left 40 | 1.0f, 1.0f // 3 top right 41 | }; 42 | private static final FloatBuffer TEX_COORDS_BUF = GlUtil.createFloatBuffer(TEX_COORDS); 43 | private final Drawable2d mRectDrawable = new Drawable2d(Drawable2d.Prefab.RECTANGLE); 44 | private Texture2dProgram mProgram; 45 | 46 | 47 | /** 48 | * Prepares the object. 49 | * 50 | * @param program The program to use. FullFrameRect takes ownership, and will release 51 | * the program when no longer needed. 52 | */ 53 | public SizeableFrameRect(Texture2dProgram program, float[] texCoords) { 54 | mProgram = program; 55 | TEX_COORDS = texCoords; 56 | 57 | Matrix.setIdentityM(IDENTITY_MATRIX, 0); 58 | } 59 | 60 | /** 61 | * Releases resources. 62 | */ 63 | public void release() { 64 | if (mProgram != null) { 65 | mProgram.release(); 66 | mProgram = null; 67 | } 68 | } 69 | 70 | /** 71 | * Returns the program currently in use. 72 | */ 73 | public Texture2dProgram getProgram() { 74 | return mProgram; 75 | } 76 | 77 | /** 78 | * Changes the program. The previous program will be released. 79 | */ 80 | public void changeProgram(Texture2dProgram program) { 81 | mProgram.release(); 82 | mProgram = program; 83 | } 84 | 85 | /** 86 | * Creates a texture object suitable for use with drawFrame(). 87 | */ 88 | public int createTextureObject() { 89 | return mProgram.createTextureObject(); 90 | } 91 | 92 | /** 93 | * Draws a rectangle in an area defined by TEX_COORDS 94 | */ 95 | public void drawFrame(int textureId, float[] texMatrix) { 96 | // Use the identity matrix for MVP so our 2x2 FULL_RECTANGLE covers the viewport. 97 | mProgram.draw(IDENTITY_MATRIX, mRectDrawable.getVertexArray(), 0, 98 | mRectDrawable.getVertexCount(), mRectDrawable.getCoordsPerVertex(), 99 | mRectDrawable.getVertexStride(), 100 | texMatrix, TEX_COORDS_BUF, textureId, TEX_COORDS_STRIDE); 101 | } 102 | 103 | /** 104 | * Pass touch event down to the 105 | * texture's shader program 106 | * 107 | * @param ev 108 | */ 109 | public void handleTouchEvent(MotionEvent ev) { 110 | mProgram.handleTouchEvent(ev); 111 | } 112 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mcxiaoke/media/av/VideoEncoderConfig.java: -------------------------------------------------------------------------------- 1 | package com.mcxiaoke.media.av; 2 | 3 | /** 4 | * @hide 5 | */ 6 | public class VideoEncoderConfig { 7 | protected final int mWidth; 8 | protected final int mHeight; 9 | protected final int mBitRate; 10 | 11 | public VideoEncoderConfig(int width, int height, int bitRate) { 12 | mWidth = width; 13 | mHeight = height; 14 | mBitRate = bitRate; 15 | } 16 | 17 | public int getWidth() { 18 | return mWidth; 19 | } 20 | 21 | public int getHeight() { 22 | return mHeight; 23 | } 24 | 25 | public int getBitRate() { 26 | return mBitRate; 27 | } 28 | 29 | @Override 30 | public String toString() { 31 | return "VideoEncoderConfig: " + mWidth + "x" + mHeight + " @" + mBitRate + " bps"; 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mcxiaoke/media/av/VideoEncoderCore.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.mcxiaoke.media.av; 18 | 19 | import android.media.MediaCodec; 20 | import android.media.MediaCodecInfo; 21 | import android.media.MediaFormat; 22 | import android.util.Log; 23 | import android.view.Surface; 24 | 25 | import java.io.IOException; 26 | 27 | /** 28 | * This class wraps up the core components used for surface-input video encoding. 29 | *
30 | * Once created, frames are fed to the input surface. Remember to provide the presentation 31 | * time stamp, and always call drainEncoder() before swapBuffers() to ensure that the 32 | * producer side doesn't get backed up. 33 | * 34 | * This class is not thread-safe, with one exception: it is valid to use the input surface 35 | * on one thread, and drain the output on a different thread. 36 | */ 37 | public class VideoEncoderCore extends AndroidEncoder { 38 | private static final String TAG = "VideoEncoderCore"; 39 | private static final boolean VERBOSE = false; 40 | 41 | // TODO: these ought to be configurable as well 42 | private static final String MIME_TYPE = "video/avc"; // H.264 Advanced Video Coding 43 | private static final int FRAME_RATE = 30; // 30fps 44 | private static final int IFRAME_INTERVAL = 3; // 5 seconds between I-frames 45 | 46 | private Surface mInputSurface; 47 | 48 | 49 | /** 50 | * Configures encoder and muxer state, and prepares the input Surface. 51 | */ 52 | public VideoEncoderCore(int width, int height, int bitRate, Muxer muxer) throws IOException { 53 | mMuxer = muxer; 54 | mBufferInfo = new MediaCodec.BufferInfo(); 55 | 56 | MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, width, height); 57 | 58 | // Set some properties. Failing to specify some of these can cause the MediaCodec 59 | // configure() call to throw an unhelpful exception. 60 | format.setInteger(MediaFormat.KEY_COLOR_FORMAT, 61 | MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); 62 | format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate); 63 | format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE); 64 | format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL); 65 | if (VERBOSE) Log.d(TAG, "format: " + format); 66 | 67 | // Create a MediaCodec encoder, and configure it with our format. Get a Surface 68 | // we can use for input and wrap it with a class that handles the EGL work. 69 | mEncoder = MediaCodec.createEncoderByType(MIME_TYPE); 70 | mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 71 | mInputSurface = mEncoder.createInputSurface(); 72 | mEncoder.start(); 73 | 74 | mTrackIndex = -1; 75 | } 76 | 77 | /** 78 | * Returns the encoder's input surface. 79 | */ 80 | public Surface getInputSurface() { 81 | return mInputSurface; 82 | } 83 | 84 | @Override 85 | protected boolean isSurfaceInputEncoder() { 86 | return true; 87 | } 88 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mcxiaoke/media/av/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.mcxiaoke.media.av; 18 | 19 | import android.graphics.SurfaceTexture; 20 | import android.view.Surface; 21 | 22 | /** 23 | * Recordable EGL window surface. 24 | *25 | * It's good practice to explicitly release() the surface, preferably from a "finally" block. 26 | * This object owns the Surface; releasing this object will release the Surface as well. 27 | * 28 | * @hide 29 | */ 30 | public class WindowSurface extends EglSurfaceBase { 31 | private Surface mSurface; 32 | 33 | /** 34 | * Associates an EGL surface with the native window surface. The Surface will be 35 | * owned by WindowSurface, and released when release() is called. 36 | */ 37 | public WindowSurface(EglCore eglCore, Surface surface) { 38 | super(eglCore); 39 | createWindowSurface(surface); 40 | mSurface = surface; 41 | } 42 | 43 | /** 44 | * Associates an EGL surface with the SurfaceTexture. 45 | */ 46 | public WindowSurface(EglCore eglCore, SurfaceTexture surfaceTexture) { 47 | super(eglCore); 48 | createWindowSurface(surfaceTexture); 49 | } 50 | 51 | /** 52 | * Releases any resources associated with the Surface and the EGL surface. 53 | */ 54 | public void release() { 55 | releaseEglSurface(); 56 | if (mSurface != null) { 57 | mSurface.release(); 58 | mSurface = null; 59 | } 60 | } 61 | 62 | /** 63 | * Recreate the EGLSurface, using the new EglBase. The caller should have already 64 | * freed the old EGLSurface with releaseEglSurface(). 65 | *
66 | * This is useful when we want to update the EGLSurface associated with a Surface. 67 | * For example, if we want to share with a different EGLContext, which can only 68 | * be done by tearing down and recreating the context. (That's handled by the caller; 69 | * this just creates a new EGLSurface for the Surface we were handed earlier.) 70 | *
71 | * If the previous EGLSurface isn't fully destroyed, e.g. it's still current on a
72 | * context somewhere, the create call will fail with complaints from the Surface
73 | * about already being connected.
74 | */
75 | public void recreate(EglCore newEglCore) {
76 | if (mSurface == null) {
77 | throw new RuntimeException("not yet implemented for SurfaceTexture");
78 | }
79 | mEglCore = newEglCore; // switch to new context
80 | createWindowSurface(mSurface); // create new surface
81 | }
82 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mcxiaoke/media/lib/CaptureConfig.java:
--------------------------------------------------------------------------------
1 | package com.mcxiaoke.media.lib;
2 |
3 | import android.media.MediaRecorder.AudioEncoder;
4 | import android.media.MediaRecorder.AudioSource;
5 | import android.media.MediaRecorder.OutputFormat;
6 | import android.media.MediaRecorder.VideoEncoder;
7 | import android.media.MediaRecorder.VideoSource;
8 |
9 | import java.io.File;
10 |
11 | /**
12 | * User: mcxiaoke
13 | * Date: 2017/7/3
14 | * Time: 16:17
15 | */
16 |
17 | public class CaptureConfig {
18 | public static final int FORMAT_MP4 = OutputFormat.MPEG_4;
19 | public static final int ENC_H264 = VideoEncoder.H264;
20 | public static final int ENC_AAC = AudioEncoder.AAC;
21 |
22 | public static final int VIDEO_IN = VideoSource.CAMERA;
23 | public static final int AUDIO_IN = AudioSource.CAMCORDER;
24 |
25 | public static final int V480P_W = 640;
26 | public static final int V480P_H = 480;
27 |
28 | // 480p 640*480
29 | public int videoWidth = V480P_W;
30 | public int videoHeight = V480P_H;
31 | public int videoFrameRate = 24;
32 | public int videoBitRate = videoWidth * videoHeight * 3;
33 |
34 | public int audioSamplingRate = 44100;
35 | public int audioChannels = 2;
36 | public int audioBitRate = 64 * 1024;
37 |
38 | public File outputFile;
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mcxiaoke/media/lib/CaptureFragment.java:
--------------------------------------------------------------------------------
1 | package com.mcxiaoke.media.lib;
2 |
3 | /**
4 | * User: mcxiaoke
5 | * Date: 2017/7/3
6 | * Time: 16:17
7 | */
8 |
9 | public class CaptureFragment {
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mcxiaoke/media/lib/CaptureHelper.java:
--------------------------------------------------------------------------------
1 | package com.mcxiaoke.media.lib;
2 |
3 | /**
4 | * User: mcxiaoke
5 | * Date: 2017/7/3
6 | * Time: 16:17
7 | */
8 |
9 | public class CaptureHelper {
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mcxiaoke/media/ocodec/CodecUtils.java:
--------------------------------------------------------------------------------
1 | package com.mcxiaoke.media.ocodec;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.content.res.Configuration;
6 | import android.hardware.Camera;
7 | import android.hardware.Camera.Size;
8 | import android.util.Log;
9 | import android.view.Surface;
10 | import com.mcxiaoke.media.Const;
11 |
12 | import java.util.Arrays;
13 |
14 | /**
15 | * User: mcxiaoke
16 | * Date: 2017/6/29
17 | * Time: 13:35
18 | */
19 |
20 | public class CodecUtils {
21 |
22 | public static int getDisplayOrientation(Activity activity,
23 | int cameraId) {
24 | android.hardware.Camera.CameraInfo info =
25 | new android.hardware.Camera.CameraInfo();
26 | android.hardware.Camera.getCameraInfo(cameraId, info);
27 | int orientation = activity.getResources().getConfiguration().orientation;
28 | int rotation = activity.getWindowManager().getDefaultDisplay()
29 | .getRotation();
30 | Log.v(Const.TAG, "getDisplayOrientation() orientation=" + orientation);
31 | Log.v(Const.TAG, "getDisplayOrientation() rotation=" + rotation);
32 | int degrees = 0;
33 | switch (rotation) {
34 | case Surface.ROTATION_0:
35 | degrees = 0;
36 | break;
37 | case Surface.ROTATION_90:
38 | degrees = 90;
39 | break;
40 | case Surface.ROTATION_180:
41 | degrees = 180;
42 | break;
43 | case Surface.ROTATION_270:
44 | degrees = 270;
45 | break;
46 | }
47 |
48 | int result;
49 | if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
50 | result = (info.orientation + degrees) % 360;
51 | result = (360 - result) % 360; // compensate the mirror
52 | } else { // back-facing
53 | result = (info.orientation - degrees + 360) % 360;
54 | }
55 | Log.d(Const.TAG, "getDisplayOrientation() = " + result);
56 | return result;
57 | }
58 |
59 | public static int getOrientation(Context context) {
60 | final int orientation = context.getResources().getConfiguration().orientation;
61 | if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
62 | return 180;
63 | } else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
64 | return 90;
65 | }
66 | return 0;
67 | }
68 |
69 | public static void fixOrientation(Context context, Camera camera) {
70 | final int orientation = context.getResources().getConfiguration().orientation;
71 | if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
72 | camera.setDisplayOrientation(180);
73 | } else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
74 | camera.setDisplayOrientation(90);
75 | }
76 | }
77 |
78 | public static void showInfo(Context context, Camera camera) {
79 | int don = context.getResources().getConfiguration().orientation;
80 | Log.v(Const.TAG, "Device Orientation = " + don);
81 | for (int i = 0; i < Camera.getNumberOfCameras(); i++) {
82 | Camera.CameraInfo info = new Camera.CameraInfo();
83 | Camera.getCameraInfo(i, info);
84 | if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
85 | Log.v(Const.TAG, i + "Camera Orientation=" + info.orientation);
86 | }
87 | }
88 | Camera.Parameters ps = camera.getParameters();
89 | for (Size s : ps.getSupportedPictureSizes()) {
90 | // Log.v(Const.TAG, "PictureSize: " + s.width + "x" + s.height);
91 | }
92 | Log.v(Const.TAG, "PictureFormats=" + ps.getSupportedPictureFormats());
93 | Log.v(Const.TAG, "PreviewFormats=" + ps.getSupportedPreviewFormats());
94 | for (int[] fs : ps.getSupportedPreviewFpsRange()) {
95 | Log.v(Const.TAG, "PreviewFpsRange: " + Arrays.toString(fs));
96 | }
97 | for (Size s : ps.getSupportedPreviewSizes()) {
98 | Log.v(Const.TAG, "PreviewSize: " + s.width + "x" + s.height);
99 | }
100 | for (Size s : ps.getSupportedVideoSizes()) {
101 | Log.v(Const.TAG, "VideoSize: " + s.width + "x" + s.height);
102 | }
103 | }
104 |
105 | public static void checkArgument(final boolean b) {
106 | // TODO: 2017/7/3
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mcxiaoke/media/ocodec/Config.java:
--------------------------------------------------------------------------------
1 | package com.mcxiaoke.media.ocodec;
2 |
3 | import android.media.AudioFormat;
4 | import android.media.MediaRecorder.AudioEncoder;
5 | import android.media.MediaRecorder.AudioSource;
6 | import android.media.MediaRecorder.OutputFormat;
7 | import android.media.MediaRecorder.VideoEncoder;
8 | import android.media.MediaRecorder.VideoSource;
9 |
10 | import java.io.File;
11 |
12 | /**
13 | * User: mcxiaoke
14 | * Date: 2017/7/3
15 | * Time: 16:17
16 | */
17 |
18 | public class Config {
19 | public static final String CODEC_VIDEO_H264 = "video/avc";
20 | public static final String CODEC_AUDIO_AAC = "audio/aac";
21 | public static final int IFRAME_INTERVAL = 5;
22 | public static final String FILE_MP4 = ".mp4";
23 | public static final int FORMAT_MP4 = OutputFormat.MPEG_4;
24 | public static final int ENC_H264 = VideoEncoder.H264;
25 | public static final int ENC_AAC = AudioEncoder.AAC;
26 |
27 | public static final int VIDEO_IN = VideoSource.CAMERA;
28 | public static final int AUDIO_IN = AudioSource.CAMCORDER;
29 |
30 | public static final int V480P_W = 720;
31 | public static final int V480P_H = 480;
32 |
33 | public int outputFormat = FORMAT_MP4;
34 | public File outputFile;
35 |
36 | public int videoEncoder = ENC_H264;
37 | public int audioEncoder = ENC_AAC;
38 | public int videoSource = VIDEO_IN;
39 | public int audioSource = AUDIO_IN;
40 | public String videoType = CODEC_VIDEO_H264;
41 | public String audioType = CODEC_AUDIO_AAC;
42 | // 480p 480*720
43 | public int videoWidth = V480P_W;
44 | public int videoHeight = V480P_H;
45 | public int videoFrameRate = 25;
46 | public int videoBitRate = videoWidth * videoHeight * 3;
47 |
48 | public int audioSamplingRate = 44100;
49 | public int audioChannels = 2;
50 | public int audioBitRate = 64 * 1024;
51 | public int audioChannelMask = AudioFormat.CHANNEL_IN_STEREO;
52 |
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mcxiaoke/media/ocodec/encoder/Recorder.java:
--------------------------------------------------------------------------------
1 | package com.mcxiaoke.media.ocodec.encoder;
2 |
3 | /**
4 | * User: mcxiaoke
5 | * Date: 2017/7/7
6 | * Time: 16:36
7 | */
8 |
9 | public class Recorder {
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mcxiaoke/media/ocodec/encoder/VideoEncoder.java:
--------------------------------------------------------------------------------
1 | package com.mcxiaoke.media.ocodec.encoder;
2 | /*
3 | * AudioVideoRecordingSample
4 | * Sample project to cature audio and video from internal mic/camera and save as MPEG4 file.
5 | *
6 | * Copyright (c) 2014-2015 saki t_saki@serenegiant.com
7 | *
8 | * File name: MediaVideoEncoder.java
9 | *
10 | * Licensed under the Apache License, Version 2.0 (the "License");
11 | * you may not use this file except in compliance with the License.
12 | * You may obtain a copy of the License at
13 | *
14 | * http://www.apache.org/licenses/LICENSE-2.0
15 | *
16 | * Unless required by applicable law or agreed to in writing, software
17 | * distributed under the License is distributed on an "AS IS" BASIS,
18 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 | * See the License for the specific language governing permissions and
20 | * limitations under the License.
21 | *
22 | * All files in the folder are under this Apache License, Version 2.0.
23 | */
24 |
25 | import android.media.MediaCodec;
26 | import android.media.MediaCodecInfo;
27 | import android.media.MediaFormat;
28 | import android.opengl.EGLContext;
29 | import android.util.Log;
30 | import android.view.Surface;
31 | import com.mcxiaoke.media.VRecorder;
32 | import com.mcxiaoke.media.ocodec.Config;
33 | import com.mcxiaoke.media.ocodec.glutils.RenderHandler;
34 |
35 | import java.io.IOException;
36 |
37 | public class VideoEncoder extends Encoder {
38 | private static final String TAG = "MediaVideoEncoder";
39 | private static final float BPP = 0.15f;
40 |
41 | private final int mWidth;
42 | private final int mHeight;
43 | private RenderHandler mRenderHandler;
44 | private Surface mSurface;
45 |
46 | public VideoEncoder(final Config config, final Muxer muxer, final MediaEncoderListener listener) {
47 | super(config, muxer, listener);
48 | if (DEBUG) Log.i(TAG, "MediaVideoEncoder: ");
49 | mWidth = config.videoWidth;
50 | mHeight = config.videoHeight;
51 | mRenderHandler = RenderHandler.createHandler(TAG);
52 | }
53 |
54 | public boolean frameAvailableSoon(final float[] tex_matrix) {
55 | boolean result;
56 | if (result = super.frameAvailableSoon())
57 | mRenderHandler.draw(tex_matrix);
58 | return result;
59 | }
60 |
61 | public boolean frameAvailableSoon(final float[] tex_matrix, final float[] mvp_matrix) {
62 | boolean result;
63 | if (result = super.frameAvailableSoon())
64 | mRenderHandler.draw(tex_matrix, mvp_matrix);
65 | return result;
66 | }
67 |
68 | @Override
69 | public boolean frameAvailableSoon() {
70 | boolean result;
71 | if (result = super.frameAvailableSoon())
72 | mRenderHandler.draw(null);
73 | return result;
74 | }
75 |
76 | @Override
77 | protected void prepare() throws IOException {
78 | if (DEBUG) Log.i(TAG, "prepare: ");
79 | mTrackIndex = -1;
80 | mMuxerStarted = mIsEOS = false;
81 |
82 | final MediaCodecInfo videoCodecInfo = VRecorder.selectCodec(config.videoType);
83 | if (videoCodecInfo == null) {
84 | Log.e(TAG, "Unable to find an appropriate codec for " + config.videoType);
85 | return;
86 | }
87 | if (DEBUG) Log.i(TAG, "selected codec: " + videoCodecInfo.getName());
88 |
89 | final MediaFormat format = MediaFormat.createVideoFormat(config.videoType, mWidth, mHeight);
90 | format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); // API >= 18
91 | format.setInteger(MediaFormat.KEY_BIT_RATE, config.videoBitRate);
92 | format.setInteger(MediaFormat.KEY_FRAME_RATE, config.videoFrameRate);
93 | format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, Config.IFRAME_INTERVAL);
94 | if (DEBUG) Log.i(TAG, "format: " + format);
95 |
96 | mMediaCodec = MediaCodec.createEncoderByType(config.videoType);
97 | mMediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
98 | // get Surface for encoder input
99 | // this method only can call between #configure and #start
100 | mSurface = mMediaCodec.createInputSurface(); // API >= 18
101 | mMediaCodec.start();
102 | if (DEBUG) Log.i(TAG, "prepare finishing");
103 | if (mListener != null) {
104 | try {
105 | mListener.onPrepared(this);
106 | } catch (final Exception e) {
107 | Log.e(TAG, "prepare:", e);
108 | }
109 | }
110 | }
111 |
112 | @Override
113 | protected void release() {
114 | if (DEBUG) Log.i(TAG, "release:");
115 | if (mSurface != null) {
116 | mSurface.release();
117 | mSurface = null;
118 | }
119 | if (mRenderHandler != null) {
120 | mRenderHandler.release();
121 | mRenderHandler = null;
122 | }
123 | super.release();
124 | }
125 |
126 | @Override
127 | protected void signalEndOfInputStream() {
128 | if (DEBUG) Log.d(TAG, "sending EOS to encoder");
129 | mMediaCodec.signalEndOfInputStream(); // API >= 18
130 | mIsEOS = true;
131 | }
132 |
133 | public void setEglContext(final EGLContext shared_context, final int tex_id) {
134 | mRenderHandler.setEglContext(shared_context, tex_id, mSurface, true);
135 | }
136 |
137 | private int calcBitRate() {
138 | final int bitrate = (int) (BPP * config.videoFrameRate * mWidth * mHeight);
139 | Log.i(TAG, String.format("bitrate=%5.2f[Mbps]", bitrate / 1024f / 1024f));
140 | return bitrate;
141 | }
142 |
143 | }
144 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mcxiaoke/media/recoder/CameraProxy.java:
--------------------------------------------------------------------------------
1 | package com.mcxiaoke.media.recoder;
2 |
3 | import android.app.Activity;
4 | import android.graphics.Point;
5 | import android.hardware.Camera;
6 | import android.hardware.Camera.CameraInfo;
7 | import android.hardware.Camera.Parameters;
8 | import android.util.Log;
9 | import com.mcxiaoke.media.CameraUtils;
10 | import com.mcxiaoke.media.Const;
11 | import com.mcxiaoke.media.ocodec.CodecUtils;
12 |
13 | /**
14 | * User: mcxiaoke
15 | * Date: 2017/7/5
16 | * Time: 16:33
17 | */
18 |
19 | public class CameraProxy {
20 | public static final String TAG = Const.TAG;
21 |
22 | private Activity activity;
23 |
24 | private int orientation;
25 | private Camera.Size videoSize;
26 | private Camera.Size previewSize;
27 |
28 | public CameraProxy(Activity activity) {
29 | this.activity = activity;
30 | }
31 |
32 |
33 | public Camera openCamera() {
34 | Camera c = null;
35 | try {
36 | c = Camera.open(CameraInfo.CAMERA_FACING_BACK); // attempt to get a Camera instance
37 | // get Camera parameters
38 | Camera.Parameters params = c.getParameters();
39 | orientation = CodecUtils.getDisplayOrientation(activity, CameraInfo.CAMERA_FACING_BACK);
40 | Point p = new Point();
41 | activity.getWindowManager().getDefaultDisplay().getSize(p);
42 | Log.d(TAG, "() orientation=" + orientation);
43 | Log.d(TAG, "() screen size=" + p);
44 | videoSize = CameraUtils.getOptimalVideoSize(720, 480, params);
45 | previewSize = CameraUtils.getOptimalPreviewSize(videoSize, params);
46 | Log.d(TAG, "() video size=" + videoSize.width + "x" + videoSize.height);
47 | Log.d(TAG, "() preview size=" + previewSize.width + "x" + previewSize.height);
48 | params.setPreviewSize(previewSize.width, previewSize.height);
49 | params.setRotation(orientation);
50 | params.setRecordingHint(true);
51 | params.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
52 | params.setFlashMode(Parameters.FLASH_MODE_OFF);
53 | params.setSceneMode(Parameters.SCENE_MODE_AUTO);
54 | c.setParameters(params);
55 | c.setDisplayOrientation(orientation);
56 |
57 | CodecUtils.showInfo(activity, c);
58 | // Log.v(TAG, "Camera Parameters: " + params.flatten());
59 | } catch (Exception e) {
60 | // Camera is not available (in use or does not exist)
61 | e.printStackTrace();
62 | }
63 | return c; // returns null if camera is unavailable
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mcxiaoke/media/support/CameraPreview.java:
--------------------------------------------------------------------------------
1 | package com.mcxiaoke.media.support;
2 |
3 | /**
4 | * User: mcxiaoke
5 | * Date: 2017/6/29
6 | * Time: 15:30
7 | */
8 |
9 | import android.content.Context;
10 | import android.hardware.Camera;
11 | import android.util.Log;
12 | import android.view.SurfaceHolder;
13 | import android.view.SurfaceView;
14 | import com.mcxiaoke.media.Const;
15 |
16 | import java.io.IOException;
17 |
18 | /**
19 | * A basic Camera preview class
20 | */
21 | public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
22 | public static final String TAG = Const.TAG;
23 |
24 | private SurfaceHolder mHolder;
25 | private Camera mCamera;
26 |
27 | public CameraPreview(Context context, Camera camera) {
28 | super(context);
29 | mCamera = camera;
30 |
31 | // Install a SurfaceHolder.Callback so we get notified when the
32 | // underlying surface is created and destroyed.
33 | mHolder = getHolder();
34 | mHolder.addCallback(this);
35 | }
36 |
37 | public void surfaceCreated(SurfaceHolder holder) {
38 | // The Surface has been created, now tell the camera where to draw the preview.
39 | try {
40 | mCamera.setPreviewDisplay(holder);
41 | mCamera.startPreview();
42 | } catch (IOException e) {
43 | e.printStackTrace();
44 | Log.d(TAG, "Error setting camera preview: " + e.getMessage());
45 | }
46 | }
47 |
48 | public void surfaceDestroyed(SurfaceHolder holder) {
49 | // empty. Take care of releasing the Camera preview in your activity.
50 | }
51 |
52 | public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
53 | // If your preview can change or rotate, take care of those events here.
54 | // Make sure to stop the preview before resizing or reformatting it.
55 |
56 | if (mHolder.getSurface() == null) {
57 | // preview surface does not exist
58 | return;
59 | }
60 |
61 | // stop preview before making changes
62 | try {
63 | mCamera.stopPreview();
64 | } catch (Exception e) {
65 | e.printStackTrace();
66 | // ignore: tried to stop a non-existent preview
67 | }
68 |
69 | // set preview size and make any resize, rotate or
70 | // reformatting changes here
71 |
72 | // start preview with new settings
73 | try {
74 | mCamera.setPreviewDisplay(mHolder);
75 | mCamera.startPreview();
76 |
77 | } catch (Exception e) {
78 | e.printStackTrace();
79 | Log.d(TAG, "Error starting camera preview: " + e.getMessage());
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mcxiaoke/media/widget/AspectRatio.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 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.mcxiaoke.media.widget;
18 |
19 | import android.os.Parcel;
20 | import android.os.Parcelable;
21 | import android.support.annotation.NonNull;
22 | import android.support.v4.util.SparseArrayCompat;
23 |
24 | /**
25 | * Immutable class for describing proportional relationship between width and height.
26 | */
27 | public class AspectRatio implements Comparable
36 | *
37 | * @param recorder video encoder object
38 | */
39 | public CameraSurfaceRenderer(CameraEncoder recorder) {
40 | mCameraEncoder = recorder;
41 |
42 | mCameraTextureId = -1;
43 | mFrameCount = -1;
44 |
45 | SessionConfig config = recorder.getConfig();
46 | mIncomingWidth = config.getVideoWidth();
47 | mIncomingHeight = config.getVideoHeight();
48 | mIncomingSizeUpdated = true; // Force texture size update on next onDrawFrame
49 |
50 | mCurrentFilter = -1;
51 | mNewFilter = Filters.FILTER_NONE;
52 |
53 | mRecordingEnabled = false;
54 | }
55 |
56 |
57 | /**
58 | * Notifies the renderer that we want to stop or start recording.
59 | */
60 | public void changeRecordingState(boolean isRecording) {
61 | Log.d(TAG, "changeRecordingState: was " + mRecordingEnabled + " now " + isRecording);
62 | mRecordingEnabled = isRecording;
63 | }
64 |
65 | @Override
66 | public void onSurfaceCreated(GL10 unused, EGLConfig config) {
67 | Log.d(TAG, "onSurfaceCreated");
68 | // Set up the texture blitter that will be used for on-screen display. This
69 | // is *not* applied to the recording, because that uses a separate shader.
70 | mFullScreenCamera = new FullFrameRect(
71 | new Texture2dProgram(Texture2dProgram.ProgramType.TEXTURE_EXT));
72 | // For texture overlay:
73 | //GLES20.glEnable(GLES20.GL_BLEND);
74 | //GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
75 | mFullScreenOverlay = new FullFrameRect(
76 | new Texture2dProgram(Texture2dProgram.ProgramType.TEXTURE_2D));
77 | //mOverlayTextureId = GlUtil.createTextureWithTextContent("hello!");
78 | //mOverlayTextureId = GlUtil.createTextureFromImage(mCameraView.getContext(), R.drawable.red_dot);
79 | mCameraTextureId = mFullScreenCamera.createTextureObject();
80 |
81 | mCameraEncoder.onSurfaceCreated(mCameraTextureId);
82 | mFrameCount = 0;
83 | }
84 |
85 | @Override
86 | public void onSurfaceChanged(GL10 unused, int width, int height) {
87 | Log.d(TAG, "onSurfaceChanged " + width + "x" + height);
88 | }
89 |
90 | @Override
91 | public void onDrawFrame(GL10 unused) {
92 | if (VERBOSE) {
93 | if (mFrameCount % 30 == 0) {
94 | Log.d(TAG, "onDrawFrame tex=" + mCameraTextureId);
95 | mCameraEncoder.logSavedEglState();
96 | }
97 | }
98 |
99 | if (mCurrentFilter != mNewFilter) {
100 | Filters.updateFilter(mFullScreenCamera, mNewFilter);
101 | mCurrentFilter = mNewFilter;
102 | mIncomingSizeUpdated = true;
103 | }
104 |
105 | if (mIncomingSizeUpdated) {
106 | mFullScreenCamera.getProgram().setTexSize(mIncomingWidth, mIncomingHeight);
107 | mFullScreenOverlay.getProgram().setTexSize(mIncomingWidth, mIncomingHeight);
108 | mIncomingSizeUpdated = false;
109 | Log.i(TAG, "setTexSize on display Texture");
110 | }
111 |
112 | // Draw the video frame.
113 | if (mCameraEncoder.isSurfaceTextureReadyForDisplay()) {
114 | mCameraEncoder.getSurfaceTextureForDisplay().updateTexImage();
115 | mCameraEncoder.getSurfaceTextureForDisplay().getTransformMatrix(mSTMatrix);
116 | //Drawing texture overlay:
117 | mFullScreenOverlay.drawFrame(mOverlayTextureId, mSTMatrix);
118 | mFullScreenCamera.drawFrame(mCameraTextureId, mSTMatrix);
119 | }
120 | mFrameCount++;
121 | }
122 |
123 | public void signalVertialVideo(FullFrameRect.SCREEN_ROTATION isVertical) {
124 | if (mFullScreenCamera != null) mFullScreenCamera.adjustForVerticalVideo(isVertical, false);
125 | }
126 |
127 | /**
128 | * Changes the filter that we're applying to the camera preview.
129 | */
130 | public void changeFilterMode(int filter) {
131 | mNewFilter = filter;
132 | }
133 |
134 | public void handleTouchEvent(MotionEvent ev) {
135 | mFullScreenCamera.handleTouchEvent(ev);
136 | }
137 |
138 | }
--------------------------------------------------------------------------------
/app/src/main/java/io/kickflip/sdk/av/Drawable2d.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 io.kickflip.sdk.av;
18 |
19 | import java.nio.FloatBuffer;
20 |
21 | /**
22 | * Base class for stuff we like to draw.
23 | */
24 | public class Drawable2d {
25 | /**
26 | * Simple triangle (roughly equilateral, 1.0 per side).
27 | */
28 | private static final float TRIANGLE_COORDS[] = {
29 | 0.0f, 0.622008459f, // top
30 | -0.5f, -0.311004243f, // bottom left
31 | 0.5f, -0.311004243f // bottom right
32 | };
33 | private static final FloatBuffer TRIANGLE_BUF = GlUtil.createFloatBuffer(TRIANGLE_COORDS);
34 |
35 | /**
36 | * Simple square, specified as a triangle strip. The square is centered on (0,0) and has
37 | * a size of 1x1.
38 | *
39 | * Triangles are 0-1-2 and 2-1-3 (counter-clockwise winding).
40 | */
41 | private static final float RECTANGLE_COORDS[] = {
42 | -0.5f, -0.5f, // 0 bottom left
43 | 0.5f, -0.5f, // 1 bottom right
44 | -0.5f, 0.5f, // 2 top left
45 | 0.5f, 0.5f, // 3 top right
46 | };
47 | private static final FloatBuffer RECTANGLE_BUF = GlUtil.createFloatBuffer(RECTANGLE_COORDS);
48 |
49 | /**
50 | * A "full" square, extending from -1 to +1 in both dimensions. When the model/view/projection
51 | * matrix is identity, this will exactly cover the viewport.
52 | *
53 | * This has texture coordinates as well.
54 | */
55 | private static final float FULL_RECTANGLE_COORDS[] = {
56 | -1.0f, -1.0f, // 0 bottom left
57 | 1.0f, -1.0f, // 1 bottom right
58 | -1.0f, 1.0f, // 2 top left
59 | 1.0f, 1.0f, // 3 top right
60 | };
61 | private static final FloatBuffer FULL_RECTANGLE_BUF =
62 | GlUtil.createFloatBuffer(FULL_RECTANGLE_COORDS);
63 |
64 | private static final int SIZEOF_FLOAT = 4;
65 |
66 | private FloatBuffer mVertexArray;
67 | private int mVertexCount;
68 | private int mCoordsPerVertex;
69 | private int mVertexStride;
70 | private Prefab mPrefab;
71 |
72 | /**
73 | * Prepares a drawable from a "pre-fabricated" shape definition.
74 | *
75 | * Does no EGL/GL operations, so this can be done at any time.
76 | */
77 | public Drawable2d(Prefab shape) {
78 | switch (shape) {
79 | case TRIANGLE:
80 | mVertexArray = TRIANGLE_BUF;
81 | mCoordsPerVertex = 2;
82 | mVertexStride = mCoordsPerVertex * SIZEOF_FLOAT;
83 | mVertexCount = TRIANGLE_COORDS.length / mCoordsPerVertex;
84 | break;
85 | case RECTANGLE:
86 | mVertexArray = RECTANGLE_BUF;
87 | mCoordsPerVertex = 2;
88 | mVertexStride = mCoordsPerVertex * SIZEOF_FLOAT;
89 | mVertexCount = RECTANGLE_COORDS.length / mCoordsPerVertex;
90 | break;
91 | case FULL_RECTANGLE:
92 | mVertexArray = FULL_RECTANGLE_BUF;
93 | mCoordsPerVertex = 2;
94 | mVertexStride = mCoordsPerVertex * SIZEOF_FLOAT;
95 | mVertexCount = FULL_RECTANGLE_COORDS.length / mCoordsPerVertex;
96 | break;
97 | default:
98 | throw new RuntimeException("Unknown shape " + shape);
99 | }
100 | mPrefab = shape;
101 | }
102 |
103 | /**
104 | * Returns the array of vertices.
105 | *
106 | * To avoid allocations, this returns internal state. The caller must not modify it.
107 | */
108 | public FloatBuffer getVertexArray() {
109 | return mVertexArray;
110 | }
111 |
112 | /**
113 | * Returns the number of vertices stored in the vertex array.
114 | */
115 | public int getVertexCount() {
116 | return mVertexCount;
117 | }
118 |
119 | /**
120 | * Returns the width, in bytes, of the data for each vertex.
121 | */
122 | public int getVertexStride() {
123 | return mVertexStride;
124 | }
125 |
126 | /**
127 | * Returns the number of position coordinates per vertex. This will be 2 or 3.
128 | */
129 | public int getCoordsPerVertex() {
130 | return mCoordsPerVertex;
131 | }
132 |
133 | @Override
134 | public String toString() {
135 | if (mPrefab != null) {
136 | return "[Drawable2d: " + mPrefab + "]";
137 | } else {
138 | return "[Drawable2d: ...]";
139 | }
140 | }
141 |
142 | /**
143 | * Enum values for constructor.
144 | */
145 | public enum Prefab {
146 | TRIANGLE, RECTANGLE, FULL_RECTANGLE
147 | }
148 | }
--------------------------------------------------------------------------------
/app/src/main/java/io/kickflip/sdk/av/EglStateSaver.java:
--------------------------------------------------------------------------------
1 | package io.kickflip.sdk.av;
2 |
3 | import android.opengl.EGL14;
4 | import android.opengl.EGLContext;
5 | import android.opengl.EGLDisplay;
6 | import android.opengl.EGLSurface;
7 | import android.util.Log;
8 |
9 |
10 | /**
11 | * @hide
12 | */
13 | public class EglStateSaver {
14 | private static final String TAG = "EglStateSaver";
15 | private static final boolean DEBUG = true;
16 |
17 | private EGLContext mSavedContext = EGL14.EGL_NO_CONTEXT;
18 | private EGLSurface mSavedReadSurface = EGL14.EGL_NO_SURFACE;
19 | private EGLSurface mSavedDrawSurface = EGL14.EGL_NO_SURFACE;
20 | private EGLDisplay mSavedDisplay = EGL14.EGL_NO_DISPLAY;
21 |
22 | public void saveEGLState() {
23 | mSavedContext = EGL14.eglGetCurrentContext();
24 | if (DEBUG && mSavedContext.equals(EGL14.EGL_NO_CONTEXT)) Log.e(TAG, "Saved EGL_NO_CONTEXT");
25 | mSavedReadSurface = EGL14.eglGetCurrentSurface(EGL14.EGL_READ);
26 | if (DEBUG && mSavedReadSurface.equals(EGL14.EGL_NO_CONTEXT)) Log.e(TAG, "Saved EGL_NO_SURFACE");
27 | mSavedDrawSurface = EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW);
28 | if (DEBUG && mSavedDrawSurface.equals(EGL14.EGL_NO_CONTEXT)) Log.e(TAG, "Saved EGL_NO_SURFACE");
29 | mSavedDisplay = EGL14.eglGetCurrentDisplay();
30 | if (DEBUG && mSavedDisplay.equals(EGL14.EGL_NO_DISPLAY)) Log.e(TAG, "Saved EGL_NO_DISPLAY");
31 |
32 | }
33 |
34 | public EGLContext getSavedEGLContext() {
35 | return mSavedContext;
36 | }
37 |
38 | public void makeSavedStateCurrent() {
39 | EGL14.eglMakeCurrent(mSavedDisplay, mSavedReadSurface, mSavedDrawSurface, mSavedContext);
40 | }
41 |
42 | public void makeNothingCurrent() {
43 | EGL14.eglMakeCurrent(mSavedDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
44 | }
45 |
46 | public void logState() {
47 | if (!mSavedContext.equals(EGL14.eglGetCurrentContext()))
48 | Log.i(TAG, "Saved context DOES NOT equal current.");
49 | else
50 | Log.i(TAG, "Saved context DOES equal current.");
51 |
52 | if (!mSavedReadSurface.equals(EGL14.eglGetCurrentSurface(EGL14.EGL_READ))) {
53 | if (mSavedReadSurface.equals(EGL14.EGL_NO_SURFACE))
54 | Log.i(TAG, "Saved read surface is EGL_NO_SURFACE");
55 | else
56 | Log.i(TAG, "Saved read surface DOES NOT equal current.");
57 | } else
58 | Log.i(TAG, "Saved read surface DOES equal current.");
59 |
60 | if (!mSavedDrawSurface.equals(EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW))) {
61 | if (mSavedDrawSurface.equals(EGL14.EGL_NO_SURFACE))
62 | Log.i(TAG, "Saved draw surface is EGL_NO_SURFACE");
63 | else
64 | Log.i(TAG, "Saved draw surface DOES NOT equal current.");
65 | } else
66 | Log.i(TAG, "Saved draw surface DOES equal current.");
67 |
68 | if (!mSavedDisplay.equals(EGL14.eglGetCurrentDisplay()))
69 | Log.i(TAG, "Saved display DOES NOT equal current.");
70 | else
71 | Log.i(TAG, "Saved display DOES equal current.");
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/app/src/main/java/io/kickflip/sdk/av/EglSurfaceBase.java:
--------------------------------------------------------------------------------
1 | /*
* Copyright 2013 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.kickflip.sdk.av;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.opengl.EGL14;
import android.opengl.EGLSurface;
import android.opengl.GLES20;
import android.util.Log;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* Common base class for EGL surfaces.
*
25 | * It's good practice to explicitly release() the surface, preferably from a "finally" block.
26 | * This object owns the Surface; releasing this object will release the Surface as well.
27 | *
28 | * @hide
29 | */
30 | public class WindowSurface extends EglSurfaceBase {
31 | private Surface mSurface;
32 |
33 | /**
34 | * Associates an EGL surface with the native window surface. The Surface will be
35 | * owned by WindowSurface, and released when release() is called.
36 | */
37 | public WindowSurface(EglCore eglCore, Surface surface) {
38 | super(eglCore);
39 | createWindowSurface(surface);
40 | mSurface = surface;
41 | }
42 |
43 | /**
44 | * Associates an EGL surface with the SurfaceTexture.
45 | */
46 | public WindowSurface(EglCore eglCore, SurfaceTexture surfaceTexture) {
47 | super(eglCore);
48 | createWindowSurface(surfaceTexture);
49 | }
50 |
51 | /**
52 | * Releases any resources associated with the Surface and the EGL surface.
53 | */
54 | public void release() {
55 | releaseEglSurface();
56 | if (mSurface != null) {
57 | mSurface.release();
58 | mSurface = null;
59 | }
60 | }
61 |
62 | /**
63 | * Recreate the EGLSurface, using the new EglBase. The caller should have already
64 | * freed the old EGLSurface with releaseEglSurface().
65 | *
66 | * This is useful when we want to update the EGLSurface associated with a Surface.
67 | * For example, if we want to share with a different EGLContext, which can only
68 | * be done by tearing down and recreating the context. (That's handled by the caller;
69 | * this just creates a new EGLSurface for the Surface we were handed earlier.)
70 | *
71 | * If the previous EGLSurface isn't fully destroyed, e.g. it's still current on a
72 | * context somewhere, the create call will fail with complaints from the Surface
73 | * about already being connected.
74 | */
75 | public void recreate(EglCore newEglCore) {
76 | if (mSurface == null) {
77 | throw new RuntimeException("not yet implemented for SurfaceTexture");
78 | }
79 | mEglCore = newEglCore; // switch to new context
80 | createWindowSurface(mSurface); // create new surface
81 | }
82 | }
--------------------------------------------------------------------------------
/app/src/main/java/io/kickflip/sdk/exception/KickflipException.java:
--------------------------------------------------------------------------------
1 | package io.kickflip.sdk.exception;
2 |
3 | import java.io.IOException;
4 |
5 | /**
6 | * Kickflip Exception
7 | */
8 | public class KickflipException extends IOException {
9 | private String mMessage;
10 | private int mCode;
11 |
12 | public KickflipException() {
13 | mMessage = "An unknown error occurred";
14 | mCode = 0;
15 | }
16 |
17 | public KickflipException(String message, int code) {
18 | mMessage = message;
19 | mCode = code;
20 | }
21 |
22 | public String getMessage() {
23 | return mMessage;
24 | }
25 |
26 | public int getCode() {
27 | return mCode;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/java/io/kickflip/sdk/location/DeviceLocation.java:
--------------------------------------------------------------------------------
1 | package io.kickflip.sdk.location;
2 |
3 | import android.content.Context;
4 | import android.location.Location;
5 | import android.location.LocationListener;
6 | import android.location.LocationManager;
7 | import android.os.Bundle;
8 | import android.util.Log;
9 |
10 | import java.util.Timer;
11 | import java.util.TimerTask;
12 |
13 | // Thanks, Fedor!
14 | // TODO: Modify getLocation to take callback
15 | public class DeviceLocation {
16 | private static final String TAG = "DeviceLocation";
17 | private static final float ACCURATE_LOCATION_THRESHOLD_METERS = 100;
18 | Timer timer1;
19 | LocationManager lm;
20 | LocationResult locationResult;
21 | boolean gps_enabled = false;
22 | boolean network_enabled = false;
23 | private Location bestLocation;
24 | private boolean waitForGpsFix;
25 | LocationListener locationListenerNetwork = new LocationListener() {
26 | public void onLocationChanged(Location location) {
27 | Log.i(TAG, "got network loc accurate to " + String.valueOf(location.getAccuracy()) + "m");
28 | if (bestLocation == null || bestLocation.getAccuracy() > location.getAccuracy())
29 | bestLocation = location;
30 |
31 | if (!waitForGpsFix || bestLocation.getAccuracy() < ACCURATE_LOCATION_THRESHOLD_METERS) {
32 | timer1.cancel();
33 | locationResult.gotLocation(bestLocation);
34 | lm.removeUpdates(this);
35 | lm.removeUpdates(locationListenerGps);
36 | }
37 |
38 | }
39 |
40 | public void onProviderDisabled(String provider) {
41 | }
42 |
43 | public void onProviderEnabled(String provider) {
44 | }
45 |
46 | public void onStatusChanged(String provider, int status, Bundle extras) {
47 | }
48 | };
49 | LocationListener locationListenerGps = new LocationListener() {
50 | public void onLocationChanged(Location location) {
51 | Log.i(TAG, "got GPS loc accurate to " + String.valueOf(location.getAccuracy()) + "m");
52 | if (bestLocation == null || bestLocation.getAccuracy() > location.getAccuracy())
53 | bestLocation = location;
54 |
55 | if (!waitForGpsFix || bestLocation.getAccuracy() < ACCURATE_LOCATION_THRESHOLD_METERS) {
56 | timer1.cancel();
57 | locationResult.gotLocation(bestLocation);
58 | lm.removeUpdates(this);
59 | lm.removeUpdates(locationListenerNetwork);
60 | }
61 |
62 | }
63 |
64 | public void onProviderDisabled(String provider) {
65 | }
66 |
67 | public void onProviderEnabled(String provider) {
68 | }
69 |
70 | public void onStatusChanged(String provider, int status, Bundle extras) {
71 | }
72 | };
73 |
74 | public static void getLocation(Context context, boolean waitForGpsFix, final LocationResult cb) {
75 | DeviceLocation deviceLocation = new DeviceLocation();
76 | deviceLocation.getLocation(context, cb, waitForGpsFix);
77 | }
78 |
79 | /**
80 | * Get the last known location.
81 | * If one is not available, fetch
82 | * a fresh location
83 | *
84 | * @param context
85 | * @param waitForGpsFix
86 | * @param cb
87 | */
88 | public static void getLastKnownLocation(Context context, boolean waitForGpsFix, final LocationResult cb) {
89 | DeviceLocation deviceLocation = new DeviceLocation();
90 |
91 | LocationManager lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
92 | Location last_loc;
93 | last_loc = lm.getLastKnownLocation(LocationManager.GPS_PROVIDER);
94 | if (last_loc == null)
95 | last_loc = lm.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
96 |
97 | if (last_loc != null && cb != null) {
98 | cb.gotLocation(last_loc);
99 | } else {
100 | deviceLocation.getLocation(context, cb, waitForGpsFix);
101 | }
102 | }
103 |
104 | public boolean getLocation(Context context, LocationResult result, boolean waitForGpsFix) {
105 | this.waitForGpsFix = waitForGpsFix;
106 |
107 | //I use LocationResult callback class to pass location value from MyLocation to user code.
108 | locationResult = result;
109 | if (lm == null)
110 | lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
111 |
112 | //exceptions will be thrown if provider is not permitted.
113 | try {
114 | gps_enabled = lm.isProviderEnabled(LocationManager.GPS_PROVIDER);
115 | } catch (Exception ex) {
116 | }
117 | try {
118 | network_enabled = lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
119 | } catch (Exception ex) {
120 | }
121 |
122 | //don't start listeners if no provider is enabled
123 | if (!gps_enabled && !network_enabled)
124 | return false;
125 |
126 | if (gps_enabled)
127 | lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListenerGps);
128 | if (network_enabled)
129 | lm.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListenerNetwork);
130 | timer1 = new Timer();
131 | timer1.schedule(new GetBestLocation(), 20000);
132 | return true;
133 | }
134 |
135 | public static abstract class LocationResult {
136 | public abstract void gotLocation(Location location);
137 | }
138 |
139 | class GetBestLocation extends TimerTask {
140 | @Override
141 | public void run() {
142 | Log.i(TAG, "Timer expired before adequate location acquired");
143 | lm.removeUpdates(locationListenerGps);
144 | lm.removeUpdates(locationListenerNetwork);
145 |
146 | Location net_loc = null, gps_loc = null;
147 | if (gps_enabled)
148 | gps_loc = lm.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
149 | if (network_enabled)
150 | net_loc = lm.getLastKnownLocation(LocationManager.GPS_PROVIDER);
151 |
152 | //if there are both values use the latest one
153 | if (gps_loc != null && net_loc != null) {
154 | if (gps_loc.getTime() > net_loc.getTime())
155 | locationResult.gotLocation(gps_loc);
156 | else
157 | locationResult.gotLocation(net_loc);
158 | return;
159 | }
160 |
161 | if (gps_loc != null) {
162 | locationResult.gotLocation(gps_loc);
163 | return;
164 | }
165 | if (net_loc != null) {
166 | locationResult.gotLocation(net_loc);
167 | return;
168 | }
169 | locationResult.gotLocation(null);
170 | }
171 | }
172 |
173 |
174 | }
--------------------------------------------------------------------------------
/app/src/main/java/io/kickflip/sdk/location/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * @hide
3 | **/
4 | package io.kickflip.sdk.location;
--------------------------------------------------------------------------------
/app/src/main/java/io/kickflip/sdk/view/GLCameraEncoderView.java:
--------------------------------------------------------------------------------
1 | package io.kickflip.sdk.view;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.MotionEvent;
6 | import io.kickflip.sdk.av.CameraEncoder;
7 |
8 | /**
9 | * Special GLSurfaceView for use with CameraEncoder
10 | * The tight coupling here allows richer touch interaction
11 | */
12 | public class GLCameraEncoderView extends GLCameraView {
13 | private static final String TAG = "GLCameraEncoderView";
14 |
15 | protected CameraEncoder mCameraEncoder;
16 |
17 | public GLCameraEncoderView(Context context) {
18 | super(context);
19 | }
20 |
21 | public GLCameraEncoderView(Context context, AttributeSet attrs) {
22 | super(context, attrs);
23 | }
24 |
25 | public void setCameraEncoder(CameraEncoder encoder) {
26 | mCameraEncoder = encoder;
27 | setCamera(mCameraEncoder.getCamera());
28 | }
29 |
30 | @Override
31 | public boolean onTouchEvent(MotionEvent ev) {
32 | if (mScaleGestureDetector != null) {
33 | mScaleGestureDetector.onTouchEvent(ev);
34 | }
35 | if (mCameraEncoder != null && ev.getPointerCount() == 1 && (ev.getAction() == MotionEvent.ACTION_MOVE)) {
36 | mCameraEncoder.handleCameraPreviewTouchEvent(ev);
37 | } else if (mCameraEncoder != null && ev.getPointerCount() == 1 && (ev.getAction() == MotionEvent.ACTION_DOWN)) {
38 | mCameraEncoder.handleCameraPreviewTouchEvent(ev);
39 | }
40 | return true;
41 | }
42 |
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/java/io/kickflip/sdk/view/GLCameraView.java:
--------------------------------------------------------------------------------
1 | package io.kickflip.sdk.view;
2 |
3 | import android.content.Context;
4 | import android.hardware.Camera;
5 | import android.opengl.GLSurfaceView;
6 | import android.util.AttributeSet;
7 | import android.view.MotionEvent;
8 | import android.view.ScaleGestureDetector;
9 |
10 | /**
11 | * Created by davidbrodsky on 1/30/14.
12 | */
13 | public class GLCameraView extends GLSurfaceView {
14 | private static final String TAG = "GLCameraView";
15 |
16 | protected ScaleGestureDetector mScaleGestureDetector;
17 | private Camera mCamera;
18 | private int mMaxZoom;
19 | private ScaleGestureDetector.SimpleOnScaleGestureListener mScaleListener = new ScaleGestureDetector.SimpleOnScaleGestureListener() {
20 |
21 | int mZoomWhenScaleBegan = 0;
22 | int mCurrentZoom = 0;
23 |
24 | @Override
25 | public boolean onScale(ScaleGestureDetector detector) {
26 | if (mCamera != null) {
27 | Camera.Parameters params = mCamera.getParameters();
28 | mCurrentZoom = (int) (mZoomWhenScaleBegan + (mMaxZoom * (detector.getScaleFactor() - 1)));
29 | mCurrentZoom = Math.min(mCurrentZoom, mMaxZoom);
30 | mCurrentZoom = Math.max(0, mCurrentZoom);
31 | params.setZoom(mCurrentZoom);
32 | mCamera.setParameters(params);
33 | }
34 |
35 | return false;
36 | }
37 |
38 | @Override
39 | public boolean onScaleBegin(ScaleGestureDetector detector) {
40 | mZoomWhenScaleBegan = mCamera.getParameters().getZoom();
41 | return true;
42 | }
43 |
44 | @Override
45 | public void onScaleEnd(ScaleGestureDetector detector) {
46 | }
47 | };
48 |
49 | public GLCameraView(Context context) {
50 | super(context);
51 | init(context);
52 | }
53 |
54 | public GLCameraView(Context context, AttributeSet attrs) {
55 | super(context, attrs);
56 | init(context);
57 | }
58 |
59 | private void init(Context context) {
60 | mMaxZoom = 0;
61 |
62 | }
63 |
64 | public void setCamera(Camera camera) {
65 | mCamera = camera;
66 | Camera.Parameters camParams = mCamera.getParameters();
67 | if (camParams.isZoomSupported()) {
68 | mMaxZoom = camParams.getMaxZoom();
69 | mScaleGestureDetector = new ScaleGestureDetector(getContext(), mScaleListener);
70 | }
71 | }
72 |
73 | public void releaseCamera() {
74 | mCamera = null;
75 | mScaleGestureDetector = null;
76 | }
77 |
78 | @Override
79 | public boolean onTouchEvent(MotionEvent ev) {
80 | if (mScaleGestureDetector != null) {
81 | if (!mScaleGestureDetector.onTouchEvent(ev)) {
82 | // No scale gesture detected
83 |
84 | }
85 | }
86 | return true;
87 | }
88 |
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/app/src/main/java/io/kickflip/sdk/view/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * @hide
3 | **/
4 | package io.kickflip.sdk.view;
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcxiaoke/MediaCodec/76fce088a9a3d19c5dda4427dcc870cec15fb783/app/src/main/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/layout/main.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 | *
21 | *
22 | * @hide
23 | */
24 | public class AVRecorder {
25 |
26 | protected CameraEncoder mCamEncoder;
27 | protected MicrophoneEncoder mMicEncoder;
28 | private SessionConfig mConfig;
29 | private boolean mIsRecording;
30 |
31 | public AVRecorder(SessionConfig config) throws IOException {
32 | init(config);
33 | }
34 |
35 | private void init(SessionConfig config) throws IOException {
36 | mCamEncoder = new CameraEncoder(config);
37 | mMicEncoder = new MicrophoneEncoder(config);
38 | mConfig = config;
39 | mIsRecording = false;
40 | }
41 |
42 | public void setPreviewDisplay(GLCameraView display) {
43 | mCamEncoder.setPreviewDisplay(display);
44 | }
45 |
46 | public void applyFilter(int filter) {
47 | mCamEncoder.applyFilter(filter);
48 | }
49 |
50 | public void requestOtherCamera() {
51 | mCamEncoder.requestOtherCamera();
52 | }
53 |
54 | public void requestCamera(int camera) {
55 | mCamEncoder.requestCamera(camera);
56 | }
57 |
58 | public void toggleFlash() {
59 | mCamEncoder.toggleFlashMode();
60 | }
61 |
62 | public void adjustVideoBitrate(int targetBitRate) {
63 | mCamEncoder.adjustBitrate(targetBitRate);
64 | }
65 |
66 | /**
67 | * Signal that the recorder should treat
68 | * incoming video frames as Vertical Video, rotating
69 | * and cropping them for proper display.
70 | *
71 | * This method only has effect if {@link io.kickflip.sdk.av.SessionConfig#setConvertVerticalVideo(boolean)}
72 | * has been set true for the current recording session.
73 | */
74 | public void signalVerticalVideo(FullFrameRect.SCREEN_ROTATION orientation) {
75 | mCamEncoder.signalVerticalVideo(orientation);
76 | }
77 |
78 | public void startRecording() {
79 | mIsRecording = true;
80 | mMicEncoder.startRecording();
81 | mCamEncoder.startRecording();
82 | }
83 |
84 | public boolean isRecording() {
85 | return mIsRecording;
86 | }
87 |
88 | public void stopRecording() {
89 | mIsRecording = false;
90 | mMicEncoder.stopRecording();
91 | mCamEncoder.stopRecording();
92 | }
93 |
94 | /**
95 | * Prepare for a subsequent recording. Must be called after {@link #stopRecording()}
96 | * and before {@link #release()}
97 | *
98 | * @param config
99 | */
100 | public void reset(SessionConfig config) throws IOException {
101 | mCamEncoder.reset(config);
102 | mMicEncoder.reset(config);
103 | mConfig = config;
104 | mIsRecording = false;
105 | }
106 |
107 | /**
108 | * Release resources. Must be called after {@link #stopRecording()} After this call
109 | * this instance may no longer be used.
110 | */
111 | public void release() {
112 | mCamEncoder.release();
113 | // MicrophoneEncoder releases all it's resources when stopRecording is called
114 | // because it doesn't have any meaningful state
115 | // between recordings. It might someday if we decide to present
116 | // persistent audio volume meters etc.
117 | // Until then, we don't need to write MicrophoneEncoder.release()
118 | }
119 |
120 | public void onHostActivityPaused() {
121 | mCamEncoder.onHostActivityPaused();
122 | }
123 |
124 | public void onHostActivityResumed() {
125 | mCamEncoder.onHostActivityResumed();
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/app/src/main/java/io/kickflip/sdk/av/AndroidMuxer.java:
--------------------------------------------------------------------------------
1 | package io.kickflip.sdk.av;
2 |
3 | import android.media.MediaCodec;
4 | import android.media.MediaFormat;
5 | import android.media.MediaMuxer;
6 | import android.util.Log;
7 |
8 | import java.io.IOException;
9 | import java.nio.ByteBuffer;
10 |
11 | /**
12 | * @hide
13 | */
14 | public class AndroidMuxer extends Muxer {
15 | private static final String TAG = "AndroidMuxer";
16 | private static final boolean VERBOSE = false;
17 |
18 | private MediaMuxer mMuxer;
19 | private boolean mStarted;
20 |
21 | private AndroidMuxer(String outputFile, FORMAT format) {
22 | super(outputFile, format);
23 | try {
24 | switch (format) {
25 | case MPEG4:
26 | mMuxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
27 | break;
28 | default:
29 | throw new IllegalArgumentException("Unrecognized format!");
30 | }
31 | } catch (IOException e) {
32 | throw new RuntimeException("MediaMuxer creation failed", e);
33 | }
34 | mStarted = false;
35 | }
36 |
37 | public static AndroidMuxer create(String outputFile, FORMAT format) {
38 | return new AndroidMuxer(outputFile, format);
39 | }
40 |
41 | @Override
42 | public int addTrack(MediaFormat trackFormat) {
43 | super.addTrack(trackFormat);
44 | if (mStarted)
45 | throw new RuntimeException("format changed twice");
46 | int track = mMuxer.addTrack(trackFormat);
47 |
48 | if (allTracksAdded()) {
49 | start();
50 | }
51 | return track;
52 | }
53 |
54 | @Override
55 | public void release() {
56 | super.release();
57 | mMuxer.release();
58 | }
59 |
60 | @Override
61 | public boolean isStarted() {
62 | return mStarted;
63 | }
64 |
65 | @Override
66 | public void writeSampleData(MediaCodec encoder, int trackIndex, int bufferIndex, ByteBuffer encodedData, MediaCodec.BufferInfo bufferInfo) {
67 | super.writeSampleData(encoder, trackIndex, bufferIndex, encodedData, bufferInfo);
68 | if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
69 | // MediaMuxer gets the codec config info via the addTrack command
70 | if (VERBOSE) Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG");
71 | encoder.releaseOutputBuffer(bufferIndex, false);
72 | return;
73 | }
74 |
75 | if (bufferInfo.size == 0) {
76 | if (VERBOSE) Log.d(TAG, "ignoring zero size buffer");
77 | encoder.releaseOutputBuffer(bufferIndex, false);
78 | return;
79 | }
80 |
81 | if (!mStarted) {
82 | Log.e(TAG, "writeSampleData called before muxer started. Ignoring packet. Track index: " + trackIndex + " tracks added: " + mNumTracks);
83 | encoder.releaseOutputBuffer(bufferIndex, false);
84 | return;
85 | }
86 |
87 | bufferInfo.presentationTimeUs = getNextRelativePts(bufferInfo.presentationTimeUs, trackIndex);
88 |
89 | mMuxer.writeSampleData(trackIndex, encodedData, bufferInfo);
90 |
91 | encoder.releaseOutputBuffer(bufferIndex, false);
92 |
93 | if (allTracksFinished()) {
94 | stop();
95 | }
96 | }
97 |
98 | @Override
99 | public void forceStop() {
100 | stop();
101 | }
102 |
103 | protected void start() {
104 | mMuxer.start();
105 | mStarted = true;
106 | }
107 |
108 | protected void stop() {
109 | mMuxer.stop();
110 | mStarted = false;
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/app/src/main/java/io/kickflip/sdk/av/AudioEncoderConfig.java:
--------------------------------------------------------------------------------
1 | package io.kickflip.sdk.av;
2 |
3 | /**
4 | * @hide
5 | */
6 | public class AudioEncoderConfig {
7 | protected final int mNumChannels;
8 | protected final int mSampleRate;
9 | protected final int mBitrate;
10 |
11 | public AudioEncoderConfig(int channels, int sampleRate, int bitRate) {
12 | mNumChannels = channels;
13 | mBitrate = bitRate;
14 | mSampleRate = sampleRate;
15 | }
16 |
17 | public int getNumChannels() {
18 | return mNumChannels;
19 | }
20 |
21 | public int getSampleRate() {
22 | return mSampleRate;
23 | }
24 |
25 | public int getBitrate() {
26 | return mBitrate;
27 | }
28 |
29 | @Override
30 | public String toString() {
31 | return "AudioEncoderConfig: " + mNumChannels + " channels totaling " + mBitrate + " bps @" + mSampleRate + " Hz";
32 | }
33 | }
--------------------------------------------------------------------------------
/app/src/main/java/io/kickflip/sdk/av/AudioEncoderCore.java:
--------------------------------------------------------------------------------
1 | package io.kickflip.sdk.av;
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 | * @hide
12 | */
13 | public class AudioEncoderCore extends AndroidEncoder {
14 |
15 | protected static final String MIME_TYPE = "audio/mp4a-latm"; // AAC Low Overhead Audio Transport Multiplex
16 | private static final String TAG = "AudioEncoderCore";
17 | private static final boolean VERBOSE = false;
18 | // Configurable options
19 | protected int mChannelConfig;
20 | protected int mSampleRate;
21 |
22 | /**
23 | * Configures encoder and muxer state, and prepares the input Surface.
24 | */
25 | public AudioEncoderCore(int numChannels, int bitRate, int sampleRate, Muxer muxer) throws IOException {
26 | switch (numChannels) {
27 | case 1:
28 | mChannelConfig = AudioFormat.CHANNEL_IN_MONO;
29 | break;
30 | case 2:
31 | mChannelConfig = AudioFormat.CHANNEL_IN_STEREO;
32 | break;
33 | default:
34 | throw new IllegalArgumentException("Invalid channel count. Must be 1 or 2");
35 | }
36 | mSampleRate = sampleRate;
37 | mMuxer = muxer;
38 | mBufferInfo = new MediaCodec.BufferInfo();
39 |
40 | MediaFormat format = MediaFormat.createAudioFormat(MIME_TYPE, mSampleRate, mChannelConfig);
41 |
42 | // Set some properties. Failing to specify some of these can cause the MediaCodec
43 | // configure() call to throw an unhelpful exception.
44 | format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
45 | format.setInteger(MediaFormat.KEY_SAMPLE_RATE, mSampleRate);
46 | format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, numChannels);
47 | format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
48 | format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 16384);
49 |
50 | // Create a MediaCodec encoder, and configure it with our format. Get a Surface
51 | // we can use for input and wrap it with a class that handles the EGL work.
52 | mEncoder = MediaCodec.createEncoderByType(MIME_TYPE);
53 | mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
54 | mEncoder.start();
55 |
56 | mTrackIndex = -1;
57 | }
58 |
59 | /**
60 | * Depending on this method ties AudioEncoderCore
61 | * to a MediaCodec-based implementation.
62 | *
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 | *
67 | * @return
68 | */
69 | public MediaCodec getMediaCodec() {
70 | return mEncoder;
71 | }
72 |
73 | @Override
74 | protected boolean isSurfaceInputEncoder() {
75 | return false;
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/app/src/main/java/io/kickflip/sdk/av/CameraSurfaceRenderer.java:
--------------------------------------------------------------------------------
1 | package io.kickflip.sdk.av;
2 |
3 | import android.opengl.GLSurfaceView;
4 | import android.util.Log;
5 | import android.view.MotionEvent;
6 |
7 | import javax.microedition.khronos.egl.EGLConfig;
8 | import javax.microedition.khronos.opengles.GL10;
9 |
10 | /**
11 | * @hide
12 | */
13 | class CameraSurfaceRenderer implements GLSurfaceView.Renderer {
14 | private static final String TAG = "CameraSurfaceRenderer";
15 | private static final boolean VERBOSE = false;
16 | private final float[] mSTMatrix = new float[16];
17 | boolean showBox = false;
18 | private CameraEncoder mCameraEncoder;
19 | private FullFrameRect mFullScreenCamera;
20 | private FullFrameRect mFullScreenOverlay; // For texture overlay
21 | private int mOverlayTextureId;
22 | private int mCameraTextureId;
23 | private boolean mRecordingEnabled;
24 | private int mFrameCount;
25 | // Keep track of selected filters + relevant state
26 | private boolean mIncomingSizeUpdated;
27 | private int mIncomingWidth;
28 | private int mIncomingHeight;
29 | private int mCurrentFilter;
30 | private int mNewFilter;
31 |
32 |
33 | /**
34 | * Constructs CameraSurfaceRenderer.
35 | *