pull/360/head
parent
c1e3cced4b
commit
4f6271d670
@ -0,0 +1,19 @@ |
|||||||
|
package com.otaliastudios.cameraview; |
||||||
|
|
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.support.test.filters.SmallTest; |
||||||
|
import android.support.test.runner.AndroidJUnit4; |
||||||
|
import android.view.ViewGroup; |
||||||
|
|
||||||
|
import org.junit.runner.RunWith; |
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class) |
||||||
|
@SmallTest |
||||||
|
public class GLCameraPreviewTest extends CameraPreviewTest { |
||||||
|
|
||||||
|
@Override |
||||||
|
protected CameraPreview createPreview(Context context, ViewGroup parent, CameraPreview.SurfaceCallback callback) { |
||||||
|
return new GLCameraPreview(context, parent, callback); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,14 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<FrameLayout |
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:layout_gravity="center" |
||||||
|
android:gravity="center"> |
||||||
|
|
||||||
|
<android.opengl.GLSurfaceView |
||||||
|
android:id="@+id/gl_surface_view" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" /> |
||||||
|
|
||||||
|
</FrameLayout> |
@ -0,0 +1,134 @@ |
|||||||
|
package com.otaliastudios.cameraview; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.graphics.SurfaceTexture; |
||||||
|
import android.opengl.GLSurfaceView; |
||||||
|
import android.support.annotation.NonNull; |
||||||
|
import android.view.LayoutInflater; |
||||||
|
import android.view.SurfaceHolder; |
||||||
|
import android.view.View; |
||||||
|
import android.view.ViewGroup; |
||||||
|
|
||||||
|
import javax.microedition.khronos.egl.EGLConfig; |
||||||
|
import javax.microedition.khronos.opengles.GL10; |
||||||
|
|
||||||
|
class GLCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture> implements GLSurfaceView.Renderer { |
||||||
|
|
||||||
|
private boolean mDispatched; |
||||||
|
private final float[] mTransformMatrix = new float[16]; |
||||||
|
private int mTextureId = -1; |
||||||
|
private SurfaceTexture mSurfaceTexture; |
||||||
|
private GLViewport mViewport; |
||||||
|
|
||||||
|
GLCameraPreview(Context context, ViewGroup parent, SurfaceCallback callback) { |
||||||
|
super(context, parent, callback); |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
@Override |
||||||
|
protected GLSurfaceView onCreateView(Context context, ViewGroup parent) { |
||||||
|
View root = LayoutInflater.from(context).inflate(R.layout.cameraview_gl_view, parent, false); |
||||||
|
parent.addView(root, 0); |
||||||
|
GLSurfaceView glView = root.findViewById(R.id.gl_surface_view); |
||||||
|
glView.setEGLContextClientVersion(2); |
||||||
|
glView.setRenderer(this); |
||||||
|
glView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); |
||||||
|
glView.getHolder().addCallback(new SurfaceHolder.Callback() { |
||||||
|
public void surfaceCreated(SurfaceHolder holder) {} |
||||||
|
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void surfaceDestroyed(SurfaceHolder holder) { |
||||||
|
onSurfaceDestroyed(); |
||||||
|
} |
||||||
|
}); |
||||||
|
return glView; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
void onResume() { |
||||||
|
super.onResume(); |
||||||
|
getView().onResume(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
void onPause() { |
||||||
|
super.onPause(); |
||||||
|
getView().onPause(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
void onDestroy() { |
||||||
|
super.onDestroy(); |
||||||
|
if (mSurfaceTexture != null) { |
||||||
|
mSurfaceTexture.release(); |
||||||
|
mSurfaceTexture = null; |
||||||
|
} |
||||||
|
if (mViewport != null) { |
||||||
|
mViewport.release(); |
||||||
|
mViewport = null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onSurfaceCreated(GL10 gl, EGLConfig config) { |
||||||
|
mViewport = new GLViewport(); |
||||||
|
mTextureId = mViewport.createTexture(); |
||||||
|
mSurfaceTexture = new SurfaceTexture(mTextureId); |
||||||
|
mSurfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() { |
||||||
|
@Override |
||||||
|
public void onFrameAvailable(SurfaceTexture surfaceTexture) { |
||||||
|
// requestRender is thread-safe.
|
||||||
|
getView().requestRender(); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onSurfaceChanged(GL10 gl, int width, int height) { |
||||||
|
if (!mDispatched) { |
||||||
|
onSurfaceAvailable(width, height); |
||||||
|
mDispatched = true; |
||||||
|
} else { |
||||||
|
onSurfaceSizeChanged(width, height); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Override |
||||||
|
public void onDrawFrame(GL10 gl) { |
||||||
|
// Latch the latest frame. If there isn't anything new,
|
||||||
|
// we'll just re-use whatever was there before.
|
||||||
|
mSurfaceTexture.updateTexImage(); |
||||||
|
if (mDesiredWidth <= 0 || mDesiredHeight <= 0) { |
||||||
|
// Skip drawing. Camera was not opened.
|
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// Draw the video frame.
|
||||||
|
mSurfaceTexture.getTransformMatrix(mTransformMatrix); |
||||||
|
mViewport.drawFrame(mTextureId, mTransformMatrix); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
Class<SurfaceTexture> getOutputClass() { |
||||||
|
return SurfaceTexture.class; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
SurfaceTexture getOutput() { |
||||||
|
return mSurfaceTexture; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Override |
||||||
|
boolean supportsCropping() { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void crop() { |
||||||
|
mCropTask.start(); |
||||||
|
mCropTask.end(null); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,92 @@ |
|||||||
|
package com.otaliastudios.cameraview; |
||||||
|
|
||||||
|
|
||||||
|
import android.opengl.GLES11Ext; |
||||||
|
import android.opengl.GLES20; |
||||||
|
import android.opengl.Matrix; |
||||||
|
|
||||||
|
import java.nio.ByteBuffer; |
||||||
|
import java.nio.ByteOrder; |
||||||
|
import java.nio.FloatBuffer; |
||||||
|
|
||||||
|
class GLElement { |
||||||
|
|
||||||
|
private final static CameraLogger LOG = CameraLogger.create(GLElement.class.getSimpleName()); |
||||||
|
|
||||||
|
// Identity matrix for general use.
|
||||||
|
protected static final float[] IDENTITY_MATRIX = new float[16]; |
||||||
|
static { |
||||||
|
Matrix.setIdentityM(IDENTITY_MATRIX, 0); |
||||||
|
} |
||||||
|
|
||||||
|
// Check for GLES errors.
|
||||||
|
protected static void check(String opName) { |
||||||
|
int error = GLES20.glGetError(); |
||||||
|
if (error != GLES20.GL_NO_ERROR) { |
||||||
|
LOG.e("Error during", opName, "glError 0x", Integer.toHexString(error)); |
||||||
|
throw new RuntimeException(CameraLogger.lastMessage); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Check for valid location.
|
||||||
|
protected static void checkLocation(int location, String label) { |
||||||
|
if (location < 0) { |
||||||
|
LOG.e("Unable to locate", label, "in program"); |
||||||
|
throw new RuntimeException(CameraLogger.lastMessage); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Compiles the given shader, returns a handle.
|
||||||
|
protected static int loadShader(int shaderType, String source) { |
||||||
|
int shader = GLES20.glCreateShader(shaderType); |
||||||
|
check("glCreateShader type=" + shaderType); |
||||||
|
GLES20.glShaderSource(shader, source); |
||||||
|
GLES20.glCompileShader(shader); |
||||||
|
int[] compiled = new int[1]; |
||||||
|
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); |
||||||
|
if (compiled[0] == 0) { |
||||||
|
LOG.e("Could not compile shader", shaderType, ":", GLES20.glGetShaderInfoLog(shader)); |
||||||
|
GLES20.glDeleteShader(shader); |
||||||
|
shader = 0; |
||||||
|
} |
||||||
|
return shader; |
||||||
|
} |
||||||
|
|
||||||
|
// Creates a program with given vertex shader and pixel shader.
|
||||||
|
protected static int createProgram(String vertexSource, String fragmentSource) { |
||||||
|
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); |
||||||
|
if (vertexShader == 0) return 0; |
||||||
|
int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); |
||||||
|
if (pixelShader == 0) return 0; |
||||||
|
|
||||||
|
int program = GLES20.glCreateProgram(); |
||||||
|
check("glCreateProgram"); |
||||||
|
if (program == 0) { |
||||||
|
LOG.e("Could not create program"); |
||||||
|
} |
||||||
|
GLES20.glAttachShader(program, vertexShader); |
||||||
|
check("glAttachShader"); |
||||||
|
GLES20.glAttachShader(program, pixelShader); |
||||||
|
check("glAttachShader"); |
||||||
|
GLES20.glLinkProgram(program); |
||||||
|
int[] linkStatus = new int[1]; |
||||||
|
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); |
||||||
|
if (linkStatus[0] != GLES20.GL_TRUE) { |
||||||
|
LOG.e("Could not link program:", GLES20.glGetProgramInfoLog(program)); |
||||||
|
GLES20.glDeleteProgram(program); |
||||||
|
program = 0; |
||||||
|
} |
||||||
|
return program; |
||||||
|
} |
||||||
|
|
||||||
|
// Allocates a direct float buffer, and populates it with the float array data.
|
||||||
|
protected static FloatBuffer floatBuffer(float[] coords) { |
||||||
|
// Allocate a direct ByteBuffer, using 4 bytes per float, and copy coords into it.
|
||||||
|
ByteBuffer bb = ByteBuffer.allocateDirect(coords.length * 4); |
||||||
|
bb.order(ByteOrder.nativeOrder()); |
||||||
|
FloatBuffer fb = bb.asFloatBuffer(); |
||||||
|
fb.put(coords); |
||||||
|
fb.position(0); |
||||||
|
return fb; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,179 @@ |
|||||||
|
package com.otaliastudios.cameraview; |
||||||
|
|
||||||
|
|
||||||
|
import android.opengl.GLES11Ext; |
||||||
|
import android.opengl.GLES20; |
||||||
|
import android.opengl.Matrix; |
||||||
|
import android.util.Log; |
||||||
|
|
||||||
|
import java.nio.FloatBuffer; |
||||||
|
|
||||||
|
class GLViewport extends GLElement { |
||||||
|
|
||||||
|
private final static CameraLogger LOG = CameraLogger.create(GLViewport.class.getSimpleName()); |
||||||
|
|
||||||
|
// Simple vertex shader.
|
||||||
|
private static final String SIMPLE_VERTEX_SHADER = |
||||||
|
"uniform mat4 uMVPMatrix;\n" + |
||||||
|
"uniform mat4 uTexMatrix;\n" + |
||||||
|
"attribute vec4 aPosition;\n" + |
||||||
|
"attribute vec4 aTextureCoord;\n" + |
||||||
|
"varying vec2 vTextureCoord;\n" + |
||||||
|
"void main() {\n" + |
||||||
|
" gl_Position = uMVPMatrix * aPosition;\n" + |
||||||
|
" vTextureCoord = (uTexMatrix * aTextureCoord).xy;\n" + |
||||||
|
"}\n"; |
||||||
|
|
||||||
|
// Simple fragment shader for use with external 2D textures
|
||||||
|
// (e.g. what we get from SurfaceTexture).
|
||||||
|
private static final String SIMPLE_FRAGMENT_SHADER = |
||||||
|
"#extension GL_OES_EGL_image_external : require\n" + |
||||||
|
"precision mediump float;\n" + |
||||||
|
"varying vec2 vTextureCoord;\n" + |
||||||
|
"uniform samplerExternalOES sTexture;\n" + |
||||||
|
"void main() {\n" + |
||||||
|
" gl_FragColor = texture2D(sTexture, vTextureCoord);\n" + |
||||||
|
"}\n"; |
||||||
|
|
||||||
|
// Stuff from Drawable2d.FULL_RECTANGLE
|
||||||
|
// A full square, extending from -1 to +1 in both dimensions.
|
||||||
|
// When the model/view/projection matrix is identity, this will exactly cover the viewport.
|
||||||
|
private static final float FULL_RECTANGLE_COORDS[] = { |
||||||
|
-1.0f, -1.0f, // 0 bottom left
|
||||||
|
1.0f, -1.0f, // 1 bottom right
|
||||||
|
-1.0f, 1.0f, // 2 top left
|
||||||
|
1.0f, 1.0f, // 3 top right
|
||||||
|
}; |
||||||
|
|
||||||
|
// Stuff from Drawable2d.FULL_RECTANGLE
|
||||||
|
// A full square, extending from -1 to +1 in both dimensions.
|
||||||
|
private static final float FULL_RECTANGLE_TEX_COORDS[] = { |
||||||
|
0.0f, 0.0f, // 0 bottom left
|
||||||
|
1.0f, 0.0f, // 1 bottom right
|
||||||
|
0.0f, 1.0f, // 2 top left
|
||||||
|
1.0f, 1.0f // 3 top right
|
||||||
|
}; |
||||||
|
|
||||||
|
// Stuff from Drawable2d.FULL_RECTANGLE
|
||||||
|
private FloatBuffer mVertexArray = floatBuffer(FULL_RECTANGLE_COORDS); |
||||||
|
private FloatBuffer mTexCoordArray = floatBuffer(FULL_RECTANGLE_TEX_COORDS); |
||||||
|
private int mVertexCount = FULL_RECTANGLE_COORDS.length / 2; |
||||||
|
private final int mCoordsPerVertex = 2; |
||||||
|
private final int mVertexStride = 8; |
||||||
|
private final int mTexCoordStride = 8; |
||||||
|
|
||||||
|
// Stuff from Texture2dProgram
|
||||||
|
private int mProgramHandle; |
||||||
|
private int mTextureTarget; |
||||||
|
private int muMVPMatrixLoc; |
||||||
|
private int muTexMatrixLoc; |
||||||
|
private int maPositionLoc; |
||||||
|
private int maTextureCoordLoc; |
||||||
|
|
||||||
|
// private int muKernelLoc; // Used for filtering
|
||||||
|
// private int muTexOffsetLoc; // Used for filtering
|
||||||
|
// private int muColorAdjustLoc; // Used for filtering
|
||||||
|
|
||||||
|
GLViewport() { |
||||||
|
mTextureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES; |
||||||
|
mProgramHandle = createProgram(SIMPLE_VERTEX_SHADER, SIMPLE_FRAGMENT_SHADER); |
||||||
|
maPositionLoc = GLES20.glGetAttribLocation(mProgramHandle, "aPosition"); |
||||||
|
checkLocation(maPositionLoc, "aPosition"); |
||||||
|
maTextureCoordLoc = GLES20.glGetAttribLocation(mProgramHandle, "aTextureCoord"); |
||||||
|
checkLocation(maTextureCoordLoc, "aTextureCoord"); |
||||||
|
muMVPMatrixLoc = GLES20.glGetUniformLocation(mProgramHandle, "uMVPMatrix"); |
||||||
|
checkLocation(muMVPMatrixLoc, "uMVPMatrix"); |
||||||
|
muTexMatrixLoc = GLES20.glGetUniformLocation(mProgramHandle, "uTexMatrix"); |
||||||
|
checkLocation(muTexMatrixLoc, "uTexMatrix"); |
||||||
|
|
||||||
|
// Stuff from Drawable2d.FULL_RECTANGLE
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
void release() { |
||||||
|
GLES20.glDeleteProgram(mProgramHandle); |
||||||
|
mProgramHandle = -1; |
||||||
|
} |
||||||
|
|
||||||
|
int createTexture() { |
||||||
|
int[] textures = new int[1]; |
||||||
|
GLES20.glGenTextures(1, textures, 0); |
||||||
|
check("glGenTextures"); |
||||||
|
|
||||||
|
int texId = textures[0]; |
||||||
|
GLES20.glBindTexture(mTextureTarget, texId); |
||||||
|
check("glBindTexture " + texId); |
||||||
|
|
||||||
|
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, |
||||||
|
GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST); |
||||||
|
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, |
||||||
|
GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); |
||||||
|
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, |
||||||
|
GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); |
||||||
|
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, |
||||||
|
GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); |
||||||
|
check("glTexParameter"); |
||||||
|
|
||||||
|
return texId; |
||||||
|
} |
||||||
|
|
||||||
|
void drawFrame(int textureId, float[] texMatrix) { |
||||||
|
drawFrame(textureId, texMatrix, |
||||||
|
IDENTITY_MATRIX, mVertexArray, 0, |
||||||
|
mVertexCount, mCoordsPerVertex, |
||||||
|
mVertexStride, mTexCoordArray, |
||||||
|
mTexCoordStride); |
||||||
|
} |
||||||
|
|
||||||
|
private void drawFrame(int textureId, float[] texMatrix, |
||||||
|
float[] mvpMatrix, FloatBuffer vertexBuffer, int firstVertex, |
||||||
|
int vertexCount, int coordsPerVertex, int vertexStride, |
||||||
|
FloatBuffer texBuffer, int texStride) { |
||||||
|
check("draw start"); |
||||||
|
|
||||||
|
// Select the program.
|
||||||
|
GLES20.glUseProgram(mProgramHandle); |
||||||
|
check("glUseProgram"); |
||||||
|
|
||||||
|
// Set the texture.
|
||||||
|
GLES20.glActiveTexture(GLES20.GL_TEXTURE0); |
||||||
|
GLES20.glBindTexture(mTextureTarget, textureId); |
||||||
|
|
||||||
|
// Copy the model / view / projection matrix over.
|
||||||
|
GLES20.glUniformMatrix4fv(muMVPMatrixLoc, 1, false, mvpMatrix, 0); |
||||||
|
check("glUniformMatrix4fv"); |
||||||
|
|
||||||
|
// Copy the texture transformation matrix over.
|
||||||
|
GLES20.glUniformMatrix4fv(muTexMatrixLoc, 1, false, texMatrix, 0); |
||||||
|
check("glUniformMatrix4fv"); |
||||||
|
|
||||||
|
// Enable the "aPosition" vertex attribute.
|
||||||
|
GLES20.glEnableVertexAttribArray(maPositionLoc); |
||||||
|
check("glEnableVertexAttribArray"); |
||||||
|
|
||||||
|
// Connect vertexBuffer to "aPosition".
|
||||||
|
GLES20.glVertexAttribPointer(maPositionLoc, coordsPerVertex, |
||||||
|
GLES20.GL_FLOAT, false, vertexStride, vertexBuffer); |
||||||
|
check("glVertexAttribPointer"); |
||||||
|
|
||||||
|
// Enable the "aTextureCoord" vertex attribute.
|
||||||
|
GLES20.glEnableVertexAttribArray(maTextureCoordLoc); |
||||||
|
check("glEnableVertexAttribArray"); |
||||||
|
|
||||||
|
// Connect texBuffer to "aTextureCoord".
|
||||||
|
GLES20.glVertexAttribPointer(maTextureCoordLoc, 2, |
||||||
|
GLES20.GL_FLOAT, false, texStride, texBuffer); |
||||||
|
check("glVertexAttribPointer"); |
||||||
|
|
||||||
|
// Draw the rect.
|
||||||
|
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, firstVertex, vertexCount); |
||||||
|
check("glDrawArrays"); |
||||||
|
|
||||||
|
// Done -- disable vertex array, texture, and program.
|
||||||
|
GLES20.glDisableVertexAttribArray(maPositionLoc); |
||||||
|
GLES20.glDisableVertexAttribArray(maTextureCoordLoc); |
||||||
|
GLES20.glBindTexture(mTextureTarget, 0); |
||||||
|
GLES20.glUseProgram(0); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue