package com.almalence.plugins.capture.video; import java.io.File; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.util.Locale; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; import com.almalence.util.FpsMeasurer; import android.content.ContentValues; import android.graphics.SurfaceTexture; import android.opengl.EGL14; import android.opengl.GLES20; import android.provider.MediaStore.Video; import android.provider.MediaStore.Video.VideoColumns; import android.util.Log; import android.widget.Toast; /* <!-- +++ import com.almalence.opencam_plus.ApplicationScreen; import com.almalence.opencam_plus.cameracontroller.CameraController; import com.almalence.opencam_plus.ui.EglEncoder; +++ --> */ //<!-- -+- import com.almalence.opencam.ApplicationScreen; import com.almalence.opencam.cameracontroller.CameraController; import com.almalence.opencam.ui.EglEncoder; //-+- --> public class DROVideoEngine { private static final String TAG = "Almalence"; private static final int GL_TEXTURE_EXTERNAL_OES = 0x00008d65; private static final String SHADER_VERTEX = "attribute vec2 vPosition;\n" + "attribute vec2 vTexCoord;\n" + "varying vec2 texCoord;\n" + "void main() {\n" + " texCoord = vTexCoord;\n" + " gl_Position = vec4 ( vPosition.x, vPosition.y, 1.0, 1.0 );\n" + "}"; private static final String SHADER_FRAGMENT = "#extension GL_OES_EGL_image_external:enable\n" + "precision mediump float;\n" + "uniform samplerExternalOES sTexture;\n" + "varying vec2 texCoord;\n" + "void main() {\n" + " gl_FragColor = texture2D(sTexture, texCoord);\n" + "}"; private static final FloatBuffer VERTEX_BUFFER; private static final FloatBuffer UV_BUFFER; static { final float[] vtmp = { 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, -1.0f }; VERTEX_BUFFER = ByteBuffer.allocateDirect(8 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer(); VERTEX_BUFFER.put(vtmp); VERTEX_BUFFER.position(0); final float[] ttmp = { 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f }; UV_BUFFER = ByteBuffer.allocateDirect(8 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer(); UV_BUFFER.put(ttmp); UV_BUFFER.position(0); } private static int loadShader(final String vss, final String fss) { int vshader = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER); GLES20.glShaderSource(vshader, vss); GLES20.glCompileShader(vshader); final int[] compiled = new int[1]; GLES20.glGetShaderiv(vshader, GLES20.GL_COMPILE_STATUS, compiled, 0); if (compiled[0] == 0) { Log.d(TAG, "Could not compile vertex shader: " + GLES20.glGetShaderInfoLog(vshader)); GLES20.glDeleteShader(vshader); vshader = 0; } int fshader = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER); GLES20.glShaderSource(fshader, fss); GLES20.glCompileShader(fshader); GLES20.glGetShaderiv(fshader, GLES20.GL_COMPILE_STATUS, compiled, 0); if (compiled[0] == 0) { Log.d(TAG, "Could not compile fragment shader: " + GLES20.glGetShaderInfoLog(fshader)); GLES20.glDeleteShader(fshader); fshader = 0; } final int program = GLES20.glCreateProgram(); GLES20.glAttachShader(program, vshader); GLES20.glAttachShader(program, fshader); GLES20.glLinkProgram(program); return program; } private final Object stateSync = new Object(); private int instance = 0; private int previewWidth = -1; private int previewHeight = -1; private volatile boolean local = true; private volatile boolean forceUpdate = false; private volatile int uv_desat = 9; private volatile int dark_uv_desat = 5; private volatile float dark_noise_pass = 0.45f; private volatile float mix_factor = 0.1f; private volatile float gamma = 0.65f; // 0.5f; private volatile float max_black_level = 64.0f; private volatile float black_level_atten = 0.5f; private volatile float max_amplify = 2.0f; private volatile float[] min_limit = new float[] { 0.5f, 0.5f, 0.5f }; private volatile float[] max_limit = new float[] { 3.0f, 2.0f, 2.0f }; private final float[] transform = new float[16]; private volatile boolean filled = false; private EglEncoder encoder = null; private FpsMeasurer fps = new FpsMeasurer(5); private volatile long recordingDelayed; private boolean paused; public DROVideoEngine() { } public void startRecording(final String path, final long delay) { this.recordingDelayed = System.currentTimeMillis() + delay; final Object sync = new Object(); synchronized (sync) { ApplicationScreen.instance.queueGLEvent(new Runnable() { @Override public void run() { synchronized (DROVideoEngine.this.stateSync) { if (DROVideoEngine.this.encoder == null && DROVideoEngine.this.instance != 0) { DROVideoEngine.this.paused = false; try { int orientation = 0; boolean cameraMirrored = CameraController.isFrontCamera(); int sensorOrientation = CameraController.getSensorOrientation(cameraMirrored); switch(sensorOrientation) { case 90: { if(cameraMirrored) orientation = 180; } break; case 270: { if(!cameraMirrored) orientation = 180; } break; } DROVideoEngine.this.encoder = new EglEncoder(path, DROVideoEngine.this.previewWidth, DROVideoEngine.this.previewHeight, 24, 20000000, (ApplicationScreen.getGUIManager().getImageDataOrientation() + orientation)%360, EGL14.eglGetCurrentContext()); } catch (RuntimeException e) { e.printStackTrace(); ApplicationScreen.instance.runOnUiThread(new Runnable() { public void run() { Toast.makeText(ApplicationScreen.instance, "Can't record HDR Video. MediaMuxer creation failed.", Toast.LENGTH_LONG).show(); } }); } } } synchronized (sync) { sync.notify(); } } }); try { sync.wait(); } catch (final InterruptedException e) { Thread.currentThread().interrupt(); } } } public void stopRecording() { final Object sync = new Object(); synchronized (sync) { ApplicationScreen.instance.queueGLEvent(new Runnable() { @Override public void run() { synchronized (DROVideoEngine.this.stateSync) { if (DROVideoEngine.this.encoder != null) { final String path = DROVideoEngine.this.encoder.getPath(); // DROVideoEngine.this.encoder.close(); DROVideoEngine.this.encoder.stopRecording(); DROVideoEngine.this.encoder = null; File fileSaved = new File(path); File parent = fileSaved.getParentFile(); String parentPath = parent.toString().toLowerCase(Locale.US); String parentName = parent.getName().toLowerCase(Locale.US); ContentValues values = new ContentValues(); values.put(VideoColumns.TITLE, fileSaved.getName().substring(0, fileSaved.getName().lastIndexOf("."))); values.put(VideoColumns.DISPLAY_NAME, fileSaved.getName()); values.put(VideoColumns.DATE_TAKEN, System.currentTimeMillis()); values.put(VideoColumns.MIME_TYPE, "video/mp4"); values.put(VideoColumns.BUCKET_ID, parentPath.hashCode()); values.put(VideoColumns.BUCKET_DISPLAY_NAME, parentName); values.put(VideoColumns.DATA, fileSaved.getAbsolutePath()); ApplicationScreen.instance.getContentResolver().insert(Video.Media.EXTERNAL_CONTENT_URI, values); } } synchronized (sync) { sync.notify(); } } }); try { sync.wait(); } catch (final InterruptedException e) { Thread.currentThread().interrupt(); } } } private void stopPreview() { Log.i(TAG, "DROVideoEngine.stopPreview()"); this.stopRecording(); final Object sync = new Object(); synchronized (sync) { ApplicationScreen.instance.queueGLEvent(new Runnable() { @Override public void run() { synchronized (DROVideoEngine.this.stateSync) { if (DROVideoEngine.this.instance != 0) { Log.i(TAG, "DRO instance is not null. Releasing."); RealtimeDRO.release(DROVideoEngine.this.instance); DROVideoEngine.this.instance = 0; Log.i(TAG, "DRO instance released"); } } synchronized (sync) { sync.notify(); } } }); try { sync.wait(); } catch (final InterruptedException e) { Thread.currentThread().interrupt(); } } } public void onPause() { this.stopPreview(); } public void onFrameAvailable() { this.filled = true; ApplicationScreen.instance.glRequestRender(); } public void setPaused(final boolean paused) { ApplicationScreen.instance.queueGLEvent(new Runnable() { @Override public void run() { if (!DROVideoEngine.this.paused && paused && DROVideoEngine.this.encoder != null) { DROVideoEngine.this.encoder.pause(); } DROVideoEngine.this.paused = paused; } }); } private int hProgram; private int surfaceWidth; private int surfaceHeight; private int texture_out; private void initGL() { final int[] tex = new int[1]; GLES20.glGenTextures(1, tex, 0); this.texture_out = tex[0]; GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, this.texture_out); GLES20.glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); GLES20.glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); GLES20.glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); GLES20.glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); this.hProgram = loadShader(SHADER_VERTEX, SHADER_FRAGMENT); } public void onSurfaceCreated(final GL10 gl, final EGLConfig config) { this.initGL(); DROVideoEngine.this.fps.flush(); this.filled = false; } public void onSurfaceChanged(final GL10 gl, final int width, final int height) { Log.i(TAG, String.format("DROVideoEngine.onSurfaceChanged(%dx%d)", width, height)); this.surfaceWidth = width; this.surfaceHeight = height; } public void onDrawFrame(final GL10 gl) { if (this.filled) { this.filled = false; try { final SurfaceTexture surfaceTexture = ApplicationScreen.instance.glGetSurfaceTexture(); surfaceTexture.updateTexImage(); surfaceTexture.getTransformMatrix(this.transform); } catch (final Exception e) { return; } } else { return; } synchronized (this.stateSync) { if (this.instance != 0) { if (ApplicationScreen.getPreviewWidth() != this.previewWidth || ApplicationScreen.getPreviewHeight() != this.previewHeight) { RealtimeDRO.release(this.instance); this.instance = 0; } } this.previewWidth = ApplicationScreen.getPreviewWidth(); this.previewHeight = ApplicationScreen.getPreviewHeight(); if (this.instance == 0) { this.instance = RealtimeDRO.initialize(this.previewWidth, this.previewHeight); Log.d(TAG, String.format("RealtimeDRO.initialize(%d, %d)", this.previewWidth, this.previewHeight)); this.forceUpdate = true; } if (this.instance != 0) { long t; t = System.currentTimeMillis(); RealtimeDRO.render(this.instance, ApplicationScreen.instance.glGetPreviewTexture(), this.transform, this.previewWidth, this.previewHeight, true, this.local, this.max_amplify, this.forceUpdate, this.uv_desat, this.dark_uv_desat, this.dark_noise_pass, this.mix_factor, this.gamma, this.max_black_level, this.black_level_atten, this.min_limit, this.max_limit, this.texture_out); t = System.currentTimeMillis() - t; DROVideoEngine.this.fps.measure(); if (this.encoder != null && System.currentTimeMillis() > this.recordingDelayed && !this.paused) { // this.encoder.encode(this.texture_out); int encodeTexture = this.texture_out; this.encoder.setTextureId(encodeTexture); this.encoder.frameAvailable(ApplicationScreen.instance.glGetSurfaceTexture(), true); } this.drawOutputTexture(); this.forceUpdate = false; } else { throw new RuntimeException("Unable to create DRO instance."); } } } private void drawOutputTexture() { GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); GLES20.glViewport(0, 0, this.surfaceWidth, this.surfaceHeight); GLES20.glUseProgram(this.hProgram); final int ph = GLES20.glGetAttribLocation(this.hProgram, "vPosition"); final int tch = GLES20.glGetAttribLocation(this.hProgram, "vTexCoord"); final int th = GLES20.glGetUniformLocation(this.hProgram, "sTexture"); GLES20.glActiveTexture(GLES20.GL_TEXTURE0); GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, this.texture_out); GLES20.glUniform1i(th, 0); GLES20.glVertexAttribPointer(ph, 2, GLES20.GL_FLOAT, false, 4 * 2, VERTEX_BUFFER); GLES20.glVertexAttribPointer(tch, 2, GLES20.GL_FLOAT, false, 4 * 2, UV_BUFFER); GLES20.glEnableVertexAttribArray(ph); GLES20.glEnableVertexAttribArray(tch); GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); GLES20.glDisableVertexAttribArray(ph); GLES20.glDisableVertexAttribArray(tch); } }