Address issues

pull/502/head
Mattia Iavarone 6 years ago
parent 3a6f3426b0
commit 573415323f
  1. 122
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  2. 4
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java
  3. 4
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java
  4. 73
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java
  5. 10
      cameraview/src/main/java/com/otaliastudios/cameraview/overlay/Overlay.java
  6. 249
      cameraview/src/main/java/com/otaliastudios/cameraview/overlay/OverlayLayout.java
  7. 27
      cameraview/src/main/java/com/otaliastudios/cameraview/overlay/OverlayLayoutInner.java
  8. 8
      cameraview/src/main/java/com/otaliastudios/cameraview/overlay/SurfaceDrawer.java
  9. 146
      cameraview/src/main/java/com/otaliastudios/cameraview/picture/SnapshotGlPictureRecorder.java
  10. 28
      cameraview/src/main/java/com/otaliastudios/cameraview/video/SnapshotVideoRecorder.java
  11. 14
      cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/TextureMediaEncoder.java
  12. 8
      cameraview/src/main/res/values/attrs.xml
  13. 6
      demo/src/main/res/layout/activity_camera.xml

@ -134,8 +134,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
private boolean mExperimental; private boolean mExperimental;
// Overlays // Overlays
private OverlayLayout mOverlayLayoutManager; // see OverlayLayoutManager for why having two of them private OverlayLayout mOverlayLayout;
private OverlayLayout mOverlayLayoutManagerBelow;
// Threading // Threading
private Handler mUiHandler; private Handler mUiHandler;
@ -193,13 +192,11 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
// Views // Views
mGridLinesLayout = new GridLinesLayout(context); mGridLinesLayout = new GridLinesLayout(context);
mOverlayLayoutManager = new OverlayLayout(context); mOverlayLayout = new OverlayLayout(context);
mOverlayLayoutManagerBelow = new OverlayLayout(context);
mMarkerLayout = new MarkerLayout(context); mMarkerLayout = new MarkerLayout(context);
addView(mGridLinesLayout); addView(mGridLinesLayout);
addView(mMarkerLayout); addView(mMarkerLayout);
addView(mOverlayLayoutManager); addView(mOverlayLayout);
addView(mOverlayLayoutManagerBelow, 0); // put it at the bottom of the FrameLayout
// Create the engine // Create the engine
doInstantiateEngine(); doInstantiateEngine();
@ -248,16 +245,12 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
*/ */
private void doInstantiateEngine() { private void doInstantiateEngine() {
if (mCameraEngine != null) { if (mCameraEngine != null) {
mCameraEngine.removePictureSurfaceDrawer(mOverlayLayoutManager); mCameraEngine.removePictureOverlay(mOverlayLayout);
mCameraEngine.removePictureSurfaceDrawer(mOverlayLayoutManagerBelow); mCameraEngine.removeVideoOverlay(mOverlayLayout);
mCameraEngine.removeVideoSurfaceDrawer(mOverlayLayoutManager);
mCameraEngine.removeVideoSurfaceDrawer(mOverlayLayoutManagerBelow);
} }
mCameraEngine = instantiateCameraEngine(mEngine, mCameraCallbacks); mCameraEngine = instantiateCameraEngine(mEngine, mCameraCallbacks);
mCameraEngine.addPictureSurfaceDrawer(mOverlayLayoutManager); mCameraEngine.addPictureOverlay(mOverlayLayout);
mCameraEngine.addPictureSurfaceDrawer(mOverlayLayoutManagerBelow); mCameraEngine.addVideoOverlay(mOverlayLayout);
mCameraEngine.addVideoSurfaceDrawer(mOverlayLayoutManager);
mCameraEngine.addVideoSurfaceDrawer(mOverlayLayoutManagerBelow);
} }
/** /**
@ -2103,106 +2096,21 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
//region Overlays //region Overlays
@Override
public void addView(View child, ViewGroup.LayoutParams params) {
if (params instanceof OverlayLayoutParams) {
if (((OverlayLayoutParams) params).drawInPreview) {
mOverlayLayoutManager.addView(child, params);
} else {
mOverlayLayoutManagerBelow.addView(child, params);
}
} else {
super.addView(child, params);
}
}
@Override
public void removeView(View child) {
if (child.getLayoutParams() instanceof OverlayLayoutParams) {
if (((OverlayLayoutParams) child.getLayoutParams()).drawInPreview) {
mOverlayLayoutManager.removeView(child);
} else {
mOverlayLayoutManagerBelow.removeView(child);
}
} else {
super.removeView(child);
}
}
@Override @Override
public LayoutParams generateLayoutParams(AttributeSet attributeSet) { public LayoutParams generateLayoutParams(AttributeSet attributeSet) {
OverlayLayoutParams toBeChecked = new OverlayLayoutParams(this.getContext(), attributeSet); if (mOverlayLayout.isOverlay(attributeSet)) {
if (toBeChecked.isOverlay()) { return mOverlayLayout.generateLayoutParams(attributeSet);
return toBeChecked;
} }
return super.generateLayoutParams(attributeSet); return super.generateLayoutParams(attributeSet);
} }
// We don't support removeView on overlays for now.
@Override @Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { public void addView(View child, int index, ViewGroup.LayoutParams params) {
if (p instanceof OverlayLayoutParams) { if (mOverlayLayout.isOverlay(params)) {
return p; mOverlayLayout.addView(child, params);
} } else {
return super.generateLayoutParams(p); super.addView(child, index, params);
}
public static class OverlayLayoutParams extends FrameLayout.LayoutParams {
private boolean drawInPreview = false;
private boolean drawInPictureSnapshot = false;
private boolean drawInVideoSnapshot = false;
public OverlayLayoutParams(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
this.readStyleParameters(context, attributeSet);
}
public OverlayLayoutParams(int width, int height) {
super(width, height);
}
public OverlayLayoutParams(ViewGroup.LayoutParams layoutParams) {
super(layoutParams);
}
private void readStyleParameters(Context context, AttributeSet attributeSet) {
TypedArray a = context.obtainStyledAttributes(attributeSet, R.styleable.CameraView_Layout);
try {
this.drawInPreview = a.getBoolean(R.styleable.CameraView_Layout_layout_drawInPreview, false);
this.drawInPictureSnapshot = a.getBoolean(R.styleable.CameraView_Layout_layout_drawInPictureSnapshot, false);
this.drawInVideoSnapshot = a.getBoolean(R.styleable.CameraView_Layout_layout_drawInVideoSnapshot, false);
} finally {
a.recycle();
}
}
public boolean isDrawInPreview() {
return drawInPreview;
}
public void setDrawInPreview(boolean drawInPreview) {
this.drawInPreview = drawInPreview;
}
public boolean isDrawInPictureSnapshot() {
return drawInPictureSnapshot;
}
public void setDrawInPictureSnapshot(boolean drawInPictureSnapshot) {
this.drawInPictureSnapshot = drawInPictureSnapshot;
}
public boolean isDrawInVideoSnapshot() {
return drawInVideoSnapshot;
}
public void setDrawInVideoSnapshot(boolean drawInVideoSnapshot) {
this.drawInVideoSnapshot = drawInVideoSnapshot;
}
public boolean isOverlay() {
return drawInPreview || drawInPictureSnapshot || drawInVideoSnapshot;
} }
} }

@ -312,7 +312,7 @@ public class Camera1Engine extends CameraEngine implements
AspectRatio outputRatio = getAngles().flip(Reference.OUTPUT, Reference.VIEW) ? viewAspectRatio.flip() : viewAspectRatio; AspectRatio outputRatio = getAngles().flip(Reference.OUTPUT, Reference.VIEW) ? viewAspectRatio.flip() : viewAspectRatio;
if (mPreview instanceof GlCameraPreview && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (mPreview instanceof GlCameraPreview && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mPictureRecorder = new SnapshotGlPictureRecorder(stub, this, (GlCameraPreview) mPreview, outputRatio, getPictureSurfaceDrawers()); mPictureRecorder = new SnapshotGlPictureRecorder(stub, this, (GlCameraPreview) mPreview, outputRatio, getPictureOverlays());
} else { } else {
mPictureRecorder = new Snapshot1PictureRecorder(stub, this, mCamera, outputRatio); mPictureRecorder = new Snapshot1PictureRecorder(stub, this, mCamera, outputRatio);
} }
@ -362,7 +362,7 @@ public class Camera1Engine extends CameraEngine implements
stub.rotation = getAngles().offset(Reference.VIEW, Reference.OUTPUT, Axis.ABSOLUTE); stub.rotation = getAngles().offset(Reference.VIEW, Reference.OUTPUT, Axis.ABSOLUTE);
// Start. // Start.
mVideoRecorder = new SnapshotVideoRecorder(Camera1Engine.this, glPreview, getVideoSurfaceDrawers()); mVideoRecorder = new SnapshotVideoRecorder(Camera1Engine.this, glPreview, getVideoOverlays());
mVideoRecorder.start(stub); mVideoRecorder.start(stub);
} }

@ -615,7 +615,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
stub.rotation = getAngles().offset(Reference.SENSOR, Reference.OUTPUT, Axis.RELATIVE_TO_SENSOR); // Actually it will be rotated and set to 0. stub.rotation = getAngles().offset(Reference.SENSOR, Reference.OUTPUT, Axis.RELATIVE_TO_SENSOR); // Actually it will be rotated and set to 0.
AspectRatio outputRatio = getAngles().flip(Reference.OUTPUT, Reference.VIEW) ? viewAspectRatio.flip() : viewAspectRatio; AspectRatio outputRatio = getAngles().flip(Reference.OUTPUT, Reference.VIEW) ? viewAspectRatio.flip() : viewAspectRatio;
if (mPreview instanceof GlCameraPreview) { if (mPreview instanceof GlCameraPreview) {
mPictureRecorder = new SnapshotGlPictureRecorder(stub, this, (GlCameraPreview) mPreview, outputRatio, getPictureSurfaceDrawers()); mPictureRecorder = new SnapshotGlPictureRecorder(stub, this, (GlCameraPreview) mPreview, outputRatio, getPictureOverlays());
} else { } else {
throw new RuntimeException("takePictureSnapshot with Camera2 is only supported with Preview.GL_SURFACE"); throw new RuntimeException("takePictureSnapshot with Camera2 is only supported with Preview.GL_SURFACE");
} }
@ -709,7 +709,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
stub.rotation = getAngles().offset(Reference.VIEW, Reference.OUTPUT, Axis.ABSOLUTE); stub.rotation = getAngles().offset(Reference.VIEW, Reference.OUTPUT, Axis.ABSOLUTE);
// Start. // Start.
mVideoRecorder = new SnapshotVideoRecorder(this, glPreview, getVideoSurfaceDrawers()); mVideoRecorder = new SnapshotVideoRecorder(this, glPreview, getVideoOverlays());
mVideoRecorder.start(stub); mVideoRecorder.start(stub);
} }

@ -18,7 +18,7 @@ import com.otaliastudios.cameraview.CameraException;
import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.CameraOptions; import com.otaliastudios.cameraview.CameraOptions;
import com.otaliastudios.cameraview.PictureResult; import com.otaliastudios.cameraview.PictureResult;
import com.otaliastudios.cameraview.overlay.SurfaceDrawer; import com.otaliastudios.cameraview.overlay.Overlay;
import com.otaliastudios.cameraview.VideoResult; import com.otaliastudios.cameraview.VideoResult;
import com.otaliastudios.cameraview.engine.offset.Angles; import com.otaliastudios.cameraview.engine.offset.Angles;
import com.otaliastudios.cameraview.engine.offset.Reference; import com.otaliastudios.cameraview.engine.offset.Reference;
@ -185,38 +185,8 @@ public abstract class CameraEngine implements
private long mAutoFocusResetDelayMillis; private long mAutoFocusResetDelayMillis;
private int mSnapshotMaxWidth = Integer.MAX_VALUE; // in REF_VIEW for consistency with SizeSelectors private int mSnapshotMaxWidth = Integer.MAX_VALUE; // in REF_VIEW for consistency with SizeSelectors
private int mSnapshotMaxHeight = Integer.MAX_VALUE; // in REF_VIEW for consistency with SizeSelectors private int mSnapshotMaxHeight = Integer.MAX_VALUE; // in REF_VIEW for consistency with SizeSelectors
private final List<SurfaceDrawer> pictureSurfaceDrawers = new ArrayList<>(); private final List<Overlay> pictureOverlays = new ArrayList<>();
private final List<SurfaceDrawer> videoSurfaceDrawers = new ArrayList<>(); private final List<Overlay> videoOverlays = new ArrayList<>();
public void addPictureSurfaceDrawer(@NonNull SurfaceDrawer surfaceDrawer) {
if (!pictureSurfaceDrawers.contains(surfaceDrawer)) {
pictureSurfaceDrawers.add(surfaceDrawer);
}
}
public void removePictureSurfaceDrawer(@NonNull SurfaceDrawer surfaceDrawer) {
pictureSurfaceDrawers.remove(surfaceDrawer);
}
@NonNull
protected List<SurfaceDrawer> getPictureSurfaceDrawers() {
return pictureSurfaceDrawers;
}
public void addVideoSurfaceDrawer(@NonNull SurfaceDrawer surfaceDrawer) {
if (!videoSurfaceDrawers.contains(surfaceDrawer)) {
videoSurfaceDrawers.add(surfaceDrawer);
}
}
public void removeVideoSurfaceDrawer(@NonNull SurfaceDrawer surfaceDrawer) {
videoSurfaceDrawers.remove(surfaceDrawer);
}
@NonNull
protected List<SurfaceDrawer> getVideoSurfaceDrawers() {
return videoSurfaceDrawers;
}
// Steps // Steps
private final Step.Callback mStepCallback = new Step.Callback() { private final Step.Callback mStepCallback = new Step.Callback() {
@ -1419,4 +1389,41 @@ public abstract class CameraEngine implements
} }
//endregion //endregion
//region Overlays
public void addPictureOverlay(@NonNull Overlay overlay) {
if (!pictureOverlays.contains(overlay)) {
pictureOverlays.add(overlay);
}
}
public void removePictureOverlay(@NonNull Overlay overlay) {
pictureOverlays.remove(overlay);
}
public void addVideoOverlay(@NonNull Overlay overlay) {
if (!videoOverlays.contains(overlay)) {
videoOverlays.add(overlay);
}
}
public void removeVideoOverlay(@NonNull Overlay overlay) {
videoOverlays.remove(overlay);
}
@SuppressWarnings("WeakerAccess")
@NonNull
protected List<Overlay> getPictureOverlays() {
return pictureOverlays;
}
@SuppressWarnings("WeakerAccess")
@NonNull
protected List<Overlay> getVideoOverlays() {
return videoOverlays;
}
//endregion
} }

@ -0,0 +1,10 @@
package com.otaliastudios.cameraview.overlay;
import android.graphics.Canvas;
import androidx.annotation.NonNull;
public interface Overlay {
void drawOnPicture(@NonNull Canvas canvas);
void drawOnVideo(@NonNull Canvas canvas);
}

@ -1,7 +1,9 @@
package com.otaliastudios.cameraview.overlay; package com.otaliastudios.cameraview.overlay;
import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View; import android.view.View;
@ -11,137 +13,172 @@ import android.widget.FrameLayout;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.otaliastudios.cameraview.CameraView; import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.R;
import java.util.HashMap;
import java.util.Map;
@SuppressLint("CustomViewStyleable")
/** public class OverlayLayout extends FrameLayout implements Overlay {
* This class manages {@link OverlayLayoutInner}s.
* The necessity for this class comes from two features of {@link View}s: private static final String TAG = OverlayLayout.class.getSimpleName();
* - a {@link View} can only have one parent private static final CameraLogger LOG = CameraLogger.create(TAG);
* - the View framework does not provide a straightforward way for a {@link ViewGroup} to draw
* only a subset of it's children.
* We have three possible target for a overlay {@link View} to be drawn on:
* - camera preview
* - picture snapshot
* - video snapshot
* Given the two constraints above in order to draw exclusively on a subset of targets we need a
* different {@link OverlayLayoutInner} for each subset of targets. This class manages those different
* {@link OverlayLayoutInner}s.
*
* A problem remains: the views are drawn on preview when {@link #draw(Canvas)} is called on this
* class, for not drawing on the preview but drawing on picture snapshot, for instance, we cannot
* change the child's visibility.
* One way to solve this problem is to have two instances of {@link OverlayLayout} and layer
* them so that the one below is covered and hidden by the camera preview. This way only the top
* {@link OverlayLayout} is shown on top of the camera preview and we can still access the
* bottom one's {@link OverlayLayoutInner#draw(Canvas)} for drawing on picture snapshots.
*/
public class OverlayLayout extends FrameLayout implements SurfaceDrawer {
private Map<OverlayType, OverlayLayoutInner> mLayouts = new HashMap<>(); private static final int DRAWING_PREVIEW = 0;
private static final int DRAWING_PICTURE = 1;
private static final int DRAWING_VIDEO = 2;
private int target = DRAWING_PREVIEW;
public OverlayLayout(@NonNull Context context) { public OverlayLayout(@NonNull Context context) {
super(context); super(context);
} }
public OverlayLayout(@NonNull Context context, @Nullable AttributeSet attrs) { /**
super(context, attrs); * Returns true if this {@link AttributeSet} belongs to an overlay.
* @param set an attribute set
* @return true if overlay
*/
public boolean isOverlay(@Nullable AttributeSet set) {
if (set == null) return false;
TypedArray a = getContext().obtainStyledAttributes(set, R.styleable.CameraView_Layout);
boolean isOverlay = a.getBoolean(R.styleable.CameraView_Layout_layout_isOverlay, false);
a.recycle();
return isOverlay;
}
/**
* Returns true if this {@link ViewGroup.LayoutParams} belongs to an overlay.
* @param params a layout params
* @return true if overlay
*/
public boolean isOverlay(@NonNull ViewGroup.LayoutParams params) {
return params instanceof LayoutParams;
} }
/**
* Generates our own overlay layout params.
* @param attrs input attrs
* @return our params
*/
@Override @Override
public void addView(View child, ViewGroup.LayoutParams params) { public OverlayLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
// params must be instance of OverlayLayoutParams return new LayoutParams(getContext(), attrs);
if (!(params instanceof CameraView.OverlayLayoutParams)) {
return;
}
OverlayType viewOverlayType = new OverlayType((CameraView.OverlayLayoutParams) params);
if (mLayouts.containsKey(viewOverlayType)) {
mLayouts.get(viewOverlayType).addView(child, params);
} else {
OverlayLayoutInner newLayout = new OverlayLayoutInner(getContext());
newLayout.addView(child, params);
super.addView(newLayout);
mLayouts.put(viewOverlayType, newLayout);
}
} }
/**
* This is called by the View hierarchy, so at this point we are
* likely drawing on the preview.
* @param canvas View canvas
*/
@Override @Override
public void removeView(View child) { public void draw(Canvas canvas) {
// params must be instance of OverlayLayoutParams synchronized (this) {
if (!(child.getLayoutParams() instanceof CameraView.OverlayLayoutParams)) { target = DRAWING_PREVIEW;
return; super.draw(canvas);
}
OverlayType viewOverlayType = new OverlayType((CameraView.OverlayLayoutParams) child.getLayoutParams());
if (mLayouts.containsKey(viewOverlayType)) {
mLayouts.get(viewOverlayType).removeView(child);
} }
} }
/**
* This is called by the overlay drawer.
* We call {@link #dispatchDraw(Canvas)} to draw our children.
*
* The input canvas has the Surface dimensions which means it is guaranteed
* to have our own aspect ratio. But we might still have to apply some scale.
*
* @param canvas the overlay canvas
*/
@Override @Override
public void drawOnSurfaceForPictureSnapshot(Canvas surfaceCanvas) { public void drawOnPicture(@NonNull Canvas canvas) {
surfaceCanvas.save(); canvas.save();
// scale factor between canvas width and this View's width float widthScale = canvas.getWidth() / (float) getWidth();
float widthScale = surfaceCanvas.getWidth() / (float) getWidth(); float heightScale = canvas.getHeight() / (float) getHeight();
// scale factor between canvas height and this View's height LOG.i("drawOnPicture",
float heightScale = surfaceCanvas.getHeight() / (float) getHeight(); "widthScale:", widthScale,
surfaceCanvas.scale(widthScale, heightScale); "heightScale:", heightScale,
for (Map.Entry<OverlayType, OverlayLayoutInner> entry : mLayouts.entrySet()) { "canvasWidth:", canvas.getWidth(),
if (entry.getKey().pictureSnapshot) { "canvasHeight:", canvas.getHeight());
entry.getValue().drawOverlay(surfaceCanvas); canvas.scale(widthScale, heightScale);
} synchronized (this) {
} target = DRAWING_PICTURE;
surfaceCanvas.restore(); dispatchDraw(canvas);
} }
canvas.restore();
}
/**
* This is called by the overlay drawer.
* We call {@link #dispatchDraw(Canvas)} to draw our children.
* @param canvas the overlay canvas
*/
@Override @Override
public void drawOnSurfaceForVideoSnapshot(Canvas surfaceCanvas) { public void drawOnVideo(@NonNull Canvas canvas) {
surfaceCanvas.save(); canvas.save();
// scale factor between canvas width and this View's width float widthScale = canvas.getWidth() / (float) getWidth();
float widthScale = surfaceCanvas.getWidth() / (float) getWidth(); float heightScale = canvas.getHeight() / (float) getHeight();
// scale factor between canvas height and this View's height LOG.i("drawOnVideo",
float heightScale = surfaceCanvas.getHeight() / (float) getHeight(); "widthScale:", widthScale,
surfaceCanvas.scale(widthScale, heightScale); "heightScale:", heightScale,
for (Map.Entry<OverlayType, OverlayLayoutInner> entry : mLayouts.entrySet()) { "canvasWidth:", canvas.getWidth(),
if (entry.getKey().videoSnapshot) { "canvasHeight:", canvas.getHeight());
entry.getValue().drawOverlay(surfaceCanvas); canvas.scale(widthScale, heightScale);
} synchronized (this) {
target = DRAWING_VIDEO;
dispatchDraw(canvas);
}
canvas.restore();
}
/**
* We end up here in all three cases, and should filter out
* views that are not meant to be drawn on that specific surface.
*/
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
LayoutParams params = (LayoutParams) child.getLayoutParams();
boolean draw = ((target == DRAWING_PREVIEW && params.drawOnPreview)
|| (target == DRAWING_VIDEO && params.drawOnVideoSnapshot)
|| (target == DRAWING_PICTURE && params.drawOnPictureSnapshot)
);
if (draw) {
return super.drawChild(canvas, child, drawingTime);
} else {
LOG.v("Skipping drawing for view:", child.getClass().getSimpleName(),
"target:", target,
"params:", params);
return false;
} }
surfaceCanvas.restore();
} }
private class OverlayType { @SuppressWarnings("WeakerAccess")
boolean preview = false; public static class LayoutParams extends FrameLayout.LayoutParams {
boolean pictureSnapshot = false;
boolean videoSnapshot = false;
OverlayType(CameraView.OverlayLayoutParams params) { @SuppressWarnings("unused")
this.preview = params.isDrawInPreview(); private boolean isOverlay;
this.pictureSnapshot = params.isDrawInPictureSnapshot(); public boolean drawOnPreview;
this.videoSnapshot = params.isDrawInVideoSnapshot(); public boolean drawOnPictureSnapshot;
} public boolean drawOnVideoSnapshot;
@Override public LayoutParams(@NonNull Context context, @Nullable AttributeSet attrs) {
public boolean equals(Object o) { super(context, attrs);
if (this == o) return true; TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CameraView_Layout);
if (o == null || getClass() != o.getClass()) return false; try {
OverlayType that = (OverlayType) o; this.isOverlay = a.getBoolean(R.styleable.CameraView_Layout_layout_isOverlay, false);
return preview == that.preview && this.drawOnPreview = a.getBoolean(R.styleable.CameraView_Layout_layout_drawOnPreview, false);
pictureSnapshot == that.pictureSnapshot && this.drawOnPictureSnapshot = a.getBoolean(R.styleable.CameraView_Layout_layout_drawOnPictureSnapshot, false);
videoSnapshot == that.videoSnapshot; this.drawOnVideoSnapshot = a.getBoolean(R.styleable.CameraView_Layout_layout_drawOnVideoSnapshot, false);
} finally {
a.recycle();
}
} }
@NonNull
@Override @Override
public int hashCode() { public String toString() {
int result = 0; return getClass().getName() + "[isOverlay:" + isOverlay
result = 31*result + (preview ? 1 : 0); + ",drawOnPreview:" + drawOnPreview
result = 31*result + (pictureSnapshot ? 1 : 0); + ",drawOnPictureSnapshot:" + drawOnPictureSnapshot
result = 31*result + (videoSnapshot ? 1 : 0); + ",drawOnVideoSnapshot:" + drawOnVideoSnapshot
return result; + "]";
} }
} }
} }

@ -1,27 +0,0 @@
package com.otaliastudios.cameraview.overlay;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class OverlayLayoutInner extends FrameLayout {
public OverlayLayoutInner(@NonNull Context context) {
super(context);
setWillNotDraw(false);
}
public OverlayLayoutInner(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
setWillNotDraw(false);
}
public void drawOverlay(Canvas canvas) {
super.draw(canvas);
}
}

@ -1,8 +0,0 @@
package com.otaliastudios.cameraview.overlay;
import android.graphics.Canvas;
public interface SurfaceDrawer {
void drawOnSurfaceForPictureSnapshot(Canvas surfaceCanvas);
void drawOnSurfaceForVideoSnapshot(Canvas surfaceCanvas);
}

@ -14,7 +14,7 @@ import android.os.Build;
import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.PictureResult; import com.otaliastudios.cameraview.PictureResult;
import com.otaliastudios.cameraview.overlay.SurfaceDrawer; import com.otaliastudios.cameraview.overlay.Overlay;
import com.otaliastudios.cameraview.controls.Facing; import com.otaliastudios.cameraview.controls.Facing;
import com.otaliastudios.cameraview.engine.CameraEngine; import com.otaliastudios.cameraview.engine.CameraEngine;
import com.otaliastudios.cameraview.engine.offset.Axis; import com.otaliastudios.cameraview.engine.offset.Axis;
@ -31,9 +31,11 @@ import com.otaliastudios.cameraview.size.AspectRatio;
import com.otaliastudios.cameraview.size.Size; import com.otaliastudios.cameraview.size.Size;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.view.Surface; import android.view.Surface;
import java.util.ArrayList;
import java.util.List; import java.util.List;
public class SnapshotGlPictureRecorder extends PictureRecorder { public class SnapshotGlPictureRecorder extends PictureRecorder {
@ -45,21 +47,23 @@ public class SnapshotGlPictureRecorder extends PictureRecorder {
private GlCameraPreview mPreview; private GlCameraPreview mPreview;
private AspectRatio mOutputRatio; private AspectRatio mOutputRatio;
private List<SurfaceDrawer> mSurfaceDrawerList; private List<Overlay> mOverlays;
private boolean mWithOverlay; private boolean mHasOverlays;
public SnapshotGlPictureRecorder( public SnapshotGlPictureRecorder(
@NonNull PictureResult.Stub stub, @NonNull PictureResult.Stub stub,
@NonNull CameraEngine engine, @NonNull CameraEngine engine,
@NonNull GlCameraPreview preview, @NonNull GlCameraPreview preview,
@NonNull AspectRatio outputRatio, @NonNull AspectRatio outputRatio,
@NonNull List<SurfaceDrawer> surfaceDrawerList) { @Nullable List<Overlay> overlays) {
super(stub, engine); super(stub, engine);
mEngine = engine; mEngine = engine;
mPreview = preview; mPreview = preview;
mOutputRatio = outputRatio; mOutputRatio = outputRatio;
mWithOverlay = true; mHasOverlays = overlays != null && !overlays.isEmpty();
mSurfaceDrawerList = surfaceDrawerList; if (mHasOverlays) {
mOverlays = new ArrayList<>(overlays);
}
} }
@TargetApi(Build.VERSION_CODES.KITKAT) @TargetApi(Build.VERSION_CODES.KITKAT)
@ -68,33 +72,32 @@ public class SnapshotGlPictureRecorder extends PictureRecorder {
mPreview.addRendererFrameCallback(new RendererFrameCallback() { mPreview.addRendererFrameCallback(new RendererFrameCallback() {
int mTextureId; int mTextureId;
int mOverlayTextureId = 0;
SurfaceTexture mSurfaceTexture; SurfaceTexture mSurfaceTexture;
SurfaceTexture mOverlaySurfaceTexture;
float[] mTransform; float[] mTransform;
int mOverlayTextureId = 0;
SurfaceTexture mOverlaySurfaceTexture;
Surface mOverlaySurface;
float[] mOverlayTransform; float[] mOverlayTransform;
EglViewport viewport;
EglViewport mViewport;
@RendererThread @RendererThread
public void onRendererTextureCreated(int textureId) { public void onRendererTextureCreated(int textureId) {
mTextureId = textureId; mTextureId = textureId;
viewport = new EglViewport(); mViewport = new EglViewport();
if (mWithOverlay) {
mOverlayTextureId = viewport.createTexture();
}
mSurfaceTexture = new SurfaceTexture(mTextureId, true); mSurfaceTexture = new SurfaceTexture(mTextureId, true);
if (mWithOverlay) {
mOverlaySurfaceTexture = new SurfaceTexture(mOverlayTextureId, true);
}
// Need to crop the size. // Need to crop the size.
Rect crop = CropHelper.computeCrop(mResult.size, mOutputRatio); Rect crop = CropHelper.computeCrop(mResult.size, mOutputRatio);
mResult.size = new Size(crop.width(), crop.height()); mResult.size = new Size(crop.width(), crop.height());
mSurfaceTexture.setDefaultBufferSize(mResult.size.getWidth(), mResult.size.getHeight()); mSurfaceTexture.setDefaultBufferSize(mResult.size.getWidth(), mResult.size.getHeight());
if (mWithOverlay) {
mOverlaySurfaceTexture.setDefaultBufferSize(mResult.size.getWidth(), mResult.size.getHeight());
}
mTransform = new float[16]; mTransform = new float[16];
if (mWithOverlay) {
if (mHasOverlays) {
mOverlayTextureId = mViewport.createTexture();
mOverlaySurfaceTexture = new SurfaceTexture(mOverlayTextureId, true);
mOverlaySurfaceTexture.setDefaultBufferSize(mResult.size.getWidth(), mResult.size.getHeight());
mOverlaySurface = new Surface(mOverlaySurfaceTexture);
mOverlayTransform = new float[16]; mOverlayTransform = new float[16];
} }
} }
@ -128,30 +131,14 @@ public class SnapshotGlPictureRecorder extends PictureRecorder {
WorkerHandler.execute(new Runnable() { WorkerHandler.execute(new Runnable() {
@Override @Override
public void run() { public void run() {
// 1. Get latest texture
EglWindowSurface surface = new EglWindowSurface(core, mSurfaceTexture); EglWindowSurface surface = new EglWindowSurface(core, mSurfaceTexture);
surface.makeCurrent(); surface.makeCurrent();
// EglViewport viewport = new EglViewport();
mSurfaceTexture.updateTexImage(); mSurfaceTexture.updateTexImage();
mSurfaceTexture.getTransformMatrix(mTransform); mSurfaceTexture.getTransformMatrix(mTransform);
Surface drawOnto = new Surface(mOverlaySurfaceTexture);
if (mWithOverlay) {
try {
final Canvas surfaceCanvas = drawOnto.lockCanvas(null);
surfaceCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
for (SurfaceDrawer surfaceDrawer : mSurfaceDrawerList) {
surfaceDrawer.drawOnSurfaceForPictureSnapshot(surfaceCanvas);
}
drawOnto.unlockCanvasAndPost(surfaceCanvas);
} catch (Surface.OutOfResourcesException e) {
e.printStackTrace();
}
mOverlaySurfaceTexture.updateTexImage();
mOverlaySurfaceTexture.getTransformMatrix(mOverlayTransform);
}
// Apply scale and crop: // 2. Apply scale and crop:
// NOTE: scaleX and scaleY are in REF_VIEW, while our input appears to be in REF_SENSOR. // scaleX and scaleY are in REF_VIEW, while our input appears to be in REF_SENSOR.
boolean flip = mEngine.getAngles().flip(Reference.VIEW, Reference.SENSOR); boolean flip = mEngine.getAngles().flip(Reference.VIEW, Reference.SENSOR);
float realScaleX = flip ? scaleY : scaleX; float realScaleX = flip ? scaleY : scaleX;
float realScaleY = flip ? scaleX : scaleY; float realScaleY = flip ? scaleX : scaleY;
@ -160,65 +147,66 @@ public class SnapshotGlPictureRecorder extends PictureRecorder {
Matrix.translateM(mTransform, 0, scaleTranslX, scaleTranslY, 0); Matrix.translateM(mTransform, 0, scaleTranslX, scaleTranslY, 0);
Matrix.scaleM(mTransform, 0, realScaleX, realScaleY, 1); Matrix.scaleM(mTransform, 0, realScaleX, realScaleY, 1);
// Fix rotation: // 3. Go back to 0,0 so that rotate and flip work well.
// Not sure why we need the minus here... It makes no sense to me. Matrix.translateM(mTransform, 0, 0.5F, 0.5F, 0);
LOG.w("Recording frame. Rotation:", mResult.rotation, "Actual:", -mResult.rotation);
int rotation = -mResult.rotation; // 4. Apply rotation:
int overlayRotation = mEngine.getAngles().offset(Reference.VIEW, Reference.OUTPUT, Axis.ABSOLUTE); // TODO check axis // Not sure why we need the minus here.
// apparently with front facing camera with don't need the minus sign Matrix.rotateM(mTransform, 0, -mResult.rotation, 0, 0, 1);
if (mResult.facing == Facing.FRONT) {
overlayRotation = -overlayRotation;
}
mResult.rotation = 0; mResult.rotation = 0;
// Go back to 0,0 so that rotate and flip work well. // 5. Flip horizontally for front camera:
Matrix.translateM(mTransform, 0, 0.5F, 0.5F, 0); if (mResult.facing == Facing.FRONT) {
if (mOverlayTransform != null) { Matrix.scaleM(mTransform, 0, -1, 1, 1);
Matrix.translateM(mOverlayTransform, 0, 0.5F, 0.5F, 0);
} }
// Apply rotation: // 6. Go back to old position.
Matrix.rotateM(mTransform, 0, rotation, 0, 0, 1); Matrix.translateM(mTransform, 0, -0.5F, -0.5F, 0);
if (mOverlayTransform != null) {
Matrix.rotateM(mOverlayTransform, 0, overlayRotation, 0, 0, 1); // 7. Do pretty much the same for overlays, though with
// some differences.
if (mHasOverlays) {
// 1. First we must draw on the texture and get latest image.
try {
final Canvas surfaceCanvas = mOverlaySurface.lockCanvas(null);
surfaceCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
for (Overlay overlay : mOverlays) {
overlay.drawOnPicture(surfaceCanvas);
}
mOverlaySurface.unlockCanvasAndPost(surfaceCanvas);
} catch (Surface.OutOfResourcesException e) {
LOG.w("Got Surface.OutOfResourcesException while drawing picture overlays", e);
} }
mOverlaySurfaceTexture.updateTexImage();
mOverlaySurfaceTexture.getTransformMatrix(mOverlayTransform);
// Flip horizontally for front camera: // 2. Then we can apply the transformations.
// TODO check the sign of this angle.
int rotation = mEngine.getAngles().offset(Reference.VIEW, Reference.OUTPUT, Axis.RELATIVE_TO_SENSOR);
Matrix.translateM(mOverlayTransform, 0, 0.5F, 0.5F, 0);
Matrix.rotateM(mOverlayTransform, 0, -rotation, 0, 0, 1);
// Not sure why we have to flip the y axis (twice?).
if (mResult.facing == Facing.FRONT) { if (mResult.facing == Facing.FRONT) {
Matrix.scaleM(mTransform, 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); 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); Matrix.scaleM(mOverlayTransform, 0, 1, -1, 1);
}
// Go back to old position.
Matrix.translateM(mTransform, 0, -0.5F, -0.5F, 0);
if (mOverlayTransform != null) {
Matrix.translateM(mOverlayTransform, 0, -0.5F, -0.5F, 0); Matrix.translateM(mOverlayTransform, 0, -0.5F, -0.5F, 0);
} }
// Future note: passing scale values to the viewport? // 8. Draw and save
// They are simply realScaleX and realScaleY. mViewport.drawFrame(mTextureId, mTransform);
viewport.drawFrame(mTextureId, mTransform); if (mHasOverlays) mViewport.drawFrame(mOverlayTextureId, mOverlayTransform);
if (mWithOverlay) {
viewport.drawFrame(mOverlayTextureId, mOverlayTransform);
}
// don't - surface.swapBuffers(); // don't - surface.swapBuffers();
mResult.data = surface.saveFrameTo(Bitmap.CompressFormat.JPEG); mResult.data = surface.saveFrameTo(Bitmap.CompressFormat.JPEG);
mResult.format = PictureResult.FORMAT_JPEG; mResult.format = PictureResult.FORMAT_JPEG;
mSurfaceTexture.releaseTexImage();
// EGL14.eglMakeCurrent(oldDisplay, oldSurface, oldSurface, eglContext); // 9. Cleanup
mSurfaceTexture.releaseTexImage();
surface.release(); surface.release();
viewport.release(); mViewport.release();
drawOnto.release();
mSurfaceTexture.release(); mSurfaceTexture.release();
if (mOverlaySurfaceTexture != null) { if (mHasOverlays) {
mOverlaySurface.release();
mOverlaySurfaceTexture.release(); mOverlaySurfaceTexture.release();
} }
core.release(); core.release();

@ -8,10 +8,11 @@ import android.opengl.EGL14;
import android.os.Build; import android.os.Build;
import android.view.Surface; import android.view.Surface;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.overlay.SurfaceDrawer; import com.otaliastudios.cameraview.overlay.Overlay;
import com.otaliastudios.cameraview.VideoResult; import com.otaliastudios.cameraview.VideoResult;
import com.otaliastudios.cameraview.controls.Audio; import com.otaliastudios.cameraview.controls.Audio;
import com.otaliastudios.cameraview.engine.CameraEngine; import com.otaliastudios.cameraview.engine.CameraEngine;
@ -59,18 +60,20 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
private int mOverlayTextureId = 0; private int mOverlayTextureId = 0;
private SurfaceTexture mOverlaySurfaceTexture; private SurfaceTexture mOverlaySurfaceTexture;
private Surface mOverlaySurface; private Surface mOverlaySurface;
private List<SurfaceDrawer> mSurfaceDrawerList; private List<Overlay> mOverlays;
private boolean mWithOverlay; private boolean mHasOverlays;
public SnapshotVideoRecorder(@NonNull CameraEngine engine, public SnapshotVideoRecorder(@NonNull CameraEngine engine,
@NonNull GlCameraPreview preview, @NonNull GlCameraPreview preview,
@NonNull List<SurfaceDrawer> surfaceDrawerList) { @Nullable List<Overlay> overlays) {
super(engine); super(engine);
mPreview = preview; mPreview = preview;
mEngine = engine; mEngine = engine;
mWithOverlay = true; mHasOverlays = overlays != null && !overlays.isEmpty();
mSurfaceDrawerList = surfaceDrawerList; if (mHasOverlays) {
mOverlays = new ArrayList<>(overlays);
}
} }
@Override @Override
@ -90,10 +93,9 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
@Override @Override
public void onRendererTextureCreated(int textureId) { public void onRendererTextureCreated(int textureId) {
mTextureId = textureId; mTextureId = textureId;
if (mWithOverlay) { if (mHasOverlays) {
EglViewport temp = new EglViewport(); EglViewport temp = new EglViewport();
mOverlayTextureId = temp.createTexture(); mOverlayTextureId = temp.createTexture();
mOverlaySurfaceTexture = new SurfaceTexture(mOverlayTextureId); mOverlaySurfaceTexture = new SurfaceTexture(mOverlayTextureId);
mOverlaySurfaceTexture.setDefaultBufferSize(mResult.size.getWidth(), mResult.size.getHeight()); mOverlaySurfaceTexture.setDefaultBufferSize(mResult.size.getWidth(), mResult.size.getHeight());
mOverlaySurface = new Surface(mOverlaySurfaceTexture); mOverlaySurface = new Surface(mOverlaySurfaceTexture);
@ -129,7 +131,7 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
mResult.videoFrameRate, mResult.videoFrameRate,
mResult.rotation, mResult.rotation,
type, mTextureId, type, mTextureId,
mWithOverlay ? mOverlayTextureId : 0, mHasOverlays ? mOverlayTextureId : TextureMediaEncoder.NO_TEXTURE,
scaleX, scaleY, scaleX, scaleY,
mFlipped, mFlipped,
EGL14.eglGetCurrentContext() EGL14.eglGetCurrentContext()
@ -158,17 +160,17 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
surfaceTexture.getTransformMatrix(textureFrame.transform); surfaceTexture.getTransformMatrix(textureFrame.transform);
// get overlay // get overlay
if (mWithOverlay) { if (mHasOverlays) {
try { try {
final Canvas surfaceCanvas = mOverlaySurface.lockCanvas(null); final Canvas surfaceCanvas = mOverlaySurface.lockCanvas(null);
surfaceCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); surfaceCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
for (SurfaceDrawer surfaceDrawer : mSurfaceDrawerList) { for (Overlay overlay : mOverlays) {
surfaceDrawer.drawOnSurfaceForVideoSnapshot(surfaceCanvas); overlay.drawOnVideo(surfaceCanvas);
} }
mOverlaySurface.unlockCanvasAndPost(surfaceCanvas); mOverlaySurface.unlockCanvasAndPost(surfaceCanvas);
} catch (Surface.OutOfResourcesException e) { } catch (Surface.OutOfResourcesException e) {
e.printStackTrace(); LOG.w("Got Surface.OutOfResourcesException while drawing video overlays", e);
} }
mOverlaySurfaceTexture.updateTexImage(); mOverlaySurfaceTexture.updateTexImage();
mOverlaySurfaceTexture.getTransformMatrix(textureFrame.overlayTransform); mOverlaySurfaceTexture.getTransformMatrix(textureFrame.overlayTransform);

@ -24,6 +24,7 @@ public class TextureMediaEncoder extends VideoMediaEncoder<TextureMediaEncoder.C
private static final CameraLogger LOG = CameraLogger.create(TAG); private static final CameraLogger LOG = CameraLogger.create(TAG);
public final static String FRAME_EVENT = "frame"; public final static String FRAME_EVENT = "frame";
public final static int NO_TEXTURE = Integer.MIN_VALUE;
public static class Config extends VideoMediaEncoder.Config { public static class Config extends VideoMediaEncoder.Config {
int textureId; int textureId;
@ -34,11 +35,6 @@ public class TextureMediaEncoder extends VideoMediaEncoder<TextureMediaEncoder.C
EGLContext eglContext; EGLContext eglContext;
int transformRotation; int transformRotation;
public Config(int width, int height, int bitRate, int frameRate, int rotation, String mimeType,
int textureId, float scaleX, float scaleY, boolean scaleFlipped, EGLContext eglContext) {
this(width, height, bitRate, frameRate, rotation, mimeType, textureId, 0, scaleX, scaleY, scaleFlipped, eglContext);
}
public Config(int width, int height, int bitRate, int frameRate, int rotation, String mimeType, public Config(int width, int height, int bitRate, int frameRate, int rotation, String mimeType,
int textureId, int overlayTextureId, float scaleX, float scaleY, boolean scaleFlipped, EGLContext eglContext) { int textureId, int overlayTextureId, float scaleX, float scaleY, boolean scaleFlipped, EGLContext eglContext) {
// We rotate the texture using transformRotation. Pass rotation=0 to super so that // We rotate the texture using transformRotation. Pass rotation=0 to super so that
@ -137,11 +133,12 @@ public class TextureMediaEncoder extends VideoMediaEncoder<TextureMediaEncoder.C
// stream, but the output video, must be correctly rotated based on the device rotation at the moment. // stream, but the output video, must be correctly rotated based on the device rotation at the moment.
// Rotation also takes place with respect to the origin (the Z axis), so we must // Rotation also takes place with respect to the origin (the Z axis), so we must
// translate to origin, rotate, then back to where we were. // translate to origin, rotate, then back to where we were.
Matrix.translateM(transform, 0, 0.5F, 0.5F, 0); Matrix.translateM(transform, 0, 0.5F, 0.5F, 0);
Matrix.rotateM(transform, 0, mConfig.transformRotation, 0, 0, 1); Matrix.rotateM(transform, 0, mConfig.transformRotation, 0, 0, 1);
Matrix.translateM(transform, 0, -0.5F, -0.5F, 0); Matrix.translateM(transform, 0, -0.5F, -0.5F, 0);
if (overlayTransform != null) {
boolean hasOverlay = mConfig.overlayTextureId != NO_TEXTURE;
if (hasOverlay) {
Matrix.translateM(overlayTransform, 0, 0.5F, 0.5F, 0); Matrix.translateM(overlayTransform, 0, 0.5F, 0.5F, 0);
Matrix.rotateM(overlayTransform, 0, mConfig.transformRotation, 0, 0, 1); Matrix.rotateM(overlayTransform, 0, mConfig.transformRotation, 0, 0, 1);
Matrix.translateM(overlayTransform, 0, -0.5F, -0.5F, 0); Matrix.translateM(overlayTransform, 0, -0.5F, -0.5F, 0);
@ -153,7 +150,10 @@ public class TextureMediaEncoder extends VideoMediaEncoder<TextureMediaEncoder.C
// but flipped based on the mConfig.scaleFlipped boolean. // but flipped based on the mConfig.scaleFlipped boolean.
LOG.v("onEvent", "frameNum:", thisFrameNum, "realFrameNum:", mFrameNum, "calling drawFrame."); LOG.v("onEvent", "frameNum:", thisFrameNum, "realFrameNum:", mFrameNum, "calling drawFrame.");
mViewport.drawFrame(mConfig.textureId, transform); mViewport.drawFrame(mConfig.textureId, transform);
if (hasOverlay) {
mViewport.drawFrame(mConfig.overlayTextureId, overlayTransform); mViewport.drawFrame(mConfig.overlayTextureId, overlayTransform);
}
mWindow.setPresentationTime(frame.timestamp); mWindow.setPresentationTime(frame.timestamp);
mWindow.swapBuffers(); mWindow.swapBuffers();
mFramePool.recycle(frame); mFramePool.recycle(frame);

@ -136,10 +136,10 @@
</declare-styleable> </declare-styleable>
<declare-styleable name="CameraView_Layout"> <declare-styleable name="CameraView_Layout">
<attr name="layout_isOverlay" format="boolean"/>
<attr name="layout_drawInPreview" format="boolean"/> <attr name="layout_drawOnPreview" format="boolean"/>
<attr name="layout_drawInPictureSnapshot" format="boolean"/> <attr name="layout_drawOnPictureSnapshot" format="boolean"/>
<attr name="layout_drawInVideoSnapshot" format="boolean"/> <attr name="layout_drawOnVideoSnapshot" format="boolean"/>
</declare-styleable> </declare-styleable>
</resources> </resources>

@ -37,9 +37,9 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:layout_gravity="bottom|end" android:layout_gravity="bottom|end"
app:layout_drawInPreview="false" app:layout_drawOnPreview="false"
app:layout_drawInVideoSnapshot="true" app:layout_drawOnVideoSnapshot="true"
app:layout_drawInPictureSnapshot="true" app:layout_drawOnPictureSnapshot="true"
android:gravity="center" android:gravity="center"
android:padding="8dp"> android:padding="8dp">
<ImageView <ImageView

Loading…
Cancel
Save