Refactor SnapshotPictureRecorder

pull/421/head
Giacomo Randazzo 7 years ago
parent 076c53aab1
commit 3939f84d8f
  1. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java
  2. 12
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java
  3. 16
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  4. 58
      cameraview/src/main/java/com/otaliastudios/cameraview/OverlayLayout.java
  5. 39
      cameraview/src/main/java/com/otaliastudios/cameraview/SnapshotPictureRecorder.java
  6. 18
      cameraview/src/main/java/com/otaliastudios/cameraview/SnapshotVideoRecorder.java
  7. 7
      cameraview/src/main/java/com/otaliastudios/cameraview/SurfaceDrawer.java
  8. 107
      cameraview/src/main/views/com/otaliastudios/cameraview/GlCameraPreview.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();
}
});

@ -38,6 +38,8 @@ abstract class CameraController implements
protected WorkerHandler mHandler;
/* for tests */ Handler mCrashHandler;
protected final ArrayList<SurfaceDrawer> 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);
}
}

@ -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<View> 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());

@ -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

@ -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<SurfaceDrawer> 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<SurfaceDrawer> 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();

@ -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);
}

@ -0,0 +1,7 @@
package com.otaliastudios.cameraview;
import android.view.Surface;
public interface SurfaceDrawer {
void drawOnSurface(Surface surface);
}

@ -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<GLSurfaceView, SurfaceTexture> 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<RendererFrameCallback> mRendererFrameCallbacks = Collections.synchronizedSet(new HashSet<RendererFrameCallback>());
/* for tests */ float mScaleX = 1F;
/* for tests */ float mScaleY = 1F;
private View mRootView;
private ArrayList<OverlayInputSurfaceListener> 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<GLSurfaceView, SurfaceTexture> 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<GLSurfaceView, SurfaceTexture> 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<GLSurfaceView, SurfaceTexture> 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<GLSurfaceView, SurfaceTexture> 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<GLSurfaceView, SurfaceTexture> 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<GLSurfaceView, SurfaceTexture> 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<GLSurfaceView, SurfaceTexture> 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<GLSurfaceView, SurfaceTexture> 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);
}
}

Loading…
Cancel
Save