├── GlUtil.java ├── InputSurface.java ├── MyRecorder.java ├── OFAndroidWindow.java ├── OFGLSurfaceView.java ├── README.md ├── RenderSrfTex.java └── VideoParam.java /GlUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 MorihiroSoft 3 | * Copyright 2013 Google Inc. All Rights Reserved. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package cc.openframeworks; 18 | 19 | import java.nio.ByteBuffer; 20 | import java.nio.ByteOrder; 21 | import java.nio.FloatBuffer; 22 | 23 | import android.opengl.EGL14; 24 | import android.opengl.GLES20; 25 | import android.opengl.Matrix; 26 | import android.util.Log; 27 | 28 | class GlUtil { 29 | private static final String TAG = "GlUtil"; 30 | 31 | public static FloatBuffer createSquareVtx() { 32 | final float vtx[] = { 33 | // XYZ, UV 34 | -1f, 1f, 0f, 0f, 1f, 35 | -1f, -1f, 0f, 0f, 0f, 36 | 1f, 1f, 0f, 1f, 1f, 37 | 1f, -1f, 0f, 1f, 0f, 38 | }; 39 | ByteBuffer bb = ByteBuffer.allocateDirect(4 * vtx.length); 40 | bb.order(ByteOrder.nativeOrder()); 41 | FloatBuffer fb = bb.asFloatBuffer(); 42 | fb.put(vtx); 43 | fb.position(0); 44 | return fb; 45 | } 46 | 47 | public static float[] createIdentityMtx() { 48 | float[] m = new float[16]; 49 | Matrix.setIdentityM(m, 0); 50 | return m; 51 | } 52 | 53 | public static int createProgram(String vertexSource, String fragmentSource) { 54 | int vs = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); 55 | int fs = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); 56 | int program = GLES20.glCreateProgram(); 57 | GLES20.glAttachShader(program, vs); 58 | GLES20.glAttachShader(program, fs); 59 | GLES20.glLinkProgram(program); 60 | // 61 | int[] linkStatus = new int[1]; 62 | GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); 63 | if (linkStatus[0] != GLES20.GL_TRUE) { 64 | Log.e(TAG, "Could not link program:"); 65 | Log.e(TAG, GLES20.glGetProgramInfoLog(program)); 66 | GLES20.glDeleteProgram(program); 67 | program = 0; 68 | } 69 | // 70 | return program; 71 | } 72 | 73 | public static int loadShader(int shaderType, String source) { 74 | int shader = GLES20.glCreateShader(shaderType); 75 | GLES20.glShaderSource(shader, source); 76 | GLES20.glCompileShader(shader); 77 | // 78 | int[] compiled = new int[1]; 79 | GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); 80 | if (compiled[0] == 0) { 81 | Log.e(TAG, "Could not compile shader(TYPE=" + shaderType + "):"); 82 | Log.e(TAG, GLES20.glGetShaderInfoLog(shader)); 83 | GLES20.glDeleteShader(shader); 84 | shader = 0; 85 | } 86 | // 87 | return shader; 88 | } 89 | 90 | public static void checkGlError(String op) { 91 | int error; 92 | while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { 93 | Log.e("KKK1", op + ": glGetError: 0x" + Integer.toHexString(error)); 94 | throw new RuntimeException("glGetError encountered (see log)"); 95 | } 96 | // Log.e("KKK1", op + ": SUCCESS"); 97 | } 98 | 99 | public static void checkEglError(String op) { 100 | int error; 101 | while ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) { 102 | Log.e("KKK1", op + ": eglGetError: 0x" + Integer.toHexString(error)); 103 | throw new RuntimeException("eglGetError encountered (see log)"); 104 | } 105 | // Log.e("KKK1", op + ": SUCCESS"); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /InputSurface.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 MorihiroSoft 3 | * Copyright 2013 Google Inc. All Rights Reserved. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package cc.openframeworks; 18 | 19 | import android.opengl.EGL14; 20 | import android.opengl.EGLConfig; 21 | import android.opengl.EGLContext; 22 | import android.opengl.EGLDisplay; 23 | import android.opengl.EGLExt; 24 | import android.opengl.EGLSurface; 25 | import android.util.Log; 26 | import android.view.Surface; 27 | 28 | class InputSurface { 29 | //--------------------------------------------------------------------- 30 | // CONSTANTS 31 | //--------------------------------------------------------------------- 32 | private static final int EGL_RECORDABLE_ANDROID = 0x3142; 33 | 34 | //--------------------------------------------------------------------- 35 | // MEMBERS 36 | //--------------------------------------------------------------------- 37 | private Surface mSurface = null; 38 | private EGLDisplay mEGLDisplay = null; 39 | private EGLContext mEGLContext = null; 40 | private EGLSurface mEGLSurface = null; 41 | 42 | //--------------------------------------------------------------------- 43 | // PUBLIC METHODS 44 | //--------------------------------------------------------------------- 45 | public InputSurface(Surface surface) { 46 | if (surface == null) { 47 | throw new NullPointerException(); 48 | } 49 | mSurface = surface; 50 | eglSetup(); 51 | } 52 | 53 | public void release() { 54 | EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface); 55 | EGL14.eglDestroyContext(mEGLDisplay, mEGLContext); 56 | EGL14.eglReleaseThread(); 57 | EGL14.eglTerminate(mEGLDisplay); 58 | 59 | mSurface.release(); 60 | 61 | mSurface = null; 62 | mEGLDisplay = null; 63 | mEGLContext = null; 64 | mEGLSurface = null; 65 | } 66 | 67 | public Surface getSurface() { 68 | return mSurface; 69 | } 70 | 71 | public void makeCurrent() { 72 | if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) { 73 | 74 | throw new RuntimeException("eglMakeCurrent failed"); 75 | } 76 | Log.i("KKK", "REC CONTEXT: " + mEGLContext.getHandle()); 77 | } 78 | 79 | public boolean swapBuffers() { 80 | return EGL14.eglSwapBuffers(mEGLDisplay, mEGLSurface); 81 | } 82 | 83 | public void setPresentationTime(long nsecs) { 84 | EGLExt.eglPresentationTimeANDROID(mEGLDisplay, mEGLSurface, nsecs); 85 | } 86 | 87 | //--------------------------------------------------------------------- 88 | // PRIVATE... 89 | //--------------------------------------------------------------------- 90 | private void eglSetup() { 91 | mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); 92 | if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) { 93 | throw new RuntimeException("unable to get EGL14 display"); 94 | } 95 | int[] version = new int[2]; 96 | if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) { 97 | mEGLDisplay = null; 98 | throw new RuntimeException("unable to initialize EGL14"); 99 | } 100 | 101 | int[] attribList = { 102 | EGL14.EGL_RED_SIZE, 8, 103 | EGL14.EGL_GREEN_SIZE, 8, 104 | EGL14.EGL_BLUE_SIZE, 8, 105 | EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, 106 | EGL_RECORDABLE_ANDROID, 1, 107 | EGL14.EGL_NONE 108 | }; 109 | EGLConfig[] configs = new EGLConfig[1]; 110 | int[] numConfigs = new int[1]; 111 | if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length, 112 | numConfigs, 0)) { 113 | throw new RuntimeException("unable to find RGB888+recordable ES2 EGL config"); 114 | } 115 | 116 | int[] attrib_list = { 117 | EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, 118 | EGL14.EGL_NONE 119 | }; 120 | Log.i("KKK", "SHARE CONTEXT: " + EGL14.eglGetCurrentContext().getHandle()); 121 | mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.eglGetCurrentContext(), 122 | attrib_list, 0); 123 | GlUtil.checkEglError("eglCreateContext"); 124 | if (mEGLContext == null) { 125 | throw new RuntimeException("null context"); 126 | } 127 | 128 | int[] surfaceAttribs = { 129 | EGL14.EGL_NONE 130 | }; 131 | mEGLSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, configs[0], mSurface, 132 | surfaceAttribs, 0); 133 | GlUtil.checkEglError("eglCreateWindowSurface"); 134 | if (mEGLSurface == null) { 135 | throw new RuntimeException("surface was null"); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /MyRecorder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 MorihiroSoft 3 | * Copyright 2013 Google Inc. All Rights Reserved. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package cc.openframeworks; 18 | 19 | import java.nio.ByteBuffer; 20 | 21 | import android.media.MediaCodec; 22 | import android.media.MediaCodecInfo; 23 | import android.media.MediaFormat; 24 | import android.media.MediaMuxer; 25 | import android.util.Log; 26 | 27 | public class MyRecorder { 28 | //--------------------------------------------------------------------- 29 | // MEMBERS 30 | //--------------------------------------------------------------------- 31 | private final VideoParam mVideoParam = VideoParam.getInstance(); 32 | 33 | private MediaCodec mMediaCodec = null; 34 | private InputSurface mInputSurface = null; 35 | private MediaCodec.BufferInfo mBufferInfo = null; 36 | private MediaMuxer mMediaMuxer = null; 37 | private int mTrackIndex = -1; 38 | private boolean mMuxerStarted = false; 39 | private int mTotalSize = 0; //TODO: DEBUG 40 | 41 | //--------------------------------------------------------------------- 42 | // PUBLIC METHODS 43 | //--------------------------------------------------------------------- 44 | public MyRecorder() { 45 | } 46 | 47 | public void prepareEncoder() { 48 | if (mMediaCodec != null || mInputSurface != null) { 49 | throw new RuntimeException("prepareEncoder called twice?"); 50 | } 51 | 52 | mBufferInfo = new MediaCodec.BufferInfo(); 53 | try { 54 | MediaFormat format = MediaFormat.createVideoFormat( 55 | mVideoParam.mMime, 56 | 800, 57 | 804); 58 | format.setInteger(MediaFormat.KEY_COLOR_FORMAT, 59 | MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); 60 | format.setInteger(MediaFormat.KEY_BIT_RATE, mVideoParam.mBps); 61 | format.setInteger(MediaFormat.KEY_FRAME_RATE, mVideoParam.getMaxFps()); 62 | format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, mVideoParam.mIfi); 63 | 64 | mMediaCodec = MediaCodec.createEncoderByType(mVideoParam.mMime); 65 | mMediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 66 | 67 | mMediaMuxer = new MediaMuxer(mVideoParam.mOutput, 68 | MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); 69 | mMuxerStarted = false; 70 | } catch (Exception e) { 71 | releaseEncoder(); 72 | throw (RuntimeException)e; 73 | } 74 | } 75 | 76 | public boolean firstTimeSetup() { 77 | if (!isRecording() || mInputSurface != null) { 78 | return false; 79 | } 80 | try { 81 | mInputSurface = new InputSurface(mMediaCodec.createInputSurface()); 82 | mMediaCodec.start(); 83 | Log.i("UUU", "MEDIACODEC START"); 84 | } catch (Exception e) { 85 | releaseEncoder(); 86 | throw (RuntimeException)e; 87 | } 88 | return true; 89 | } 90 | 91 | public boolean isRecording() { 92 | return mMediaCodec != null; 93 | } 94 | 95 | public void makeCurrent() { 96 | mInputSurface.makeCurrent(); 97 | } 98 | 99 | synchronized public void swapBuffers() { 100 | // Log.i("KKK", "SWP BUFF"); 101 | if (!isRecording()) { 102 | return; 103 | } 104 | drainEncoder(false); 105 | mInputSurface.swapBuffers(); 106 | mInputSurface.setPresentationTime(System.nanoTime()); 107 | } 108 | 109 | synchronized public void stop() { 110 | drainEncoder(true); 111 | releaseEncoder(); 112 | } 113 | 114 | //--------------------------------------------------------------------- 115 | // PRIVATE... 116 | //--------------------------------------------------------------------- 117 | private void releaseEncoder() { 118 | if (mMediaCodec != null) { 119 | mMediaCodec.stop(); 120 | mMediaCodec.release(); 121 | mMediaCodec = null; 122 | } 123 | if (mInputSurface != null) { 124 | mInputSurface.release(); 125 | mInputSurface = null; 126 | } 127 | if (mMediaMuxer != null) { 128 | mMediaMuxer.stop(); 129 | mMediaMuxer.release(); 130 | mMediaMuxer = null; 131 | } 132 | } 133 | 134 | private void drainEncoder(boolean endOfStream) { 135 | if (endOfStream) { 136 | mMediaCodec.signalEndOfInputStream(); 137 | } 138 | ByteBuffer[] encoderOutputBuffers = mMediaCodec.getOutputBuffers(); 139 | while (true) { 140 | int encoderStatus = mMediaCodec.dequeueOutputBuffer(mBufferInfo, 0); 141 | if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { 142 | if (!endOfStream) { 143 | break; 144 | } 145 | } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { 146 | encoderOutputBuffers = mMediaCodec.getOutputBuffers(); 147 | } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 148 | if (mMuxerStarted) { 149 | throw new RuntimeException("format changed twice"); 150 | } 151 | MediaFormat newFormat = mMediaCodec.getOutputFormat(); 152 | mTrackIndex = mMediaMuxer.addTrack(newFormat); 153 | mMediaMuxer.start(); 154 | mMuxerStarted = true; 155 | } else { 156 | ByteBuffer encodedData = encoderOutputBuffers[encoderStatus]; 157 | if (encodedData == null) { 158 | throw new RuntimeException("encoderOutputBuffer " + encoderStatus + " was null"); 159 | } 160 | if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { 161 | mBufferInfo.size = 0; 162 | } 163 | if (mBufferInfo.size != 0) { 164 | if (!mMuxerStarted) { 165 | throw new RuntimeException("muxer hasn't started"); 166 | } 167 | encodedData.position(mBufferInfo.offset); 168 | encodedData.limit(mBufferInfo.offset + mBufferInfo.size); 169 | 170 | boolean calc_time = true; //TODO: DEBUG 171 | if (calc_time) { 172 | long t0 = System.currentTimeMillis(); 173 | mMediaMuxer.writeSampleData(mTrackIndex, encodedData, mBufferInfo); 174 | mTotalSize += mBufferInfo.size; 175 | long dt = System.currentTimeMillis() - t0; 176 | if (dt>50) Log.e("DEBUG", String.format("XXX: dt=%d, size=%.2f",dt,(float)mTotalSize/1024/1024)); 177 | } else { 178 | mMediaMuxer.writeSampleData(mTrackIndex, encodedData, mBufferInfo); 179 | } 180 | } 181 | mMediaCodec.releaseOutputBuffer(encoderStatus, false); 182 | if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 183 | break; 184 | } 185 | } 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /OFAndroidWindow.java: -------------------------------------------------------------------------------- 1 | package cc.openframeworks; 2 | 3 | import javax.microedition.khronos.egl.EGL10; 4 | import javax.microedition.khronos.egl.EGLConfig; 5 | import javax.microedition.khronos.egl.EGLDisplay; 6 | import javax.microedition.khronos.opengles.GL; 7 | import javax.microedition.khronos.opengles.GL10; 8 | 9 | import android.content.Context; 10 | import android.graphics.PixelFormat; 11 | import android.opengl.GLSurfaceView; 12 | import android.util.AttributeSet; 13 | import android.util.Log; 14 | import android.view.SurfaceHolder; 15 | 16 | //import com.intel.inde.mp.GLCapture; 17 | //import com.intel.inde.mp.IProgressListener; 18 | //import com.intel.inde.mp.android.AndroidMediaObjectFactory; 19 | //import com.intel.inde.mp.android.VideoFormatAndroid; 20 | 21 | import java.io.File; 22 | import java.io.IOException; 23 | 24 | class OFEGLConfigChooser implements GLSurfaceView.EGLConfigChooser { 25 | 26 | public OFEGLConfigChooser(int r, int g, int b, int a, int depth, int stencil) { 27 | mRedSize = r; 28 | mGreenSize = g; 29 | mBlueSize = b; 30 | mAlphaSize = a; 31 | mDepthSize = depth; 32 | mStencilSize = stencil; 33 | } 34 | 35 | public static void setGLESVersion(int version){ 36 | if(version==1) EGL_OPENGL_ES_BIT=1; 37 | else EGL_OPENGL_ES_BIT=4; 38 | } 39 | 40 | public static int getGLESVersion(){ 41 | return EGL_OPENGL_ES_BIT==1?1:2; 42 | } 43 | 44 | /* This EGL config specification is used to specify 1.x rendering. 45 | * We use a minimum size of 4 bits for red/green/blue, but will 46 | * perform actual matching in chooseConfig() below. 47 | */ 48 | private static boolean DEBUG = false; 49 | private static int EGL_OPENGL_ES_BIT = 1; 50 | private static int[] s_configAttribs2 = 51 | { 52 | EGL10.EGL_RED_SIZE, 4, 53 | EGL10.EGL_GREEN_SIZE, 4, 54 | EGL10.EGL_BLUE_SIZE, 4, 55 | EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES_BIT, 56 | EGL10.EGL_NONE 57 | }; 58 | 59 | public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { 60 | 61 | /* Get the number of minimally matching EGL configurations 62 | */ 63 | int[] num_config = new int[1]; 64 | egl.eglChooseConfig(display, s_configAttribs2, null, 0, num_config); 65 | 66 | int numConfigs = num_config[0]; 67 | 68 | if (numConfigs <= 0) { 69 | throw new IllegalArgumentException("No configs match configSpec"); 70 | } 71 | 72 | /* Allocate then read the array of minimally matching EGL configs 73 | */ 74 | EGLConfig[] configs = new EGLConfig[numConfigs]; 75 | egl.eglChooseConfig(display, s_configAttribs2, configs, numConfigs, num_config); 76 | 77 | if (DEBUG) { 78 | printConfigs(egl, display, configs); 79 | } 80 | /* Now return the "best" one 81 | */ 82 | return chooseConfig(egl, display, configs); 83 | } 84 | 85 | public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, 86 | EGLConfig[] configs) { 87 | for(EGLConfig config : configs) { 88 | int d = findConfigAttrib(egl, display, config, 89 | EGL10.EGL_DEPTH_SIZE, 0); 90 | int s = findConfigAttrib(egl, display, config, 91 | EGL10.EGL_STENCIL_SIZE, 0); 92 | 93 | // We need at least mDepthSize and mStencilSize bits 94 | if (d < mDepthSize || s < mStencilSize) 95 | continue; 96 | 97 | // We want an *exact* match for red/green/blue/alpha 98 | int r = findConfigAttrib(egl, display, config, 99 | EGL10.EGL_RED_SIZE, 0); 100 | int g = findConfigAttrib(egl, display, config, 101 | EGL10.EGL_GREEN_SIZE, 0); 102 | int b = findConfigAttrib(egl, display, config, 103 | EGL10.EGL_BLUE_SIZE, 0); 104 | int a = findConfigAttrib(egl, display, config, 105 | EGL10.EGL_ALPHA_SIZE, 0); 106 | 107 | if (r == mRedSize && g == mGreenSize && b == mBlueSize && a == mAlphaSize) 108 | return config; 109 | } 110 | return null; 111 | } 112 | 113 | private int findConfigAttrib(EGL10 egl, EGLDisplay display, 114 | EGLConfig config, int attribute, int defaultValue) { 115 | 116 | if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) { 117 | return mValue[0]; 118 | } 119 | return defaultValue; 120 | } 121 | 122 | private void printConfigs(EGL10 egl, EGLDisplay display, 123 | EGLConfig[] configs) { 124 | int numConfigs = configs.length; 125 | Log.w("OF", String.format("%d configurations", numConfigs)); 126 | for (int i = 0; i < numConfigs; i++) { 127 | Log.w("OF", String.format("Configuration %d:\n", i)); 128 | printConfig(egl, display, configs[i]); 129 | } 130 | } 131 | 132 | private void printConfig(EGL10 egl, EGLDisplay display, 133 | EGLConfig config) { 134 | int[] attributes = { 135 | EGL10.EGL_BUFFER_SIZE, 136 | EGL10.EGL_ALPHA_SIZE, 137 | EGL10.EGL_BLUE_SIZE, 138 | EGL10.EGL_GREEN_SIZE, 139 | EGL10.EGL_RED_SIZE, 140 | EGL10.EGL_DEPTH_SIZE, 141 | EGL10.EGL_STENCIL_SIZE, 142 | EGL10.EGL_CONFIG_CAVEAT, 143 | EGL10.EGL_CONFIG_ID, 144 | EGL10.EGL_LEVEL, 145 | EGL10.EGL_MAX_PBUFFER_HEIGHT, 146 | EGL10.EGL_MAX_PBUFFER_PIXELS, 147 | EGL10.EGL_MAX_PBUFFER_WIDTH, 148 | EGL10.EGL_NATIVE_RENDERABLE, 149 | EGL10.EGL_NATIVE_VISUAL_ID, 150 | EGL10.EGL_NATIVE_VISUAL_TYPE, 151 | 0x3030, // EGL10.EGL_PRESERVED_RESOURCES, 152 | EGL10.EGL_SAMPLES, 153 | EGL10.EGL_SAMPLE_BUFFERS, 154 | EGL10.EGL_SURFACE_TYPE, 155 | EGL10.EGL_TRANSPARENT_TYPE, 156 | EGL10.EGL_TRANSPARENT_RED_VALUE, 157 | EGL10.EGL_TRANSPARENT_GREEN_VALUE, 158 | EGL10.EGL_TRANSPARENT_BLUE_VALUE, 159 | 0x3039, // EGL10.EGL_BIND_TO_TEXTURE_RGB, 160 | 0x303A, // EGL10.EGL_BIND_TO_TEXTURE_RGBA, 161 | 0x303B, // EGL10.EGL_MIN_SWAP_INTERVAL, 162 | 0x303C, // EGL10.EGL_MAX_SWAP_INTERVAL, 163 | EGL10.EGL_LUMINANCE_SIZE, 164 | EGL10.EGL_ALPHA_MASK_SIZE, 165 | EGL10.EGL_COLOR_BUFFER_TYPE, 166 | EGL10.EGL_RENDERABLE_TYPE, 167 | 0x3042 // EGL10.EGL_CONFORMANT 168 | }; 169 | String[] names = { 170 | "EGL_BUFFER_SIZE", 171 | "EGL_ALPHA_SIZE", 172 | "EGL_BLUE_SIZE", 173 | "EGL_GREEN_SIZE", 174 | "EGL_RED_SIZE", 175 | "EGL_DEPTH_SIZE", 176 | "EGL_STENCIL_SIZE", 177 | "EGL_CONFIG_CAVEAT", 178 | "EGL_CONFIG_ID", 179 | "EGL_LEVEL", 180 | "EGL_MAX_PBUFFER_HEIGHT", 181 | "EGL_MAX_PBUFFER_PIXELS", 182 | "EGL_MAX_PBUFFER_WIDTH", 183 | "EGL_NATIVE_RENDERABLE", 184 | "EGL_NATIVE_VISUAL_ID", 185 | "EGL_NATIVE_VISUAL_TYPE", 186 | "EGL_PRESERVED_RESOURCES", 187 | "EGL_SAMPLES", 188 | "EGL_SAMPLE_BUFFERS", 189 | "EGL_SURFACE_TYPE", 190 | "EGL_TRANSPARENT_TYPE", 191 | "EGL_TRANSPARENT_RED_VALUE", 192 | "EGL_TRANSPARENT_GREEN_VALUE", 193 | "EGL_TRANSPARENT_BLUE_VALUE", 194 | "EGL_BIND_TO_TEXTURE_RGB", 195 | "EGL_BIND_TO_TEXTURE_RGBA", 196 | "EGL_MIN_SWAP_INTERVAL", 197 | "EGL_MAX_SWAP_INTERVAL", 198 | "EGL_LUMINANCE_SIZE", 199 | "EGL_ALPHA_MASK_SIZE", 200 | "EGL_COLOR_BUFFER_TYPE", 201 | "EGL_RENDERABLE_TYPE", 202 | "EGL_CONFORMANT" 203 | }; 204 | int[] value = new int[1]; 205 | for (int i = 0; i < attributes.length; i++) { 206 | int attribute = attributes[i]; 207 | String name = names[i]; 208 | if ( egl.eglGetConfigAttrib(display, config, attribute, value)) { 209 | Log.w("OF", String.format(" %s: %d\n", name, value[0])); 210 | } else { 211 | // Log.w(TAG, String.format(" %s: failed\n", name)); 212 | while (egl.eglGetError() != EGL10.EGL_SUCCESS); 213 | } 214 | } 215 | } 216 | 217 | // Subclasses can adjust these values: 218 | protected int mRedSize; 219 | protected int mGreenSize; 220 | protected int mBlueSize; 221 | protected int mAlphaSize; 222 | protected int mDepthSize; 223 | protected int mStencilSize; 224 | private int[] mValue = new int[1]; 225 | } 226 | 227 | class OFAndroidWindow implements GLSurfaceView.Renderer { 228 | 229 | public OFAndroidWindow(int w, int h){ 230 | this.w = w; 231 | this.h = h; 232 | } 233 | 234 | private OFGLSurfaceView mView = null; 235 | 236 | // RECORDING 237 | private RenderSrfTex mRenderSrfTex = null; 238 | int texID = 0; 239 | // END 240 | 241 | 242 | public void setRecorder(MyRecorder recorder, int myTexID) { 243 | texID = myTexID; 244 | synchronized(this) { 245 | if (recorder != null) { 246 | mRenderSrfTex = new RenderSrfTex( 247 | 800, 804, 248 | myTexID, recorder); 249 | } else { 250 | mRenderSrfTex = null; 251 | } 252 | } 253 | } 254 | 255 | @Override 256 | public void onSurfaceCreated(GL10 gl, EGLConfig config) { 257 | Log.i("OF","onSurfaceCreated"); 258 | OFAndroid.onSurfaceCreated(); 259 | try{ 260 | ((OFActivity)OFAndroid.getContext()).onGLSurfaceCreated(); 261 | }catch(Exception e){ 262 | Log.e("OF","couldn call onGLSurfaceCreated",e); 263 | } 264 | return; 265 | 266 | } 267 | 268 | @Override 269 | public void onSurfaceChanged(GL10 gl, int w, int h) { 270 | this.w = w; 271 | this.h = h; 272 | if(!setup && OFAndroid.unpackingDone){ 273 | setup(); 274 | } 275 | OFGestureListener.swipe_Min_Distance = (int)(Math.max(w, h)*.04); 276 | OFGestureListener.swipe_Max_Distance = (int)(Math.max(w, h)*.6); 277 | OFAndroid.resize(w, h); 278 | } 279 | 280 | private void setup(){ 281 | OFAndroid.setup(w,h); 282 | setup = true; 283 | android.os.Process.setThreadPriority(8); 284 | OFGestureListener.swipe_Min_Distance = (int)(Math.max(w, h)*.04); 285 | OFGestureListener.swipe_Max_Distance = (int)(Math.max(w, h)*.6); 286 | try{ 287 | ((OFActivity)OFAndroid.getContext()).onGLSurfaceCreated(); 288 | }catch(Exception e){ 289 | Log.e("OF","couldn call onGLSurfaceCreated",e); 290 | } 291 | 292 | } 293 | 294 | @Override 295 | public void onDrawFrame(GL10 gl) { 296 | // cntRec++; 297 | if(setup && OFAndroid.unpackingDone){ 298 | OFAndroid.render(); 299 | 300 | // GlUtil.checkGlError("onDrawFrame_E RENDER OF"); 301 | 302 | if (mRenderSrfTex != null) { 303 | mRenderSrfTex.draw(); 304 | } 305 | 306 | // GlUtil.checkGlError("onDrawFrame_E RENDER SRF TEX"); 307 | 308 | }else if(!setup && OFAndroid.unpackingDone){ 309 | setup(); 310 | }else{ 311 | gl.glClear(GL10.GL_COLOR_BUFFER_BIT); 312 | gl.glClearColor(.5f, .5f, .5f, 1.f); 313 | } 314 | } 315 | 316 | public boolean isSetup(){ 317 | return setup; 318 | } 319 | 320 | private static boolean setup; 321 | private int w,h; 322 | 323 | } -------------------------------------------------------------------------------- /OFGLSurfaceView.java: -------------------------------------------------------------------------------- 1 | package cc.openframeworks; 2 | 3 | import android.content.Context; 4 | import android.graphics.PixelFormat; 5 | import android.opengl.EGL14; 6 | import android.opengl.EGLSurface; 7 | import android.opengl.GLSurfaceView; 8 | import android.util.AttributeSet; 9 | import android.util.Log; 10 | import android.view.Surface; 11 | import android.view.SurfaceHolder; 12 | 13 | public class OFGLSurfaceView extends GLSurfaceView { 14 | //public class OFGLSurfaceView { // extends GLSurfaceView { 15 | 16 | public OFEGLConfigChooser configChooser; 17 | 18 | private MyRecorder mRecorder = null; 19 | 20 | private int myTexID = -1; 21 | 22 | public void prepareRec(int texID) { 23 | 24 | myTexID = texID; 25 | } 26 | 27 | public void startVideo() { 28 | if (mRecorder == null) { 29 | mRecorder = new MyRecorder(); 30 | mRecorder.prepareEncoder(); 31 | mRenderer.setRecorder(mRecorder, myTexID); 32 | } 33 | } 34 | 35 | public void stopVideo() { 36 | if (mRecorder != null) { 37 | mRecorder.stop(); 38 | mRecorder = null; 39 | mRenderer.setRecorder(null, 0); 40 | } 41 | } 42 | 43 | public OFGLSurfaceView(Context context) { 44 | super(context); 45 | mRenderer = new OFAndroidWindow(getWidth(),getHeight(), this); 46 | getHolder().setFormat( PixelFormat.OPAQUE ); 47 | 48 | if(OFEGLConfigChooser.getGLESVersion()==2){ 49 | Log.i("XXX", "HERE"); 50 | setEGLContextClientVersion(2); 51 | } 52 | configChooser = new OFEGLConfigChooser(5,6,5,0,16,0); 53 | //setEGLConfigChooser(configChooser); 54 | setRenderer(mRenderer); 55 | } 56 | 57 | public OFGLSurfaceView(Context context,AttributeSet attributes) { 58 | super(context,attributes); 59 | mRenderer = new OFAndroidWindow(getWidth(),getHeight(), this); 60 | getHolder().setFormat( PixelFormat.OPAQUE ); 61 | if(OFEGLConfigChooser.getGLESVersion()==2){ 62 | Log.i("XXX", "THERE"); 63 | setEGLContextClientVersion(2); 64 | } 65 | // OFEGLConfigChooser configChooser = new OFEGLConfigChooser(5,6,5,0,16,0); 66 | configChooser = new OFEGLConfigChooser(8, 8, 8, 8, 16, 0); 67 | 68 | setEGLConfigChooser(configChooser); // XXX TRICK 69 | 70 | setRenderer(mRenderer); 71 | 72 | //xx 73 | //setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); 74 | } 75 | 76 | @Override 77 | public void surfaceDestroyed(SurfaceHolder holder) { 78 | super.surfaceDestroyed(holder); 79 | OFAndroid.onSurfaceDestroyed(); 80 | } 81 | 82 | boolean isSetup(){ 83 | return mRenderer.isSetup(); 84 | } 85 | 86 | public OFAndroidWindow mRenderer; 87 | 88 | public void setSurface(Surface _surf) { 89 | // getHolder().getSurface() = surf; 90 | // surf = _surf; 91 | } 92 | 93 | public void setup() { 94 | // mRenderer = new OFAndroidWindow(getWidth(),getHeight()); 95 | // surf.setFormat(PixelFormat.OPAQUE); 96 | // 97 | // if(OFEGLConfigChooser.getGLESVersion()==2){ 98 | // Log.i("XXX", "HERE"); 99 | // setEGLContextClientVersion(2); 100 | // } 101 | // OFEGLConfigChooser configChooser = new OFEGLConfigChooser(5,6,5,0,16,0); 102 | // //setEGLConfigChooser(configChooser); 103 | // setRenderer(mRenderer); 104 | } 105 | 106 | public Surface getSurface() { 107 | return getHolder().getSurface(); 108 | } 109 | 110 | public EGLSurface getEGLSurface() { 111 | return EGL14.eglGetCurrentSurface(0); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # openFramework-Android-Recording 2 | The files needed to get recording of an ofFbo in openFrameworks via MediaCodec 3 | 4 | These are the various files involved to get real time recording of an ofFbo 5 | passing the GLUint of the fbo's texture to android sdk MediaCodec. 6 | 7 | To use it, with android 0.8.4 android version, you have to: 8 | 9 | - add the classes RenderSrfTex, InputSurface, VideoParam and MyRecorder to your project 10 | - change the ofAndroidWindow.java class with the one from this git, or merge 11 | it manually 12 | - to start the recording put somewhere in your ofActivity : 13 | 14 | ofApp.mGLView.prepareRec(getTexID()); 15 | ofApp.mGLView.startVideo(); 16 | 17 | and to stop it: 18 | 19 | ofApp.mGLView.stopVideo(); 20 | 21 | - add to your ofApp.cpp this jni function: 22 | 23 | JNIEXPORT jint Java_cc_openframeworks_YOURAPPNAME_OFActivity_getTexID( 24 | JNIEnv * env, jobject obj) 25 | { 26 | ofLog() << "SENDING TEXID TO JAVA: " << texID; 27 | return texID; 28 | } 29 | 30 | - then assign to texID the textureID of the ofFbo you want to record, like: 31 | texID = fbo.getTextureReference().getTextureData().textureID; 32 | 33 | 34 | Hope that this can help somebody who needs to record real time clips on Android, 35 | for me understanding all of this was one of the hard times ever X). 36 | 37 | 38 | 39 | 40 | 41 | TODO 42 | 43 | - get rid of the VideoParam class 44 | 45 | 46 | 47 | CREDITS 48 | 49 | - long life to OF, and thanks to Arturo Castro that pointed me in studying the way in which OF 50 | sends textures to java in ofVideoPlayer.cpp android version 51 | 52 | - the classes are from this super useful example: https://github.com/MorihiroSoft/Android_MediaCodecTest 53 | 54 | - and finally thanks to fadden (http://www.fadden.com/), the biggest source of MediaCodec related info, 55 | for all the great articles and examples 56 | -------------------------------------------------------------------------------- /RenderSrfTex.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 MorihiroSoft 3 | * Copyright 2013 Google Inc. All Rights Reserved. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package cc.openframeworks; 18 | 19 | import java.nio.FloatBuffer; 20 | 21 | import android.opengl.EGL14; 22 | import android.opengl.EGLContext; 23 | import android.opengl.EGLDisplay; 24 | import android.opengl.EGLSurface; 25 | import android.opengl.GLES20; 26 | import android.util.Log; 27 | 28 | class RenderSrfTex { 29 | //--------------------------------------------------------------------- 30 | // MEMBERS 31 | //--------------------------------------------------------------------- 32 | private final FloatBuffer mVtxBuf = GlUtil.createSquareVtx(); 33 | 34 | private final int mFboTexW; 35 | private final int mFboTexH; 36 | private final int mFboTexId; 37 | private final MyRecorder mRecorder; 38 | 39 | private int mProgram = -1; 40 | private int maPositionHandle = -1; 41 | private int maTexCoordHandle = -1; 42 | private int muSamplerHandle = -1; 43 | 44 | private EGLDisplay mSavedEglDisplay = null; 45 | private EGLSurface mSavedEglDrawSurface = null; 46 | private EGLSurface mSavedEglReadSurface = null; 47 | private EGLContext mSavedEglContext = null; 48 | 49 | //--------------------------------------------------------------------- 50 | // PUBLIC METHODS 51 | //--------------------------------------------------------------------- 52 | public RenderSrfTex(int w, int h, int id, MyRecorder recorder) { 53 | mFboTexW = w; 54 | mFboTexH = h; 55 | mFboTexId = id; 56 | mRecorder = recorder; 57 | } 58 | 59 | public void draw() { 60 | saveRenderState(); 61 | { 62 | GlUtil.checkGlError("draw_S"); 63 | 64 | if (mRecorder.firstTimeSetup()) { 65 | mRecorder.makeCurrent(); 66 | initGL(); 67 | } else { 68 | mRecorder.makeCurrent(); 69 | } 70 | 71 | GLES20.glViewport(0, 0, mFboTexW, mFboTexH); 72 | 73 | GLES20.glClearColor(0f, 0f, 1f, 1f); 74 | GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT); 75 | 76 | GLES20.glUseProgram(mProgram); 77 | 78 | mVtxBuf.position(0); 79 | GLES20.glVertexAttribPointer(maPositionHandle, 80 | 3, GLES20.GL_FLOAT, false, 4*(3+2), mVtxBuf); 81 | GLES20.glEnableVertexAttribArray(maPositionHandle); 82 | 83 | mVtxBuf.position(3); 84 | GLES20.glVertexAttribPointer(maTexCoordHandle, 85 | 2, GLES20.GL_FLOAT, false, 4*(3+2), mVtxBuf); 86 | GLES20.glEnableVertexAttribArray(maTexCoordHandle); 87 | 88 | GLES20.glUniform1i(muSamplerHandle, 0); 89 | 90 | GLES20.glActiveTexture(GLES20.GL_TEXTURE0); 91 | 92 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mFboTexId); 93 | 94 | GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); 95 | 96 | mRecorder.swapBuffers(); 97 | 98 | GlUtil.checkGlError("draw_E"); 99 | } 100 | restoreRenderState(); 101 | } 102 | 103 | //--------------------------------------------------------------------- 104 | // PRIVATE... 105 | //--------------------------------------------------------------------- 106 | private void initGL() { 107 | GlUtil.checkGlError("initGL_S"); 108 | 109 | final String vertexShader = 110 | // 111 | "attribute vec4 aPosition;\n" + 112 | "attribute vec4 aTexCoord;\n" + 113 | "varying vec2 vTexCoord;\n" + 114 | "void main() {\n" + 115 | " gl_Position = aPosition;\n" + 116 | " vTexCoord = aTexCoord.xy;\n" + 117 | "}\n"; 118 | final String fragmentShader = 119 | // 120 | "precision mediump float;\n" + 121 | "uniform sampler2D uSampler;\n" + 122 | "varying vec2 vTexCoord;\n" + 123 | "void main() {\n" + 124 | " gl_FragColor = texture2D(uSampler, vTexCoord);\n" + 125 | // " gl_FragColor = vec4(1,0,0,1);\n" + 126 | "}\n"; 127 | mProgram = GlUtil.createProgram(vertexShader, fragmentShader); 128 | maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition"); 129 | maTexCoordHandle = GLES20.glGetAttribLocation(mProgram, "aTexCoord"); 130 | muSamplerHandle = GLES20.glGetUniformLocation(mProgram, "uSampler"); 131 | 132 | GLES20.glDisable(GLES20.GL_DEPTH_TEST); 133 | GLES20.glDisable(GLES20.GL_CULL_FACE); 134 | GLES20.glDisable(GLES20.GL_BLEND); 135 | 136 | GlUtil.checkGlError("initGL_E"); 137 | } 138 | 139 | private void saveRenderState() { 140 | mSavedEglDisplay = EGL14.eglGetCurrentDisplay(); 141 | mSavedEglDrawSurface = EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW); 142 | mSavedEglReadSurface = EGL14.eglGetCurrentSurface(EGL14.EGL_READ); 143 | mSavedEglContext = EGL14.eglGetCurrentContext(); 144 | 145 | Log.i("KKK", "OF CONTEXT: "+mSavedEglContext.getHandle()); 146 | } 147 | 148 | private void restoreRenderState() { 149 | if (!EGL14.eglMakeCurrent( 150 | mSavedEglDisplay, 151 | mSavedEglDrawSurface, 152 | mSavedEglReadSurface, 153 | mSavedEglContext)) { 154 | throw new RuntimeException("eglMakeCurrent failed"); 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /VideoParam.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 MorihiroSoft 3 | * Copyright 2013 Google Inc. All Rights Reserved. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package cc.openframeworks; 18 | 19 | import android.graphics.ImageFormat; 20 | import android.media.MediaCodecInfo; 21 | import android.media.MediaCodecList; 22 | import android.os.Environment; 23 | import android.util.Log; 24 | 25 | public class VideoParam { 26 | private static final boolean DEBUG = true; 27 | private static final String TAG = "VideoParam"; 28 | 29 | //--------------------------------------------------------------------- 30 | // CONSTANTS 31 | //--------------------------------------------------------------------- 32 | private static final int VIDEO_W = 800; 33 | private static final int VIDEO_H = 804; 34 | private static final int FPS = 30; 35 | private static final String MIME = "video/avc"; 36 | private static final int BPS = 4*1024*1024; 37 | private static final int IFI = 5; 38 | private static final String SDCARD = Environment.getExternalStorageDirectory().getPath(); 39 | private static final String OUTPUT = SDCARD + "/video.mp4"; 40 | 41 | //--------------------------------------------------------------------- 42 | // MEMBERS 43 | //--------------------------------------------------------------------- 44 | // public final int mCameraId; 45 | // public final boolean mFacingFront; 46 | // public final Size mSize; 47 | // public final int[] mFpsRange; 48 | public final String mMime; 49 | public final int mBps = BPS; 50 | public final int mIfi = IFI; 51 | public final String mOutput = OUTPUT; 52 | 53 | //--------------------------------------------------------------------- 54 | // SINGLETON 55 | //--------------------------------------------------------------------- 56 | private static volatile VideoParam sInstance = null; 57 | private static final Object sSyncObj = new Object(); 58 | 59 | public static VideoParam getInstance() { 60 | if (sInstance == null) { 61 | synchronized (sSyncObj) { 62 | if (sInstance == null) { 63 | sInstance = new VideoParam(); 64 | } 65 | } 66 | } 67 | return sInstance; 68 | } 69 | 70 | private VideoParam() { 71 | // Id 72 | 73 | // MIME 74 | String mime = null; 75 | int codec_num = MediaCodecList.getCodecCount(); 76 | for (int i=0; i