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;
CameraException(Throwable cause) {
public CameraException(Throwable cause) {
super(cause);
}
CameraException(Throwable cause, int reason) {
public CameraException(Throwable cause, int reason) {
super(cause);
this.reason = reason;
}
CameraException(int reason) {
public CameraException(int reason) {
super();
this.reason = reason;
}

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

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

@ -1238,7 +1238,8 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
* @see #takePictureSnapshot()
*/
public void takePicture() {
mCameraEngine.takePicture();
PictureResult.Stub stub = new PictureResult.Stub();
mCameraEngine.takePicture(stub);
}
@ -1254,7 +1255,8 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
*/
public void takePictureSnapshot() {
if (getWidth() == 0 || getHeight() == 0) return;
mCameraEngine.takePictureSnapshot(AspectRatio.of(getWidth(), getHeight()));
PictureResult.Stub stub = new PictureResult.Stub();
mCameraEngine.takePictureSnapshot(stub, AspectRatio.of(getWidth(), getHeight()));
}
@ -1265,7 +1267,8 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
* @param file a file where the video will be saved
*/
public void takeVideo(@NonNull File file) {
mCameraEngine.takeVideo(file);
VideoResult.Stub stub = new VideoResult.Stub();
mCameraEngine.takeVideo(stub, file);
mUiHandler.post(new Runnable() {
@Override
public void run() {
@ -1286,7 +1289,8 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
*/
public void takeVideoSnapshot(@NonNull File file) {
if (getWidth() == 0 || getHeight() == 0) return;
mCameraEngine.takeVideoSnapshot(file, AspectRatio.of(getWidth(), getHeight()));
VideoResult.Stub stub = new VideoResult.Stub();
mCameraEngine.takeVideoSnapshot(stub, file, AspectRatio.of(getWidth(), getHeight()));
mUiHandler.post(new Runnable() {
@Override
public void run() {
@ -1609,20 +1613,6 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
//region Callbacks and dispatching
interface CameraCallbacks extends OrientationHelper.Callback {
void dispatchOnCameraOpened(CameraOptions options);
void dispatchOnCameraClosed();
void onCameraPreviewStreamSizeChanged();
void onShutter(boolean shouldPlaySound);
void dispatchOnVideoTaken(VideoResult result);
void dispatchOnPictureTaken(PictureResult result);
void dispatchOnFocusStart(@Nullable Gesture trigger, @NonNull PointF where);
void dispatchOnFocusEnd(@Nullable Gesture trigger, boolean success, @NonNull PointF where);
void dispatchOnZoomChanged(final float newValue, @Nullable final PointF[] fingers);
void dispatchOnExposureCorrectionChanged(float newValue, @NonNull float[] bounds, @Nullable PointF[] fingers);
void dispatchFrame(Frame frame);
void dispatchError(CameraException exception);
}
private class Callbacks implements CameraCallbacks {

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

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

@ -17,19 +17,8 @@ import android.view.SurfaceHolder;
import com.otaliastudios.cameraview.CameraException;
import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.CameraOptions;
import com.otaliastudios.cameraview.CameraView;
import com.otaliastudios.cameraview.CropHelper;
import com.otaliastudios.cameraview.frame.Frame;
import com.otaliastudios.cameraview.FullPictureRecorder;
import com.otaliastudios.cameraview.FullVideoRecorder;
import com.otaliastudios.cameraview.GlCameraPreview;
import com.otaliastudios.cameraview.Mapper1;
import com.otaliastudios.cameraview.PictureRecorder;
import com.otaliastudios.cameraview.PictureResult;
import com.otaliastudios.cameraview.SnapshotPictureRecorder;
import com.otaliastudios.cameraview.SnapshotVideoRecorder;
import com.otaliastudios.cameraview.Task;
import com.otaliastudios.cameraview.VideoRecorder;
import com.otaliastudios.cameraview.VideoResult;
import com.otaliastudios.cameraview.controls.Audio;
import com.otaliastudios.cameraview.controls.Facing;
@ -38,8 +27,17 @@ import com.otaliastudios.cameraview.gesture.Gesture;
import com.otaliastudios.cameraview.controls.Hdr;
import com.otaliastudios.cameraview.controls.Mode;
import com.otaliastudios.cameraview.controls.WhiteBalance;
import com.otaliastudios.cameraview.internal.utils.CropHelper;
import com.otaliastudios.cameraview.internal.utils.Task;
import com.otaliastudios.cameraview.picture.FullPictureRecorder;
import com.otaliastudios.cameraview.picture.PictureRecorder;
import com.otaliastudios.cameraview.picture.SnapshotPictureRecorder;
import com.otaliastudios.cameraview.preview.GlCameraPreview;
import com.otaliastudios.cameraview.size.AspectRatio;
import com.otaliastudios.cameraview.size.Size;
import com.otaliastudios.cameraview.video.FullVideoRecorder;
import com.otaliastudios.cameraview.video.SnapshotVideoRecorder;
import com.otaliastudios.cameraview.video.VideoRecorder;
import java.io.File;
import java.io.IOException;
@ -73,9 +71,9 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
}
};
Camera1Engine(@NonNull CameraView.CameraCallbacks callback) {
Camera1Engine(@NonNull Callback callback) {
super(callback);
mMapper = new Mapper1();
mMapper = Mapper.get();
}
private void schedule(@Nullable final Task<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.
private void startPreview(String log) {
LOG.i(log, "Dispatching onCameraPreviewStreamSizeChanged.");
mCameraCallbacks.onCameraPreviewStreamSizeChanged();
mCallback.onCameraPreviewStreamSizeChanged();
Size previewSize = getPreviewStreamSize(REF_VIEW);
if (previewSize == null) {
throw new IllegalStateException("previewStreamSize should not be null at this point.");
}
boolean wasFlipped = flip(REF_SENSOR, REF_VIEW);
mPreview.setStreamSize(previewSize.getWidth(), previewSize.getHeight(), wasFlipped);
@ -226,7 +227,7 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
mCamera.setPreviewCallbackWithBuffer(null); // Release anything left
mCamera.setPreviewCallbackWithBuffer(this); // Add ourselves
mFrameManager.allocate(ImageFormat.getBitsPerPixel(mPreviewFormat), mPreviewStreamSize);
mFrameManager.allocateBuffers(ImageFormat.getBitsPerPixel(mPreviewFormat), mPreviewStreamSize);
LOG.i(log, "Starting preview with startPreview().");
try {
@ -363,8 +364,8 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
return;
}
LOG.e("Internal Camera1 error.", error);
Exception runtime = new RuntimeException(CameraLogger.lastMessage);
String message = LOG.e("Internal Camera1 error.", error);
Exception runtime = new RuntimeException(message);
int reason;
switch (error) {
case Camera.CAMERA_ERROR_EVICTED: reason = CameraException.REASON_DISCONNECTED; break;
@ -400,7 +401,8 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
});
}
private boolean applyLocation(@NonNull Camera.Parameters params, @Nullable Location oldLocation) {
private boolean applyLocation(@NonNull Camera.Parameters params,
@SuppressWarnings("unused") @Nullable Location oldLocation) {
if (mLocation != null) {
params.setGpsLatitude(mLocation.getLatitude());
params.setGpsLongitude(mLocation.getLongitude());
@ -563,23 +565,23 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
@Override
public void onPictureShutter(boolean didPlaySound) {
mCameraCallbacks.onShutter(!didPlaySound);
mCallback.onShutter(!didPlaySound);
}
@Override
public void onPictureResult(@Nullable PictureResult result) {
public void onPictureResult(@Nullable PictureResult.Stub result) {
mPictureRecorder = null;
if (result != null) {
mCameraCallbacks.dispatchOnPictureTaken(result);
mCallback.dispatchOnPictureTaken(result);
} else {
// Something went wrong.
mCameraCallbacks.dispatchError(new CameraException(CameraException.REASON_PICTURE_FAILED));
mCallback.dispatchError(new CameraException(CameraException.REASON_PICTURE_FAILED));
LOG.e("onPictureResult", "result is null: something went wrong.");
}
}
@Override
void takePicture() {
void takePicture(final @NonNull PictureResult.Stub stub) {
LOG.v("takePicture: scheduling");
schedule(null, true, new Runnable() {
@Override
@ -592,13 +594,12 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
LOG.v("takePicture: performing.", isTakingPicture());
if (isTakingPicture()) return;
PictureResult result = new PictureResult();
result.isSnapshot = false;
result.location = mLocation;
result.rotation = offset(REF_SENSOR, REF_OUTPUT);
result.size = getPictureSize(REF_OUTPUT);
result.facing = mFacing;
mPictureRecorder = new FullPictureRecorder(result, Camera1Engine.this, mCamera);
stub.isSnapshot = false;
stub.location = mLocation;
stub.rotation = offset(REF_SENSOR, REF_OUTPUT);
stub.size = getPictureSize(REF_OUTPUT);
stub.facing = mFacing;
mPictureRecorder = new FullPictureRecorder(stub, Camera1Engine.this, mCamera);
mPictureRecorder.take();
}
});
@ -609,7 +610,7 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
* @param viewAspectRatio the view aspect ratio
*/
@Override
void takePictureSnapshot(@NonNull final AspectRatio viewAspectRatio) {
void takePictureSnapshot(final @NonNull PictureResult.Stub stub, @NonNull final AspectRatio viewAspectRatio) {
LOG.v("takePictureSnapshot: scheduling");
schedule(null, true, new Runnable() {
@Override
@ -617,12 +618,11 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
LOG.v("takePictureSnapshot: performing.", isTakingPicture());
if (isTakingPicture()) return;
PictureResult result = new PictureResult();
result.location = mLocation;
result.isSnapshot = true;
result.facing = mFacing;
result.size = getUncroppedSnapshotSize(REF_OUTPUT); // Not the real size: it will be cropped to match the view ratio
result.rotation = offset(REF_SENSOR, REF_OUTPUT); // Actually it will be rotated and set to 0.
stub.location = mLocation;
stub.isSnapshot = true;
stub.facing = mFacing;
stub.size = getUncroppedSnapshotSize(REF_OUTPUT); // Not the real size: it will be cropped to match the view ratio
stub.rotation = offset(REF_SENSOR, REF_OUTPUT); // Actually it will be rotated and set to 0.
AspectRatio outputRatio = flip(REF_OUTPUT, REF_VIEW) ? viewAspectRatio.flip() : viewAspectRatio;
// LOG.e("ROTBUG_pic", "aspectRatio (REF_VIEW):", viewAspectRatio);
// LOG.e("ROTBUG_pic", "aspectRatio (REF_OUTPUT):", outputRatio);
@ -633,7 +633,7 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
LOG.v("Rotations", "SO", offset(REF_SENSOR, REF_OUTPUT), "OS", offset(REF_OUTPUT, REF_SENSOR));
LOG.v("Rotations", "VO", offset(REF_VIEW, REF_OUTPUT), "OV", offset(REF_OUTPUT, REF_VIEW));
mPictureRecorder = new SnapshotPictureRecorder(result, Camera1Engine.this, mCamera, outputRatio);
mPictureRecorder = new SnapshotPictureRecorder(stub, Camera1Engine.this, mCamera, outputRatio);
mPictureRecorder.take();
}
});
@ -646,7 +646,7 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
offset(REF_SENSOR, REF_OUTPUT),
mPreviewStreamSize,
mPreviewFormat);
mCameraCallbacks.dispatchFrame(frame);
mCallback.dispatchFrame(frame);
}
private boolean isCameraAvailable() {
@ -673,19 +673,19 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
// Video recording stuff.
@Override
public void onVideoResult(@Nullable VideoResult result, @Nullable Exception exception) {
public void onVideoResult(@Nullable VideoResult.Stub result, @Nullable Exception exception) {
mVideoRecorder = null;
if (result != null) {
mCameraCallbacks.dispatchOnVideoTaken(result);
mCallback.dispatchOnVideoTaken(result);
} else {
// Something went wrong, lock the camera again.
mCameraCallbacks.dispatchError(new CameraException(exception, CameraException.REASON_VIDEO_FAILED));
mCallback.dispatchError(new CameraException(exception, CameraException.REASON_VIDEO_FAILED));
mCamera.lock();
}
}
@Override
void takeVideo(@NonNull final File videoFile) {
void takeVideo(final @NonNull VideoResult.Stub stub, @NonNull final File videoFile) {
schedule(mStartVideoTask, true, new Runnable() {
@Override
public void run() {
@ -696,19 +696,18 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
if (isTakingVideo()) return;
// Create the video result stub
VideoResult videoResult = new VideoResult();
videoResult.file = videoFile;
videoResult.isSnapshot = false;
videoResult.codec = mVideoCodec;
videoResult.location = mLocation;
videoResult.facing = mFacing;
videoResult.rotation = offset(REF_SENSOR, REF_OUTPUT);
videoResult.size = flip(REF_SENSOR, REF_OUTPUT) ? mCaptureSize.flip() : mCaptureSize;
videoResult.audio = mAudio;
videoResult.maxSize = mVideoMaxSize;
videoResult.maxDuration = mVideoMaxDuration;
videoResult.videoBitRate = mVideoBitRate;
videoResult.audioBitRate = mAudioBitRate;
stub.file = videoFile;
stub.isSnapshot = false;
stub.videoCodec = mVideoCodec;
stub.location = mLocation;
stub.facing = mFacing;
stub.rotation = offset(REF_SENSOR, REF_OUTPUT);
stub.size = flip(REF_SENSOR, REF_OUTPUT) ? mCaptureSize.flip() : mCaptureSize;
stub.audio = mAudio;
stub.maxSize = mVideoMaxSize;
stub.maxDuration = mVideoMaxDuration;
stub.videoBitRate = mVideoBitRate;
stub.audioBitRate = mAudioBitRate;
// Unlock the camera and start recording.
try {
@ -719,7 +718,7 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
onVideoResult(null, e);
return;
}
mVideoRecorder = new FullVideoRecorder(videoResult, Camera1Engine.this,
mVideoRecorder = new FullVideoRecorder(stub, Camera1Engine.this,
Camera1Engine.this, mCamera, mCameraId);
mVideoRecorder.start();
}
@ -732,7 +731,7 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
*/
@SuppressLint("NewApi")
@Override
void takeVideoSnapshot(@NonNull final File file, @NonNull final AspectRatio viewAspectRatio) {
void takeVideoSnapshot(final @NonNull VideoResult.Stub stub, @NonNull final File file, @NonNull final AspectRatio viewAspectRatio) {
if (!(mPreview instanceof GlCameraPreview)) {
throw new IllegalStateException("Video snapshots are only supported with GlCameraPreview.");
}
@ -745,17 +744,16 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
if (isTakingVideo()) return;
// Create the video result stub
VideoResult videoResult = new VideoResult();
videoResult.file = file;
videoResult.isSnapshot = true;
videoResult.codec = mVideoCodec;
videoResult.location = mLocation;
videoResult.facing = mFacing;
videoResult.videoBitRate = mVideoBitRate;
videoResult.audioBitRate = mAudioBitRate;
videoResult.audio = mAudio;
videoResult.maxSize = mVideoMaxSize;
videoResult.maxDuration = mVideoMaxDuration;
stub.file = file;
stub.isSnapshot = true;
stub.videoCodec = mVideoCodec;
stub.location = mLocation;
stub.facing = mFacing;
stub.videoBitRate = mVideoBitRate;
stub.audioBitRate = mAudioBitRate;
stub.audio = mAudio;
stub.maxSize = mVideoMaxSize;
stub.maxDuration = mVideoMaxDuration;
// Size and rotation turned out to be extremely tricky. In case of SnapshotPictureRecorder
// we use the preview size in REF_OUTPUT (cropped) and offset(REF_SENSOR, REF_OUTPUT) as rotation.
@ -793,11 +791,14 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
// and maybe we can improve. The reason why this happen is beyond my understanding.
Size outputSize = getUncroppedSnapshotSize(REF_OUTPUT);
if (outputSize == null) {
throw new IllegalStateException("outputSize should not be null.");
}
AspectRatio outputRatio = flip(REF_OUTPUT, REF_VIEW) ? viewAspectRatio.flip() : viewAspectRatio;
Rect outputCrop = CropHelper.computeCrop(outputSize, outputRatio);
outputSize = new Size(outputCrop.width(), outputCrop.height());
videoResult.size = outputSize;
videoResult.rotation = offset(REF_VIEW, REF_OUTPUT);
stub.size = outputSize;
stub.rotation = offset(REF_VIEW, REF_OUTPUT);
// LOG.e("ROTBUG_video", "aspectRatio (REF_VIEW):", viewAspectRatio);
// LOG.e("ROTBUG_video", "aspectRatio (REF_OUTPUT):", outputRatio);
// LOG.e("ROTBUG_video", "sizeUncropped (REF_OUTPUT):", outputSize);
@ -807,7 +808,7 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
// Reset facing and start.
mFacing = realFacing;
GlCameraPreview cameraPreview = (GlCameraPreview) mPreview;
mVideoRecorder = new SnapshotVideoRecorder(videoResult, Camera1Engine.this, cameraPreview);
mVideoRecorder = new SnapshotVideoRecorder(stub, Camera1Engine.this, cameraPreview);
mVideoRecorder.start();
}
});
@ -845,7 +846,7 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
mCamera.setParameters(params);
if (notify) {
mCameraCallbacks.dispatchOnZoomChanged(zoom, points);
mCallback.dispatchOnZoomChanged(zoom, points);
}
}
});
@ -870,7 +871,7 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
mCamera.setParameters(params);
if (notify) {
mCameraCallbacks.dispatchOnExposureCorrectionChanged(value, bounds, points);
mCallback.dispatchOnExposureCorrectionChanged(value, bounds, points);
}
}
});
@ -908,14 +909,14 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
if (maxAE > 0) params.setMeteringAreas(maxAE > 1 ? meteringAreas2 : meteringAreas1);
params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
mCamera.setParameters(params);
mCameraCallbacks.dispatchOnFocusStart(gesture, p);
mCallback.dispatchOnFocusStart(gesture, p);
// TODO this is not guaranteed to be called... Fix.
try {
mCamera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
// TODO lock auto exposure and white balance for a while
mCameraCallbacks.dispatchOnFocusEnd(gesture, success, p);
mCallback.dispatchOnFocusEnd(gesture, success, p);
mHandler.get().removeCallbacks(mPostFocusResetRunnable);
if (shouldResetAutoFocus()) {
mHandler.get().postDelayed(mPostFocusResetRunnable, getAutoFocusResetDelay());
@ -926,7 +927,7 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
// Handling random auto-focus exception on some devices
// See https://github.com/natario1/CameraView/issues/181
LOG.e("startAutoFocus:", "Error calling autoFocus", e);
mCameraCallbacks.dispatchOnFocusEnd(gesture, false, p);
mCallback.dispatchOnFocusEnd(gesture, false, p);
}
}
});
@ -979,9 +980,8 @@ class Camera1Engine extends CameraEngine implements Camera.PreviewCallback, Came
// Size stuff.
@Nullable
private List<Size> sizesFromList(@Nullable List<Camera.Size> sizes) {
if (sizes == null) return null;
@NonNull
private List<Size> sizesFromList(@NonNull List<Camera.Size> sizes) {
List<Size> result = new ArrayList<>(sizes.size());
for (Camera.Size size : sizes) {
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.CameraLogger;
import com.otaliastudios.cameraview.CameraOptions;
import com.otaliastudios.cameraview.PictureResult;
import com.otaliastudios.cameraview.VideoResult;
import com.otaliastudios.cameraview.frame.Frame;
import com.otaliastudios.cameraview.frame.FrameManager;
import com.otaliastudios.cameraview.internal.utils.Task;
import com.otaliastudios.cameraview.internal.utils.WorkerHandler;
import com.otaliastudios.cameraview.picture.PictureRecorder;
import com.otaliastudios.cameraview.preview.CameraPreview;
import com.otaliastudios.cameraview.CameraView;
import com.otaliastudios.cameraview.FrameManager;
import com.otaliastudios.cameraview.Mapper;
import com.otaliastudios.cameraview.PictureRecorder;
import com.otaliastudios.cameraview.Task;
import com.otaliastudios.cameraview.VideoRecorder;
import com.otaliastudios.cameraview.WorkerHandler;
import com.otaliastudios.cameraview.controls.Audio;
import com.otaliastudios.cameraview.controls.Facing;
import com.otaliastudios.cameraview.controls.Flash;
@ -30,6 +30,7 @@ import com.otaliastudios.cameraview.size.AspectRatio;
import com.otaliastudios.cameraview.size.Size;
import com.otaliastudios.cameraview.size.SizeSelector;
import com.otaliastudios.cameraview.size.SizeSelectors;
import com.otaliastudios.cameraview.video.VideoRecorder;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@ -46,6 +47,21 @@ public abstract class CameraEngine implements
FrameManager.BufferCallback,
Thread.UncaughtExceptionHandler {
public interface Callback {
void dispatchOnCameraOpened(CameraOptions options);
void dispatchOnCameraClosed();
void onCameraPreviewStreamSizeChanged();
void onShutter(boolean shouldPlaySound);
void dispatchOnVideoTaken(VideoResult.Stub result);
void dispatchOnPictureTaken(PictureResult.Stub result);
void dispatchOnFocusStart(@Nullable Gesture trigger, @NonNull PointF where);
void dispatchOnFocusEnd(@Nullable Gesture trigger, boolean success, @NonNull PointF where);
void dispatchOnZoomChanged(final float newValue, @Nullable final PointF[] fingers);
void dispatchOnExposureCorrectionChanged(float newValue, @NonNull float[] bounds, @Nullable PointF[] fingers);
void dispatchFrame(Frame frame);
void dispatchError(CameraException exception);
}
private static final String TAG = CameraEngine.class.getSimpleName();
private static final CameraLogger LOG = CameraLogger.create(TAG);
@ -58,10 +74,10 @@ public abstract class CameraEngine implements
static final int REF_VIEW = 1;
static final int REF_OUTPUT = 2;
protected final CameraView.CameraCallbacks mCameraCallbacks;
protected final Callback mCallback;
protected CameraPreview mPreview;
protected WorkerHandler mHandler;
/* for tests */ Handler mCrashHandler;
@VisibleForTesting Handler mCrashHandler;
protected Facing mFacing;
protected Flash mFlash;
@ -79,10 +95,8 @@ public abstract class CameraEngine implements
private SizeSelector mPictureSizeSelector;
private SizeSelector mVideoSizeSelector;
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
int mSnapshotMaxWidth = Integer.MAX_VALUE; // in REF_VIEW for consistency with SizeSelectors
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
int mSnapshotMaxHeight = Integer.MAX_VALUE; // in REF_VIEW for consistency with SizeSelectors
@VisibleForTesting int mSnapshotMaxWidth = Integer.MAX_VALUE; // in REF_VIEW for consistency with SizeSelectors
@VisibleForTesting int mSnapshotMaxHeight = Integer.MAX_VALUE; // in REF_VIEW for consistency with SizeSelectors
protected int mCameraId;
protected CameraOptions mCameraOptions;
@ -106,17 +120,17 @@ public abstract class CameraEngine implements
protected int mState = STATE_STOPPED;
// Used for testing.
Task<Void> mZoomTask = new Task<>();
Task<Void> mExposureCorrectionTask = new Task<>();
Task<Void> mFlashTask = new Task<>();
Task<Void> mWhiteBalanceTask = new Task<>();
Task<Void> mHdrTask = new Task<>();
Task<Void> mLocationTask = new Task<>();
Task<Void> mStartVideoTask = new Task<>();
Task<Void> mPlaySoundsTask = new Task<>();
CameraEngine(CameraView.CameraCallbacks callback) {
mCameraCallbacks = callback;
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mZoomTask = new Task<>();
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mExposureCorrectionTask = new Task<>();
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mFlashTask = new Task<>();
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mWhiteBalanceTask = new Task<>();
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mHdrTask = new Task<>();
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mLocationTask = new Task<>();
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mStartVideoTask = new Task<>();
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mPlaySoundsTask = new Task<>();
CameraEngine(Callback callback) {
mCallback = callback;
mCrashHandler = new Handler(Looper.getMainLooper());
mHandler = WorkerHandler.get("CameraViewController");
mHandler.getThread().setUncaughtExceptionHandler(this);
@ -171,7 +185,7 @@ public abstract class CameraEngine implements
@Override
public void run() {
stopImmediately();
mCameraCallbacks.dispatchError(error);
mCallback.dispatchError(error);
}
});
}
@ -215,7 +229,7 @@ public abstract class CameraEngine implements
onStart();
LOG.i("Start:", "returned from onStart().", "Dispatching.", ss());
mState = STATE_STARTED;
mCameraCallbacks.dispatchOnCameraOpened(mCameraOptions);
mCallback.dispatchOnCameraOpened(mCameraOptions);
}
});
}
@ -234,7 +248,7 @@ public abstract class CameraEngine implements
onStop();
LOG.i("Stop:", "returned from onStop().", "Dispatching.");
mState = STATE_STOPPED;
mCameraCallbacks.dispatchOnCameraClosed();
mCallback.dispatchOnCameraClosed();
}
});
}
@ -270,7 +284,7 @@ public abstract class CameraEngine implements
onStop();
mState = STATE_STOPPED;
LOG.i("Restart:", "stopped. Dispatching.", ss());
mCameraCallbacks.dispatchOnCameraClosed();
mCallback.dispatchOnCameraClosed();
}
LOG.i("Restart: about to start. State:", ss());
@ -278,7 +292,7 @@ public abstract class CameraEngine implements
onStart();
mState = STATE_STARTED;
LOG.i("Restart: returned from start. Dispatching. State:", ss());
mCameraCallbacks.dispatchOnCameraOpened(mCameraOptions);
mCallback.dispatchOnCameraOpened(mCameraOptions);
}
});
}
@ -384,13 +398,13 @@ public abstract class CameraEngine implements
// Just set.
abstract void setAudio(@NonNull Audio audio);
abstract void takePicture();
abstract void takePicture(@NonNull PictureResult.Stub stub);
abstract void takePictureSnapshot(@NonNull AspectRatio viewAspectRatio);
abstract void takePictureSnapshot(@NonNull PictureResult.Stub stub, @NonNull AspectRatio viewAspectRatio);
abstract void takeVideo(@NonNull File file);
abstract void takeVideo(@NonNull VideoResult.Stub stub, @NonNull File file);
abstract void takeVideoSnapshot(@NonNull File file, @NonNull AspectRatio viewAspectRatio);
abstract void takeVideoSnapshot(@NonNull VideoResult.Stub stub, @NonNull File file, @NonNull AspectRatio viewAspectRatio);
abstract void stopVideo();

@ -1,11 +1,21 @@
package com.otaliastudios.cameraview.engine;
import android.hardware.Camera;
import android.os.Build;
import com.otaliastudios.cameraview.controls.Facing;
import com.otaliastudios.cameraview.controls.Flash;
import com.otaliastudios.cameraview.controls.Hdr;
import com.otaliastudios.cameraview.controls.WhiteBalance;
abstract class Mapper {
import java.util.HashMap;
/**
* A Mapper maps camera engine constants to CameraView constants.
*/
public abstract class Mapper {
private Mapper() {}
abstract <T> T map(Flash flash);
abstract <T> T map(Facing facing);
@ -15,4 +25,91 @@ abstract class Mapper {
abstract <T> Facing unmapFacing(T cameraConstant);
abstract <T> WhiteBalance unmapWhiteBalance(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 androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
/**
* A preview frame to be processed by {@link FrameProcessor}s.
*/
public class Frame {
/* for tests */ FrameManager mManager;
@VisibleForTesting FrameManager mManager;
private byte[] mData = null;
private long mTime = -1;
@ -21,6 +22,7 @@ public class Frame {
mManager = manager;
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean isAlive() {
return mData != null;
}

@ -23,9 +23,13 @@ import java.util.concurrent.LinkedBlockingQueue;
* - Frame pool:
* We keep a list of mPoolSize recycled instances, to be reused when a new buffer is available.
*/
class FrameManager {
public class FrameManager {
interface BufferCallback {
/**
* Receives callbacks on buffer availability
* (when a Frame is released, we reuse its buffer).
*/
public interface BufferCallback {
void onBufferAvailable(@NonNull byte[] buffer);
}
@ -34,14 +38,45 @@ class FrameManager {
private BufferCallback mCallback;
private LinkedBlockingQueue<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;
mCallback = callback;
mQueue = new LinkedBlockingQueue<>(mPoolSize);
mBufferSize = -1;
}
void release() {
/**
* Allocates a {@link #mPoolSize} number of buffers. Should be called once
* the preview size and the bitsPerPixel value are known.
*
* This method can be called again after {@link #release()} has been called.
*
* @param bitsPerPixel bits per pixel, depends on image format
* @param previewSize the preview size
* @return the buffer size
*/
public int allocateBuffers(int bitsPerPixel, @NonNull Size previewSize) {
// TODO throw if called twice without release?
mBufferSize = getBufferSize(bitsPerPixel, previewSize);
for (int i = 0; i < mPoolSize; i++) {
mCallback.onBufferAvailable(new byte[mBufferSize]);
}
return mBufferSize;
}
/**
* Releases all frames controlled by this manager and
* clears the pool.
*/
public void release() {
for (Frame frame : mQueue) {
frame.releaseManager();
frame.release();
@ -69,8 +104,8 @@ class FrameManager {
/**
* Returns a new Frame for the given data. This must be called
* - after {@link #allocate(int, Size)}, which sets the buffer size
* - after the byte buffer given by allocate() has been filled.
* - after {@link #allocateBuffers(int, Size)}, which sets the buffer size
* - after the byte buffer given by allocateBuffers() has been filled.
* If this is called X times in a row without releasing frames, it will allocate
* X frames and that's bad. Callers must wait for the preview buffer to be available.
*
@ -78,21 +113,13 @@ class FrameManager {
*
* @return a new frame
*/
Frame getFrame(@NonNull byte[] data, long time, int rotation, @NonNull Size previewSize, int previewFormat) {
public Frame getFrame(@NonNull byte[] data, long time, int rotation, @NonNull Size previewSize, int previewFormat) {
Frame frame = mQueue.poll();
if (frame == null) frame = new Frame(this);
frame.set(data, time, rotation, previewSize, previewFormat);
return frame;
}
int allocate(int bitsPerPixel, @NonNull Size previewSize) {
mBufferSize = getBufferSize(bitsPerPixel, previewSize);
for (int i = 0; i < mPoolSize; i++) {
mCallback.onBufferAvailable(new byte[mBufferSize]);
}
return mBufferSize;
}
private int getBufferSize(int bitsPerPixel, @NonNull Size previewSize) {
long sizeInBits = previewSize.getHeight() * previewSize.getWidth() * bitsPerPixel;
return (int) Math.ceil(sizeInBits / 8.0d);

@ -5,9 +5,6 @@ import com.otaliastudios.cameraview.CameraView;
import androidx.annotation.NonNull;
import java.util.Arrays;
import java.util.List;
/**
* Gestures listen to finger gestures over the {@link CameraView} bounds and can be mapped

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

@ -1,14 +1,6 @@
package com.otaliastudios.cameraview.gesture;
import com.otaliastudios.cameraview.CameraView;
import java.util.Arrays;
import java.util.List;
import androidx.annotation.NonNull;
/**
* Gestures and gesture actions can both have a type. For a gesture to be able to be mapped to
* a certain {@link GestureAction}, both of them might be of the same type.

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

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

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

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

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

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

@ -7,11 +7,14 @@ import com.otaliastudios.cameraview.size.Size;
import androidx.annotation.NonNull;
class CropHelper {
/**
* Simply computes the crop between a full size and a desired aspect ratio.
*/
public class CropHelper {
// It's important that size and aspect ratio belong to the same reference.
@NonNull
static Rect computeCrop(@NonNull Size currentSize, @NonNull AspectRatio targetRatio) {
public static Rect computeCrop(@NonNull Size currentSize, @NonNull AspectRatio targetRatio) {
int currentWidth = currentSize.getWidth();
int currentHeight = currentSize.getHeight();
if (targetRatio.matches(currentSize)) {

@ -3,24 +3,37 @@ package com.otaliastudios.cameraview.internal.utils;
import android.content.Context;
import android.hardware.SensorManager;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import android.view.Display;
import android.view.OrientationEventListener;
import android.view.Surface;
import android.view.WindowManager;
class OrientationHelper {
/**
* Helps with keeping track of both device orientation (which changes when device is rotated)
* and the display offset (which depends on the activity orientation wrt the device default orientation).
*/
public class OrientationHelper {
final OrientationEventListener mListener;
/**
* Receives callback about the device orientation changes.
*/
public interface Callback {
void onDeviceOrientationChanged(int deviceOrientation);
}
@VisibleForTesting final OrientationEventListener mListener;
private final Callback mCallback;
private int mDeviceOrientation = -1;
private int mDisplayOffset = -1;
interface Callback {
void onDeviceOrientationChanged(int deviceOrientation);
}
OrientationHelper(@NonNull Context context, @NonNull Callback callback) {
/**
* Creates a new orientation helper.
* @param context a valid context
* @param callback a {@link Callback}
*/
public OrientationHelper(@NonNull Context context, @NonNull Callback callback) {
mCallback = callback;
mListener = new OrientationEventListener(context.getApplicationContext(), SensorManager.SENSOR_DELAY_NORMAL) {
@ -48,7 +61,11 @@ class OrientationHelper {
};
}
void enable(@NonNull Context context) {
/**
* Enables this listener.
* @param context a context
*/
public void enable(@NonNull Context context) {
Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
switch (display.getRotation()) {
case Surface.ROTATION_0: mDisplayOffset = 0; break;
@ -60,16 +77,27 @@ class OrientationHelper {
mListener.enable();
}
void disable() {
/**
* Disables this listener.
*/
public void disable() {
mListener.disable();
mDisplayOffset = -1;
mDeviceOrientation = -1;
}
/**
* Returns the current device orientation.
* @return device orientation
*/
int getDeviceOrientation() {
return mDeviceOrientation;
}
/**
* Returns the current display offset.
* @return display offset
*/
int getDisplayOffset() {
return mDisplayOffset;
}

@ -8,6 +8,10 @@ import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* Base class for pools of recycleable objects.
* @param <T> the object type
*/
class Pool<T> {
private static final String TAG = Pool.class.getSimpleName();
@ -18,27 +22,49 @@ class Pool<T> {
private LinkedBlockingQueue<T> mQueue;
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();
}
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.mQueue = new LinkedBlockingQueue<>(maxPoolSize);
this.factory = factory;
}
boolean isEmpty() {
/**
* Whether the pool is empty. This means that {@link #get()} will return
* a null item, because all objects were reclaimed and not recycled yet.
*
* @return whether the pool is empty
*/
public boolean isEmpty() {
return count() >= maxPoolSize;
}
/**
* Returns a new item, from the recycled pool if possible (if there are recycled items),
* or instantiating one through the factory (if we can respect the pool size).
* If these conditions are not met, this returns null.
*
* @return an item or null
*/
@Nullable
T get() {
T buffer = mQueue.poll();
if (buffer != null) {
public T get() {
T item = mQueue.poll();
if (item != null) {
activeCount++; // poll decreases, this fixes
LOG.v("GET: Reusing recycled item.", this);
return buffer;
return item;
}
if (isEmpty()) {
@ -51,8 +77,13 @@ class Pool<T> {
return factory.create();
}
void recycle(@NonNull T item) {
/**
* Recycles an item after it has been used. The item should come from a previous
* {@link #get()} call.
*
* @param item used item
*/
public void recycle(@NonNull T item) {
LOG.v("RECYCLE: Recycling item.", this);
if (--activeCount < 0) {
throw new IllegalStateException("Trying to recycle an item which makes activeCount < 0." +
@ -66,26 +97,49 @@ class Pool<T> {
}
}
@NonNull
@Override
public String toString() {
return getClass().getSimpleName() + " -- count:" + count() + ", active:" + activeCount() + ", cached:" + cachedCount();
/**
* Clears the pool of recycled items.
*/
@CallSuper
public void clear() {
mQueue.clear();
}
final int count() {
return activeCount() + cachedCount();
/**
* Returns the count of all items managed by this pool. Includes
* - active items: currently being used
* - recycled items: used and recycled, available for second use
*
* @return count
*/
public final int count() {
return activeCount() + recycledCount();
}
final int activeCount() {
/**
* Returns the active items managed by this pools, which means, items
* currently being used.
*
* @return active count
*/
public final int activeCount() {
return activeCount;
}
final int cachedCount() {
/**
* Returns the recycled items managed by this pool, which means, items
* that were used and later recycled, and are currently available for
* second use.
*
* @return recycled count
*/
public final int recycledCount() {
return mQueue.size();
}
@CallSuper
void clear() {
mQueue.clear();
@NonNull
@Override
public String toString() {
return getClass().getSimpleName() + " -- count:" + count() + ", active:" + activeCount() + ", recycled:" + recycledCount();
}
}

@ -12,7 +12,14 @@ import androidx.annotation.NonNull;
@Deprecated
class RotationHelper {
static byte[] rotate(@NonNull final byte[] yuv, @NonNull final Size size, final int rotation) {
/**
* Rotates the given yuv image into another yuv array, by the given angle.
* @param yuv image
* @param size image size
* @param rotation desired angle
* @return a new yuv array
*/
public static byte[] rotate(@NonNull final byte[] yuv, @NonNull final Size size, final int rotation) {
if (rotation == 0) return yuv;
if (rotation % 90 != 0 || rotation < 0 || rotation > 270) {
throw new IllegalArgumentException("0 <= rotation < 360, rotation % 90 == 0");

@ -9,53 +9,92 @@ import java.util.concurrent.TimeUnit;
* A naive implementation of {@link java.util.concurrent.CountDownLatch}
* to help in testing.
*/
class Task<T> {
public class Task<T> {
private CountDownLatch mLatch;
private T mResult;
private int mCount;
Task() {
}
Task(boolean startListening) {
/**
* Creates an empty task.
*
* Listeners should:
* - call {@link #listen()} to notify they are interested in the next action
* - call {@link #await()} to know when the action is performed.
*
* Task owners should:
* - call {@link #start()} when task started
* - call {@link #end(Object)} when task ends
*/
public Task() { }
/**
* Creates an empty task and starts listening.
* @param startListening whether to call listen
*/
public Task(boolean startListening) {
if (startListening) listen();
}
private boolean listening() {
private boolean isListening() {
return mLatch != null;
}
void listen() {
if (listening()) throw new RuntimeException("Should not happen.");
mResult = null;
mLatch = new CountDownLatch(1);
}
void start() {
if (!listening()) mCount++;
/**
* Task owner method: notifies the action started.
*/
public void start() {
if (!isListening()) mCount++;
}
void end(T result) {
/**
* Task owner method: notifies the action ended.
* @param result the action result
*/
public void end(T result) {
if (mCount > 0) {
mCount--;
return;
}
if (listening()) { // Should be always true.
if (isListening()) { // Should be always true.
mResult = result;
mLatch.countDown();
}
}
T await(long millis) {
/**
* Listener method: notifies we are interested in the next action.
*/
public void listen() {
if (isListening()) throw new RuntimeException("Should not happen.");
mResult = null;
mLatch = new CountDownLatch(1);
}
/**
* Listener method: waits for next task action to end.
* @param millis milliseconds
* @return the action result
*/
public T await(long millis) {
return await(millis, TimeUnit.MILLISECONDS);
}
T await() {
/**
* Listener method: waits 1 minute for next task action to end.
* @return the action result
*/
public T await() {
return await(1, TimeUnit.MINUTES);
}
/**
* Listener method: waits for next task action to end.
* @param time time
* @param unit the time unit
* @return the action result
*/
private T await(long time, @NonNull TimeUnit unit) {
try {
mLatch.await(time, unit);

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

@ -16,14 +16,14 @@ import java.io.IOException;
/**
* A {@link PictureResult} that uses standard APIs.
*/
class FullPictureRecorder extends PictureRecorder {
public class FullPictureRecorder extends PictureRecorder {
private static final String TAG = FullPictureRecorder.class.getSimpleName();
private static final CameraLogger LOG = CameraLogger.create(TAG);
private Camera mCamera;
FullPictureRecorder(@NonNull PictureResult stub, @Nullable PictureResultListener listener, @NonNull Camera camera) {
public FullPictureRecorder(@NonNull PictureResult.Stub stub, @Nullable PictureResultListener listener, @NonNull Camera camera) {
super(stub, listener);
mCamera = camera;

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

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

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

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

@ -23,7 +23,7 @@ import androidx.annotation.RequiresApi;
* A {@link VideoRecorder} that uses {@link android.media.MediaCodec} APIs.
*/
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
class SnapshotVideoRecorder extends VideoRecorder implements GlCameraPreview.RendererFrameCallback,
public class SnapshotVideoRecorder extends VideoRecorder implements GlCameraPreview.RendererFrameCallback,
MediaEncoderEngine.Listener {
private static final String TAG = SnapshotVideoRecorder.class.getSimpleName();
@ -43,7 +43,7 @@ class SnapshotVideoRecorder extends VideoRecorder implements GlCameraPreview.Ren
private int mDesiredState = STATE_NOT_RECORDING;
private int mTextureId = 0;
SnapshotVideoRecorder(@NonNull VideoResult stub, @Nullable VideoResultListener listener, @NonNull GlCameraPreview preview) {
public SnapshotVideoRecorder(@NonNull VideoResult.Stub stub, @Nullable VideoResultListener listener, @NonNull GlCameraPreview preview) {
super(stub, listener);
mPreview = preview;
mPreview.addRendererFrameCallback(this);

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

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

Loading…
Cancel
Save