blob: a4c65aeb105039a76086287792cef676ae4e0cdd [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.hardware.camera2.legacy;
import android.graphics.ImageFormat;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraCharacteristics;
import android.os.Environment;
import android.opengl.EGL14;
import android.opengl.EGLConfig;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.EGLSurface;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import android.opengl.Matrix;
import android.util.Log;
import android.util.Pair;
import android.util.Size;
import android.view.Surface;
import android.os.SystemProperties;
import java.io.File;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
/**
* A renderer class that manages the GL state, and can draw a frame into a set of output
* {@link Surface}s.
*/
public class SurfaceTextureRenderer {
private static final String TAG = SurfaceTextureRenderer.class.getSimpleName();
private static final boolean DEBUG = false;
private static final int EGL_RECORDABLE_ANDROID = 0x3142; // from EGL/eglext.h
private static final int GL_MATRIX_SIZE = 16;
private static final int VERTEX_POS_SIZE = 3;
private static final int VERTEX_UV_SIZE = 2;
private static final int EGL_COLOR_BITLENGTH = 8;
private static final int GLES_VERSION = 2;
private static final int PBUFFER_PIXEL_BYTES = 4;
private static final int FLIP_TYPE_NONE = 0;
private static final int FLIP_TYPE_HORIZONTAL = 1;
private static final int FLIP_TYPE_VERTICAL = 2;
private static final int FLIP_TYPE_BOTH = FLIP_TYPE_HORIZONTAL | FLIP_TYPE_VERTICAL;
private static final DateTimeFormatter LOG_NAME_TIME_FORMATTER =
DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss", Locale.ROOT);
private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY;
private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT;
private EGLConfig mConfigs;
private class EGLSurfaceHolder {
Surface surface;
EGLSurface eglSurface;
int width;
int height;
}
private List<EGLSurfaceHolder> mSurfaces = new ArrayList<EGLSurfaceHolder>();
private List<EGLSurfaceHolder> mConversionSurfaces = new ArrayList<EGLSurfaceHolder>();
private ByteBuffer mPBufferPixels;
// Hold this to avoid GC
private volatile SurfaceTexture mSurfaceTexture;
private static final int FLOAT_SIZE_BYTES = 4;
private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
// Sampling is mirrored across the horizontal axis
private static final float[] sHorizontalFlipTriangleVertices = {
// X, Y, Z, U, V
-1.0f, -1.0f, 0, 1.f, 0.f,
1.0f, -1.0f, 0, 0.f, 0.f,
-1.0f, 1.0f, 0, 1.f, 1.f,
1.0f, 1.0f, 0, 0.f, 1.f,
};
// Sampling is mirrored across the vertical axis
private static final float[] sVerticalFlipTriangleVertices = {
// X, Y, Z, U, V
-1.0f, -1.0f, 0, 0.f, 1.f,
1.0f, -1.0f, 0, 1.f, 1.f,
-1.0f, 1.0f, 0, 0.f, 0.f,
1.0f, 1.0f, 0, 1.f, 0.f,
};
// Sampling is mirrored across the both axes
private static final float[] sBothFlipTriangleVertices = {
// X, Y, Z, U, V
-1.0f, -1.0f, 0, 1.f, 1.f,
1.0f, -1.0f, 0, 0.f, 1.f,
-1.0f, 1.0f, 0, 1.f, 0.f,
1.0f, 1.0f, 0, 0.f, 0.f,
};
// Sampling is 1:1 for a straight copy for the back camera
private static final float[] sRegularTriangleVertices = {
// X, Y, Z, U, V
-1.0f, -1.0f, 0, 0.f, 0.f,
1.0f, -1.0f, 0, 1.f, 0.f,
-1.0f, 1.0f, 0, 0.f, 1.f,
1.0f, 1.0f, 0, 1.f, 1.f,
};
private FloatBuffer mRegularTriangleVertices;
private FloatBuffer mHorizontalFlipTriangleVertices;
private FloatBuffer mVerticalFlipTriangleVertices;
private FloatBuffer mBothFlipTriangleVertices;
private final int mFacing;
/**
* As used in this file, this vertex shader maps a unit square to the view, and
* tells the fragment shader to interpolate over it. Each surface pixel position
* is mapped to a 2D homogeneous texture coordinate of the form (s, t, 0, 1) with
* s and t in the inclusive range [0, 1], and the matrix from
* {@link SurfaceTexture#getTransformMatrix(float[])} is used to map this
* coordinate to a texture location.
*/
private static final String VERTEX_SHADER =
"uniform mat4 uMVPMatrix;\n" +
"uniform mat4 uSTMatrix;\n" +
"attribute vec4 aPosition;\n" +
"attribute vec4 aTextureCoord;\n" +
"varying vec2 vTextureCoord;\n" +
"void main() {\n" +
" gl_Position = uMVPMatrix * aPosition;\n" +
" vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
"}\n";
/**
* This fragment shader simply draws the color in the 2D texture at
* the location from the {@code VERTEX_SHADER}.
*/
private static final String 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";
private float[] mMVPMatrix = new float[GL_MATRIX_SIZE];
private float[] mSTMatrix = new float[GL_MATRIX_SIZE];
private int mProgram;
private int mTextureID = 0;
private int muMVPMatrixHandle;
private int muSTMatrixHandle;
private int maPositionHandle;
private int maTextureHandle;
private PerfMeasurement mPerfMeasurer = null;
private static final String LEGACY_PERF_PROPERTY = "persist.camera.legacy_perf";
public SurfaceTextureRenderer(int facing) {
mFacing = facing;
mRegularTriangleVertices = ByteBuffer.allocateDirect(sRegularTriangleVertices.length *
FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
mRegularTriangleVertices.put(sRegularTriangleVertices).position(0);
mHorizontalFlipTriangleVertices = ByteBuffer.allocateDirect(
sHorizontalFlipTriangleVertices.length * FLOAT_SIZE_BYTES).
order(ByteOrder.nativeOrder()).asFloatBuffer();
mHorizontalFlipTriangleVertices.put(sHorizontalFlipTriangleVertices).position(0);
mVerticalFlipTriangleVertices = ByteBuffer.allocateDirect(
sVerticalFlipTriangleVertices.length * FLOAT_SIZE_BYTES).
order(ByteOrder.nativeOrder()).asFloatBuffer();
mVerticalFlipTriangleVertices.put(sVerticalFlipTriangleVertices).position(0);
mBothFlipTriangleVertices = ByteBuffer.allocateDirect(
sBothFlipTriangleVertices.length * FLOAT_SIZE_BYTES).
order(ByteOrder.nativeOrder()).asFloatBuffer();
mBothFlipTriangleVertices.put(sBothFlipTriangleVertices).position(0);
Matrix.setIdentityM(mSTMatrix, 0);
}
private int loadShader(int shaderType, String source) {
int shader = GLES20.glCreateShader(shaderType);
checkGlError("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(TAG, "Could not compile shader " + shaderType + ":");
Log.e(TAG, " " + GLES20.glGetShaderInfoLog(shader));
GLES20.glDeleteShader(shader);
// TODO: handle this more gracefully
throw new IllegalStateException("Could not compile shader " + shaderType);
}
return shader;
}
private 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();
checkGlError("glCreateProgram");
if (program == 0) {
Log.e(TAG, "Could not create program");
}
GLES20.glAttachShader(program, vertexShader);
checkGlError("glAttachShader");
GLES20.glAttachShader(program, pixelShader);
checkGlError("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(TAG, "Could not link program: ");
Log.e(TAG, GLES20.glGetProgramInfoLog(program));
GLES20.glDeleteProgram(program);
// TODO: handle this more gracefully
throw new IllegalStateException("Could not link program");
}
return program;
}
private void drawFrame(SurfaceTexture st, int width, int height, int flipType)
throws LegacyExceptionUtils.BufferQueueAbandonedException {
checkGlError("onDrawFrame start");
st.getTransformMatrix(mSTMatrix);
Matrix.setIdentityM(mMVPMatrix, /*smOffset*/0);
// Find intermediate buffer dimensions
Size dimens;
try {
dimens = LegacyCameraDevice.getTextureSize(st);
} catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
// Should never hit this.
throw new IllegalStateException("Surface abandoned, skipping drawFrame...", e);
}
float texWidth = dimens.getWidth();
float texHeight = dimens.getHeight();
if (texWidth <= 0 || texHeight <= 0) {
throw new IllegalStateException("Illegal intermediate texture with dimension of 0");
}
// Letterbox or pillar-box output dimensions into intermediate dimensions.
RectF intermediate = new RectF(/*left*/0, /*top*/0, /*right*/texWidth, /*bottom*/texHeight);
RectF output = new RectF(/*left*/0, /*top*/0, /*right*/width, /*bottom*/height);
android.graphics.Matrix boxingXform = new android.graphics.Matrix();
boxingXform.setRectToRect(output, intermediate, android.graphics.Matrix.ScaleToFit.CENTER);
boxingXform.mapRect(output);
// Find scaling factor from pillar-boxed/letter-boxed output dimensions to intermediate
// buffer dimensions.
float scaleX = intermediate.width() / output.width();
float scaleY = intermediate.height() / output.height();
// Intermediate texture is implicitly scaled to 'fill' the output dimensions in clip space
// coordinates in the shader. To avoid stretching, we need to scale the larger dimension
// of the intermediate buffer so that the output buffer is actually letter-boxed
// or pillar-boxed into the intermediate buffer after clipping.
Matrix.scaleM(mMVPMatrix, /*offset*/0, /*x*/scaleX, /*y*/scaleY, /*z*/1);
if (DEBUG) {
Log.d(TAG, "Scaling factors (S_x = " + scaleX + ",S_y = " + scaleY + ") used for " +
width + "x" + height + " surface, intermediate buffer size is " + texWidth +
"x" + texHeight);
}
// Set viewport to be output buffer dimensions
GLES20.glViewport(0, 0, width, height);
if (DEBUG) {
GLES20.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
}
GLES20.glUseProgram(mProgram);
checkGlError("glUseProgram");
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID);
FloatBuffer triangleVertices;
switch(flipType) {
case FLIP_TYPE_HORIZONTAL:
triangleVertices = mHorizontalFlipTriangleVertices;
break;
case FLIP_TYPE_VERTICAL:
triangleVertices = mVerticalFlipTriangleVertices;
break;
case FLIP_TYPE_BOTH:
triangleVertices = mBothFlipTriangleVertices;
break;
default:
triangleVertices = mRegularTriangleVertices;
break;
}
triangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
GLES20.glVertexAttribPointer(maPositionHandle, VERTEX_POS_SIZE, GLES20.GL_FLOAT,
/*normalized*/ false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
checkGlError("glVertexAttribPointer maPosition");
GLES20.glEnableVertexAttribArray(maPositionHandle);
checkGlError("glEnableVertexAttribArray maPositionHandle");
triangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
GLES20.glVertexAttribPointer(maTextureHandle, VERTEX_UV_SIZE, GLES20.GL_FLOAT,
/*normalized*/ false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
checkGlError("glVertexAttribPointer maTextureHandle");
GLES20.glEnableVertexAttribArray(maTextureHandle);
checkGlError("glEnableVertexAttribArray maTextureHandle");
GLES20.glUniformMatrix4fv(muMVPMatrixHandle, /*count*/ 1, /*transpose*/ false, mMVPMatrix,
/*offset*/ 0);
GLES20.glUniformMatrix4fv(muSTMatrixHandle, /*count*/ 1, /*transpose*/ false, mSTMatrix,
/*offset*/ 0);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /*offset*/ 0, /*count*/ 4);
checkGlDrawError("glDrawArrays");
}
/**
* Initializes GL state. Call this after the EGL surface has been created and made current.
*/
private void initializeGLState() {
mProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
if (mProgram == 0) {
throw new IllegalStateException("failed creating program");
}
maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
checkGlError("glGetAttribLocation aPosition");
if (maPositionHandle == -1) {
throw new IllegalStateException("Could not get attrib location for aPosition");
}
maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord");
checkGlError("glGetAttribLocation aTextureCoord");
if (maTextureHandle == -1) {
throw new IllegalStateException("Could not get attrib location for aTextureCoord");
}
muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
checkGlError("glGetUniformLocation uMVPMatrix");
if (muMVPMatrixHandle == -1) {
throw new IllegalStateException("Could not get attrib location for uMVPMatrix");
}
muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix");
checkGlError("glGetUniformLocation uSTMatrix");
if (muSTMatrixHandle == -1) {
throw new IllegalStateException("Could not get attrib location for uSTMatrix");
}
int[] textures = new int[1];
GLES20.glGenTextures(/*n*/ 1, textures, /*offset*/ 0);
mTextureID = textures[0];
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID);
checkGlError("glBindTexture mTextureID");
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);
checkGlError("glTexParameter");
}
private int getTextureId() {
return mTextureID;
}
private void clearState() {
mSurfaces.clear();
for (EGLSurfaceHolder holder : mConversionSurfaces) {
try {
LegacyCameraDevice.disconnectSurface(holder.surface);
} catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
Log.w(TAG, "Surface abandoned, skipping...", e);
}
}
mConversionSurfaces.clear();
mPBufferPixels = null;
if (mSurfaceTexture != null) {
mSurfaceTexture.release();
}
mSurfaceTexture = null;
}
private void configureEGLContext() {
mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
throw new IllegalStateException("No EGL14 display");
}
int[] version = new int[2];
if (!EGL14.eglInitialize(mEGLDisplay, version, /*offset*/ 0, version, /*offset*/ 1)) {
throw new IllegalStateException("Cannot initialize EGL14");
}
int[] attribList = {
EGL14.EGL_RED_SIZE, EGL_COLOR_BITLENGTH,
EGL14.EGL_GREEN_SIZE, EGL_COLOR_BITLENGTH,
EGL14.EGL_BLUE_SIZE, EGL_COLOR_BITLENGTH,
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
EGL_RECORDABLE_ANDROID, 1,
EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT | EGL14.EGL_WINDOW_BIT,
EGL14.EGL_NONE
};
EGLConfig[] configs = new EGLConfig[1];
int[] numConfigs = new int[1];
EGL14.eglChooseConfig(mEGLDisplay, attribList, /*offset*/ 0, configs, /*offset*/ 0,
configs.length, numConfigs, /*offset*/ 0);
checkEglError("eglCreateContext RGB888+recordable ES2");
mConfigs = configs[0];
int[] attrib_list = {
EGL14.EGL_CONTEXT_CLIENT_VERSION, GLES_VERSION,
EGL14.EGL_NONE
};
mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT,
attrib_list, /*offset*/ 0);
checkEglError("eglCreateContext");
if(mEGLContext == EGL14.EGL_NO_CONTEXT) {
throw new IllegalStateException("No EGLContext could be made");
}
}
private void configureEGLOutputSurfaces(Collection<EGLSurfaceHolder> surfaces) {
if (surfaces == null || surfaces.size() == 0) {
throw new IllegalStateException("No Surfaces were provided to draw to");
}
int[] surfaceAttribs = {
EGL14.EGL_NONE
};
for (EGLSurfaceHolder holder : surfaces) {
holder.eglSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mConfigs,
holder.surface, surfaceAttribs, /*offset*/ 0);
checkEglError("eglCreateWindowSurface");
}
}
private void configureEGLPbufferSurfaces(Collection<EGLSurfaceHolder> surfaces) {
if (surfaces == null || surfaces.size() == 0) {
throw new IllegalStateException("No Surfaces were provided to draw to");
}
int maxLength = 0;
for (EGLSurfaceHolder holder : surfaces) {
int length = holder.width * holder.height;
// Find max surface size, ensure PBuffer can hold this many pixels
maxLength = (length > maxLength) ? length : maxLength;
int[] surfaceAttribs = {
EGL14.EGL_WIDTH, holder.width,
EGL14.EGL_HEIGHT, holder.height,
EGL14.EGL_NONE
};
holder.eglSurface =
EGL14.eglCreatePbufferSurface(mEGLDisplay, mConfigs, surfaceAttribs, 0);
checkEglError("eglCreatePbufferSurface");
}
mPBufferPixels = ByteBuffer.allocateDirect(maxLength * PBUFFER_PIXEL_BYTES)
.order(ByteOrder.nativeOrder());
}
private void releaseEGLContext() {
if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
EGL14.EGL_NO_CONTEXT);
dumpGlTiming();
if (mSurfaces != null) {
for (EGLSurfaceHolder holder : mSurfaces) {
if (holder.eglSurface != null) {
EGL14.eglDestroySurface(mEGLDisplay, holder.eglSurface);
}
}
}
if (mConversionSurfaces != null) {
for (EGLSurfaceHolder holder : mConversionSurfaces) {
if (holder.eglSurface != null) {
EGL14.eglDestroySurface(mEGLDisplay, holder.eglSurface);
}
}
}
EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
EGL14.eglReleaseThread();
EGL14.eglTerminate(mEGLDisplay);
}
mConfigs = null;
mEGLDisplay = EGL14.EGL_NO_DISPLAY;
mEGLContext = EGL14.EGL_NO_CONTEXT;
clearState();
}
private void makeCurrent(EGLSurface surface)
throws LegacyExceptionUtils.BufferQueueAbandonedException {
EGL14.eglMakeCurrent(mEGLDisplay, surface, surface, mEGLContext);
checkEglDrawError("makeCurrent");
}
private boolean swapBuffers(EGLSurface surface)
throws LegacyExceptionUtils.BufferQueueAbandonedException {
boolean result = EGL14.eglSwapBuffers(mEGLDisplay, surface);
int error = EGL14.eglGetError();
switch (error) {
case EGL14.EGL_SUCCESS:
return result;
// Check for an abandoned buffer queue, or other error conditions out
// of the user's control.
//
// From the EGL 1.4 spec (2013-12-04), Section 3.9.4 Posting Errors:
//
// If eglSwapBuffers is called and the native window associated with
// surface is no longer valid, an EGL_BAD_NATIVE_WINDOW error is
// generated.
//
// We also interpret EGL_BAD_SURFACE as indicating an abandoned
// surface, even though the EGL spec does not document it as such, for
// backwards compatibility with older versions of this file.
case EGL14.EGL_BAD_NATIVE_WINDOW:
case EGL14.EGL_BAD_SURFACE:
throw new LegacyExceptionUtils.BufferQueueAbandonedException();
default:
throw new IllegalStateException(
"swapBuffers: EGL error: 0x" + Integer.toHexString(error));
}
}
private void checkEglDrawError(String msg)
throws LegacyExceptionUtils.BufferQueueAbandonedException {
int error;
if ((error = EGL14.eglGetError()) == EGL14.EGL_BAD_NATIVE_WINDOW) {
throw new LegacyExceptionUtils.BufferQueueAbandonedException();
}
if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) {
throw new IllegalStateException(msg + ": EGL error: 0x" + Integer.toHexString(error));
}
}
private void checkEglError(String msg) {
int error;
if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) {
throw new IllegalStateException(msg + ": EGL error: 0x" + Integer.toHexString(error));
}
}
private void checkGlError(String msg) {
int error;
while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
throw new IllegalStateException(
msg + ": GLES20 error: 0x" + Integer.toHexString(error));
}
}
private void checkGlDrawError(String msg)
throws LegacyExceptionUtils.BufferQueueAbandonedException {
int error;
boolean surfaceAbandoned = false;
boolean glError = false;
while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
if (error == GLES20.GL_OUT_OF_MEMORY) {
surfaceAbandoned = true;
} else {
glError = true;
}
}
if (glError) {
throw new IllegalStateException(
msg + ": GLES20 error: 0x" + Integer.toHexString(error));
}
if (surfaceAbandoned) {
throw new LegacyExceptionUtils.BufferQueueAbandonedException();
}
}
/**
* Save a measurement dump to disk, in
* {@code /sdcard/CameraLegacy/durations_<time>_<width1>x<height1>_...txt}
*/
private void dumpGlTiming() {
if (mPerfMeasurer == null) return;
File legacyStorageDir = new File(Environment.getExternalStorageDirectory(), "CameraLegacy");
if (!legacyStorageDir.exists()){
if (!legacyStorageDir.mkdirs()){
Log.e(TAG, "Failed to create directory for data dump");
return;
}
}
StringBuilder path = new StringBuilder(legacyStorageDir.getPath());
path.append(File.separator);
path.append("durations_");
path.append(formatTimestamp(System.currentTimeMillis()));
path.append("_S");
for (EGLSurfaceHolder surface : mSurfaces) {
path.append(String.format("_%d_%d", surface.width, surface.height));
}
path.append("_C");
for (EGLSurfaceHolder surface : mConversionSurfaces) {
path.append(String.format("_%d_%d", surface.width, surface.height));
}
path.append(".txt");
mPerfMeasurer.dumpPerformanceData(path.toString());
}
private static String formatTimestamp(long timeMillis) {
// This is a replacement for {@link Time#format2445()} that doesn't suffer from Y2038
// issues.
Instant instant = Instant.ofEpochMilli(timeMillis);
ZoneId zoneId = ZoneId.systemDefault();
LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zoneId);
return LOG_NAME_TIME_FORMATTER.format(localDateTime);
}
private void setupGlTiming() {
if (PerfMeasurement.isGlTimingSupported()) {
Log.d(TAG, "Enabling GL performance measurement");
mPerfMeasurer = new PerfMeasurement();
} else {
Log.d(TAG, "GL performance measurement not supported on this device");
mPerfMeasurer = null;
}
}
private void beginGlTiming() {
if (mPerfMeasurer == null) return;
mPerfMeasurer.startTimer();
}
private void addGlTimestamp(long timestamp) {
if (mPerfMeasurer == null) return;
mPerfMeasurer.addTimestamp(timestamp);
}
private void endGlTiming() {
if (mPerfMeasurer == null) return;
mPerfMeasurer.stopTimer();
}
/**
* Return the surface texture to draw to - this is the texture use to when producing output
* surface buffers.
*
* @return a {@link SurfaceTexture}.
*/
public SurfaceTexture getSurfaceTexture() {
return mSurfaceTexture;
}
/**
* Set a collection of output {@link Surface}s that can be drawn to.
*
* @param surfaces a {@link Collection} of surfaces.
*/
public void configureSurfaces(Collection<Pair<Surface, Size>> surfaces) {
releaseEGLContext();
if (surfaces == null || surfaces.size() == 0) {
Log.w(TAG, "No output surfaces configured for GL drawing.");
return;
}
for (Pair<Surface, Size> p : surfaces) {
Surface s = p.first;
Size surfaceSize = p.second;
// If pixel conversions aren't handled by egl, use a pbuffer
try {
EGLSurfaceHolder holder = new EGLSurfaceHolder();
holder.surface = s;
holder.width = surfaceSize.getWidth();
holder.height = surfaceSize.getHeight();
if (LegacyCameraDevice.needsConversion(s)) {
mConversionSurfaces.add(holder);
// LegacyCameraDevice is the producer of surfaces if it's not handled by EGL,
// so LegacyCameraDevice needs to connect to the surfaces.
LegacyCameraDevice.connectSurface(s);
} else {
mSurfaces.add(holder);
}
} catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
Log.w(TAG, "Surface abandoned, skipping configuration... ", e);
}
}
// Set up egl display
configureEGLContext();
// Set up regular egl surfaces if needed
if (mSurfaces.size() > 0) {
configureEGLOutputSurfaces(mSurfaces);
}
// Set up pbuffer surface if needed
if (mConversionSurfaces.size() > 0) {
configureEGLPbufferSurfaces(mConversionSurfaces);
}
try {
makeCurrent((mSurfaces.size() > 0) ? mSurfaces.get(0).eglSurface :
mConversionSurfaces.get(0).eglSurface);
} catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
Log.w(TAG, "Surface abandoned, skipping configuration... ", e);
}
initializeGLState();
mSurfaceTexture = new SurfaceTexture(getTextureId());
// Set up performance tracking if enabled
if (SystemProperties.getBoolean(LEGACY_PERF_PROPERTY, false)) {
setupGlTiming();
}
}
/**
* Draw the current buffer in the {@link SurfaceTexture} returned from
* {@link #getSurfaceTexture()} into the set of target {@link Surface}s
* in the next request from the given {@link CaptureCollector}, or drop
* the frame if none is available.
*
* <p>
* Any {@link Surface}s targeted must be a subset of the {@link Surface}s
* set in the last {@link #configureSurfaces(java.util.Collection)} call.
* </p>
*
* @param targetCollector the surfaces to draw to.
*/
public void drawIntoSurfaces(CaptureCollector targetCollector) {
if ((mSurfaces == null || mSurfaces.size() == 0)
&& (mConversionSurfaces == null || mConversionSurfaces.size() == 0)) {
return;
}
boolean doTiming = targetCollector.hasPendingPreviewCaptures();
checkGlError("before updateTexImage");
if (doTiming) {
beginGlTiming();
}
mSurfaceTexture.updateTexImage();
long timestamp = mSurfaceTexture.getTimestamp();
Pair<RequestHolder, Long> captureHolder = targetCollector.previewCaptured(timestamp);
// No preview request queued, drop frame.
if (captureHolder == null) {
if (DEBUG) {
Log.d(TAG, "Dropping preview frame.");
}
if (doTiming) {
endGlTiming();
}
return;
}
RequestHolder request = captureHolder.first;
Collection<Surface> targetSurfaces = request.getHolderTargets();
if (doTiming) {
addGlTimestamp(timestamp);
}
List<Long> targetSurfaceIds = new ArrayList();
try {
targetSurfaceIds = LegacyCameraDevice.getSurfaceIds(targetSurfaces);
} catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
Log.w(TAG, "Surface abandoned, dropping frame. ", e);
request.setOutputAbandoned();
}
for (EGLSurfaceHolder holder : mSurfaces) {
if (LegacyCameraDevice.containsSurfaceId(holder.surface, targetSurfaceIds)) {
try{
LegacyCameraDevice.setSurfaceDimens(holder.surface, holder.width,
holder.height);
makeCurrent(holder.eglSurface);
LegacyCameraDevice.setNextTimestamp(holder.surface, captureHolder.second);
drawFrame(mSurfaceTexture, holder.width, holder.height,
(mFacing == CameraCharacteristics.LENS_FACING_FRONT) ?
FLIP_TYPE_HORIZONTAL : FLIP_TYPE_NONE);
swapBuffers(holder.eglSurface);
} catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
Log.w(TAG, "Surface abandoned, dropping frame. ", e);
request.setOutputAbandoned();
}
}
}
for (EGLSurfaceHolder holder : mConversionSurfaces) {
if (LegacyCameraDevice.containsSurfaceId(holder.surface, targetSurfaceIds)) {
// glReadPixels reads from the bottom of the buffer, so add an extra vertical flip
try {
makeCurrent(holder.eglSurface);
drawFrame(mSurfaceTexture, holder.width, holder.height,
(mFacing == CameraCharacteristics.LENS_FACING_FRONT) ?
FLIP_TYPE_BOTH : FLIP_TYPE_VERTICAL);
} catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
// Should never hit this.
throw new IllegalStateException("Surface abandoned, skipping drawFrame...", e);
}
mPBufferPixels.clear();
GLES20.glReadPixels(/*x*/ 0, /*y*/ 0, holder.width, holder.height,
GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mPBufferPixels);
checkGlError("glReadPixels");
try {
int format = LegacyCameraDevice.detectSurfaceType(holder.surface);
LegacyCameraDevice.setSurfaceDimens(holder.surface, holder.width,
holder.height);
LegacyCameraDevice.setNextTimestamp(holder.surface, captureHolder.second);
LegacyCameraDevice.produceFrame(holder.surface, mPBufferPixels.array(),
holder.width, holder.height, format);
} catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
Log.w(TAG, "Surface abandoned, dropping frame. ", e);
request.setOutputAbandoned();
}
}
}
targetCollector.previewProduced();
if (doTiming) {
endGlTiming();
}
}
/**
* Clean up the current GL context.
*/
public void cleanupEGLContext() {
releaseEGLContext();
}
/**
* Drop all current GL operations on the floor.
*/
public void flush() {
// TODO: implement flush
Log.e(TAG, "Flush not yet implemented.");
}
}