From ec5210cd08cde2c3c8509eb06fb097b9897e6edf Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Fri, 21 Jun 2019 10:59:19 -0500 Subject: [PATCH] Refactor and document some packages --- .../cameraview/CameraException.java | 6 +- .../cameraview/CameraLogger.java | 36 ++-- .../cameraview/CameraOptions.java | 2 +- .../otaliastudios/cameraview/CameraView.java | 26 +-- .../cameraview/PictureResult.java | 39 ++++- .../otaliastudios/cameraview/VideoResult.java | 69 ++++++-- .../cameraview/engine/Camera1Engine.java | 160 +++++++++--------- .../cameraview/engine/CameraEngine.java | 80 +++++---- .../cameraview/engine/Mapper.java | 99 ++++++++++- .../cameraview/engine/Mapper1.java | 90 ---------- .../otaliastudios/cameraview/frame/Frame.java | 4 +- .../cameraview/frame/FrameManager.java | 57 +++++-- .../cameraview/gesture/Gesture.java | 3 - .../cameraview/gesture/GestureParser.java | 1 - .../cameraview/gesture/GestureType.java | 8 - .../gesture/PinchGestureLayout.java | 2 - .../gesture/ScrollGestureLayout.java | 2 - .../cameraview/gesture/TapGestureLayout.java | 1 - .../cameraview/internal/GridLinesLayout.java | 33 +++- .../cameraview/internal/egl/EglElement.java | 8 +- .../internal/utils/CamcorderProfiles.java | 2 + .../cameraview/internal/utils/CropHelper.java | 7 +- .../internal/utils/OrientationHelper.java | 46 ++++- .../cameraview/internal/utils/Pool.java | 94 +++++++--- .../internal/utils/RotationHelper.java | 9 +- .../cameraview/internal/utils/Task.java | 75 ++++++-- .../internal/utils/WorkerHandler.java | 49 +++++- .../picture/FullPictureRecorder.java | 4 +- .../cameraview/picture/PictureRecorder.java | 12 +- .../picture/SnapshotPictureRecorder.java | 8 +- .../cameraview/preview/CameraPreview.java | 16 +- .../cameraview/preview/GlCameraPreview.java | 2 +- .../cameraview/video/FullVideoRecorder.java | 4 +- .../video/SnapshotVideoRecorder.java | 4 +- .../cameraview/video/VideoRecorder.java | 17 +- .../cameraview/FrameManagerTest.java | 16 +- 36 files changed, 684 insertions(+), 407 deletions(-) delete mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/engine/Mapper1.java diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraException.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraException.java index dd88ce81..ef25f448 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraException.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraException.java @@ -53,16 +53,16 @@ public class CameraException extends RuntimeException { private int reason = REASON_UNKNOWN; - CameraException(Throwable cause) { + public CameraException(Throwable cause) { super(cause); } - CameraException(Throwable cause, int reason) { + public CameraException(Throwable cause, int reason) { super(cause); this.reason = reason; } - CameraException(int reason) { + public CameraException(int reason) { super(); this.reason = reason; } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraLogger.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraLogger.java index eaaa8bf2..28d0d236 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraLogger.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraLogger.java @@ -3,6 +3,8 @@ package com.otaliastudios.cameraview; import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + import android.util.Log; import java.lang.annotation.Retention; @@ -49,8 +51,8 @@ public final class CameraLogger { void log(@LogLevel int level, @NonNull String tag, @NonNull String message, @Nullable Throwable throwable); } - static String lastMessage; - static String lastTag; + @VisibleForTesting static String lastMessage; + @VisibleForTesting static String lastTag; private static int sLevel; private static List sLoggers; @@ -131,37 +133,46 @@ public final class CameraLogger { /** * Log to the verbose channel. * @param data log contents + * @return the log message, if logged */ - public void v(@NonNull Object... data) { - log(LEVEL_VERBOSE, data); + @Nullable + public String v(@NonNull Object... data) { + return log(LEVEL_VERBOSE, data); } /** * Log to the info channel. * @param data log contents + * @return the log message, if logged */ - public void i(@NonNull Object... data) { - log(LEVEL_INFO, data); + @Nullable + public String i(@NonNull Object... data) { + return log(LEVEL_INFO, data); } /** * Log to the warning channel. * @param data log contents + * @return the log message, if logged */ - void w(@NonNull Object... data) { - log(LEVEL_WARNING, data); + @Nullable + public String w(@NonNull Object... data) { + return log(LEVEL_WARNING, data); } /** * Log to the error channel. * @param data log contents + * @return the log message, if logged */ - void e(@NonNull Object... data) { - log(LEVEL_ERROR, data); + @Nullable + public String e(@NonNull Object... data) { + return log(LEVEL_ERROR, data); } - private void log(@LogLevel int level, @NonNull Object... data) { - if (!should(level)) return; + @Nullable + private String log(@LogLevel int level, @NonNull Object... data) { + if (!should(level)) return null; StringBuilder message = new StringBuilder(); Throwable throwable = null; @@ -178,6 +189,7 @@ public final class CameraLogger { } lastMessage = string; lastTag = mTag; + return string; } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java index fe303975..8b935003 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java @@ -52,7 +52,7 @@ public class CameraOptions { // Camera1Engine constructor. @SuppressWarnings("deprecation") - CameraOptions(@NonNull Camera.Parameters params, boolean flipSizes) { + public CameraOptions(@NonNull Camera.Parameters params, boolean flipSizes) { List strings; Mapper mapper = new Mapper1(); diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java index 39be18d0..69913392 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java @@ -1238,7 +1238,8 @@ public class CameraView extends FrameLayout implements LifecycleObserver { * @see #takePictureSnapshot() */ public void takePicture() { - mCameraEngine.takePicture(); + PictureResult.Stub stub = new PictureResult.Stub(); + mCameraEngine.takePicture(stub); } @@ -1254,7 +1255,8 @@ public class CameraView extends FrameLayout implements LifecycleObserver { */ public void takePictureSnapshot() { if (getWidth() == 0 || getHeight() == 0) return; - mCameraEngine.takePictureSnapshot(AspectRatio.of(getWidth(), getHeight())); + PictureResult.Stub stub = new PictureResult.Stub(); + mCameraEngine.takePictureSnapshot(stub, AspectRatio.of(getWidth(), getHeight())); } @@ -1265,7 +1267,8 @@ public class CameraView extends FrameLayout implements LifecycleObserver { * @param file a file where the video will be saved */ public void takeVideo(@NonNull File file) { - mCameraEngine.takeVideo(file); + VideoResult.Stub stub = new VideoResult.Stub(); + mCameraEngine.takeVideo(stub, file); mUiHandler.post(new Runnable() { @Override public void run() { @@ -1286,7 +1289,8 @@ public class CameraView extends FrameLayout implements LifecycleObserver { */ public void takeVideoSnapshot(@NonNull File file) { if (getWidth() == 0 || getHeight() == 0) return; - mCameraEngine.takeVideoSnapshot(file, AspectRatio.of(getWidth(), getHeight())); + VideoResult.Stub stub = new VideoResult.Stub(); + mCameraEngine.takeVideoSnapshot(stub, file, AspectRatio.of(getWidth(), getHeight())); mUiHandler.post(new Runnable() { @Override public void run() { @@ -1609,20 +1613,6 @@ public class CameraView extends FrameLayout implements LifecycleObserver { //region Callbacks and dispatching - interface CameraCallbacks extends OrientationHelper.Callback { - void dispatchOnCameraOpened(CameraOptions options); - void dispatchOnCameraClosed(); - void onCameraPreviewStreamSizeChanged(); - void onShutter(boolean shouldPlaySound); - void dispatchOnVideoTaken(VideoResult result); - void dispatchOnPictureTaken(PictureResult result); - void dispatchOnFocusStart(@Nullable Gesture trigger, @NonNull PointF where); - void dispatchOnFocusEnd(@Nullable Gesture trigger, boolean success, @NonNull PointF where); - void dispatchOnZoomChanged(final float newValue, @Nullable final PointF[] fingers); - void dispatchOnExposureCorrectionChanged(float newValue, @NonNull float[] bounds, @Nullable PointF[] fingers); - void dispatchFrame(Frame frame); - void dispatchError(CameraException exception); - } private class Callbacks implements CameraCallbacks { diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/PictureResult.java b/cameraview/src/main/java/com/otaliastudios/cameraview/PictureResult.java index 25af5338..71f32e78 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/PictureResult.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/PictureResult.java @@ -17,18 +17,39 @@ import androidx.annotation.Nullable; */ public class PictureResult { + public static class Stub { + + Stub() {} + + public boolean isSnapshot; + public Location location; + public int rotation; + public Size size; + public Facing facing; + public byte[] data; + public int format; + } + public final static int FORMAT_JPEG = 0; // public final static int FORMAT_PNG = 1; - boolean isSnapshot; - Location location; - int rotation; - Size size; - Facing facing; - byte[] data; - int format; - - PictureResult() {} + private final boolean isSnapshot; + private final Location location; + private final int rotation; + private final Size size; + private final Facing facing; + private final byte[] data; + private final int format; + + PictureResult(@NonNull Stub builder) { + isSnapshot = builder.isSnapshot; + location = builder.location; + rotation = builder.rotation; + size = builder.size; + facing = builder.facing; + data = builder.data; + format = builder.format; + } /** * Returns whether this result comes from a snapshot. diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/VideoResult.java b/cameraview/src/main/java/com/otaliastudios/cameraview/VideoResult.java index a450804d..2c787d90 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/VideoResult.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/VideoResult.java @@ -17,6 +17,26 @@ import java.io.File; */ public class VideoResult { + public static class Stub { + + Stub() {} + + public boolean isSnapshot; + public Location location; + public int rotation; + public Size size; + public File file; + public Facing facing; + public VideoCodec videoCodec; + public Audio audio; + public long maxSize; + public int maxDuration; + public int endReason; + public int videoBitRate; + public int videoFrameRate; + public int audioBitRate; + } + @SuppressWarnings({"WeakerAccess", "unused"}) public static final int REASON_USER = 0; @@ -26,22 +46,37 @@ public class VideoResult { @SuppressWarnings("WeakerAccess") public static final int REASON_MAX_DURATION_REACHED = 2; - boolean isSnapshot; - Location location; - int rotation; - Size size; - File file; - Facing facing; - VideoCodec codec; - Audio audio; - long maxSize; - int maxDuration; - int endReason; - int videoBitRate; - int videoFrameRate; - int audioBitRate; - - VideoResult() {} + private final boolean isSnapshot; + private final Location location; + private final int rotation; + private final Size size; + private final File file; + private final Facing facing; + private final VideoCodec videoCodec; + private final Audio audio; + private final long maxSize; + private final int maxDuration; + private final int endReason; + private final int videoBitRate; + private final int videoFrameRate; + private final int audioBitRate; + + VideoResult(@NonNull Stub builder) { + isSnapshot = builder.isSnapshot; + location = builder.location; + rotation = builder.rotation; + size = builder.size; + file = builder.file; + facing = builder.facing; + videoCodec = builder.videoCodec; + audio = builder.audio; + maxSize = builder.maxSize; + maxDuration = builder.maxDuration; + endReason = builder.endReason; + videoBitRate = builder.videoBitRate; + videoFrameRate = builder.videoFrameRate; + audioBitRate = builder.audioBitRate; + } /** * Returns whether this result comes from a snapshot. @@ -111,7 +146,7 @@ public class VideoResult { */ @NonNull public VideoCodec getVideoCodec() { - return codec; + return videoCodec; } /** diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java index 8243f371..1ff26c8e 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java @@ -17,19 +17,8 @@ import android.view.SurfaceHolder; import com.otaliastudios.cameraview.CameraException; import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.CameraOptions; -import com.otaliastudios.cameraview.CameraView; -import com.otaliastudios.cameraview.CropHelper; import com.otaliastudios.cameraview.frame.Frame; -import com.otaliastudios.cameraview.FullPictureRecorder; -import com.otaliastudios.cameraview.FullVideoRecorder; -import com.otaliastudios.cameraview.GlCameraPreview; -import com.otaliastudios.cameraview.Mapper1; -import com.otaliastudios.cameraview.PictureRecorder; import com.otaliastudios.cameraview.PictureResult; -import com.otaliastudios.cameraview.SnapshotPictureRecorder; -import com.otaliastudios.cameraview.SnapshotVideoRecorder; -import com.otaliastudios.cameraview.Task; -import com.otaliastudios.cameraview.VideoRecorder; import com.otaliastudios.cameraview.VideoResult; import com.otaliastudios.cameraview.controls.Audio; import com.otaliastudios.cameraview.controls.Facing; @@ -38,8 +27,17 @@ import com.otaliastudios.cameraview.gesture.Gesture; import com.otaliastudios.cameraview.controls.Hdr; import com.otaliastudios.cameraview.controls.Mode; import com.otaliastudios.cameraview.controls.WhiteBalance; +import com.otaliastudios.cameraview.internal.utils.CropHelper; +import com.otaliastudios.cameraview.internal.utils.Task; +import com.otaliastudios.cameraview.picture.FullPictureRecorder; +import com.otaliastudios.cameraview.picture.PictureRecorder; +import com.otaliastudios.cameraview.picture.SnapshotPictureRecorder; +import com.otaliastudios.cameraview.preview.GlCameraPreview; import com.otaliastudios.cameraview.size.AspectRatio; import com.otaliastudios.cameraview.size.Size; +import com.otaliastudios.cameraview.video.FullVideoRecorder; +import com.otaliastudios.cameraview.video.SnapshotVideoRecorder; +import com.otaliastudios.cameraview.video.VideoRecorder; import java.io.File; import java.io.IOException; @@ -73,9 +71,9 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came } }; - Camera1Engine(@NonNull CameraView.CameraCallbacks callback) { + Camera1Engine(@NonNull Callback callback) { super(callback); - mMapper = new Mapper1(); + mMapper = Mapper.get(); } private void schedule(@Nullable final Task task, final boolean ensureAvailable, final Runnable action) { @@ -203,9 +201,12 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came // To be called when the preview size is setup or changed. private void startPreview(String log) { LOG.i(log, "Dispatching onCameraPreviewStreamSizeChanged."); - mCameraCallbacks.onCameraPreviewStreamSizeChanged(); + mCallback.onCameraPreviewStreamSizeChanged(); Size previewSize = getPreviewStreamSize(REF_VIEW); + if (previewSize == null) { + throw new IllegalStateException("previewStreamSize should not be null at this point."); + } boolean wasFlipped = flip(REF_SENSOR, REF_VIEW); mPreview.setStreamSize(previewSize.getWidth(), previewSize.getHeight(), wasFlipped); @@ -226,7 +227,7 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came mCamera.setPreviewCallbackWithBuffer(null); // Release anything left mCamera.setPreviewCallbackWithBuffer(this); // Add ourselves - mFrameManager.allocate(ImageFormat.getBitsPerPixel(mPreviewFormat), mPreviewStreamSize); + mFrameManager.allocateBuffers(ImageFormat.getBitsPerPixel(mPreviewFormat), mPreviewStreamSize); LOG.i(log, "Starting preview with startPreview()."); try { @@ -363,8 +364,8 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came return; } - LOG.e("Internal Camera1 error.", error); - Exception runtime = new RuntimeException(CameraLogger.lastMessage); + String message = LOG.e("Internal Camera1 error.", error); + Exception runtime = new RuntimeException(message); int reason; switch (error) { case Camera.CAMERA_ERROR_EVICTED: reason = CameraException.REASON_DISCONNECTED; break; @@ -400,7 +401,8 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came }); } - private boolean applyLocation(@NonNull Camera.Parameters params, @Nullable Location oldLocation) { + private boolean applyLocation(@NonNull Camera.Parameters params, + @SuppressWarnings("unused") @Nullable Location oldLocation) { if (mLocation != null) { params.setGpsLatitude(mLocation.getLatitude()); params.setGpsLongitude(mLocation.getLongitude()); @@ -563,23 +565,23 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came @Override public void onPictureShutter(boolean didPlaySound) { - mCameraCallbacks.onShutter(!didPlaySound); + mCallback.onShutter(!didPlaySound); } @Override - public void onPictureResult(@Nullable PictureResult result) { + public void onPictureResult(@Nullable PictureResult.Stub result) { mPictureRecorder = null; if (result != null) { - mCameraCallbacks.dispatchOnPictureTaken(result); + mCallback.dispatchOnPictureTaken(result); } else { // Something went wrong. - mCameraCallbacks.dispatchError(new CameraException(CameraException.REASON_PICTURE_FAILED)); + mCallback.dispatchError(new CameraException(CameraException.REASON_PICTURE_FAILED)); LOG.e("onPictureResult", "result is null: something went wrong."); } } @Override - void takePicture() { + void takePicture(final @NonNull PictureResult.Stub stub) { LOG.v("takePicture: scheduling"); schedule(null, true, new Runnable() { @Override @@ -592,13 +594,12 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came LOG.v("takePicture: performing.", isTakingPicture()); if (isTakingPicture()) return; - PictureResult result = new PictureResult(); - result.isSnapshot = false; - result.location = mLocation; - result.rotation = offset(REF_SENSOR, REF_OUTPUT); - result.size = getPictureSize(REF_OUTPUT); - result.facing = mFacing; - mPictureRecorder = new FullPictureRecorder(result, Camera1Engine.this, mCamera); + stub.isSnapshot = false; + stub.location = mLocation; + stub.rotation = offset(REF_SENSOR, REF_OUTPUT); + stub.size = getPictureSize(REF_OUTPUT); + stub.facing = mFacing; + mPictureRecorder = new FullPictureRecorder(stub, Camera1Engine.this, mCamera); mPictureRecorder.take(); } }); @@ -609,7 +610,7 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came * @param viewAspectRatio the view aspect ratio */ @Override - void takePictureSnapshot(@NonNull final AspectRatio viewAspectRatio) { + void takePictureSnapshot(final @NonNull PictureResult.Stub stub, @NonNull final AspectRatio viewAspectRatio) { LOG.v("takePictureSnapshot: scheduling"); schedule(null, true, new Runnable() { @Override @@ -617,12 +618,11 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came LOG.v("takePictureSnapshot: performing.", isTakingPicture()); if (isTakingPicture()) return; - PictureResult result = new PictureResult(); - result.location = mLocation; - result.isSnapshot = true; - result.facing = mFacing; - result.size = getUncroppedSnapshotSize(REF_OUTPUT); // Not the real size: it will be cropped to match the view ratio - result.rotation = offset(REF_SENSOR, REF_OUTPUT); // Actually it will be rotated and set to 0. + stub.location = mLocation; + stub.isSnapshot = true; + stub.facing = mFacing; + stub.size = getUncroppedSnapshotSize(REF_OUTPUT); // Not the real size: it will be cropped to match the view ratio + stub.rotation = offset(REF_SENSOR, REF_OUTPUT); // Actually it will be rotated and set to 0. AspectRatio outputRatio = flip(REF_OUTPUT, REF_VIEW) ? viewAspectRatio.flip() : viewAspectRatio; // LOG.e("ROTBUG_pic", "aspectRatio (REF_VIEW):", viewAspectRatio); // LOG.e("ROTBUG_pic", "aspectRatio (REF_OUTPUT):", outputRatio); @@ -633,7 +633,7 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came 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, Camera1Engine.this, mCamera, outputRatio); + mPictureRecorder = new SnapshotPictureRecorder(stub, Camera1Engine.this, mCamera, outputRatio); mPictureRecorder.take(); } }); @@ -646,7 +646,7 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came offset(REF_SENSOR, REF_OUTPUT), mPreviewStreamSize, mPreviewFormat); - mCameraCallbacks.dispatchFrame(frame); + mCallback.dispatchFrame(frame); } private boolean isCameraAvailable() { @@ -673,19 +673,19 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came // Video recording stuff. @Override - public void onVideoResult(@Nullable VideoResult result, @Nullable Exception exception) { + public void onVideoResult(@Nullable VideoResult.Stub result, @Nullable Exception exception) { mVideoRecorder = null; if (result != null) { - mCameraCallbacks.dispatchOnVideoTaken(result); + mCallback.dispatchOnVideoTaken(result); } else { // Something went wrong, lock the camera again. - mCameraCallbacks.dispatchError(new CameraException(exception, CameraException.REASON_VIDEO_FAILED)); + mCallback.dispatchError(new CameraException(exception, CameraException.REASON_VIDEO_FAILED)); mCamera.lock(); } } @Override - void takeVideo(@NonNull final File videoFile) { + void takeVideo(final @NonNull VideoResult.Stub stub, @NonNull final File videoFile) { schedule(mStartVideoTask, true, new Runnable() { @Override public void run() { @@ -696,19 +696,18 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came if (isTakingVideo()) return; // Create the video result stub - VideoResult videoResult = new VideoResult(); - videoResult.file = videoFile; - videoResult.isSnapshot = false; - videoResult.codec = mVideoCodec; - videoResult.location = mLocation; - videoResult.facing = mFacing; - videoResult.rotation = offset(REF_SENSOR, REF_OUTPUT); - videoResult.size = flip(REF_SENSOR, REF_OUTPUT) ? mCaptureSize.flip() : mCaptureSize; - videoResult.audio = mAudio; - videoResult.maxSize = mVideoMaxSize; - videoResult.maxDuration = mVideoMaxDuration; - videoResult.videoBitRate = mVideoBitRate; - videoResult.audioBitRate = mAudioBitRate; + stub.file = videoFile; + stub.isSnapshot = false; + stub.videoCodec = mVideoCodec; + stub.location = mLocation; + stub.facing = mFacing; + stub.rotation = offset(REF_SENSOR, REF_OUTPUT); + stub.size = flip(REF_SENSOR, REF_OUTPUT) ? mCaptureSize.flip() : mCaptureSize; + stub.audio = mAudio; + stub.maxSize = mVideoMaxSize; + stub.maxDuration = mVideoMaxDuration; + stub.videoBitRate = mVideoBitRate; + stub.audioBitRate = mAudioBitRate; // Unlock the camera and start recording. try { @@ -719,7 +718,7 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came onVideoResult(null, e); return; } - mVideoRecorder = new FullVideoRecorder(videoResult, Camera1Engine.this, + mVideoRecorder = new FullVideoRecorder(stub, Camera1Engine.this, Camera1Engine.this, mCamera, mCameraId); mVideoRecorder.start(); } @@ -732,7 +731,7 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came */ @SuppressLint("NewApi") @Override - void takeVideoSnapshot(@NonNull final File file, @NonNull final AspectRatio viewAspectRatio) { + void takeVideoSnapshot(final @NonNull VideoResult.Stub stub, @NonNull final File file, @NonNull final AspectRatio viewAspectRatio) { if (!(mPreview instanceof GlCameraPreview)) { throw new IllegalStateException("Video snapshots are only supported with GlCameraPreview."); } @@ -745,17 +744,16 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came if (isTakingVideo()) return; // Create the video result stub - VideoResult videoResult = new VideoResult(); - videoResult.file = file; - videoResult.isSnapshot = true; - videoResult.codec = mVideoCodec; - videoResult.location = mLocation; - videoResult.facing = mFacing; - videoResult.videoBitRate = mVideoBitRate; - videoResult.audioBitRate = mAudioBitRate; - videoResult.audio = mAudio; - videoResult.maxSize = mVideoMaxSize; - videoResult.maxDuration = mVideoMaxDuration; + stub.file = file; + stub.isSnapshot = true; + stub.videoCodec = mVideoCodec; + stub.location = mLocation; + stub.facing = mFacing; + stub.videoBitRate = mVideoBitRate; + stub.audioBitRate = mAudioBitRate; + stub.audio = mAudio; + stub.maxSize = mVideoMaxSize; + stub.maxDuration = mVideoMaxDuration; // Size and rotation turned out to be extremely tricky. In case of SnapshotPictureRecorder // we use the preview size in REF_OUTPUT (cropped) and offset(REF_SENSOR, REF_OUTPUT) as rotation. @@ -793,11 +791,14 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came // and maybe we can improve. The reason why this happen is beyond my understanding. Size outputSize = getUncroppedSnapshotSize(REF_OUTPUT); + if (outputSize == null) { + throw new IllegalStateException("outputSize should not be null."); + } AspectRatio outputRatio = flip(REF_OUTPUT, REF_VIEW) ? viewAspectRatio.flip() : viewAspectRatio; Rect outputCrop = CropHelper.computeCrop(outputSize, outputRatio); outputSize = new Size(outputCrop.width(), outputCrop.height()); - videoResult.size = outputSize; - videoResult.rotation = offset(REF_VIEW, REF_OUTPUT); + stub.size = outputSize; + stub.rotation = offset(REF_VIEW, REF_OUTPUT); // LOG.e("ROTBUG_video", "aspectRatio (REF_VIEW):", viewAspectRatio); // LOG.e("ROTBUG_video", "aspectRatio (REF_OUTPUT):", outputRatio); // LOG.e("ROTBUG_video", "sizeUncropped (REF_OUTPUT):", outputSize); @@ -807,7 +808,7 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came // Reset facing and start. mFacing = realFacing; GlCameraPreview cameraPreview = (GlCameraPreview) mPreview; - mVideoRecorder = new SnapshotVideoRecorder(videoResult, Camera1Engine.this, cameraPreview); + mVideoRecorder = new SnapshotVideoRecorder(stub, Camera1Engine.this, cameraPreview); mVideoRecorder.start(); } }); @@ -845,7 +846,7 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came mCamera.setParameters(params); if (notify) { - mCameraCallbacks.dispatchOnZoomChanged(zoom, points); + mCallback.dispatchOnZoomChanged(zoom, points); } } }); @@ -870,7 +871,7 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came mCamera.setParameters(params); if (notify) { - mCameraCallbacks.dispatchOnExposureCorrectionChanged(value, bounds, points); + mCallback.dispatchOnExposureCorrectionChanged(value, bounds, points); } } }); @@ -908,14 +909,14 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came if (maxAE > 0) params.setMeteringAreas(maxAE > 1 ? meteringAreas2 : meteringAreas1); params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); mCamera.setParameters(params); - mCameraCallbacks.dispatchOnFocusStart(gesture, p); + mCallback.dispatchOnFocusStart(gesture, p); // TODO this is not guaranteed to be called... Fix. try { mCamera.autoFocus(new Camera.AutoFocusCallback() { @Override public void onAutoFocus(boolean success, Camera camera) { // TODO lock auto exposure and white balance for a while - mCameraCallbacks.dispatchOnFocusEnd(gesture, success, p); + mCallback.dispatchOnFocusEnd(gesture, success, p); mHandler.get().removeCallbacks(mPostFocusResetRunnable); if (shouldResetAutoFocus()) { mHandler.get().postDelayed(mPostFocusResetRunnable, getAutoFocusResetDelay()); @@ -926,7 +927,7 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came // Handling random auto-focus exception on some devices // See https://github.com/natario1/CameraView/issues/181 LOG.e("startAutoFocus:", "Error calling autoFocus", e); - mCameraCallbacks.dispatchOnFocusEnd(gesture, false, p); + mCallback.dispatchOnFocusEnd(gesture, false, p); } } }); @@ -979,9 +980,8 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came // Size stuff. - @Nullable - private List sizesFromList(@Nullable List sizes) { - if (sizes == null) return null; + @NonNull + private List sizesFromList(@NonNull List sizes) { List result = new ArrayList<>(sizes.size()); for (Camera.Size size : sizes) { Size add = new Size(size.width, size.height); diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java index 22036d67..146e1260 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java @@ -10,14 +10,14 @@ import android.os.Looper; import com.otaliastudios.cameraview.CameraException; import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.CameraOptions; +import com.otaliastudios.cameraview.PictureResult; +import com.otaliastudios.cameraview.VideoResult; +import com.otaliastudios.cameraview.frame.Frame; +import com.otaliastudios.cameraview.frame.FrameManager; +import com.otaliastudios.cameraview.internal.utils.Task; +import com.otaliastudios.cameraview.internal.utils.WorkerHandler; +import com.otaliastudios.cameraview.picture.PictureRecorder; import com.otaliastudios.cameraview.preview.CameraPreview; -import com.otaliastudios.cameraview.CameraView; -import com.otaliastudios.cameraview.FrameManager; -import com.otaliastudios.cameraview.Mapper; -import com.otaliastudios.cameraview.PictureRecorder; -import com.otaliastudios.cameraview.Task; -import com.otaliastudios.cameraview.VideoRecorder; -import com.otaliastudios.cameraview.WorkerHandler; import com.otaliastudios.cameraview.controls.Audio; import com.otaliastudios.cameraview.controls.Facing; import com.otaliastudios.cameraview.controls.Flash; @@ -30,6 +30,7 @@ import com.otaliastudios.cameraview.size.AspectRatio; import com.otaliastudios.cameraview.size.Size; import com.otaliastudios.cameraview.size.SizeSelector; import com.otaliastudios.cameraview.size.SizeSelectors; +import com.otaliastudios.cameraview.video.VideoRecorder; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -46,6 +47,21 @@ public abstract class CameraEngine implements FrameManager.BufferCallback, Thread.UncaughtExceptionHandler { + public interface Callback { + void dispatchOnCameraOpened(CameraOptions options); + void dispatchOnCameraClosed(); + void onCameraPreviewStreamSizeChanged(); + void onShutter(boolean shouldPlaySound); + void dispatchOnVideoTaken(VideoResult.Stub result); + void dispatchOnPictureTaken(PictureResult.Stub result); + void dispatchOnFocusStart(@Nullable Gesture trigger, @NonNull PointF where); + void dispatchOnFocusEnd(@Nullable Gesture trigger, boolean success, @NonNull PointF where); + void dispatchOnZoomChanged(final float newValue, @Nullable final PointF[] fingers); + void dispatchOnExposureCorrectionChanged(float newValue, @NonNull float[] bounds, @Nullable PointF[] fingers); + void dispatchFrame(Frame frame); + void dispatchError(CameraException exception); + } + private static final String TAG = CameraEngine.class.getSimpleName(); private static final CameraLogger LOG = CameraLogger.create(TAG); @@ -58,10 +74,10 @@ public abstract class CameraEngine implements static final int REF_VIEW = 1; static final int REF_OUTPUT = 2; - protected final CameraView.CameraCallbacks mCameraCallbacks; + protected final Callback mCallback; protected CameraPreview mPreview; protected WorkerHandler mHandler; - /* for tests */ Handler mCrashHandler; + @VisibleForTesting Handler mCrashHandler; protected Facing mFacing; protected Flash mFlash; @@ -79,10 +95,8 @@ public abstract class CameraEngine implements private SizeSelector mPictureSizeSelector; private SizeSelector mVideoSizeSelector; - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - int mSnapshotMaxWidth = Integer.MAX_VALUE; // in REF_VIEW for consistency with SizeSelectors - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - int mSnapshotMaxHeight = Integer.MAX_VALUE; // in REF_VIEW for consistency with SizeSelectors + @VisibleForTesting int mSnapshotMaxWidth = Integer.MAX_VALUE; // in REF_VIEW for consistency with SizeSelectors + @VisibleForTesting int mSnapshotMaxHeight = Integer.MAX_VALUE; // in REF_VIEW for consistency with SizeSelectors protected int mCameraId; protected CameraOptions mCameraOptions; @@ -106,17 +120,17 @@ public abstract class CameraEngine implements protected int mState = STATE_STOPPED; // Used for testing. - Task mZoomTask = new Task<>(); - Task mExposureCorrectionTask = new Task<>(); - Task mFlashTask = new Task<>(); - Task mWhiteBalanceTask = new Task<>(); - Task mHdrTask = new Task<>(); - Task mLocationTask = new Task<>(); - Task mStartVideoTask = new Task<>(); - Task mPlaySoundsTask = new Task<>(); - - CameraEngine(CameraView.CameraCallbacks callback) { - mCameraCallbacks = callback; + @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task mZoomTask = new Task<>(); + @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task mExposureCorrectionTask = new Task<>(); + @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task mFlashTask = new Task<>(); + @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task mWhiteBalanceTask = new Task<>(); + @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task mHdrTask = new Task<>(); + @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task mLocationTask = new Task<>(); + @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task mStartVideoTask = new Task<>(); + @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task mPlaySoundsTask = new Task<>(); + + CameraEngine(Callback callback) { + mCallback = callback; mCrashHandler = new Handler(Looper.getMainLooper()); mHandler = WorkerHandler.get("CameraViewController"); mHandler.getThread().setUncaughtExceptionHandler(this); @@ -171,7 +185,7 @@ public abstract class CameraEngine implements @Override public void run() { stopImmediately(); - mCameraCallbacks.dispatchError(error); + mCallback.dispatchError(error); } }); } @@ -215,7 +229,7 @@ public abstract class CameraEngine implements onStart(); LOG.i("Start:", "returned from onStart().", "Dispatching.", ss()); mState = STATE_STARTED; - mCameraCallbacks.dispatchOnCameraOpened(mCameraOptions); + mCallback.dispatchOnCameraOpened(mCameraOptions); } }); } @@ -234,7 +248,7 @@ public abstract class CameraEngine implements onStop(); LOG.i("Stop:", "returned from onStop().", "Dispatching."); mState = STATE_STOPPED; - mCameraCallbacks.dispatchOnCameraClosed(); + mCallback.dispatchOnCameraClosed(); } }); } @@ -270,7 +284,7 @@ public abstract class CameraEngine implements onStop(); mState = STATE_STOPPED; LOG.i("Restart:", "stopped. Dispatching.", ss()); - mCameraCallbacks.dispatchOnCameraClosed(); + mCallback.dispatchOnCameraClosed(); } LOG.i("Restart: about to start. State:", ss()); @@ -278,7 +292,7 @@ public abstract class CameraEngine implements onStart(); mState = STATE_STARTED; LOG.i("Restart: returned from start. Dispatching. State:", ss()); - mCameraCallbacks.dispatchOnCameraOpened(mCameraOptions); + mCallback.dispatchOnCameraOpened(mCameraOptions); } }); } @@ -384,13 +398,13 @@ public abstract class CameraEngine implements // Just set. abstract void setAudio(@NonNull Audio audio); - abstract void takePicture(); + abstract void takePicture(@NonNull PictureResult.Stub stub); - abstract void takePictureSnapshot(@NonNull AspectRatio viewAspectRatio); + abstract void takePictureSnapshot(@NonNull PictureResult.Stub stub, @NonNull AspectRatio viewAspectRatio); - abstract void takeVideo(@NonNull File file); + abstract void takeVideo(@NonNull VideoResult.Stub stub, @NonNull File file); - abstract void takeVideoSnapshot(@NonNull File file, @NonNull AspectRatio viewAspectRatio); + abstract void takeVideoSnapshot(@NonNull VideoResult.Stub stub, @NonNull File file, @NonNull AspectRatio viewAspectRatio); abstract void stopVideo(); diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Mapper.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Mapper.java index 3a0494fd..ec9ad610 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Mapper.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Mapper.java @@ -1,11 +1,21 @@ package com.otaliastudios.cameraview.engine; +import android.hardware.Camera; +import android.os.Build; + import com.otaliastudios.cameraview.controls.Facing; import com.otaliastudios.cameraview.controls.Flash; import com.otaliastudios.cameraview.controls.Hdr; import com.otaliastudios.cameraview.controls.WhiteBalance; -abstract class Mapper { +import java.util.HashMap; + +/** + * A Mapper maps camera engine constants to CameraView constants. + */ +public abstract class Mapper { + + private Mapper() {} abstract T map(Flash flash); abstract T map(Facing facing); @@ -15,4 +25,91 @@ abstract class Mapper { abstract Facing unmapFacing(T cameraConstant); abstract WhiteBalance unmapWhiteBalance(T cameraConstant); abstract Hdr unmapHdr(T cameraConstant); + + private static Mapper CAMERA1; + + static Mapper get() { + if (CAMERA1 == null) { + CAMERA1 = new Camera1Mapper(); + } + return CAMERA1; + } + + @SuppressWarnings("unchecked") + private static class Camera1Mapper extends Mapper { + + private static final HashMap FLASH = new HashMap<>(); + private static final HashMap WB = new HashMap<>(); + private static final HashMap FACING = new HashMap<>(); + private static final HashMap HDR = new HashMap<>(); + + static { + FLASH.put(Flash.OFF, Camera.Parameters.FLASH_MODE_OFF); + FLASH.put(Flash.ON, Camera.Parameters.FLASH_MODE_ON); + FLASH.put(Flash.AUTO, Camera.Parameters.FLASH_MODE_AUTO); + FLASH.put(Flash.TORCH, Camera.Parameters.FLASH_MODE_TORCH); + FACING.put(Facing.BACK, Camera.CameraInfo.CAMERA_FACING_BACK); + FACING.put(Facing.FRONT, Camera.CameraInfo.CAMERA_FACING_FRONT); + WB.put(WhiteBalance.AUTO, Camera.Parameters.WHITE_BALANCE_AUTO); + WB.put(WhiteBalance.INCANDESCENT, Camera.Parameters.WHITE_BALANCE_INCANDESCENT); + WB.put(WhiteBalance.FLUORESCENT, Camera.Parameters.WHITE_BALANCE_FLUORESCENT); + WB.put(WhiteBalance.DAYLIGHT, Camera.Parameters.WHITE_BALANCE_DAYLIGHT); + WB.put(WhiteBalance.CLOUDY, Camera.Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT); + HDR.put(Hdr.OFF, Camera.Parameters.SCENE_MODE_AUTO); + if (Build.VERSION.SDK_INT >= 17) { + HDR.put(Hdr.ON, Camera.Parameters.SCENE_MODE_HDR); + } else { + HDR.put(Hdr.ON, "hdr"); + } + } + + @Override + T map(Flash flash) { + return (T) FLASH.get(flash); + } + + @Override + T map(Facing facing) { + return (T) FACING.get(facing); + } + + @Override + T map(WhiteBalance whiteBalance) { + return (T) WB.get(whiteBalance); + } + + @Override + T map(Hdr hdr) { + return (T) HDR.get(hdr); + } + + private T reverseLookup(HashMap map, Object object) { + for (T value : map.keySet()) { + if (object.equals(map.get(value))) { + return value; + } + } + return null; + } + + @Override + Flash unmapFlash(T cameraConstant) { + return reverseLookup(FLASH, cameraConstant); + } + + @Override + Facing unmapFacing(T cameraConstant) { + return reverseLookup(FACING, cameraConstant); + } + + @Override + WhiteBalance unmapWhiteBalance(T cameraConstant) { + return reverseLookup(WB, cameraConstant); + } + + @Override + Hdr unmapHdr(T cameraConstant) { + return reverseLookup(HDR, cameraConstant); + } + } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Mapper1.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Mapper1.java deleted file mode 100644 index 6f953727..00000000 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Mapper1.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.otaliastudios.cameraview.engine; - -import android.hardware.Camera; -import android.os.Build; - -import com.otaliastudios.cameraview.controls.Facing; -import com.otaliastudios.cameraview.controls.Flash; -import com.otaliastudios.cameraview.controls.Hdr; -import com.otaliastudios.cameraview.controls.WhiteBalance; - -import java.util.HashMap; - - -@SuppressWarnings("unchecked") -class Mapper1 extends Mapper { - - private static final HashMap FLASH = new HashMap<>(); - private static final HashMap WB = new HashMap<>(); - private static final HashMap FACING = new HashMap<>(); - private static final HashMap HDR = new HashMap<>(); - - static { - FLASH.put(Flash.OFF, Camera.Parameters.FLASH_MODE_OFF); - FLASH.put(Flash.ON, Camera.Parameters.FLASH_MODE_ON); - FLASH.put(Flash.AUTO, Camera.Parameters.FLASH_MODE_AUTO); - FLASH.put(Flash.TORCH, Camera.Parameters.FLASH_MODE_TORCH); - FACING.put(Facing.BACK, Camera.CameraInfo.CAMERA_FACING_BACK); - FACING.put(Facing.FRONT, Camera.CameraInfo.CAMERA_FACING_FRONT); - WB.put(WhiteBalance.AUTO, Camera.Parameters.WHITE_BALANCE_AUTO); - WB.put(WhiteBalance.INCANDESCENT, Camera.Parameters.WHITE_BALANCE_INCANDESCENT); - WB.put(WhiteBalance.FLUORESCENT, Camera.Parameters.WHITE_BALANCE_FLUORESCENT); - WB.put(WhiteBalance.DAYLIGHT, Camera.Parameters.WHITE_BALANCE_DAYLIGHT); - WB.put(WhiteBalance.CLOUDY, Camera.Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT); - HDR.put(Hdr.OFF, Camera.Parameters.SCENE_MODE_AUTO); - if (Build.VERSION.SDK_INT >= 17) { - HDR.put(Hdr.ON, Camera.Parameters.SCENE_MODE_HDR); - } else { - HDR.put(Hdr.ON, "hdr"); - } - } - - @Override - T map(Flash flash) { - return (T) FLASH.get(flash); - } - - @Override - T map(Facing facing) { - return (T) FACING.get(facing); - } - - @Override - T map(WhiteBalance whiteBalance) { - return (T) WB.get(whiteBalance); - } - - @Override - T map(Hdr hdr) { - return (T) HDR.get(hdr); - } - - private T reverseLookup(HashMap map, Object object) { - for (T value : map.keySet()) { - if (map.get(value).equals(object)) { - return value; - } - } - return null; - } - - @Override - Flash unmapFlash(T cameraConstant) { - return reverseLookup(FLASH, cameraConstant); - } - - @Override - Facing unmapFacing(T cameraConstant) { - return reverseLookup(FACING, cameraConstant); - } - - @Override - WhiteBalance unmapWhiteBalance(T cameraConstant) { - return reverseLookup(WB, cameraConstant); - } - - @Override - Hdr unmapHdr(T cameraConstant) { - return reverseLookup(HDR, cameraConstant); - } -} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/frame/Frame.java b/cameraview/src/main/java/com/otaliastudios/cameraview/frame/Frame.java index 8a174952..38f709fd 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/frame/Frame.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/frame/Frame.java @@ -3,13 +3,14 @@ package com.otaliastudios.cameraview.frame; import com.otaliastudios.cameraview.size.Size; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; /** * A preview frame to be processed by {@link FrameProcessor}s. */ public class Frame { - /* for tests */ FrameManager mManager; + @VisibleForTesting FrameManager mManager; private byte[] mData = null; private long mTime = -1; @@ -21,6 +22,7 @@ public class Frame { mManager = manager; } + @SuppressWarnings("BooleanMethodIsAlwaysInverted") private boolean isAlive() { return mData != null; } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/frame/FrameManager.java b/cameraview/src/main/java/com/otaliastudios/cameraview/frame/FrameManager.java index e2404762..5bf7c3f6 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/frame/FrameManager.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/frame/FrameManager.java @@ -23,9 +23,13 @@ import java.util.concurrent.LinkedBlockingQueue; * - Frame pool: * We keep a list of mPoolSize recycled instances, to be reused when a new buffer is available. */ -class FrameManager { +public class FrameManager { - interface BufferCallback { + /** + * Receives callbacks on buffer availability + * (when a Frame is released, we reuse its buffer). + */ + public interface BufferCallback { void onBufferAvailable(@NonNull byte[] buffer); } @@ -34,14 +38,45 @@ class FrameManager { private BufferCallback mCallback; private LinkedBlockingQueue mQueue; - FrameManager(int poolSize, @Nullable BufferCallback callback) { + /** + * Construct a new frame manager. + * The construction must be followed by an {@link #allocateBuffers(int, Size)} call + * as soon as the parameters are known. + * + * @param poolSize the size of the backing pool. + * @param callback a callback + */ + public FrameManager(int poolSize, @Nullable BufferCallback callback) { mPoolSize = poolSize; mCallback = callback; mQueue = new LinkedBlockingQueue<>(mPoolSize); mBufferSize = -1; } - void release() { + /** + * Allocates a {@link #mPoolSize} number of buffers. Should be called once + * the preview size and the bitsPerPixel value are known. + * + * This method can be called again after {@link #release()} has been called. + * + * @param bitsPerPixel bits per pixel, depends on image format + * @param previewSize the preview size + * @return the buffer size + */ + public int allocateBuffers(int bitsPerPixel, @NonNull Size previewSize) { + // TODO throw if called twice without release? + mBufferSize = getBufferSize(bitsPerPixel, previewSize); + for (int i = 0; i < mPoolSize; i++) { + mCallback.onBufferAvailable(new byte[mBufferSize]); + } + return mBufferSize; + } + + /** + * Releases all frames controlled by this manager and + * clears the pool. + */ + public void release() { for (Frame frame : mQueue) { frame.releaseManager(); frame.release(); @@ -69,8 +104,8 @@ class FrameManager { /** * Returns a new Frame for the given data. This must be called - * - after {@link #allocate(int, Size)}, which sets the buffer size - * - after the byte buffer given by allocate() has been filled. + * - after {@link #allocateBuffers(int, Size)}, which sets the buffer size + * - after the byte buffer given by allocateBuffers() has been filled. * If this is called X times in a row without releasing frames, it will allocate * X frames and that's bad. Callers must wait for the preview buffer to be available. * @@ -78,21 +113,13 @@ class FrameManager { * * @return a new frame */ - Frame getFrame(@NonNull byte[] data, long time, int rotation, @NonNull Size previewSize, int previewFormat) { + public Frame getFrame(@NonNull byte[] data, long time, int rotation, @NonNull Size previewSize, int previewFormat) { Frame frame = mQueue.poll(); if (frame == null) frame = new Frame(this); frame.set(data, time, rotation, previewSize, previewFormat); return frame; } - int allocate(int bitsPerPixel, @NonNull Size previewSize) { - mBufferSize = getBufferSize(bitsPerPixel, previewSize); - for (int i = 0; i < mPoolSize; i++) { - mCallback.onBufferAvailable(new byte[mBufferSize]); - } - return mBufferSize; - } - private int getBufferSize(int bitsPerPixel, @NonNull Size previewSize) { long sizeInBits = previewSize.getHeight() * previewSize.getWidth() * bitsPerPixel; return (int) Math.ceil(sizeInBits / 8.0d); diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/gesture/Gesture.java b/cameraview/src/main/java/com/otaliastudios/cameraview/gesture/Gesture.java index ffa0e817..26420628 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/gesture/Gesture.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/gesture/Gesture.java @@ -5,9 +5,6 @@ import com.otaliastudios.cameraview.CameraView; import androidx.annotation.NonNull; -import java.util.Arrays; -import java.util.List; - /** * Gestures listen to finger gestures over the {@link CameraView} bounds and can be mapped diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/gesture/GestureParser.java b/cameraview/src/main/java/com/otaliastudios/cameraview/gesture/GestureParser.java index c44d85e1..84fde50a 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/gesture/GestureParser.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/gesture/GestureParser.java @@ -1,6 +1,5 @@ package com.otaliastudios.cameraview.gesture; -import android.content.Context; import android.content.res.TypedArray; import com.otaliastudios.cameraview.R; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/gesture/GestureType.java b/cameraview/src/main/java/com/otaliastudios/cameraview/gesture/GestureType.java index e4d96ea8..5d6e3e08 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/gesture/GestureType.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/gesture/GestureType.java @@ -1,14 +1,6 @@ package com.otaliastudios.cameraview.gesture; -import com.otaliastudios.cameraview.CameraView; - -import java.util.Arrays; -import java.util.List; - -import androidx.annotation.NonNull; - - /** * Gestures and gesture actions can both have a type. For a gesture to be able to be mapped to * a certain {@link GestureAction}, both of them might be of the same type. diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/gesture/PinchGestureLayout.java b/cameraview/src/main/java/com/otaliastudios/cameraview/gesture/PinchGestureLayout.java index 6e77b724..3c0acac8 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/gesture/PinchGestureLayout.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/gesture/PinchGestureLayout.java @@ -1,8 +1,6 @@ package com.otaliastudios.cameraview.gesture; -import android.annotation.SuppressLint; import android.content.Context; -import android.graphics.PointF; import android.os.Build; import androidx.annotation.NonNull; import android.view.MotionEvent; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/gesture/ScrollGestureLayout.java b/cameraview/src/main/java/com/otaliastudios/cameraview/gesture/ScrollGestureLayout.java index 8ffa8da8..97d30f86 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/gesture/ScrollGestureLayout.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/gesture/ScrollGestureLayout.java @@ -1,8 +1,6 @@ package com.otaliastudios.cameraview.gesture; -import android.annotation.SuppressLint; import android.content.Context; -import android.graphics.PointF; import androidx.annotation.NonNull; import android.view.GestureDetector; import android.view.MotionEvent; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/gesture/TapGestureLayout.java b/cameraview/src/main/java/com/otaliastudios/cameraview/gesture/TapGestureLayout.java index 6d388d54..ddf40bd2 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/gesture/TapGestureLayout.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/gesture/TapGestureLayout.java @@ -2,7 +2,6 @@ package com.otaliastudios.cameraview.gesture; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.annotation.SuppressLint; import android.content.Context; import android.graphics.PointF; import androidx.annotation.NonNull; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/GridLinesLayout.java b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/GridLinesLayout.java index 0425227b..636d95b2 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/GridLinesLayout.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/GridLinesLayout.java @@ -8,6 +8,8 @@ import android.graphics.drawable.ColorDrawable; import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + import android.util.AttributeSet; import android.util.TypedValue; import android.view.View; @@ -15,7 +17,10 @@ import android.view.View; import com.otaliastudios.cameraview.controls.Grid; import com.otaliastudios.cameraview.internal.utils.Task; -class GridLinesLayout extends View { +/** + * A layout overlay that draws grid lines based on the {@link Grid} parameter. + */ +public class GridLinesLayout extends View { private final static float GOLDEN_RATIO_INV = 0.61803398874989f; public final static int DEFAULT_COLOR = Color.argb(160, 255, 255, 255); @@ -27,7 +32,7 @@ class GridLinesLayout extends View { private ColorDrawable vert; private final float width; - Task drawTask = new Task<>(); + @VisibleForTesting Task drawTask = new Task<>(); public GridLinesLayout(@NonNull Context context) { this(context, null); @@ -47,16 +52,36 @@ class GridLinesLayout extends View { vert.setBounds(0, top, (int) width, bottom); } + /** + * Returns the current grid value. + * @return the grid mode + */ @NonNull public Grid getGridMode() { return gridMode; } + /** + * Sets a new grid value + * @param gridMode the new value + */ public void setGridMode(@NonNull Grid gridMode) { this.gridMode = gridMode; postInvalidate(); } + /** + * Returns the current grid color. + * @return the grid color + */ + public int getGridColor() { + return gridColor; + } + + /** + * Sets a new grid color. + * @param gridColor the new color + */ public void setGridColor(@ColorInt int gridColor) { this.gridColor = gridColor; horiz.setColor(gridColor); @@ -64,10 +89,6 @@ class GridLinesLayout extends View { postInvalidate(); } - public int getGridColor() { - return gridColor; - } - private int getLineCount() { switch (gridMode) { case OFF: return 0; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglElement.java b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglElement.java index 8af7787d..5be000fa 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglElement.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglElement.java @@ -24,16 +24,16 @@ class EglElement { protected static void check(String opName) { int error = GLES20.glGetError(); if (error != GLES20.GL_NO_ERROR) { - LOG.e("Error during", opName, "glError 0x", Integer.toHexString(error)); - throw new RuntimeException(CameraLogger.lastMessage); + String message = LOG.e("Error during", opName, "glError 0x", Integer.toHexString(error)); + throw new RuntimeException(message); } } // Check for valid location. protected static void checkLocation(int location, String label) { if (location < 0) { - LOG.e("Unable to locate", label, "in program"); - throw new RuntimeException(CameraLogger.lastMessage); + String message = LOG.e("Unable to locate", label, "in program"); + throw new RuntimeException(message); } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/CamcorderProfiles.java b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/CamcorderProfiles.java index bd896f5c..6348a69d 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/CamcorderProfiles.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/CamcorderProfiles.java @@ -4,6 +4,8 @@ import android.annotation.SuppressLint; import android.media.CamcorderProfile; import android.os.Build; +import com.otaliastudios.cameraview.size.Size; + import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/CropHelper.java b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/CropHelper.java index b143da6d..59586120 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/CropHelper.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/CropHelper.java @@ -7,11 +7,14 @@ import com.otaliastudios.cameraview.size.Size; import androidx.annotation.NonNull; -class CropHelper { +/** + * Simply computes the crop between a full size and a desired aspect ratio. + */ +public class CropHelper { // It's important that size and aspect ratio belong to the same reference. @NonNull - static Rect computeCrop(@NonNull Size currentSize, @NonNull AspectRatio targetRatio) { + public static Rect computeCrop(@NonNull Size currentSize, @NonNull AspectRatio targetRatio) { int currentWidth = currentSize.getWidth(); int currentHeight = currentSize.getHeight(); if (targetRatio.matches(currentSize)) { diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/OrientationHelper.java b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/OrientationHelper.java index 5b1f79e9..2f6eb8fb 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/OrientationHelper.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/OrientationHelper.java @@ -3,24 +3,37 @@ package com.otaliastudios.cameraview.internal.utils; import android.content.Context; import android.hardware.SensorManager; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; + import android.view.Display; import android.view.OrientationEventListener; import android.view.Surface; import android.view.WindowManager; -class OrientationHelper { +/** + * Helps with keeping track of both device orientation (which changes when device is rotated) + * and the display offset (which depends on the activity orientation wrt the device default orientation). + */ +public class OrientationHelper { - final OrientationEventListener mListener; + /** + * Receives callback about the device orientation changes. + */ + public interface Callback { + void onDeviceOrientationChanged(int deviceOrientation); + } + @VisibleForTesting final OrientationEventListener mListener; private final Callback mCallback; private int mDeviceOrientation = -1; private int mDisplayOffset = -1; - interface Callback { - void onDeviceOrientationChanged(int deviceOrientation); - } - - OrientationHelper(@NonNull Context context, @NonNull Callback callback) { + /** + * Creates a new orientation helper. + * @param context a valid context + * @param callback a {@link Callback} + */ + public OrientationHelper(@NonNull Context context, @NonNull Callback callback) { mCallback = callback; mListener = new OrientationEventListener(context.getApplicationContext(), SensorManager.SENSOR_DELAY_NORMAL) { @@ -48,7 +61,11 @@ class OrientationHelper { }; } - void enable(@NonNull Context context) { + /** + * Enables this listener. + * @param context a context + */ + public void enable(@NonNull Context context) { Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); switch (display.getRotation()) { case Surface.ROTATION_0: mDisplayOffset = 0; break; @@ -60,16 +77,27 @@ class OrientationHelper { mListener.enable(); } - void disable() { + /** + * Disables this listener. + */ + public void disable() { mListener.disable(); mDisplayOffset = -1; mDeviceOrientation = -1; } + /** + * Returns the current device orientation. + * @return device orientation + */ int getDeviceOrientation() { return mDeviceOrientation; } + /** + * Returns the current display offset. + * @return display offset + */ int getDisplayOffset() { return mDisplayOffset; } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/Pool.java b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/Pool.java index 5b9e3dcd..29e53c59 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/Pool.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/Pool.java @@ -8,6 +8,10 @@ import androidx.annotation.CallSuper; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +/** + * Base class for pools of recycleable objects. + * @param the object type + */ class Pool { private static final String TAG = Pool.class.getSimpleName(); @@ -18,27 +22,49 @@ class Pool { private LinkedBlockingQueue mQueue; private Factory factory; - interface Factory { + /** + * Used to create new instances of objects when needed. + * @param object type + */ + public interface Factory { T create(); } - Pool(int maxPoolSize, Factory factory) { + /** + * Creates a new pool with the given pool size and factory. + * @param maxPoolSize the max pool size + * @param factory the factory + */ + public Pool(int maxPoolSize, @NonNull Factory factory) { this.maxPoolSize = maxPoolSize; this.mQueue = new LinkedBlockingQueue<>(maxPoolSize); this.factory = factory; } - boolean isEmpty() { + /** + * Whether the pool is empty. This means that {@link #get()} will return + * a null item, because all objects were reclaimed and not recycled yet. + * + * @return whether the pool is empty + */ + public boolean isEmpty() { return count() >= maxPoolSize; } + /** + * Returns a new item, from the recycled pool if possible (if there are recycled items), + * or instantiating one through the factory (if we can respect the pool size). + * If these conditions are not met, this returns null. + * + * @return an item or null + */ @Nullable - T get() { - T buffer = mQueue.poll(); - if (buffer != null) { + public T get() { + T item = mQueue.poll(); + if (item != null) { activeCount++; // poll decreases, this fixes LOG.v("GET: Reusing recycled item.", this); - return buffer; + return item; } if (isEmpty()) { @@ -51,8 +77,13 @@ class Pool { return factory.create(); } - - void recycle(@NonNull T item) { + /** + * Recycles an item after it has been used. The item should come from a previous + * {@link #get()} call. + * + * @param item used item + */ + public void recycle(@NonNull T item) { LOG.v("RECYCLE: Recycling item.", this); if (--activeCount < 0) { throw new IllegalStateException("Trying to recycle an item which makes activeCount < 0." + @@ -66,26 +97,49 @@ class Pool { } } - @NonNull - @Override - public String toString() { - return getClass().getSimpleName() + " -- count:" + count() + ", active:" + activeCount() + ", cached:" + cachedCount(); + /** + * Clears the pool of recycled items. + */ + @CallSuper + public void clear() { + mQueue.clear(); } - final int count() { - return activeCount() + cachedCount(); + /** + * Returns the count of all items managed by this pool. Includes + * - active items: currently being used + * - recycled items: used and recycled, available for second use + * + * @return count + */ + public final int count() { + return activeCount() + recycledCount(); } - final int activeCount() { + /** + * Returns the active items managed by this pools, which means, items + * currently being used. + * + * @return active count + */ + public final int activeCount() { return activeCount; } - final int cachedCount() { + /** + * Returns the recycled items managed by this pool, which means, items + * that were used and later recycled, and are currently available for + * second use. + * + * @return recycled count + */ + public final int recycledCount() { return mQueue.size(); } - @CallSuper - void clear() { - mQueue.clear(); + @NonNull + @Override + public String toString() { + return getClass().getSimpleName() + " -- count:" + count() + ", active:" + activeCount() + ", recycled:" + recycledCount(); } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/RotationHelper.java b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/RotationHelper.java index f538371e..688cf3f9 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/RotationHelper.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/RotationHelper.java @@ -12,7 +12,14 @@ import androidx.annotation.NonNull; @Deprecated class RotationHelper { - static byte[] rotate(@NonNull final byte[] yuv, @NonNull final Size size, final int rotation) { + /** + * Rotates the given yuv image into another yuv array, by the given angle. + * @param yuv image + * @param size image size + * @param rotation desired angle + * @return a new yuv array + */ + public static byte[] rotate(@NonNull final byte[] yuv, @NonNull final Size size, final int rotation) { if (rotation == 0) return yuv; if (rotation % 90 != 0 || rotation < 0 || rotation > 270) { throw new IllegalArgumentException("0 <= rotation < 360, rotation % 90 == 0"); diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/Task.java b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/Task.java index 991e57b1..9e220b44 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/Task.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/Task.java @@ -9,53 +9,92 @@ import java.util.concurrent.TimeUnit; * A naive implementation of {@link java.util.concurrent.CountDownLatch} * to help in testing. */ -class Task { +public class Task { private CountDownLatch mLatch; private T mResult; private int mCount; - Task() { - } - - Task(boolean startListening) { + /** + * Creates an empty task. + * + * Listeners should: + * - call {@link #listen()} to notify they are interested in the next action + * - call {@link #await()} to know when the action is performed. + * + * Task owners should: + * - call {@link #start()} when task started + * - call {@link #end(Object)} when task ends + */ + public Task() { } + + /** + * Creates an empty task and starts listening. + * @param startListening whether to call listen + */ + public Task(boolean startListening) { if (startListening) listen(); } - private boolean listening() { + private boolean isListening() { return mLatch != null; } - void listen() { - if (listening()) throw new RuntimeException("Should not happen."); - mResult = null; - mLatch = new CountDownLatch(1); - } - - void start() { - if (!listening()) mCount++; + /** + * Task owner method: notifies the action started. + */ + public void start() { + if (!isListening()) mCount++; } - void end(T result) { + /** + * Task owner method: notifies the action ended. + * @param result the action result + */ + public void end(T result) { if (mCount > 0) { mCount--; return; } - if (listening()) { // Should be always true. + if (isListening()) { // Should be always true. mResult = result; mLatch.countDown(); } } - T await(long millis) { + /** + * Listener method: notifies we are interested in the next action. + */ + public void listen() { + if (isListening()) throw new RuntimeException("Should not happen."); + mResult = null; + mLatch = new CountDownLatch(1); + } + + /** + * Listener method: waits for next task action to end. + * @param millis milliseconds + * @return the action result + */ + public T await(long millis) { return await(millis, TimeUnit.MILLISECONDS); } - T await() { + /** + * Listener method: waits 1 minute for next task action to end. + * @return the action result + */ + public T await() { return await(1, TimeUnit.MINUTES); } + /** + * Listener method: waits for next task action to end. + * @param time time + * @param unit the time unit + * @return the action result + */ private T await(long time, @NonNull TimeUnit unit) { try { mLatch.await(time, unit); diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/WorkerHandler.java b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/WorkerHandler.java index bbb24218..17f58439 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/WorkerHandler.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/WorkerHandler.java @@ -15,14 +15,20 @@ import java.util.concurrent.ConcurrentHashMap; * Class holding a background handler. * We want them to survive configuration changes if there's still job to do. */ -class WorkerHandler { +public class WorkerHandler { private final static CameraLogger LOG = CameraLogger.create(WorkerHandler.class.getSimpleName()); private final static ConcurrentHashMap> sCache = new ConcurrentHashMap<>(4); + /** + * Gets a possibly cached handler with the given name. + * @param name the handler name + * @return a handler + */ @NonNull public static WorkerHandler get(@NonNull String name) { if (sCache.containsKey(name)) { + //noinspection ConstantConditions WorkerHandler cached = sCache.get(name).get(); if (cached != null) { HandlerThread thread = cached.mThread; @@ -41,9 +47,13 @@ class WorkerHandler { return handler; } - // Handy util to perform action in a fallback thread. - // Not to be used for long-running operations since they will - // block the fallback thread. + /** + * Handy utility to perform an action in a fallback thread. + * Not to be used for long-running operations since they will block + * the fallback thread. + * + * @param action the action + */ public static void run(@NonNull Runnable action) { get("FallbackCameraThread").post(action); } @@ -58,27 +68,48 @@ class WorkerHandler { mHandler = new Handler(mThread.getLooper()); } - public Handler get() { - return mHandler; - } - + /** + * Post an action on this handler. + * @param runnable the action + */ public void post(@NonNull Runnable runnable) { mHandler.post(runnable); } + /** + * Returns the android backing {@link Handler}. + * @return the handler + */ + public Handler get() { + return mHandler; + } + + /** + * Returns the android backing {@link HandlerThread}. + * @return the thread + */ @NonNull public HandlerThread getThread() { return mThread; } + /** + * Returns the android backing {@link Looper}. + * @return the looper + */ @NonNull public Looper getLooper() { return mThread.getLooper(); } - static void destroy() { + /** + * Destroys all handlers, interrupting their work and + * removing them from our cache. + */ + public static void destroy() { for (String key : sCache.keySet()) { WeakReference ref = sCache.get(key); + //noinspection ConstantConditions WorkerHandler handler = ref.get(); if (handler != null && handler.getThread().isAlive()) { handler.getThread().interrupt(); diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/picture/FullPictureRecorder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/picture/FullPictureRecorder.java index 97e2c925..d2299f2c 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/picture/FullPictureRecorder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/picture/FullPictureRecorder.java @@ -16,14 +16,14 @@ import java.io.IOException; /** * A {@link PictureResult} that uses standard APIs. */ -class FullPictureRecorder extends PictureRecorder { +public class FullPictureRecorder extends PictureRecorder { private static final String TAG = FullPictureRecorder.class.getSimpleName(); private static final CameraLogger LOG = CameraLogger.create(TAG); private Camera mCamera; - FullPictureRecorder(@NonNull PictureResult stub, @Nullable PictureResultListener listener, @NonNull Camera camera) { + public FullPictureRecorder(@NonNull PictureResult.Stub stub, @Nullable PictureResultListener listener, @NonNull Camera camera) { super(stub, listener); mCamera = camera; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/picture/PictureRecorder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/picture/PictureRecorder.java index 3568ce1f..c2a432f2 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/picture/PictureRecorder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/picture/PictureRecorder.java @@ -10,17 +10,17 @@ import androidx.annotation.Nullable; * Don't call start if already started. Don't call stop if already stopped. * Don't reuse. */ -abstract class PictureRecorder { +public abstract class PictureRecorder { - /* tests */ PictureResult mResult; + /* tests */ PictureResult.Stub mResult; /* tests */ PictureResultListener mListener; - PictureRecorder(@NonNull PictureResult stub, @Nullable PictureResultListener listener) { + PictureRecorder(@NonNull PictureResult.Stub stub, @Nullable PictureResultListener listener) { mResult = stub; mListener = listener; } - abstract void take(); + public abstract void take(); @SuppressWarnings("WeakerAccess") protected void dispatchOnShutter(boolean didPlaySound) { @@ -35,8 +35,8 @@ abstract class PictureRecorder { } } - interface PictureResultListener { + public interface PictureResultListener { void onPictureShutter(boolean didPlaySound); - void onPictureResult(@Nullable PictureResult result); + void onPictureResult(@Nullable PictureResult.Stub result); } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/picture/SnapshotPictureRecorder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/picture/SnapshotPictureRecorder.java index 33cd5786..f420f53d 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/picture/SnapshotPictureRecorder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/picture/SnapshotPictureRecorder.java @@ -36,7 +36,7 @@ import java.io.ByteArrayOutputStream; /** * A {@link PictureResult} that uses standard APIs. */ -class SnapshotPictureRecorder extends PictureRecorder { +public class SnapshotPictureRecorder extends PictureRecorder { private static final String TAG = SnapshotPictureRecorder.class.getSimpleName(); private static final CameraLogger LOG = CameraLogger.create(TAG); @@ -48,7 +48,7 @@ class SnapshotPictureRecorder extends PictureRecorder { private Size mSensorPreviewSize; private int mFormat; - SnapshotPictureRecorder(@NonNull PictureResult stub, @NonNull Camera1Engine controller, + public SnapshotPictureRecorder(@NonNull PictureResult.Stub stub, @NonNull Camera1Engine controller, @NonNull Camera camera, @NonNull AspectRatio outputRatio) { super(stub, controller); mController = controller; @@ -60,7 +60,7 @@ class SnapshotPictureRecorder extends PictureRecorder { } @Override - void take() { + public void take() { if (mPreview instanceof GlCameraPreview && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { takeGl((GlCameraPreview) mPreview); } else { @@ -208,7 +208,7 @@ class SnapshotPictureRecorder extends PictureRecorder { // It seems that the buffers are already cleared here, so we need to allocate again. camera.setPreviewCallbackWithBuffer(null); // Release anything left camera.setPreviewCallbackWithBuffer(mController); // Add ourselves - mController.mFrameManager.allocate(ImageFormat.getBitsPerPixel(mFormat), mController.mPreviewStreamSize); + mController.mFrameManager.allocateBuffers(ImageFormat.getBitsPerPixel(mFormat), mController.mPreviewStreamSize); } }); } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/preview/CameraPreview.java b/cameraview/src/main/java/com/otaliastudios/cameraview/preview/CameraPreview.java index 9ad9445b..28aaa7c7 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/preview/CameraPreview.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/preview/CameraPreview.java @@ -27,7 +27,7 @@ public abstract class CameraPreview { // This is used to notify CameraEngine to recompute its camera Preview size. // After that, CameraView will need a new layout pass to adapt to the Preview size. - interface SurfaceCallback { + public interface SurfaceCallback { void onSurfaceAvailable(); void onSurfaceChanged(); void onSurfaceDestroyed(); @@ -55,7 +55,7 @@ public abstract class CameraPreview { protected abstract T onCreateView(@NonNull Context context, @NonNull ViewGroup parent); @NonNull - final T getView() { + public final T getView() { return mView; } @@ -64,15 +64,15 @@ public abstract class CameraPreview { abstract View getRootView(); @NonNull - abstract Class getOutputClass(); + public abstract Class getOutputClass(); @NonNull - abstract Output getOutput(); + public abstract Output getOutput(); // As far as I can see, these are the actual preview dimensions, as set in CameraParameters. // This is called by the CameraImpl. // These must be alredy rotated, if needed, to be consistent with surface/view sizes. - void setStreamSize(int width, int height, boolean wasFlipped) { + public void setStreamSize(int width, int height, boolean wasFlipped) { LOG.i("setStreamSize:", "desiredW=", width, "desiredH=", height); mInputStreamWidth = width; mInputStreamHeight = height; @@ -88,11 +88,11 @@ public abstract class CameraPreview { } @NonNull - final Size getSurfaceSize() { + public final Size getSurfaceSize() { return new Size(mOutputSurfaceWidth, mOutputSurfaceHeight); } - final void setSurfaceCallback(@NonNull SurfaceCallback callback) { + public final void setSurfaceCallback(@NonNull SurfaceCallback callback) { mSurfaceCallback = callback; // If surface already available, dispatch. if (mOutputSurfaceWidth != 0 || mOutputSurfaceHeight != 0) { @@ -144,7 +144,7 @@ public abstract class CameraPreview { // Public for mockito (CameraViewTest) public void onDestroy() {} - final boolean hasSurface() { + public final boolean hasSurface() { return mOutputSurfaceWidth > 0 && mOutputSurfaceHeight > 0; } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/preview/GlCameraPreview.java b/cameraview/src/main/java/com/otaliastudios/cameraview/preview/GlCameraPreview.java index 436fa731..170503e6 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/preview/GlCameraPreview.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/preview/GlCameraPreview.java @@ -57,7 +57,7 @@ import javax.microedition.khronos.opengles.GL10; * Callbacks are guaranteed to be called on the renderer thread, which means that we can fetch * the GL context that was created and is managed by the {@link GLSurfaceView}. */ -class GlCameraPreview extends CameraPreview implements GLSurfaceView.Renderer { +public class GlCameraPreview extends CameraPreview implements GLSurfaceView.Renderer { private boolean mDispatched; private final float[] mTransformMatrix = new float[16]; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/video/FullVideoRecorder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/video/FullVideoRecorder.java index 87b7131f..090210b9 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/video/FullVideoRecorder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/video/FullVideoRecorder.java @@ -18,7 +18,7 @@ import androidx.annotation.Nullable; /** * A {@link VideoRecorder} that uses {@link android.media.MediaRecorder} APIs. */ -class FullVideoRecorder extends VideoRecorder { +public class FullVideoRecorder extends VideoRecorder { private static final String TAG = FullVideoRecorder.class.getSimpleName(); private static final CameraLogger LOG = CameraLogger.create(TAG); @@ -29,7 +29,7 @@ class FullVideoRecorder extends VideoRecorder { private Camera mCamera; private Size mSize; - FullVideoRecorder(@NonNull VideoResult stub, @Nullable VideoResultListener listener, + public FullVideoRecorder(@NonNull VideoResult.Stub stub, @Nullable VideoResultListener listener, @NonNull Camera1Engine controller, @NonNull Camera camera, int cameraId) { super(stub, listener); mCamera = camera; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/video/SnapshotVideoRecorder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/video/SnapshotVideoRecorder.java index d1952781..1c500c66 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/video/SnapshotVideoRecorder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/video/SnapshotVideoRecorder.java @@ -23,7 +23,7 @@ import androidx.annotation.RequiresApi; * A {@link VideoRecorder} that uses {@link android.media.MediaCodec} APIs. */ @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) -class SnapshotVideoRecorder extends VideoRecorder implements GlCameraPreview.RendererFrameCallback, +public class SnapshotVideoRecorder extends VideoRecorder implements GlCameraPreview.RendererFrameCallback, MediaEncoderEngine.Listener { private static final String TAG = SnapshotVideoRecorder.class.getSimpleName(); @@ -43,7 +43,7 @@ class SnapshotVideoRecorder extends VideoRecorder implements GlCameraPreview.Ren private int mDesiredState = STATE_NOT_RECORDING; private int mTextureId = 0; - SnapshotVideoRecorder(@NonNull VideoResult stub, @Nullable VideoResultListener listener, @NonNull GlCameraPreview preview) { + public SnapshotVideoRecorder(@NonNull VideoResult.Stub stub, @Nullable VideoResultListener listener, @NonNull GlCameraPreview preview) { super(stub, listener); mPreview = preview; mPreview.addRendererFrameCallback(this); diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/video/VideoRecorder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/video/VideoRecorder.java index 7febb512..029910e2 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/video/VideoRecorder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/video/VideoRecorder.java @@ -1,5 +1,7 @@ package com.otaliastudios.cameraview.video; +import com.otaliastudios.cameraview.VideoResult; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -8,20 +10,20 @@ import androidx.annotation.Nullable; * Don't call start if already started. Don't call stop if already stopped. * Don't reuse. */ -abstract class VideoRecorder { +public abstract class VideoRecorder { - /* tests */ VideoResult mResult; + /* tests */ VideoResult.Stub mResult; /* tests */ VideoResultListener mListener; protected Exception mError; - VideoRecorder(@NonNull VideoResult stub, @Nullable VideoResultListener listener) { + VideoRecorder(@NonNull VideoResult.Stub stub, @Nullable VideoResultListener listener) { mResult = stub; mListener = listener; } - abstract void start(); + public abstract void start(); - abstract void stop(); + public abstract void stop(); @SuppressWarnings("WeakerAccess") protected void dispatchResult() { @@ -33,8 +35,7 @@ abstract class VideoRecorder { } } - - interface VideoResultListener { - void onVideoResult(@Nullable VideoResult result, @Nullable Exception exception); + public interface VideoResultListener { + void onVideoResult(@Nullable VideoResult.Stub result, @Nullable Exception exception); } } diff --git a/cameraview/src/test/java/com/otaliastudios/cameraview/FrameManagerTest.java b/cameraview/src/test/java/com/otaliastudios/cameraview/FrameManagerTest.java index ee92bce9..35b4594e 100644 --- a/cameraview/src/test/java/com/otaliastudios/cameraview/FrameManagerTest.java +++ b/cameraview/src/test/java/com/otaliastudios/cameraview/FrameManagerTest.java @@ -38,12 +38,12 @@ public class FrameManagerTest { @Test public void testAllocate() { FrameManager manager = new FrameManager(1, callback); - manager.allocate(4, new Size(50, 50)); + manager.allocateBuffers(4, new Size(50, 50)); verify(callback, times(1)).onBufferAvailable(any(byte[].class)); reset(callback); manager = new FrameManager(5, callback); - manager.allocate(4, new Size(50, 50)); + manager.allocateBuffers(4, new Size(50, 50)); verify(callback, times(5)).onBufferAvailable(any(byte[].class)); } @@ -51,7 +51,7 @@ public class FrameManagerTest { public void testFrameRecycling() { // A 1-pool manager will always recycle the same frame. FrameManager manager = new FrameManager(1, callback); - manager.allocate(4, new Size(50, 50)); + manager.allocateBuffers(4, new Size(50, 50)); Frame first = manager.getFrame(null, 0, 0, null, 0); first.release(); @@ -65,7 +65,7 @@ public class FrameManagerTest { @Test public void testOnFrameReleased_alreadyFull() { FrameManager manager = new FrameManager(1, callback); - int length = manager.allocate(4, new Size(50, 50)); + int length = manager.allocateBuffers(4, new Size(50, 50)); Frame frame1 = manager.getFrame(new byte[length], 0, 0, null, 0); // Since frame1 is already taken and poolSize = 1, a new Frame is created. @@ -82,7 +82,7 @@ public class FrameManagerTest { @Test public void testOnFrameReleased_sameLength() { FrameManager manager = new FrameManager(1, callback); - int length = manager.allocate(4, new Size(50, 50)); + int length = manager.allocateBuffers(4, new Size(50, 50)); // A camera preview frame comes. Request a frame. byte[] picture = new byte[length]; @@ -97,14 +97,14 @@ public class FrameManagerTest { @Test public void testOnFrameReleased_differentLength() { FrameManager manager = new FrameManager(1, callback); - int length = manager.allocate(4, new Size(50, 50)); + int length = manager.allocateBuffers(4, new Size(50, 50)); // A camera preview frame comes. Request a frame. byte[] picture = new byte[length]; Frame frame = manager.getFrame(picture, 0, 0, null, 0); // Don't release the frame. Change the allocation size. - manager.allocate(2, new Size(15, 15)); + manager.allocateBuffers(2, new Size(15, 15)); // Now release the old frame and ensure that onBufferAvailable is NOT called, // because the released data has wrong length. @@ -116,7 +116,7 @@ public class FrameManagerTest { @Test public void testRelease() { FrameManager manager = new FrameManager(1, callback); - int length = manager.allocate(4, new Size(50, 50)); + int length = manager.allocateBuffers(4, new Size(50, 50)); Frame first = manager.getFrame(new byte[length], 0, 0, null, 0); first.release(); // Store this frame in the queue.