diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java index def14c84..56d87204 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java @@ -616,7 +616,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera LOG.v("Rotations", "SO", offset(REF_SENSOR, REF_OUTPUT), "OS", offset(REF_OUTPUT, REF_SENSOR)); LOG.v("Rotations", "VO", offset(REF_VIEW, REF_OUTPUT), "OV", offset(REF_OUTPUT, REF_VIEW)); - mPictureRecorder = new SnapshotPictureRecorder(result, Camera1.this, mCamera, outputRatio); + mPictureRecorder = new SnapshotPictureRecorder(result, Camera1.this, mCamera, outputRatio, pictureSurfaceDrawerList); mPictureRecorder.take(); } }); diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java index ab32d2a3..61ec211b 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java @@ -38,6 +38,8 @@ abstract class CameraController implements protected WorkerHandler mHandler; /* for tests */ Handler mCrashHandler; + protected final ArrayList pictureSurfaceDrawerList = new ArrayList<>(); + protected Facing mFacing; protected Flash mFlash; protected WhiteBalance mWhiteBalance; @@ -668,4 +670,14 @@ abstract class CameraController implements } //endregion + + public void addPictureSurfaceDrawer(@NonNull SurfaceDrawer surfaceDrawer) { + if (!pictureSurfaceDrawerList.contains(surfaceDrawer)) { + pictureSurfaceDrawerList.add(surfaceDrawer); + } + } + + public void removePictureSurfaceDrawer(@NonNull SurfaceDrawer surfaceDrawer) { + pictureSurfaceDrawerList.remove(surfaceDrawer); + } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java index b327be79..21e05aa1 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java @@ -259,7 +259,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver { } case GL_SURFACE: default: { mPreview = Preview.GL_SURFACE; - return new GlCameraPreview(context, container, null, mHasOverlay); + return new GlCameraPreview(context, container, null); } } } @@ -291,18 +291,6 @@ public class CameraView extends FrameLayout implements LifecycleObserver { mPreviewOverlayLayout = findViewById(R.id.preview_overlay_layout); - ((GlCameraPreview) mCameraPreview).addOverlayInputSurfaceListener(new GlCameraPreview.OverlayInputSurfaceListener() { - @Override - public void onSurface(@NonNull final Surface surface) { - mUiHandler.post(new Runnable() { - @Override - public void run() { - mPreviewOverlayLayout.setOutputSurface(surface); - } - }); - } - }); - LinkedList overlayViews = new LinkedList<>(); for (int i = 0; i < getChildCount(); i++) { View view = getChildAt(i); @@ -318,6 +306,8 @@ public class CameraView extends FrameLayout implements LifecycleObserver { mPreviewOverlayLayout.addView(view); } } + + mCameraController.addPictureSurfaceDrawer(mPreviewOverlayLayout); } if (!isInEditMode()) { mOrientationHelper.enable(getContext()); diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/OverlayLayout.java b/cameraview/src/main/java/com/otaliastudios/cameraview/OverlayLayout.java index 0418bb94..d6d0e926 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/OverlayLayout.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/OverlayLayout.java @@ -13,9 +13,7 @@ import android.widget.FrameLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -public class OverlayLayout extends FrameLayout { - - private Surface outputSurface = null; +public class OverlayLayout extends FrameLayout implements SurfaceDrawer { public OverlayLayout(@NonNull Context context) { super(context); @@ -29,36 +27,46 @@ public class OverlayLayout extends FrameLayout { @Override public void draw(Canvas canvas) { - if (outputSurface != null ) { - // Requires a try/catch for .lockCanvas( null ) - try { - final Canvas surfaceCanvas = outputSurface.lockCanvas(null); - - float xScale = surfaceCanvas.getWidth() / (float) canvas.getWidth(); - float yScale = surfaceCanvas.getHeight() / (float) canvas.getHeight(); - surfaceCanvas.scale(xScale, yScale); - surfaceCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); - super.draw(surfaceCanvas); - - outputSurface.unlockCanvasAndPost(surfaceCanvas); - } catch (Surface.OutOfResourcesException e) { - e.printStackTrace(); - } - } +// if (outputSurface != null ) { +// // Requires a try/catch for .lockCanvas( null ) +// try { +// final Canvas surfaceCanvas = outputSurface.lockCanvas(null); +// +// float xScale = surfaceCanvas.getWidth() / (float) canvas.getWidth(); +// float yScale = surfaceCanvas.getHeight() / (float) canvas.getHeight(); +// surfaceCanvas.scale(xScale, yScale); +// surfaceCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); +// super.draw(surfaceCanvas); +// +// outputSurface.unlockCanvasAndPost(surfaceCanvas); +// } catch (Surface.OutOfResourcesException e) { +// e.printStackTrace(); +// } +// } // draw the view as always, since we are already drawing on the preview this step can be // skipped - //super.draw(canvas); + super.draw(canvas); } + @Override + public void drawOnSurface(Surface outputSurface) { +// postInvalidate(); +// postInvalidateRecursive(this); + try { + final Canvas surfaceCanvas = outputSurface.lockCanvas(null); - public void setOutputSurface(Surface outputSurface) { - this.outputSurface = outputSurface; + float xScale = surfaceCanvas.getWidth() / (float) getWidth(); + float yScale = surfaceCanvas.getHeight() / (float) getHeight(); + surfaceCanvas.scale(xScale, yScale); + surfaceCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); + super.draw(surfaceCanvas); - // force redrawing - postInvalidate(); - postInvalidateRecursive(this); + outputSurface.unlockCanvasAndPost(surfaceCanvas); + } catch (Surface.OutOfResourcesException e) { + e.printStackTrace(); + } } // invalidates children (and nested children) recursively diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/SnapshotPictureRecorder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/SnapshotPictureRecorder.java index 4a510049..3201c6d3 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/SnapshotPictureRecorder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/SnapshotPictureRecorder.java @@ -11,10 +11,13 @@ import android.opengl.EGL14; import android.opengl.EGLContext; import android.opengl.Matrix; import android.util.Log; +import android.view.Surface; import androidx.annotation.NonNull; import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.List; /** * A {@link PictureResult} that uses standard APIs. @@ -30,11 +33,13 @@ class SnapshotPictureRecorder extends PictureRecorder { private AspectRatio mOutputRatio; private Size mSensorPreviewSize; private int mFormat; + private List mSurfaceDrawerList; private boolean mWithOverlay; SnapshotPictureRecorder(@NonNull PictureResult stub, @NonNull Camera1 controller, - @NonNull Camera camera, @NonNull AspectRatio outputRatio) { + @NonNull Camera camera, @NonNull AspectRatio outputRatio, + @NonNull List surfaceDrawerList) { super(stub, controller); mController = controller; mPreview = controller.mPreview; @@ -43,7 +48,7 @@ class SnapshotPictureRecorder extends PictureRecorder { mFormat = mController.mPreviewFormat; mSensorPreviewSize = mController.mPreviewStreamSize; mWithOverlay = mController.mDisableOverlayFor != DisableOverlayFor.PICTURE; - + mSurfaceDrawerList = surfaceDrawerList; } @Override @@ -65,17 +70,18 @@ class SnapshotPictureRecorder extends PictureRecorder { SurfaceTexture mOverlaySurfaceTexture; float[] mTransform; float[] mOverlayTransform; + EglViewport overlayViewport; @RendererThread - public void onRendererTextureCreated(int... textureIds) { - mTextureId = textureIds[0]; - if (textureIds.length > 1 && mWithOverlay) { - mOverlayTextureId = textureIds[1]; + public void onRendererTextureCreated(int textureId) { + mTextureId = textureId; + overlayViewport = new EglViewport(); + if (mWithOverlay) { + mOverlayTextureId = overlayViewport.createTexture(); } mSurfaceTexture = new SurfaceTexture(mTextureId, true); if (mOverlayTextureId != 0) { - mOverlaySurfaceTexture = null; -// mOverlaySurfaceTexture = new SurfaceTexture(mOverlayTextureId, true); + mOverlaySurfaceTexture = new SurfaceTexture(mOverlayTextureId, true); } // Need to crop the size. Rect crop = CropHelper.computeCrop(mResult.size, mOutputRatio); @@ -92,7 +98,7 @@ class SnapshotPictureRecorder extends PictureRecorder { @RendererThread @Override - public void onRendererFrame(SurfaceTexture surfaceTexture, final SurfaceTexture overlaySurfaceTexture, final float scaleX, final float scaleY) { + public void onRendererFrame(SurfaceTexture surfaceTexture, final float scaleX, final float scaleY) { preview.removeRendererFrameCallback(this); // This kinda work but has drawbacks: @@ -124,7 +130,12 @@ class SnapshotPictureRecorder extends PictureRecorder { EglViewport viewport = new EglViewport(); mSurfaceTexture.updateTexImage(); mSurfaceTexture.getTransformMatrix(mTransform); + Surface drawOnto = new Surface(mOverlaySurfaceTexture); if (mOverlaySurfaceTexture != null) { + for (SurfaceDrawer surfaceDrawer : mSurfaceDrawerList) { + surfaceDrawer.drawOnSurface(drawOnto); + } + mOverlaySurfaceTexture.updateTexImage(); mOverlaySurfaceTexture.getTransformMatrix(mOverlayTransform); } @@ -142,7 +153,7 @@ class SnapshotPictureRecorder extends PictureRecorder { // TODO Not sure why we need the minus here... It makes no sense to me. LOG.w("Recording frame. Rotation:", mResult.rotation, "Actual:", -mResult.rotation); int rotation = -mResult.rotation; - int overlayRotation = -mController.offset(CameraController.REF_VIEW, CameraController.REF_OUTPUT); + int overlayRotation = mController.offset(CameraController.REF_VIEW, CameraController.REF_OUTPUT); // apparently with front facing camera with don't need the minus sign if (mResult.facing == Facing.FRONT) { overlayRotation = -overlayRotation; @@ -169,6 +180,10 @@ class SnapshotPictureRecorder extends PictureRecorder { Matrix.scaleM(mOverlayTransform, 0, -1, -1, 1); } } + if (mOverlayTransform != null) { + // not sure why we have to flip the mirror the y axis + Matrix.scaleM(mOverlayTransform, 0, 1, -1, 1); + } // Go back to old position. Matrix.translateM(mTransform, 0, -0.5F, -0.5F, 0); @@ -180,7 +195,7 @@ class SnapshotPictureRecorder extends PictureRecorder { // They are simply realScaleX and realScaleY. viewport.drawFrame(mTextureId, mTransform); if (mOverlayTransform != null) { -// viewport.drawFrameOverlay(mOverlayTextureId, mOverlayTransform); + overlayViewport.drawFrame(mOverlayTextureId, mOverlayTransform); } // don't - surface.swapBuffers(); mResult.data = surface.saveFrameTo(Bitmap.CompressFormat.JPEG); @@ -190,6 +205,8 @@ class SnapshotPictureRecorder extends PictureRecorder { // EGL14.eglMakeCurrent(oldDisplay, oldSurface, oldSurface, eglContext); surface.release(); viewport.release(); + overlayViewport.release(); + drawOnto.release(); mSurfaceTexture.release(); if (mOverlaySurfaceTexture != null) { mOverlaySurfaceTexture.release(); diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/SnapshotVideoRecorder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/SnapshotVideoRecorder.java index bbe82426..0051fd1d 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/SnapshotVideoRecorder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/SnapshotVideoRecorder.java @@ -54,16 +54,16 @@ class SnapshotVideoRecorder extends VideoRecorder implements GlCameraPreview.Ren @RendererThread @Override - public void onRendererTextureCreated(int... textureId) { - mTextureId = textureId[0]; - if (textureId.length > 1) { - mOverlayTextureId = textureId[1]; - } + public void onRendererTextureCreated(int textureId) { + mTextureId = textureId; +// if (textureId.length > 1) { +// mOverlayTextureId = textureId[1]; +// } } @RendererThread @Override - public void onRendererFrame(@NonNull SurfaceTexture surfaceTexture, SurfaceTexture overlaySurfaceTexture, float scaleX, float scaleY) { + public void onRendererFrame(@NonNull SurfaceTexture surfaceTexture, float scaleX, float scaleY) { if (mCurrentState == STATE_NOT_RECORDING && mDesiredState == STATE_RECORDING) { // Set default options if (mResult.videoBitRate <= 0) mResult.videoBitRate = DEFAULT_VIDEO_BITRATE; @@ -114,9 +114,9 @@ class SnapshotVideoRecorder extends VideoRecorder implements GlCameraPreview.Ren TextureMediaEncoder.TextureFrame textureFrame = textureEncoder.acquireFrame(); textureFrame.timestamp = surfaceTexture.getTimestamp(); surfaceTexture.getTransformMatrix(textureFrame.transform); - if (mWithOverlay && overlaySurfaceTexture != null) { - overlaySurfaceTexture.getTransformMatrix(textureFrame.overlayTransform); - } +// if (mWithOverlay && overlaySurfaceTexture != null) { +// overlaySurfaceTexture.getTransformMatrix(textureFrame.overlayTransform); +// } mEncoderEngine.notify(TextureMediaEncoder.FRAME_EVENT, textureFrame); } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/SurfaceDrawer.java b/cameraview/src/main/java/com/otaliastudios/cameraview/SurfaceDrawer.java new file mode 100644 index 00000000..1c64cb51 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/SurfaceDrawer.java @@ -0,0 +1,7 @@ +package com.otaliastudios.cameraview; + +import android.view.Surface; + +public interface SurfaceDrawer { + void drawOnSurface(Surface surface); +} diff --git a/cameraview/src/main/views/com/otaliastudios/cameraview/GlCameraPreview.java b/cameraview/src/main/views/com/otaliastudios/cameraview/GlCameraPreview.java index 5f5c3609..4a454577 100644 --- a/cameraview/src/main/views/com/otaliastudios/cameraview/GlCameraPreview.java +++ b/cameraview/src/main/views/com/otaliastudios/cameraview/GlCameraPreview.java @@ -7,14 +7,11 @@ import android.opengl.Matrix; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import android.util.Log; import android.view.LayoutInflater; -import android.view.Surface; import android.view.SurfaceHolder; import android.view.View; import android.view.ViewGroup; -import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -58,23 +55,17 @@ class GlCameraPreview extends CameraPreview imple private boolean mDispatched; private final float[] mTransformMatrix = new float[16]; - private final float[] mOverlayTransformMatrix = new float[16]; - private int[] mOutputTextureIds = new int[]{0, 0}; + private int mOutputTextureId = 0; private SurfaceTexture mInputSurfaceTexture; - private SurfaceTexture mOverlaySurfaceTexture; - private Surface mOverlaySurface; private EglViewport mOutputViewport; - private EglViewport mOutputOverlayViewport; private Set mRendererFrameCallbacks = Collections.synchronizedSet(new HashSet()); /* for tests */ float mScaleX = 1F; /* for tests */ float mScaleY = 1F; private View mRootView; - private ArrayList overlayListeners = new ArrayList<>(); - - GlCameraPreview(@NonNull Context context, @NonNull ViewGroup parent, @Nullable SurfaceCallback callback, boolean hasOverlay) { - super(context, parent, callback, hasOverlay); + GlCameraPreview(@NonNull Context context, @NonNull ViewGroup parent, @Nullable SurfaceCallback callback) { + super(context, parent, callback); } @NonNull @@ -131,54 +122,24 @@ class GlCameraPreview extends CameraPreview imple mInputSurfaceTexture.release(); mInputSurfaceTexture = null; } - if (mOverlaySurface != null) { - mOverlaySurface.release(); - mOverlaySurface = null; - } - if (mOverlaySurfaceTexture != null) { - mOverlaySurfaceTexture.release(); - mOverlaySurfaceTexture = null; - } - mOutputTextureIds[0] = 0; - mOutputTextureIds[1] = 0; + mOutputTextureId = 0; if (mOutputViewport != null) { mOutputViewport.release(); mOutputViewport = null; } - if (mOutputOverlayViewport != null) { - mOutputOverlayViewport.release(); - mOutputOverlayViewport = null; - } } @RendererThread @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { mOutputViewport = new EglViewport(); - mOutputOverlayViewport = new EglViewport(); - mOutputTextureIds[0] = mOutputViewport.createTexture(); - if (hasOverlay()) { - mOutputTextureIds[1] = mOutputOverlayViewport.createTexture(); - mOverlaySurfaceTexture = new SurfaceTexture(mOutputTextureIds[1]); - mOverlaySurface = new Surface(mOverlaySurfaceTexture); - for (OverlayInputSurfaceListener listener : overlayListeners) { - if (listener != null) { - listener.onSurface(mOverlaySurface); - } - } - - mOverlaySurfaceTexture.setDefaultBufferSize(mInputStreamWidth, mInputStreamHeight); - } - mInputSurfaceTexture = new SurfaceTexture(mOutputTextureIds[0]); + mOutputTextureId = mOutputViewport.createTexture(); + mInputSurfaceTexture = new SurfaceTexture(mOutputTextureId); getView().queueEvent(new Runnable() { @Override public void run() { for (RendererFrameCallback callback : mRendererFrameCallbacks) { - if (mOutputTextureIds[1] != 0) { - callback.onRendererTextureCreated(mOutputTextureIds[0], mOutputTextureIds[1]); - } else { - callback.onRendererTextureCreated(mOutputTextureIds[0]); - } + callback.onRendererTextureCreated(mOutputTextureId); } } }); @@ -197,25 +158,12 @@ class GlCameraPreview extends CameraPreview imple @SuppressWarnings("StatementWithEmptyBody") @Override public void onSurfaceChanged(GL10 gl, final int width, final int height) { + gl.glViewport(0, 0, width, height); if (!mDispatched) { dispatchOnSurfaceAvailable(width, height); mDispatched = true; - } else if (mOutputSurfaceWidth == width && mOutputSurfaceHeight == height) { - // I was experimenting and this was happening. - // Not sure if it is stil needed now. - } else { - // With other CameraPreview implementation we could just dispatch the 'size changed' event - // to the controller and everything would go straight. In case of GL, apparently we have to - // force recreate the EGLContext by calling onPause and onResume in the UI thread. - dispatchOnSurfaceDestroyed(); - getView().post(new Runnable() { - @Override - public void run() { - getView().onPause(); - getView().onResume(); - dispatchOnSurfaceAvailable(width, height); - } - }); + } else if (width != mOutputSurfaceWidth || height != mOutputSurfaceHeight){ + dispatchOnSurfaceSizeChanged(width, height); } } @@ -225,18 +173,12 @@ class GlCameraPreview extends CameraPreview imple // Latch the latest frame. If there isn't anything new, // we'll just re-use whatever was there before. mInputSurfaceTexture.updateTexImage(); - if (hasOverlay()) { - mOverlaySurfaceTexture.updateTexImage(); - } if (mInputStreamWidth <= 0 || mInputStreamHeight <= 0) { // Skip drawing. Camera was not opened. return; } mInputSurfaceTexture.getTransformMatrix(mTransformMatrix); - if (hasOverlay()) { - mOverlaySurfaceTexture.getTransformMatrix(mOverlayTransformMatrix); - } if (isCropping()) { // Scaling is easy. However: // If the view is 10x1000 (very tall), it will show only the left strip of the preview (not the center one). @@ -249,12 +191,9 @@ class GlCameraPreview extends CameraPreview imple } // Future note: passing scale to the viewport? // They are scaleX an scaleY, but flipped based on mInputFlipped. - mOutputViewport.drawFrame(mOutputTextureIds[0], mTransformMatrix); - if (hasOverlay() && mOutputTextureIds[1] != 0) { - mOutputOverlayViewport.drawFrame(mOutputTextureIds[1], mOverlayTransformMatrix); - } + mOutputViewport.drawFrame(mOutputTextureId, mTransformMatrix); for (RendererFrameCallback callback : mRendererFrameCallbacks) { - callback.onRendererFrame(mInputSurfaceTexture, mOverlaySurfaceTexture, mScaleX, mScaleY); + callback.onRendererFrame(mInputSurfaceTexture, mScaleX, mScaleY); } } @@ -315,10 +254,10 @@ class GlCameraPreview extends CameraPreview imple * Called on the renderer thread, hopefully only once, to notify that * the texture was created (or to inform a new callback of the old texture). * - * @param textureId the GL textures linked to the image stream + * @param textureId the GL texture linked to the image stream */ @RendererThread - void onRendererTextureCreated(int... textureId); + void onRendererTextureCreated(int textureId); /** * Called on the renderer thread after each frame was drawn. @@ -330,7 +269,7 @@ class GlCameraPreview extends CameraPreview imple * @param scaleY the scaleY (in REF_VIEW) value */ @RendererThread - void onRendererFrame(SurfaceTexture surfaceTexture, SurfaceTexture overlaySurfaceTexture, float scaleX, float scaleY); + void onRendererFrame(SurfaceTexture surfaceTexture, float scaleX, float scaleY); } void addRendererFrameCallback(@NonNull final RendererFrameCallback callback) { @@ -338,10 +277,7 @@ class GlCameraPreview extends CameraPreview imple @Override public void run() { mRendererFrameCallbacks.add(callback); - if (mOutputTextureIds[0] != 0 && mOutputTextureIds[1] != 0) { - callback.onRendererTextureCreated(mOutputTextureIds); - } - else if (mOutputTextureIds[0] != 0) callback.onRendererTextureCreated(mOutputTextureIds[0]); + if (mOutputTextureId != 0) callback.onRendererTextureCreated(mOutputTextureId); } }); } @@ -349,15 +285,4 @@ class GlCameraPreview extends CameraPreview imple void removeRendererFrameCallback(@NonNull final RendererFrameCallback callback) { mRendererFrameCallbacks.remove(callback); } - - public void addOverlayInputSurfaceListener(@NonNull OverlayInputSurfaceListener listener) { - overlayListeners.add(listener); - if (mOverlaySurface != null) { - listener.onSurface(mOverlaySurface); - } - } - - interface OverlayInputSurfaceListener { - void onSurface(@NonNull Surface surface); - } }