content: 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);
  	}
  }
  