Refactor and document some packages

pull/482/head
Mattia Iavarone 6 years ago
parent 0d69df4b4e
commit ec5210cd08
  1. 6
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraException.java
  2. 36
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraLogger.java
  3. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java
  4. 26
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  5. 39
      cameraview/src/main/java/com/otaliastudios/cameraview/PictureResult.java
  6. 69
      cameraview/src/main/java/com/otaliastudios/cameraview/VideoResult.java
  7. 160
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java
  8. 80
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java
  9. 99
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Mapper.java
  10. 90
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Mapper1.java
  11. 4
      cameraview/src/main/java/com/otaliastudios/cameraview/frame/Frame.java
  12. 57
      cameraview/src/main/java/com/otaliastudios/cameraview/frame/FrameManager.java
  13. 3
      cameraview/src/main/java/com/otaliastudios/cameraview/gesture/Gesture.java
  14. 1
      cameraview/src/main/java/com/otaliastudios/cameraview/gesture/GestureParser.java
  15. 8
      cameraview/src/main/java/com/otaliastudios/cameraview/gesture/GestureType.java
  16. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/gesture/PinchGestureLayout.java
  17. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/gesture/ScrollGestureLayout.java
  18. 1
      cameraview/src/main/java/com/otaliastudios/cameraview/gesture/TapGestureLayout.java
  19. 33
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/GridLinesLayout.java
  20. 8
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglElement.java
  21. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/CamcorderProfiles.java
  22. 7
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/CropHelper.java
  23. 46
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/OrientationHelper.java
  24. 94
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/Pool.java
  25. 9
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/RotationHelper.java
  26. 75
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/Task.java
  27. 49
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/WorkerHandler.java
  28. 4
      cameraview/src/main/java/com/otaliastudios/cameraview/picture/FullPictureRecorder.java
  29. 12
      cameraview/src/main/java/com/otaliastudios/cameraview/picture/PictureRecorder.java
  30. 8
      cameraview/src/main/java/com/otaliastudios/cameraview/picture/SnapshotPictureRecorder.java
  31. 16
      cameraview/src/main/java/com/otaliastudios/cameraview/preview/CameraPreview.java
  32. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/preview/GlCameraPreview.java
  33. 4
      cameraview/src/main/java/com/otaliastudios/cameraview/video/FullVideoRecorder.java
  34. 4
      cameraview/src/main/java/com/otaliastudios/cameraview/video/SnapshotVideoRecorder.java
  35. 17
      cameraview/src/main/java/com/otaliastudios/cameraview/video/VideoRecorder.java
  36. 16
      cameraview/src/test/java/com/otaliastudios/cameraview/FrameManagerTest.java

@ -53,16 +53,16 @@ public class CameraException extends RuntimeException {
private int reason = REASON_UNKNOWN; private int reason = REASON_UNKNOWN;
CameraException(Throwable cause) { public CameraException(Throwable cause) {
super(cause); super(cause);
} }
CameraException(Throwable cause, int reason) { public CameraException(Throwable cause, int reason) {
super(cause); super(cause);
this.reason = reason; this.reason = reason;
} }
CameraException(int reason) { public CameraException(int reason) {
super(); super();
this.reason = reason; this.reason = reason;
} }

@ -3,6 +3,8 @@ package com.otaliastudios.cameraview;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import android.util.Log; import android.util.Log;
import java.lang.annotation.Retention; 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); void log(@LogLevel int level, @NonNull String tag, @NonNull String message, @Nullable Throwable throwable);
} }
static String lastMessage; @VisibleForTesting static String lastMessage;
static String lastTag; @VisibleForTesting static String lastTag;
private static int sLevel; private static int sLevel;
private static List<Logger> sLoggers; private static List<Logger> sLoggers;
@ -131,37 +133,46 @@ public final class CameraLogger {
/** /**
* Log to the verbose channel. * Log to the verbose channel.
* @param data log contents * @param data log contents
* @return the log message, if logged
*/ */
public void v(@NonNull Object... data) { @Nullable
log(LEVEL_VERBOSE, data); public String v(@NonNull Object... data) {
return log(LEVEL_VERBOSE, data);
} }
/** /**
* Log to the info channel. * Log to the info channel.
* @param data log contents * @param data log contents
* @return the log message, if logged
*/ */
public void i(@NonNull Object... data) { @Nullable
log(LEVEL_INFO, data); public String i(@NonNull Object... data) {
return log(LEVEL_INFO, data);
} }
/** /**
* Log to the warning channel. * Log to the warning channel.
* @param data log contents * @param data log contents
* @return the log message, if logged
*/ */
void w(@NonNull Object... data) { @Nullable
log(LEVEL_WARNING, data); public String w(@NonNull Object... data) {
return log(LEVEL_WARNING, data);
} }
/** /**
* Log to the error channel. * Log to the error channel.
* @param data log contents * @param data log contents
* @return the log message, if logged
*/ */
void e(@NonNull Object... data) { @Nullable
log(LEVEL_ERROR, data); public String e(@NonNull Object... data) {
return log(LEVEL_ERROR, data);
} }
private void log(@LogLevel int level, @NonNull Object... data) { @Nullable
if (!should(level)) return; private String log(@LogLevel int level, @NonNull Object... data) {
if (!should(level)) return null;
StringBuilder message = new StringBuilder(); StringBuilder message = new StringBuilder();
Throwable throwable = null; Throwable throwable = null;
@ -178,6 +189,7 @@ public final class CameraLogger {
} }
lastMessage = string; lastMessage = string;
lastTag = mTag; lastTag = mTag;
return string;
} }
} }

@ -52,7 +52,7 @@ public class CameraOptions {
// Camera1Engine constructor. // Camera1Engine constructor.
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
CameraOptions(@NonNull Camera.Parameters params, boolean flipSizes) { public CameraOptions(@NonNull Camera.Parameters params, boolean flipSizes) {
List<String> strings; List<String> strings;
Mapper mapper = new Mapper1(); Mapper mapper = new Mapper1();

@ -1238,7 +1238,8 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
* @see #takePictureSnapshot() * @see #takePictureSnapshot()
*/ */
public void takePicture() { 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() { public void takePictureSnapshot() {
if (getWidth() == 0 || getHeight() == 0) return; 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 * @param file a file where the video will be saved
*/ */
public void takeVideo(@NonNull File file) { public void takeVideo(@NonNull File file) {
mCameraEngine.takeVideo(file); VideoResult.Stub stub = new VideoResult.Stub();
mCameraEngine.takeVideo(stub, file);
mUiHandler.post(new Runnable() { mUiHandler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
@ -1286,7 +1289,8 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
*/ */
public void takeVideoSnapshot(@NonNull File file) { public void takeVideoSnapshot(@NonNull File file) {
if (getWidth() == 0 || getHeight() == 0) return; 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() { mUiHandler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
@ -1609,20 +1613,6 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
//region Callbacks and dispatching //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 { private class Callbacks implements CameraCallbacks {

@ -17,18 +17,39 @@ import androidx.annotation.Nullable;
*/ */
public class PictureResult { 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_JPEG = 0;
// public final static int FORMAT_PNG = 1; // public final static int FORMAT_PNG = 1;
boolean isSnapshot; private final boolean isSnapshot;
Location location; private final Location location;
int rotation; private final int rotation;
Size size; private final Size size;
Facing facing; private final Facing facing;
byte[] data; private final byte[] data;
int format; private final int format;
PictureResult() {} 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. * Returns whether this result comes from a snapshot.

@ -17,6 +17,26 @@ import java.io.File;
*/ */
public class VideoResult { 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"}) @SuppressWarnings({"WeakerAccess", "unused"})
public static final int REASON_USER = 0; public static final int REASON_USER = 0;
@ -26,22 +46,37 @@ public class VideoResult {
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
public static final int REASON_MAX_DURATION_REACHED = 2; public static final int REASON_MAX_DURATION_REACHED = 2;
boolean isSnapshot; private final boolean isSnapshot;
Location location; private final Location location;
int rotation; private final int rotation;
Size size; private final Size size;
File file; private final File file;
Facing facing; private final Facing facing;
VideoCodec codec; private final VideoCodec videoCodec;
Audio audio; private final Audio audio;
long maxSize; private final long maxSize;
int maxDuration; private final int maxDuration;
int endReason; private final int endReason;
int videoBitRate; private final int videoBitRate;
int videoFrameRate; private final int videoFrameRate;
int audioBitRate; private final int audioBitRate;
VideoResult() {} 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. * Returns whether this result comes from a snapshot.
@ -111,7 +146,7 @@ public class VideoResult {
*/ */
@NonNull @NonNull
public VideoCodec getVideoCodec() { public VideoCodec getVideoCodec() {
return codec; return videoCodec;
} }
/** /**

@ -17,19 +17,8 @@ import android.view.SurfaceHolder;
import com.otaliastudios.cameraview.CameraException; 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.CameraView;
import com.otaliastudios.cameraview.CropHelper;
import com.otaliastudios.cameraview.frame.Frame; 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.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.VideoResult;
import com.otaliastudios.cameraview.controls.Audio; import com.otaliastudios.cameraview.controls.Audio;
import com.otaliastudios.cameraview.controls.Facing; 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.Hdr;
import com.otaliastudios.cameraview.controls.Mode; import com.otaliastudios.cameraview.controls.Mode;
import com.otaliastudios.cameraview.controls.WhiteBalance; 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.AspectRatio;
import com.otaliastudios.cameraview.size.Size; 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.File;
import java.io.IOException; 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); super(callback);
mMapper = new Mapper1(); mMapper = Mapper.get();
} }
private void schedule(@Nullable final Task<Void> task, final boolean ensureAvailable, final Runnable action) { private void schedule(@Nullable final Task<Void> 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. // To be called when the preview size is setup or changed.
private void startPreview(String log) { private void startPreview(String log) {
LOG.i(log, "Dispatching onCameraPreviewStreamSizeChanged."); LOG.i(log, "Dispatching onCameraPreviewStreamSizeChanged.");
mCameraCallbacks.onCameraPreviewStreamSizeChanged(); mCallback.onCameraPreviewStreamSizeChanged();
Size previewSize = getPreviewStreamSize(REF_VIEW); 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); boolean wasFlipped = flip(REF_SENSOR, REF_VIEW);
mPreview.setStreamSize(previewSize.getWidth(), previewSize.getHeight(), wasFlipped); 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(null); // Release anything left
mCamera.setPreviewCallbackWithBuffer(this); // Add ourselves mCamera.setPreviewCallbackWithBuffer(this); // Add ourselves
mFrameManager.allocate(ImageFormat.getBitsPerPixel(mPreviewFormat), mPreviewStreamSize); mFrameManager.allocateBuffers(ImageFormat.getBitsPerPixel(mPreviewFormat), mPreviewStreamSize);
LOG.i(log, "Starting preview with startPreview()."); LOG.i(log, "Starting preview with startPreview().");
try { try {
@ -363,8 +364,8 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
return; return;
} }
LOG.e("Internal Camera1 error.", error); String message = LOG.e("Internal Camera1 error.", error);
Exception runtime = new RuntimeException(CameraLogger.lastMessage); Exception runtime = new RuntimeException(message);
int reason; int reason;
switch (error) { switch (error) {
case Camera.CAMERA_ERROR_EVICTED: reason = CameraException.REASON_DISCONNECTED; break; 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) { if (mLocation != null) {
params.setGpsLatitude(mLocation.getLatitude()); params.setGpsLatitude(mLocation.getLatitude());
params.setGpsLongitude(mLocation.getLongitude()); params.setGpsLongitude(mLocation.getLongitude());
@ -563,23 +565,23 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
@Override @Override
public void onPictureShutter(boolean didPlaySound) { public void onPictureShutter(boolean didPlaySound) {
mCameraCallbacks.onShutter(!didPlaySound); mCallback.onShutter(!didPlaySound);
} }
@Override @Override
public void onPictureResult(@Nullable PictureResult result) { public void onPictureResult(@Nullable PictureResult.Stub result) {
mPictureRecorder = null; mPictureRecorder = null;
if (result != null) { if (result != null) {
mCameraCallbacks.dispatchOnPictureTaken(result); mCallback.dispatchOnPictureTaken(result);
} else { } else {
// Something went wrong. // 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."); LOG.e("onPictureResult", "result is null: something went wrong.");
} }
} }
@Override @Override
void takePicture() { void takePicture(final @NonNull PictureResult.Stub stub) {
LOG.v("takePicture: scheduling"); LOG.v("takePicture: scheduling");
schedule(null, true, new Runnable() { schedule(null, true, new Runnable() {
@Override @Override
@ -592,13 +594,12 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
LOG.v("takePicture: performing.", isTakingPicture()); LOG.v("takePicture: performing.", isTakingPicture());
if (isTakingPicture()) return; if (isTakingPicture()) return;
PictureResult result = new PictureResult(); stub.isSnapshot = false;
result.isSnapshot = false; stub.location = mLocation;
result.location = mLocation; stub.rotation = offset(REF_SENSOR, REF_OUTPUT);
result.rotation = offset(REF_SENSOR, REF_OUTPUT); stub.size = getPictureSize(REF_OUTPUT);
result.size = getPictureSize(REF_OUTPUT); stub.facing = mFacing;
result.facing = mFacing; mPictureRecorder = new FullPictureRecorder(stub, Camera1Engine.this, mCamera);
mPictureRecorder = new FullPictureRecorder(result, Camera1Engine.this, mCamera);
mPictureRecorder.take(); mPictureRecorder.take();
} }
}); });
@ -609,7 +610,7 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
* @param viewAspectRatio the view aspect ratio * @param viewAspectRatio the view aspect ratio
*/ */
@Override @Override
void takePictureSnapshot(@NonNull final AspectRatio viewAspectRatio) { void takePictureSnapshot(final @NonNull PictureResult.Stub stub, @NonNull final AspectRatio viewAspectRatio) {
LOG.v("takePictureSnapshot: scheduling"); LOG.v("takePictureSnapshot: scheduling");
schedule(null, true, new Runnable() { schedule(null, true, new Runnable() {
@Override @Override
@ -617,12 +618,11 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
LOG.v("takePictureSnapshot: performing.", isTakingPicture()); LOG.v("takePictureSnapshot: performing.", isTakingPicture());
if (isTakingPicture()) return; if (isTakingPicture()) return;
PictureResult result = new PictureResult(); stub.location = mLocation;
result.location = mLocation; stub.isSnapshot = true;
result.isSnapshot = true; stub.facing = mFacing;
result.facing = mFacing; stub.size = getUncroppedSnapshotSize(REF_OUTPUT); // Not the real size: it will be cropped to match the view ratio
result.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.
result.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; AspectRatio outputRatio = flip(REF_OUTPUT, REF_VIEW) ? viewAspectRatio.flip() : viewAspectRatio;
// LOG.e("ROTBUG_pic", "aspectRatio (REF_VIEW):", viewAspectRatio); // LOG.e("ROTBUG_pic", "aspectRatio (REF_VIEW):", viewAspectRatio);
// LOG.e("ROTBUG_pic", "aspectRatio (REF_OUTPUT):", outputRatio); // 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", "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)); 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(); mPictureRecorder.take();
} }
}); });
@ -646,7 +646,7 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
offset(REF_SENSOR, REF_OUTPUT), offset(REF_SENSOR, REF_OUTPUT),
mPreviewStreamSize, mPreviewStreamSize,
mPreviewFormat); mPreviewFormat);
mCameraCallbacks.dispatchFrame(frame); mCallback.dispatchFrame(frame);
} }
private boolean isCameraAvailable() { private boolean isCameraAvailable() {
@ -673,19 +673,19 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
// Video recording stuff. // Video recording stuff.
@Override @Override
public void onVideoResult(@Nullable VideoResult result, @Nullable Exception exception) { public void onVideoResult(@Nullable VideoResult.Stub result, @Nullable Exception exception) {
mVideoRecorder = null; mVideoRecorder = null;
if (result != null) { if (result != null) {
mCameraCallbacks.dispatchOnVideoTaken(result); mCallback.dispatchOnVideoTaken(result);
} else { } else {
// Something went wrong, lock the camera again. // 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(); mCamera.lock();
} }
} }
@Override @Override
void takeVideo(@NonNull final File videoFile) { void takeVideo(final @NonNull VideoResult.Stub stub, @NonNull final File videoFile) {
schedule(mStartVideoTask, true, new Runnable() { schedule(mStartVideoTask, true, new Runnable() {
@Override @Override
public void run() { public void run() {
@ -696,19 +696,18 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
if (isTakingVideo()) return; if (isTakingVideo()) return;
// Create the video result stub // Create the video result stub
VideoResult videoResult = new VideoResult(); stub.file = videoFile;
videoResult.file = videoFile; stub.isSnapshot = false;
videoResult.isSnapshot = false; stub.videoCodec = mVideoCodec;
videoResult.codec = mVideoCodec; stub.location = mLocation;
videoResult.location = mLocation; stub.facing = mFacing;
videoResult.facing = mFacing; stub.rotation = offset(REF_SENSOR, REF_OUTPUT);
videoResult.rotation = offset(REF_SENSOR, REF_OUTPUT); stub.size = flip(REF_SENSOR, REF_OUTPUT) ? mCaptureSize.flip() : mCaptureSize;
videoResult.size = flip(REF_SENSOR, REF_OUTPUT) ? mCaptureSize.flip() : mCaptureSize; stub.audio = mAudio;
videoResult.audio = mAudio; stub.maxSize = mVideoMaxSize;
videoResult.maxSize = mVideoMaxSize; stub.maxDuration = mVideoMaxDuration;
videoResult.maxDuration = mVideoMaxDuration; stub.videoBitRate = mVideoBitRate;
videoResult.videoBitRate = mVideoBitRate; stub.audioBitRate = mAudioBitRate;
videoResult.audioBitRate = mAudioBitRate;
// Unlock the camera and start recording. // Unlock the camera and start recording.
try { try {
@ -719,7 +718,7 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
onVideoResult(null, e); onVideoResult(null, e);
return; return;
} }
mVideoRecorder = new FullVideoRecorder(videoResult, Camera1Engine.this, mVideoRecorder = new FullVideoRecorder(stub, Camera1Engine.this,
Camera1Engine.this, mCamera, mCameraId); Camera1Engine.this, mCamera, mCameraId);
mVideoRecorder.start(); mVideoRecorder.start();
} }
@ -732,7 +731,7 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
*/ */
@SuppressLint("NewApi") @SuppressLint("NewApi")
@Override @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)) { if (!(mPreview instanceof GlCameraPreview)) {
throw new IllegalStateException("Video snapshots are only supported with 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; if (isTakingVideo()) return;
// Create the video result stub // Create the video result stub
VideoResult videoResult = new VideoResult(); stub.file = file;
videoResult.file = file; stub.isSnapshot = true;
videoResult.isSnapshot = true; stub.videoCodec = mVideoCodec;
videoResult.codec = mVideoCodec; stub.location = mLocation;
videoResult.location = mLocation; stub.facing = mFacing;
videoResult.facing = mFacing; stub.videoBitRate = mVideoBitRate;
videoResult.videoBitRate = mVideoBitRate; stub.audioBitRate = mAudioBitRate;
videoResult.audioBitRate = mAudioBitRate; stub.audio = mAudio;
videoResult.audio = mAudio; stub.maxSize = mVideoMaxSize;
videoResult.maxSize = mVideoMaxSize; stub.maxDuration = mVideoMaxDuration;
videoResult.maxDuration = mVideoMaxDuration;
// Size and rotation turned out to be extremely tricky. In case of SnapshotPictureRecorder // 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. // 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. // and maybe we can improve. The reason why this happen is beyond my understanding.
Size outputSize = getUncroppedSnapshotSize(REF_OUTPUT); 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; AspectRatio outputRatio = flip(REF_OUTPUT, REF_VIEW) ? viewAspectRatio.flip() : viewAspectRatio;
Rect outputCrop = CropHelper.computeCrop(outputSize, outputRatio); Rect outputCrop = CropHelper.computeCrop(outputSize, outputRatio);
outputSize = new Size(outputCrop.width(), outputCrop.height()); outputSize = new Size(outputCrop.width(), outputCrop.height());
videoResult.size = outputSize; stub.size = outputSize;
videoResult.rotation = offset(REF_VIEW, REF_OUTPUT); stub.rotation = offset(REF_VIEW, REF_OUTPUT);
// LOG.e("ROTBUG_video", "aspectRatio (REF_VIEW):", viewAspectRatio); // LOG.e("ROTBUG_video", "aspectRatio (REF_VIEW):", viewAspectRatio);
// LOG.e("ROTBUG_video", "aspectRatio (REF_OUTPUT):", outputRatio); // LOG.e("ROTBUG_video", "aspectRatio (REF_OUTPUT):", outputRatio);
// LOG.e("ROTBUG_video", "sizeUncropped (REF_OUTPUT):", outputSize); // LOG.e("ROTBUG_video", "sizeUncropped (REF_OUTPUT):", outputSize);
@ -807,7 +808,7 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
// Reset facing and start. // Reset facing and start.
mFacing = realFacing; mFacing = realFacing;
GlCameraPreview cameraPreview = (GlCameraPreview) mPreview; GlCameraPreview cameraPreview = (GlCameraPreview) mPreview;
mVideoRecorder = new SnapshotVideoRecorder(videoResult, Camera1Engine.this, cameraPreview); mVideoRecorder = new SnapshotVideoRecorder(stub, Camera1Engine.this, cameraPreview);
mVideoRecorder.start(); mVideoRecorder.start();
} }
}); });
@ -845,7 +846,7 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
mCamera.setParameters(params); mCamera.setParameters(params);
if (notify) { 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); mCamera.setParameters(params);
if (notify) { 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); if (maxAE > 0) params.setMeteringAreas(maxAE > 1 ? meteringAreas2 : meteringAreas1);
params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
mCamera.setParameters(params); mCamera.setParameters(params);
mCameraCallbacks.dispatchOnFocusStart(gesture, p); mCallback.dispatchOnFocusStart(gesture, p);
// TODO this is not guaranteed to be called... Fix. // TODO this is not guaranteed to be called... Fix.
try { try {
mCamera.autoFocus(new Camera.AutoFocusCallback() { mCamera.autoFocus(new Camera.AutoFocusCallback() {
@Override @Override
public void onAutoFocus(boolean success, Camera camera) { public void onAutoFocus(boolean success, Camera camera) {
// TODO lock auto exposure and white balance for a while // TODO lock auto exposure and white balance for a while
mCameraCallbacks.dispatchOnFocusEnd(gesture, success, p); mCallback.dispatchOnFocusEnd(gesture, success, p);
mHandler.get().removeCallbacks(mPostFocusResetRunnable); mHandler.get().removeCallbacks(mPostFocusResetRunnable);
if (shouldResetAutoFocus()) { if (shouldResetAutoFocus()) {
mHandler.get().postDelayed(mPostFocusResetRunnable, getAutoFocusResetDelay()); 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 // Handling random auto-focus exception on some devices
// See https://github.com/natario1/CameraView/issues/181 // See https://github.com/natario1/CameraView/issues/181
LOG.e("startAutoFocus:", "Error calling autoFocus", e); 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. // Size stuff.
@Nullable @NonNull
private List<Size> sizesFromList(@Nullable List<Camera.Size> sizes) { private List<Size> sizesFromList(@NonNull List<Camera.Size> sizes) {
if (sizes == null) return null;
List<Size> result = new ArrayList<>(sizes.size()); List<Size> result = new ArrayList<>(sizes.size());
for (Camera.Size size : sizes) { for (Camera.Size size : sizes) {
Size add = new Size(size.width, size.height); Size add = new Size(size.width, size.height);

@ -10,14 +10,14 @@ import android.os.Looper;
import com.otaliastudios.cameraview.CameraException; 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.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.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.Audio;
import com.otaliastudios.cameraview.controls.Facing; import com.otaliastudios.cameraview.controls.Facing;
import com.otaliastudios.cameraview.controls.Flash; 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.Size;
import com.otaliastudios.cameraview.size.SizeSelector; import com.otaliastudios.cameraview.size.SizeSelector;
import com.otaliastudios.cameraview.size.SizeSelectors; import com.otaliastudios.cameraview.size.SizeSelectors;
import com.otaliastudios.cameraview.video.VideoRecorder;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -46,6 +47,21 @@ public abstract class CameraEngine implements
FrameManager.BufferCallback, FrameManager.BufferCallback,
Thread.UncaughtExceptionHandler { 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 String TAG = CameraEngine.class.getSimpleName();
private static final CameraLogger LOG = CameraLogger.create(TAG); 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_VIEW = 1;
static final int REF_OUTPUT = 2; static final int REF_OUTPUT = 2;
protected final CameraView.CameraCallbacks mCameraCallbacks; protected final Callback mCallback;
protected CameraPreview mPreview; protected CameraPreview mPreview;
protected WorkerHandler mHandler; protected WorkerHandler mHandler;
/* for tests */ Handler mCrashHandler; @VisibleForTesting Handler mCrashHandler;
protected Facing mFacing; protected Facing mFacing;
protected Flash mFlash; protected Flash mFlash;
@ -79,10 +95,8 @@ public abstract class CameraEngine implements
private SizeSelector mPictureSizeSelector; private SizeSelector mPictureSizeSelector;
private SizeSelector mVideoSizeSelector; private SizeSelector mVideoSizeSelector;
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) @VisibleForTesting int mSnapshotMaxWidth = Integer.MAX_VALUE; // in REF_VIEW for consistency with SizeSelectors
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
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
int mSnapshotMaxHeight = Integer.MAX_VALUE; // in REF_VIEW for consistency with SizeSelectors
protected int mCameraId; protected int mCameraId;
protected CameraOptions mCameraOptions; protected CameraOptions mCameraOptions;
@ -106,17 +120,17 @@ public abstract class CameraEngine implements
protected int mState = STATE_STOPPED; protected int mState = STATE_STOPPED;
// Used for testing. // Used for testing.
Task<Void> mZoomTask = new Task<>(); @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mZoomTask = new Task<>();
Task<Void> mExposureCorrectionTask = new Task<>(); @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mExposureCorrectionTask = new Task<>();
Task<Void> mFlashTask = new Task<>(); @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mFlashTask = new Task<>();
Task<Void> mWhiteBalanceTask = new Task<>(); @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mWhiteBalanceTask = new Task<>();
Task<Void> mHdrTask = new Task<>(); @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mHdrTask = new Task<>();
Task<Void> mLocationTask = new Task<>(); @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mLocationTask = new Task<>();
Task<Void> mStartVideoTask = new Task<>(); @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mStartVideoTask = new Task<>();
Task<Void> mPlaySoundsTask = new Task<>(); @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mPlaySoundsTask = new Task<>();
CameraEngine(CameraView.CameraCallbacks callback) { CameraEngine(Callback callback) {
mCameraCallbacks = callback; mCallback = callback;
mCrashHandler = new Handler(Looper.getMainLooper()); mCrashHandler = new Handler(Looper.getMainLooper());
mHandler = WorkerHandler.get("CameraViewController"); mHandler = WorkerHandler.get("CameraViewController");
mHandler.getThread().setUncaughtExceptionHandler(this); mHandler.getThread().setUncaughtExceptionHandler(this);
@ -171,7 +185,7 @@ public abstract class CameraEngine implements
@Override @Override
public void run() { public void run() {
stopImmediately(); stopImmediately();
mCameraCallbacks.dispatchError(error); mCallback.dispatchError(error);
} }
}); });
} }
@ -215,7 +229,7 @@ public abstract class CameraEngine implements
onStart(); onStart();
LOG.i("Start:", "returned from onStart().", "Dispatching.", ss()); LOG.i("Start:", "returned from onStart().", "Dispatching.", ss());
mState = STATE_STARTED; mState = STATE_STARTED;
mCameraCallbacks.dispatchOnCameraOpened(mCameraOptions); mCallback.dispatchOnCameraOpened(mCameraOptions);
} }
}); });
} }
@ -234,7 +248,7 @@ public abstract class CameraEngine implements
onStop(); onStop();
LOG.i("Stop:", "returned from onStop().", "Dispatching."); LOG.i("Stop:", "returned from onStop().", "Dispatching.");
mState = STATE_STOPPED; mState = STATE_STOPPED;
mCameraCallbacks.dispatchOnCameraClosed(); mCallback.dispatchOnCameraClosed();
} }
}); });
} }
@ -270,7 +284,7 @@ public abstract class CameraEngine implements
onStop(); onStop();
mState = STATE_STOPPED; mState = STATE_STOPPED;
LOG.i("Restart:", "stopped. Dispatching.", ss()); LOG.i("Restart:", "stopped. Dispatching.", ss());
mCameraCallbacks.dispatchOnCameraClosed(); mCallback.dispatchOnCameraClosed();
} }
LOG.i("Restart: about to start. State:", ss()); LOG.i("Restart: about to start. State:", ss());
@ -278,7 +292,7 @@ public abstract class CameraEngine implements
onStart(); onStart();
mState = STATE_STARTED; mState = STATE_STARTED;
LOG.i("Restart: returned from start. Dispatching. State:", ss()); 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. // Just set.
abstract void setAudio(@NonNull Audio audio); 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(); abstract void stopVideo();

@ -1,11 +1,21 @@
package com.otaliastudios.cameraview.engine; package com.otaliastudios.cameraview.engine;
import android.hardware.Camera;
import android.os.Build;
import com.otaliastudios.cameraview.controls.Facing; import com.otaliastudios.cameraview.controls.Facing;
import com.otaliastudios.cameraview.controls.Flash; import com.otaliastudios.cameraview.controls.Flash;
import com.otaliastudios.cameraview.controls.Hdr; import com.otaliastudios.cameraview.controls.Hdr;
import com.otaliastudios.cameraview.controls.WhiteBalance; 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> T map(Flash flash); abstract <T> T map(Flash flash);
abstract <T> T map(Facing facing); abstract <T> T map(Facing facing);
@ -15,4 +25,91 @@ abstract class Mapper {
abstract <T> Facing unmapFacing(T cameraConstant); abstract <T> Facing unmapFacing(T cameraConstant);
abstract <T> WhiteBalance unmapWhiteBalance(T cameraConstant); abstract <T> WhiteBalance unmapWhiteBalance(T cameraConstant);
abstract <T> Hdr unmapHdr(T cameraConstant); abstract <T> 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, String> FLASH = new HashMap<>();
private static final HashMap<WhiteBalance, String> WB = new HashMap<>();
private static final HashMap<Facing, Integer> FACING = new HashMap<>();
private static final HashMap<Hdr, String> 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> T map(Flash flash) {
return (T) FLASH.get(flash);
}
@Override
<T> T map(Facing facing) {
return (T) FACING.get(facing);
}
@Override
<T> T map(WhiteBalance whiteBalance) {
return (T) WB.get(whiteBalance);
}
@Override
<T> T map(Hdr hdr) {
return (T) HDR.get(hdr);
}
private <T> T reverseLookup(HashMap<T, ?> map, Object object) {
for (T value : map.keySet()) {
if (object.equals(map.get(value))) {
return value;
}
}
return null;
}
@Override
<T> Flash unmapFlash(T cameraConstant) {
return reverseLookup(FLASH, cameraConstant);
}
@Override
<T> Facing unmapFacing(T cameraConstant) {
return reverseLookup(FACING, cameraConstant);
}
@Override
<T> WhiteBalance unmapWhiteBalance(T cameraConstant) {
return reverseLookup(WB, cameraConstant);
}
@Override
<T> Hdr unmapHdr(T cameraConstant) {
return reverseLookup(HDR, cameraConstant);
}
}
} }

@ -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, String> FLASH = new HashMap<>();
private static final HashMap<WhiteBalance, String> WB = new HashMap<>();
private static final HashMap<Facing, Integer> FACING = new HashMap<>();
private static final HashMap<Hdr, String> 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> T map(Flash flash) {
return (T) FLASH.get(flash);
}
@Override
<T> T map(Facing facing) {
return (T) FACING.get(facing);
}
@Override
<T> T map(WhiteBalance whiteBalance) {
return (T) WB.get(whiteBalance);
}
@Override
<T> T map(Hdr hdr) {
return (T) HDR.get(hdr);
}
private <T> T reverseLookup(HashMap<T, ?> map, Object object) {
for (T value : map.keySet()) {
if (map.get(value).equals(object)) {
return value;
}
}
return null;
}
@Override
<T> Flash unmapFlash(T cameraConstant) {
return reverseLookup(FLASH, cameraConstant);
}
@Override
<T> Facing unmapFacing(T cameraConstant) {
return reverseLookup(FACING, cameraConstant);
}
@Override
<T> WhiteBalance unmapWhiteBalance(T cameraConstant) {
return reverseLookup(WB, cameraConstant);
}
@Override
<T> Hdr unmapHdr(T cameraConstant) {
return reverseLookup(HDR, cameraConstant);
}
}

@ -3,13 +3,14 @@ package com.otaliastudios.cameraview.frame;
import com.otaliastudios.cameraview.size.Size; import com.otaliastudios.cameraview.size.Size;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
/** /**
* A preview frame to be processed by {@link FrameProcessor}s. * A preview frame to be processed by {@link FrameProcessor}s.
*/ */
public class Frame { public class Frame {
/* for tests */ FrameManager mManager; @VisibleForTesting FrameManager mManager;
private byte[] mData = null; private byte[] mData = null;
private long mTime = -1; private long mTime = -1;
@ -21,6 +22,7 @@ public class Frame {
mManager = manager; mManager = manager;
} }
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean isAlive() { private boolean isAlive() {
return mData != null; return mData != null;
} }

@ -23,9 +23,13 @@ import java.util.concurrent.LinkedBlockingQueue;
* - Frame pool: * - Frame pool:
* We keep a list of mPoolSize recycled instances, to be reused when a new buffer is available. * 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); void onBufferAvailable(@NonNull byte[] buffer);
} }
@ -34,14 +38,45 @@ class FrameManager {
private BufferCallback mCallback; private BufferCallback mCallback;
private LinkedBlockingQueue<Frame> mQueue; private LinkedBlockingQueue<Frame> 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; mPoolSize = poolSize;
mCallback = callback; mCallback = callback;
mQueue = new LinkedBlockingQueue<>(mPoolSize); mQueue = new LinkedBlockingQueue<>(mPoolSize);
mBufferSize = -1; 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) { for (Frame frame : mQueue) {
frame.releaseManager(); frame.releaseManager();
frame.release(); frame.release();
@ -69,8 +104,8 @@ class FrameManager {
/** /**
* Returns a new Frame for the given data. This must be called * Returns a new Frame for the given data. This must be called
* - after {@link #allocate(int, Size)}, which sets the buffer size * - after {@link #allocateBuffers(int, Size)}, which sets the buffer size
* - after the byte buffer given by allocate() has been filled. * - 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 * 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. * 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 * @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(); Frame frame = mQueue.poll();
if (frame == null) frame = new Frame(this); if (frame == null) frame = new Frame(this);
frame.set(data, time, rotation, previewSize, previewFormat); frame.set(data, time, rotation, previewSize, previewFormat);
return frame; 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) { private int getBufferSize(int bitsPerPixel, @NonNull Size previewSize) {
long sizeInBits = previewSize.getHeight() * previewSize.getWidth() * bitsPerPixel; long sizeInBits = previewSize.getHeight() * previewSize.getWidth() * bitsPerPixel;
return (int) Math.ceil(sizeInBits / 8.0d); return (int) Math.ceil(sizeInBits / 8.0d);

@ -5,9 +5,6 @@ import com.otaliastudios.cameraview.CameraView;
import androidx.annotation.NonNull; 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 * Gestures listen to finger gestures over the {@link CameraView} bounds and can be mapped

@ -1,6 +1,5 @@
package com.otaliastudios.cameraview.gesture; package com.otaliastudios.cameraview.gesture;
import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import com.otaliastudios.cameraview.R; import com.otaliastudios.cameraview.R;

@ -1,14 +1,6 @@
package com.otaliastudios.cameraview.gesture; 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 * 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. * a certain {@link GestureAction}, both of them might be of the same type.

@ -1,8 +1,6 @@
package com.otaliastudios.cameraview.gesture; package com.otaliastudios.cameraview.gesture;
import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.graphics.PointF;
import android.os.Build; import android.os.Build;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import android.view.MotionEvent; import android.view.MotionEvent;

@ -1,8 +1,6 @@
package com.otaliastudios.cameraview.gesture; package com.otaliastudios.cameraview.gesture;
import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.graphics.PointF;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import android.view.GestureDetector; import android.view.GestureDetector;
import android.view.MotionEvent; import android.view.MotionEvent;

@ -2,7 +2,6 @@ package com.otaliastudios.cameraview.gesture;
import android.animation.Animator; import android.animation.Animator;
import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorListenerAdapter;
import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.graphics.PointF; import android.graphics.PointF;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;

@ -8,6 +8,8 @@ import android.graphics.drawable.ColorDrawable;
import androidx.annotation.ColorInt; import androidx.annotation.ColorInt;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.View; import android.view.View;
@ -15,7 +17,10 @@ import android.view.View;
import com.otaliastudios.cameraview.controls.Grid; import com.otaliastudios.cameraview.controls.Grid;
import com.otaliastudios.cameraview.internal.utils.Task; 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; private final static float GOLDEN_RATIO_INV = 0.61803398874989f;
public final static int DEFAULT_COLOR = Color.argb(160, 255, 255, 255); public final static int DEFAULT_COLOR = Color.argb(160, 255, 255, 255);
@ -27,7 +32,7 @@ class GridLinesLayout extends View {
private ColorDrawable vert; private ColorDrawable vert;
private final float width; private final float width;
Task<Integer> drawTask = new Task<>(); @VisibleForTesting Task<Integer> drawTask = new Task<>();
public GridLinesLayout(@NonNull Context context) { public GridLinesLayout(@NonNull Context context) {
this(context, null); this(context, null);
@ -47,16 +52,36 @@ class GridLinesLayout extends View {
vert.setBounds(0, top, (int) width, bottom); vert.setBounds(0, top, (int) width, bottom);
} }
/**
* Returns the current grid value.
* @return the grid mode
*/
@NonNull @NonNull
public Grid getGridMode() { public Grid getGridMode() {
return gridMode; return gridMode;
} }
/**
* Sets a new grid value
* @param gridMode the new value
*/
public void setGridMode(@NonNull Grid gridMode) { public void setGridMode(@NonNull Grid gridMode) {
this.gridMode = gridMode; this.gridMode = gridMode;
postInvalidate(); 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) { public void setGridColor(@ColorInt int gridColor) {
this.gridColor = gridColor; this.gridColor = gridColor;
horiz.setColor(gridColor); horiz.setColor(gridColor);
@ -64,10 +89,6 @@ class GridLinesLayout extends View {
postInvalidate(); postInvalidate();
} }
public int getGridColor() {
return gridColor;
}
private int getLineCount() { private int getLineCount() {
switch (gridMode) { switch (gridMode) {
case OFF: return 0; case OFF: return 0;

@ -24,16 +24,16 @@ class EglElement {
protected static void check(String opName) { protected static void check(String opName) {
int error = GLES20.glGetError(); int error = GLES20.glGetError();
if (error != GLES20.GL_NO_ERROR) { if (error != GLES20.GL_NO_ERROR) {
LOG.e("Error during", opName, "glError 0x", Integer.toHexString(error)); String message = LOG.e("Error during", opName, "glError 0x", Integer.toHexString(error));
throw new RuntimeException(CameraLogger.lastMessage); throw new RuntimeException(message);
} }
} }
// Check for valid location. // Check for valid location.
protected static void checkLocation(int location, String label) { protected static void checkLocation(int location, String label) {
if (location < 0) { if (location < 0) {
LOG.e("Unable to locate", label, "in program"); String message = LOG.e("Unable to locate", label, "in program");
throw new RuntimeException(CameraLogger.lastMessage); throw new RuntimeException(message);
} }
} }

@ -4,6 +4,8 @@ import android.annotation.SuppressLint;
import android.media.CamcorderProfile; import android.media.CamcorderProfile;
import android.os.Build; import android.os.Build;
import com.otaliastudios.cameraview.size.Size;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;

@ -7,11 +7,14 @@ import com.otaliastudios.cameraview.size.Size;
import androidx.annotation.NonNull; 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. // It's important that size and aspect ratio belong to the same reference.
@NonNull @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 currentWidth = currentSize.getWidth();
int currentHeight = currentSize.getHeight(); int currentHeight = currentSize.getHeight();
if (targetRatio.matches(currentSize)) { if (targetRatio.matches(currentSize)) {

@ -3,24 +3,37 @@ package com.otaliastudios.cameraview.internal.utils;
import android.content.Context; import android.content.Context;
import android.hardware.SensorManager; import android.hardware.SensorManager;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import android.view.Display; import android.view.Display;
import android.view.OrientationEventListener; import android.view.OrientationEventListener;
import android.view.Surface; import android.view.Surface;
import android.view.WindowManager; 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 final Callback mCallback;
private int mDeviceOrientation = -1; private int mDeviceOrientation = -1;
private int mDisplayOffset = -1; private int mDisplayOffset = -1;
interface Callback { /**
void onDeviceOrientationChanged(int deviceOrientation); * Creates a new orientation helper.
} * @param context a valid context
* @param callback a {@link Callback}
OrientationHelper(@NonNull Context context, @NonNull Callback callback) { */
public OrientationHelper(@NonNull Context context, @NonNull Callback callback) {
mCallback = callback; mCallback = callback;
mListener = new OrientationEventListener(context.getApplicationContext(), SensorManager.SENSOR_DELAY_NORMAL) { 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(); Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
switch (display.getRotation()) { switch (display.getRotation()) {
case Surface.ROTATION_0: mDisplayOffset = 0; break; case Surface.ROTATION_0: mDisplayOffset = 0; break;
@ -60,16 +77,27 @@ class OrientationHelper {
mListener.enable(); mListener.enable();
} }
void disable() { /**
* Disables this listener.
*/
public void disable() {
mListener.disable(); mListener.disable();
mDisplayOffset = -1; mDisplayOffset = -1;
mDeviceOrientation = -1; mDeviceOrientation = -1;
} }
/**
* Returns the current device orientation.
* @return device orientation
*/
int getDeviceOrientation() { int getDeviceOrientation() {
return mDeviceOrientation; return mDeviceOrientation;
} }
/**
* Returns the current display offset.
* @return display offset
*/
int getDisplayOffset() { int getDisplayOffset() {
return mDisplayOffset; return mDisplayOffset;
} }

@ -8,6 +8,10 @@ import androidx.annotation.CallSuper;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
/**
* Base class for pools of recycleable objects.
* @param <T> the object type
*/
class Pool<T> { class Pool<T> {
private static final String TAG = Pool.class.getSimpleName(); private static final String TAG = Pool.class.getSimpleName();
@ -18,27 +22,49 @@ class Pool<T> {
private LinkedBlockingQueue<T> mQueue; private LinkedBlockingQueue<T> mQueue;
private Factory<T> factory; private Factory<T> factory;
interface Factory<T> { /**
* Used to create new instances of objects when needed.
* @param <T> object type
*/
public interface Factory<T> {
T create(); T create();
} }
Pool(int maxPoolSize, Factory<T> 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<T> factory) {
this.maxPoolSize = maxPoolSize; this.maxPoolSize = maxPoolSize;
this.mQueue = new LinkedBlockingQueue<>(maxPoolSize); this.mQueue = new LinkedBlockingQueue<>(maxPoolSize);
this.factory = factory; 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; 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 @Nullable
T get() { public T get() {
T buffer = mQueue.poll(); T item = mQueue.poll();
if (buffer != null) { if (item != null) {
activeCount++; // poll decreases, this fixes activeCount++; // poll decreases, this fixes
LOG.v("GET: Reusing recycled item.", this); LOG.v("GET: Reusing recycled item.", this);
return buffer; return item;
} }
if (isEmpty()) { if (isEmpty()) {
@ -51,8 +77,13 @@ class Pool<T> {
return factory.create(); 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); LOG.v("RECYCLE: Recycling item.", this);
if (--activeCount < 0) { if (--activeCount < 0) {
throw new IllegalStateException("Trying to recycle an item which makes activeCount < 0." + throw new IllegalStateException("Trying to recycle an item which makes activeCount < 0." +
@ -66,26 +97,49 @@ class Pool<T> {
} }
} }
@NonNull /**
@Override * Clears the pool of recycled items.
public String toString() { */
return getClass().getSimpleName() + " -- count:" + count() + ", active:" + activeCount() + ", cached:" + cachedCount(); @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; 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(); return mQueue.size();
} }
@CallSuper @NonNull
void clear() { @Override
mQueue.clear(); public String toString() {
return getClass().getSimpleName() + " -- count:" + count() + ", active:" + activeCount() + ", recycled:" + recycledCount();
} }
} }

@ -12,7 +12,14 @@ import androidx.annotation.NonNull;
@Deprecated @Deprecated
class RotationHelper { 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 == 0) return yuv;
if (rotation % 90 != 0 || rotation < 0 || rotation > 270) { if (rotation % 90 != 0 || rotation < 0 || rotation > 270) {
throw new IllegalArgumentException("0 <= rotation < 360, rotation % 90 == 0"); throw new IllegalArgumentException("0 <= rotation < 360, rotation % 90 == 0");

@ -9,53 +9,92 @@ import java.util.concurrent.TimeUnit;
* A naive implementation of {@link java.util.concurrent.CountDownLatch} * A naive implementation of {@link java.util.concurrent.CountDownLatch}
* to help in testing. * to help in testing.
*/ */
class Task<T> { public class Task<T> {
private CountDownLatch mLatch; private CountDownLatch mLatch;
private T mResult; private T mResult;
private int mCount; private int mCount;
Task() { /**
} * Creates an empty task.
*
Task(boolean startListening) { * 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(); if (startListening) listen();
} }
private boolean listening() { private boolean isListening() {
return mLatch != null; return mLatch != null;
} }
void listen() { /**
if (listening()) throw new RuntimeException("Should not happen."); * Task owner method: notifies the action started.
mResult = null; */
mLatch = new CountDownLatch(1); public void start() {
} if (!isListening()) mCount++;
void start() {
if (!listening()) 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) { if (mCount > 0) {
mCount--; mCount--;
return; return;
} }
if (listening()) { // Should be always true. if (isListening()) { // Should be always true.
mResult = result; mResult = result;
mLatch.countDown(); 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); 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); 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) { private T await(long time, @NonNull TimeUnit unit) {
try { try {
mLatch.await(time, unit); mLatch.await(time, unit);

@ -15,14 +15,20 @@ import java.util.concurrent.ConcurrentHashMap;
* Class holding a background handler. * Class holding a background handler.
* We want them to survive configuration changes if there's still job to do. * 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 CameraLogger LOG = CameraLogger.create(WorkerHandler.class.getSimpleName());
private final static ConcurrentHashMap<String, WeakReference<WorkerHandler>> sCache = new ConcurrentHashMap<>(4); private final static ConcurrentHashMap<String, WeakReference<WorkerHandler>> sCache = new ConcurrentHashMap<>(4);
/**
* Gets a possibly cached handler with the given name.
* @param name the handler name
* @return a handler
*/
@NonNull @NonNull
public static WorkerHandler get(@NonNull String name) { public static WorkerHandler get(@NonNull String name) {
if (sCache.containsKey(name)) { if (sCache.containsKey(name)) {
//noinspection ConstantConditions
WorkerHandler cached = sCache.get(name).get(); WorkerHandler cached = sCache.get(name).get();
if (cached != null) { if (cached != null) {
HandlerThread thread = cached.mThread; HandlerThread thread = cached.mThread;
@ -41,9 +47,13 @@ class WorkerHandler {
return handler; return handler;
} }
// Handy util to perform action in a fallback thread. /**
// Not to be used for long-running operations since they will * Handy utility to perform an action in a fallback thread.
// block the 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) { public static void run(@NonNull Runnable action) {
get("FallbackCameraThread").post(action); get("FallbackCameraThread").post(action);
} }
@ -58,27 +68,48 @@ class WorkerHandler {
mHandler = new Handler(mThread.getLooper()); 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) { public void post(@NonNull Runnable runnable) {
mHandler.post(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 @NonNull
public HandlerThread getThread() { public HandlerThread getThread() {
return mThread; return mThread;
} }
/**
* Returns the android backing {@link Looper}.
* @return the looper
*/
@NonNull @NonNull
public Looper getLooper() { public Looper getLooper() {
return mThread.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()) { for (String key : sCache.keySet()) {
WeakReference<WorkerHandler> ref = sCache.get(key); WeakReference<WorkerHandler> ref = sCache.get(key);
//noinspection ConstantConditions
WorkerHandler handler = ref.get(); WorkerHandler handler = ref.get();
if (handler != null && handler.getThread().isAlive()) { if (handler != null && handler.getThread().isAlive()) {
handler.getThread().interrupt(); handler.getThread().interrupt();

@ -16,14 +16,14 @@ import java.io.IOException;
/** /**
* A {@link PictureResult} that uses standard APIs. * 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 String TAG = FullPictureRecorder.class.getSimpleName();
private static final CameraLogger LOG = CameraLogger.create(TAG); private static final CameraLogger LOG = CameraLogger.create(TAG);
private Camera mCamera; 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); super(stub, listener);
mCamera = camera; mCamera = camera;

@ -10,17 +10,17 @@ import androidx.annotation.Nullable;
* Don't call start if already started. Don't call stop if already stopped. * Don't call start if already started. Don't call stop if already stopped.
* Don't reuse. * Don't reuse.
*/ */
abstract class PictureRecorder { public abstract class PictureRecorder {
/* tests */ PictureResult mResult; /* tests */ PictureResult.Stub mResult;
/* tests */ PictureResultListener mListener; /* tests */ PictureResultListener mListener;
PictureRecorder(@NonNull PictureResult stub, @Nullable PictureResultListener listener) { PictureRecorder(@NonNull PictureResult.Stub stub, @Nullable PictureResultListener listener) {
mResult = stub; mResult = stub;
mListener = listener; mListener = listener;
} }
abstract void take(); public abstract void take();
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
protected void dispatchOnShutter(boolean didPlaySound) { protected void dispatchOnShutter(boolean didPlaySound) {
@ -35,8 +35,8 @@ abstract class PictureRecorder {
} }
} }
interface PictureResultListener { public interface PictureResultListener {
void onPictureShutter(boolean didPlaySound); void onPictureShutter(boolean didPlaySound);
void onPictureResult(@Nullable PictureResult result); void onPictureResult(@Nullable PictureResult.Stub result);
} }
} }

@ -36,7 +36,7 @@ import java.io.ByteArrayOutputStream;
/** /**
* A {@link PictureResult} that uses standard APIs. * 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 String TAG = SnapshotPictureRecorder.class.getSimpleName();
private static final CameraLogger LOG = CameraLogger.create(TAG); private static final CameraLogger LOG = CameraLogger.create(TAG);
@ -48,7 +48,7 @@ class SnapshotPictureRecorder extends PictureRecorder {
private Size mSensorPreviewSize; private Size mSensorPreviewSize;
private int mFormat; 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) { @NonNull Camera camera, @NonNull AspectRatio outputRatio) {
super(stub, controller); super(stub, controller);
mController = controller; mController = controller;
@ -60,7 +60,7 @@ class SnapshotPictureRecorder extends PictureRecorder {
} }
@Override @Override
void take() { public void take() {
if (mPreview instanceof GlCameraPreview && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (mPreview instanceof GlCameraPreview && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
takeGl((GlCameraPreview) mPreview); takeGl((GlCameraPreview) mPreview);
} else { } else {
@ -208,7 +208,7 @@ class SnapshotPictureRecorder extends PictureRecorder {
// It seems that the buffers are already cleared here, so we need to allocate again. // It seems that the buffers are already cleared here, so we need to allocate again.
camera.setPreviewCallbackWithBuffer(null); // Release anything left camera.setPreviewCallbackWithBuffer(null); // Release anything left
camera.setPreviewCallbackWithBuffer(mController); // Add ourselves camera.setPreviewCallbackWithBuffer(mController); // Add ourselves
mController.mFrameManager.allocate(ImageFormat.getBitsPerPixel(mFormat), mController.mPreviewStreamSize); mController.mFrameManager.allocateBuffers(ImageFormat.getBitsPerPixel(mFormat), mController.mPreviewStreamSize);
} }
}); });
} }

@ -27,7 +27,7 @@ public abstract class CameraPreview<T extends View, Output> {
// This is used to notify CameraEngine to recompute its camera Preview size. // 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. // After that, CameraView will need a new layout pass to adapt to the Preview size.
interface SurfaceCallback { public interface SurfaceCallback {
void onSurfaceAvailable(); void onSurfaceAvailable();
void onSurfaceChanged(); void onSurfaceChanged();
void onSurfaceDestroyed(); void onSurfaceDestroyed();
@ -55,7 +55,7 @@ public abstract class CameraPreview<T extends View, Output> {
protected abstract T onCreateView(@NonNull Context context, @NonNull ViewGroup parent); protected abstract T onCreateView(@NonNull Context context, @NonNull ViewGroup parent);
@NonNull @NonNull
final T getView() { public final T getView() {
return mView; return mView;
} }
@ -64,15 +64,15 @@ public abstract class CameraPreview<T extends View, Output> {
abstract View getRootView(); abstract View getRootView();
@NonNull @NonNull
abstract Class<Output> getOutputClass(); public abstract Class<Output> getOutputClass();
@NonNull @NonNull
abstract Output getOutput(); public abstract Output getOutput();
// As far as I can see, these are the actual preview dimensions, as set in CameraParameters. // As far as I can see, these are the actual preview dimensions, as set in CameraParameters.
// This is called by the CameraImpl. // This is called by the CameraImpl.
// These must be alredy rotated, if needed, to be consistent with surface/view sizes. // 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); LOG.i("setStreamSize:", "desiredW=", width, "desiredH=", height);
mInputStreamWidth = width; mInputStreamWidth = width;
mInputStreamHeight = height; mInputStreamHeight = height;
@ -88,11 +88,11 @@ public abstract class CameraPreview<T extends View, Output> {
} }
@NonNull @NonNull
final Size getSurfaceSize() { public final Size getSurfaceSize() {
return new Size(mOutputSurfaceWidth, mOutputSurfaceHeight); return new Size(mOutputSurfaceWidth, mOutputSurfaceHeight);
} }
final void setSurfaceCallback(@NonNull SurfaceCallback callback) { public final void setSurfaceCallback(@NonNull SurfaceCallback callback) {
mSurfaceCallback = callback; mSurfaceCallback = callback;
// If surface already available, dispatch. // If surface already available, dispatch.
if (mOutputSurfaceWidth != 0 || mOutputSurfaceHeight != 0) { if (mOutputSurfaceWidth != 0 || mOutputSurfaceHeight != 0) {
@ -144,7 +144,7 @@ public abstract class CameraPreview<T extends View, Output> {
// Public for mockito (CameraViewTest) // Public for mockito (CameraViewTest)
public void onDestroy() {} public void onDestroy() {}
final boolean hasSurface() { public final boolean hasSurface() {
return mOutputSurfaceWidth > 0 && mOutputSurfaceHeight > 0; return mOutputSurfaceWidth > 0 && mOutputSurfaceHeight > 0;
} }

@ -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 * 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}. * the GL context that was created and is managed by the {@link GLSurfaceView}.
*/ */
class GlCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture> implements GLSurfaceView.Renderer { public class GlCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture> implements GLSurfaceView.Renderer {
private boolean mDispatched; private boolean mDispatched;
private final float[] mTransformMatrix = new float[16]; private final float[] mTransformMatrix = new float[16];

@ -18,7 +18,7 @@ import androidx.annotation.Nullable;
/** /**
* A {@link VideoRecorder} that uses {@link android.media.MediaRecorder} APIs. * 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 String TAG = FullVideoRecorder.class.getSimpleName();
private static final CameraLogger LOG = CameraLogger.create(TAG); private static final CameraLogger LOG = CameraLogger.create(TAG);
@ -29,7 +29,7 @@ class FullVideoRecorder extends VideoRecorder {
private Camera mCamera; private Camera mCamera;
private Size mSize; 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) { @NonNull Camera1Engine controller, @NonNull Camera camera, int cameraId) {
super(stub, listener); super(stub, listener);
mCamera = camera; mCamera = camera;

@ -23,7 +23,7 @@ import androidx.annotation.RequiresApi;
* A {@link VideoRecorder} that uses {@link android.media.MediaCodec} APIs. * A {@link VideoRecorder} that uses {@link android.media.MediaCodec} APIs.
*/ */
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) @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 { MediaEncoderEngine.Listener {
private static final String TAG = SnapshotVideoRecorder.class.getSimpleName(); 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 mDesiredState = STATE_NOT_RECORDING;
private int mTextureId = 0; 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); super(stub, listener);
mPreview = preview; mPreview = preview;
mPreview.addRendererFrameCallback(this); mPreview.addRendererFrameCallback(this);

@ -1,5 +1,7 @@
package com.otaliastudios.cameraview.video; package com.otaliastudios.cameraview.video;
import com.otaliastudios.cameraview.VideoResult;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; 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 call start if already started. Don't call stop if already stopped.
* Don't reuse. * Don't reuse.
*/ */
abstract class VideoRecorder { public abstract class VideoRecorder {
/* tests */ VideoResult mResult; /* tests */ VideoResult.Stub mResult;
/* tests */ VideoResultListener mListener; /* tests */ VideoResultListener mListener;
protected Exception mError; protected Exception mError;
VideoRecorder(@NonNull VideoResult stub, @Nullable VideoResultListener listener) { VideoRecorder(@NonNull VideoResult.Stub stub, @Nullable VideoResultListener listener) {
mResult = stub; mResult = stub;
mListener = listener; mListener = listener;
} }
abstract void start(); public abstract void start();
abstract void stop(); public abstract void stop();
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
protected void dispatchResult() { protected void dispatchResult() {
@ -33,8 +35,7 @@ abstract class VideoRecorder {
} }
} }
public interface VideoResultListener {
interface VideoResultListener { void onVideoResult(@Nullable VideoResult.Stub result, @Nullable Exception exception);
void onVideoResult(@Nullable VideoResult result, @Nullable Exception exception);
} }
} }

@ -38,12 +38,12 @@ public class FrameManagerTest {
@Test @Test
public void testAllocate() { public void testAllocate() {
FrameManager manager = new FrameManager(1, callback); 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)); verify(callback, times(1)).onBufferAvailable(any(byte[].class));
reset(callback); reset(callback);
manager = new FrameManager(5, 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)); verify(callback, times(5)).onBufferAvailable(any(byte[].class));
} }
@ -51,7 +51,7 @@ public class FrameManagerTest {
public void testFrameRecycling() { public void testFrameRecycling() {
// A 1-pool manager will always recycle the same frame. // A 1-pool manager will always recycle the same frame.
FrameManager manager = new FrameManager(1, callback); 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); Frame first = manager.getFrame(null, 0, 0, null, 0);
first.release(); first.release();
@ -65,7 +65,7 @@ public class FrameManagerTest {
@Test @Test
public void testOnFrameReleased_alreadyFull() { public void testOnFrameReleased_alreadyFull() {
FrameManager manager = new FrameManager(1, callback); 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); Frame frame1 = manager.getFrame(new byte[length], 0, 0, null, 0);
// Since frame1 is already taken and poolSize = 1, a new Frame is created. // Since frame1 is already taken and poolSize = 1, a new Frame is created.
@ -82,7 +82,7 @@ public class FrameManagerTest {
@Test @Test
public void testOnFrameReleased_sameLength() { public void testOnFrameReleased_sameLength() {
FrameManager manager = new FrameManager(1, callback); 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. // A camera preview frame comes. Request a frame.
byte[] picture = new byte[length]; byte[] picture = new byte[length];
@ -97,14 +97,14 @@ public class FrameManagerTest {
@Test @Test
public void testOnFrameReleased_differentLength() { public void testOnFrameReleased_differentLength() {
FrameManager manager = new FrameManager(1, callback); 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. // A camera preview frame comes. Request a frame.
byte[] picture = new byte[length]; byte[] picture = new byte[length];
Frame frame = manager.getFrame(picture, 0, 0, null, 0); Frame frame = manager.getFrame(picture, 0, 0, null, 0);
// Don't release the frame. Change the allocation size. // 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, // Now release the old frame and ensure that onBufferAvailable is NOT called,
// because the released data has wrong length. // because the released data has wrong length.
@ -116,7 +116,7 @@ public class FrameManagerTest {
@Test @Test
public void testRelease() { public void testRelease() {
FrameManager manager = new FrameManager(1, callback); 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); Frame first = manager.getFrame(new byte[length], 0, 0, null, 0);
first.release(); // Store this frame in the queue. first.release(); // Store this frame in the queue.

Loading…
Cancel
Save