* Use CopyOnWrite set in GL preview (Fix #626)

* Demo app - do not add frame processor when opening (Fix #628)

* Add comments

* Create EngineThread annotation

* Fix onVideoResult crash for snapshots

* Catch IllegalStateExceptions in applyRepeatingRequestBuilder

* Add exception to LOG.e

* Add changelog

* Release v2.3.1

* Move snapshot logs to verbose
pull/659/head v2.3.1
Mattia Iavarone 5 years ago committed by GitHub
parent 19f607a959
commit 55e7a26278
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      README.md
  2. 2
      cameraview/build.gradle
  3. 21
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java
  4. 47
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java
  5. 52
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java
  6. 7
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/EngineThread.java
  7. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/overlay/OverlayLayout.java
  8. 28
      cameraview/src/main/java/com/otaliastudios/cameraview/preview/GlCameraPreview.java
  9. 4
      cameraview/src/main/java/com/otaliastudios/cameraview/video/SnapshotVideoRecorder.java
  10. 10
      cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/AudioMediaEncoder.java
  11. 4
      cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/MediaEncoder.java
  12. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/MediaEncoderEngine.java
  13. 6
      cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/TextureMediaEncoder.java
  14. 5
      demo/build.gradle
  15. 6
      demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java
  16. 10
      docs/_posts/2018-12-20-changelog.md
  17. 2
      docs/_posts/2018-12-20-install.md

@ -22,7 +22,7 @@ CameraView is a well documented, high-level library that makes capturing picture
addressing most of the common issues and needs, and still leaving you with flexibility where needed. addressing most of the common issues and needs, and still leaving you with flexibility where needed.
```groovy ```groovy
api 'com.otaliastudios:cameraview:2.3.0' api 'com.otaliastudios:cameraview:2.3.1'
``` ```
- Fast & reliable - Fast & reliable

@ -3,7 +3,7 @@ apply plugin: 'com.github.dcendents.android-maven'
apply plugin: 'com.jfrog.bintray' apply plugin: 'com.jfrog.bintray'
// Required by bintray // Required by bintray
version = '2.3.0' version = '2.3.1'
group = 'com.otaliastudios' group = 'com.otaliastudios'
//region android dependencies //region android dependencies

@ -97,6 +97,7 @@ public class Camera1Engine extends CameraEngine implements
//region Protected APIs //region Protected APIs
@EngineThread
@NonNull @NonNull
@Override @Override
protected List<Size> getPreviewStreamAvailableSizes() { protected List<Size> getPreviewStreamAvailableSizes() {
@ -110,12 +111,13 @@ public class Camera1Engine extends CameraEngine implements
return result; return result;
} }
@WorkerThread @EngineThread
@Override @Override
protected void onPreviewStreamSizeChanged() { protected void onPreviewStreamSizeChanged() {
restartPreview(); restartPreview();
} }
@EngineThread
@Override @Override
protected boolean collectCameraInfo(@NonNull Facing facing) { protected boolean collectCameraInfo(@NonNull Facing facing) {
int internalFacing = mMapper.mapFacing(facing); int internalFacing = mMapper.mapFacing(facing);
@ -140,7 +142,7 @@ public class Camera1Engine extends CameraEngine implements
//region Start //region Start
@NonNull @NonNull
@WorkerThread @EngineThread
@Override @Override
protected Task<Void> onStartEngine() { protected Task<Void> onStartEngine() {
try { try {
@ -164,6 +166,7 @@ public class Camera1Engine extends CameraEngine implements
return Tasks.forResult(null); return Tasks.forResult(null);
} }
@EngineThread
@NonNull @NonNull
@Override @Override
protected Task<Void> onStartBind() { protected Task<Void> onStartBind() {
@ -187,6 +190,7 @@ public class Camera1Engine extends CameraEngine implements
return Tasks.forResult(null); return Tasks.forResult(null);
} }
@EngineThread
@NonNull @NonNull
@Override @Override
protected Task<Void> onStartPreview() { protected Task<Void> onStartPreview() {
@ -238,6 +242,7 @@ public class Camera1Engine extends CameraEngine implements
//region Stop //region Stop
@EngineThread
@NonNull @NonNull
@Override @Override
protected Task<Void> onStopPreview() { protected Task<Void> onStopPreview() {
@ -256,6 +261,7 @@ public class Camera1Engine extends CameraEngine implements
return Tasks.forResult(null); return Tasks.forResult(null);
} }
@EngineThread
@NonNull @NonNull
@Override @Override
protected Task<Void> onStopBind() { protected Task<Void> onStopBind() {
@ -275,8 +281,8 @@ public class Camera1Engine extends CameraEngine implements
return Tasks.forResult(null); return Tasks.forResult(null);
} }
@EngineThread
@NonNull @NonNull
@WorkerThread
@Override @Override
protected Task<Void> onStopEngine() { protected Task<Void> onStopEngine() {
LOG.i("onStopEngine:", "About to clean up."); LOG.i("onStopEngine:", "About to clean up.");
@ -306,7 +312,7 @@ public class Camera1Engine extends CameraEngine implements
//region Pictures //region Pictures
@WorkerThread @EngineThread
@Override @Override
protected void onTakePicture(@NonNull PictureResult.Stub stub, boolean doMetering) { protected void onTakePicture(@NonNull PictureResult.Stub stub, boolean doMetering) {
stub.rotation = getAngles().offset(Reference.SENSOR, Reference.OUTPUT, stub.rotation = getAngles().offset(Reference.SENSOR, Reference.OUTPUT,
@ -316,7 +322,7 @@ public class Camera1Engine extends CameraEngine implements
mPictureRecorder.take(); mPictureRecorder.take();
} }
@WorkerThread @EngineThread
@Override @Override
protected void onTakePictureSnapshot(@NonNull PictureResult.Stub stub, protected void onTakePictureSnapshot(@NonNull PictureResult.Stub stub,
@NonNull AspectRatio outputRatio, @NonNull AspectRatio outputRatio,
@ -337,6 +343,7 @@ public class Camera1Engine extends CameraEngine implements
//region Videos //region Videos
@EngineThread
@Override @Override
protected void onTakeVideo(@NonNull VideoResult.Stub stub) { protected void onTakeVideo(@NonNull VideoResult.Stub stub) {
stub.rotation = getAngles().offset(Reference.SENSOR, Reference.OUTPUT, stub.rotation = getAngles().offset(Reference.SENSOR, Reference.OUTPUT,
@ -357,7 +364,7 @@ public class Camera1Engine extends CameraEngine implements
} }
@SuppressLint("NewApi") @SuppressLint("NewApi")
@WorkerThread @EngineThread
@Override @Override
protected void onTakeVideoSnapshot(@NonNull VideoResult.Stub stub, protected void onTakeVideoSnapshot(@NonNull VideoResult.Stub stub,
@NonNull AspectRatio outputRatio) { @NonNull AspectRatio outputRatio) {
@ -765,7 +772,7 @@ public class Camera1Engine extends CameraEngine implements
} }
@NonNull @NonNull
@WorkerThread @EngineThread
private static List<Camera.Area> computeMeteringAreas(double viewClickX, double viewClickY, private static List<Camera.Area> computeMeteringAreas(double viewClickX, double viewClickY,
int viewWidth, int viewHeight, int viewWidth, int viewHeight,
int sensorToDisplay) { int sensorToDisplay) {

@ -28,7 +28,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
import com.google.android.gms.tasks.Task; import com.google.android.gms.tasks.Task;
import com.google.android.gms.tasks.TaskCompletionSource; import com.google.android.gms.tasks.TaskCompletionSource;
@ -246,6 +245,17 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
mRepeatingRequestCallback, null); mRepeatingRequestCallback, null);
} catch (CameraAccessException e) { } catch (CameraAccessException e) {
throw new CameraException(e, errorReason); throw new CameraException(e, errorReason);
} catch (IllegalStateException e) {
// mSession is invalid - has been closed. This is extremely worrying because
// it means that the session state and getPreviewState() are not synced.
// This probably signals an error in the setup/teardown synchronization.
LOG.e("applyRepeatingRequestBuilder: session is invalid!", e,
"checkStarted:", checkStarted,
"currentThread:", Thread.currentThread().getName(),
"previewState:", getPreviewState(),
"bindState:", getBindState(),
"engineState:", getEngineState());
throw new CameraException(CameraException.REASON_DISCONNECTED);
} }
} }
} }
@ -286,6 +296,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
//region Protected APIs //region Protected APIs
@EngineThread
@NonNull @NonNull
@Override @Override
protected List<Size> getPreviewStreamAvailableSizes() { protected List<Size> getPreviewStreamAvailableSizes() {
@ -310,12 +321,13 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
} }
} }
@WorkerThread @EngineThread
@Override @Override
protected void onPreviewStreamSizeChanged() { protected void onPreviewStreamSizeChanged() {
restartBind(); restartBind();
} }
@EngineThread
@Override @Override
protected final boolean collectCameraInfo(@NonNull Facing facing) { protected final boolean collectCameraInfo(@NonNull Facing facing) {
int internalFacing = mMapper.mapFacing(facing); int internalFacing = mMapper.mapFacing(facing);
@ -353,6 +365,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
//region Start //region Start
@EngineThread
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
@NonNull @NonNull
@Override @Override
@ -402,6 +415,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
return task.getTask(); return task.getTask();
} }
@EngineThread
@NonNull @NonNull
@Override @Override
protected Task<Void> onStartBind() { protected Task<Void> onStartBind() {
@ -525,6 +539,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
return task.getTask(); return task.getTask();
} }
@EngineThread
@NonNull @NonNull
@Override @Override
protected Task<Void> onStartPreview() { protected Task<Void> onStartPreview() {
@ -569,6 +584,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
//region Stop //region Stop
@EngineThread
@NonNull @NonNull
@Override @Override
protected Task<Void> onStopPreview() { protected Task<Void> onStopPreview() {
@ -598,7 +614,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
return Tasks.forResult(null); return Tasks.forResult(null);
} }
@EngineThread
@NonNull @NonNull
@Override @Override
protected Task<Void> onStopBind() { protected Task<Void> onStopBind() {
@ -627,7 +643,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
return Tasks.forResult(null); return Tasks.forResult(null);
} }
@EngineThread
@NonNull @NonNull
@Override @Override
protected Task<Void> onStopEngine() { protected Task<Void> onStopEngine() {
@ -660,7 +676,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
//region Pictures //region Pictures
@WorkerThread @EngineThread
@Override @Override
protected void onTakePictureSnapshot(@NonNull final PictureResult.Stub stub, protected void onTakePictureSnapshot(@NonNull final PictureResult.Stub stub,
@NonNull final AspectRatio outputRatio, @NonNull final AspectRatio outputRatio,
@ -692,6 +708,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
} }
} }
@EngineThread
@Override @Override
protected void onTakePicture(@NonNull final PictureResult.Stub stub, boolean doMetering) { protected void onTakePicture(@NonNull final PictureResult.Stub stub, boolean doMetering) {
if (doMetering) { if (doMetering) {
@ -752,7 +769,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
//region Videos //region Videos
@WorkerThread @EngineThread
@Override @Override
protected void onTakeVideo(@NonNull VideoResult.Stub stub) { protected void onTakeVideo(@NonNull VideoResult.Stub stub) {
LOG.i("onTakeVideo", "called."); LOG.i("onTakeVideo", "called.");
@ -787,7 +804,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
} }
} }
@WorkerThread @EngineThread
@Override @Override
protected void onTakeVideoSnapshot(@NonNull VideoResult.Stub stub, protected void onTakeVideoSnapshot(@NonNull VideoResult.Stub stub,
@NonNull AspectRatio outputRatio) { @NonNull AspectRatio outputRatio) {
@ -832,7 +849,9 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
@Override @Override
public void onVideoRecordingEnd() { public void onVideoRecordingEnd() {
super.onVideoRecordingEnd(); super.onVideoRecordingEnd();
boolean needsIssue549Workaround = (mVideoRecorder instanceof Full2VideoRecorder) || // SnapshotRecorder will invoke this on its own thread which is risky, but if it was a
// snapshot, this function returns early so its safe.
boolean needsIssue549Workaround = (mVideoRecorder instanceof Full2VideoRecorder) &&
(readCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL, -1) (readCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL, -1)
== CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY); == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY);
if (needsIssue549Workaround) { if (needsIssue549Workaround) {
@ -843,7 +862,16 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
@Override @Override
public void onVideoResult(@Nullable VideoResult.Stub result, @Nullable Exception exception) { public void onVideoResult(@Nullable VideoResult.Stub result, @Nullable Exception exception) {
super.onVideoResult(result, exception); super.onVideoResult(result, exception);
maybeRestorePreviewTemplateAfterVideo(); // SnapshotRecorder will invoke this on its own thread, so let's post in our own thread
// and check camera state before trying to restore the preview. Engine might have been
// torn down in the engine thread while this was still being called.
mHandler.run(new Runnable() {
@Override
public void run() {
if (getBindState() < STATE_STARTED) return;
maybeRestorePreviewTemplateAfterVideo();
}
});
} }
/** /**
@ -854,6 +882,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
* This method avoids doing this twice by checking the request tag, as set by * This method avoids doing this twice by checking the request tag, as set by
* the {@link #createRepeatingRequestBuilder(int)} method. * the {@link #createRepeatingRequestBuilder(int)} method.
*/ */
@EngineThread
private void maybeRestorePreviewTemplateAfterVideo() { private void maybeRestorePreviewTemplateAfterVideo() {
int template = (int) mRepeatingRequestBuilder.build().getTag(); int template = (int) mRepeatingRequestBuilder.build().getTag();
if (template != CameraDevice.TEMPLATE_PREVIEW) { if (template != CameraDevice.TEMPLATE_PREVIEW) {

@ -47,7 +47,6 @@ import androidx.annotation.CallSuper;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
@ -303,7 +302,7 @@ public abstract class CameraEngine implements
final CameraException cameraException = (CameraException) throwable; final CameraException cameraException = (CameraException) throwable;
LOG.e("uncaughtException:", "Got CameraException:", cameraException, LOG.e("uncaughtException:", "Got CameraException:", cameraException,
"on engine state:", getEngineStateName()); "on engine state:", getEngineState());
if (fromExceptionHandler) { if (fromExceptionHandler) {
// Got to restart the handler. // Got to restart the handler.
thread.interrupt(); thread.interrupt();
@ -336,11 +335,6 @@ public abstract class CameraEngine implements
return mPreviewStep.getState(); return mPreviewStep.getState();
} }
@NonNull
private String getEngineStateName() {
return mEngineStep.getStateName();
}
private boolean canStartEngine() { private boolean canStartEngine() {
return mEngineStep.isStoppingOrStopped(); return mEngineStep.isStoppingOrStopped();
} }
@ -375,7 +369,7 @@ public abstract class CameraEngine implements
//region Start & Stop the engine //region Start & Stop the engine
@NonNull @NonNull
@WorkerThread @EngineThread
private Task<Void> startEngine() { private Task<Void> startEngine() {
if (canStartEngine()) { if (canStartEngine()) {
mEngineStep.doStart(false, new Callable<Task<Void>>() { mEngineStep.doStart(false, new Callable<Task<Void>>() {
@ -398,7 +392,7 @@ public abstract class CameraEngine implements
} }
@NonNull @NonNull
@WorkerThread @EngineThread
private Task<Void> stopEngine(boolean swallowExceptions) { private Task<Void> stopEngine(boolean swallowExceptions) {
if (needsStopEngine()) { if (needsStopEngine()) {
mEngineStep.doStop(swallowExceptions, new Callable<Task<Void>>() { mEngineStep.doStop(swallowExceptions, new Callable<Task<Void>>() {
@ -421,7 +415,7 @@ public abstract class CameraEngine implements
* @return a task * @return a task
*/ */
@NonNull @NonNull
@WorkerThread @EngineThread
protected abstract Task<Void> onStartEngine(); protected abstract Task<Void> onStartEngine();
/** /**
@ -431,7 +425,7 @@ public abstract class CameraEngine implements
* @return a task * @return a task
*/ */
@NonNull @NonNull
@WorkerThread @EngineThread
protected abstract Task<Void> onStopEngine(); protected abstract Task<Void> onStopEngine();
//endregion //endregion
@ -439,7 +433,7 @@ public abstract class CameraEngine implements
//region Start & Stop binding //region Start & Stop binding
@NonNull @NonNull
@WorkerThread @EngineThread
private Task<Void> startBind() { private Task<Void> startBind() {
if (canStartBind()) { if (canStartBind()) {
mBindStep.doStart(false, new Callable<Task<Void>>() { mBindStep.doStart(false, new Callable<Task<Void>>() {
@ -453,7 +447,7 @@ public abstract class CameraEngine implements
} }
@NonNull @NonNull
@WorkerThread @EngineThread
private Task<Void> stopBind(boolean swallowExceptions) { private Task<Void> stopBind(boolean swallowExceptions) {
if (needsStopBind()) { if (needsStopBind()) {
mBindStep.doStop(swallowExceptions, new Callable<Task<Void>>() { mBindStep.doStop(swallowExceptions, new Callable<Task<Void>>() {
@ -471,7 +465,7 @@ public abstract class CameraEngine implements
* @return a task * @return a task
*/ */
@NonNull @NonNull
@WorkerThread @EngineThread
protected abstract Task<Void> onStartBind(); protected abstract Task<Void> onStartBind();
/** /**
@ -481,7 +475,7 @@ public abstract class CameraEngine implements
* @return a task * @return a task
*/ */
@NonNull @NonNull
@WorkerThread @EngineThread
protected abstract Task<Void> onStopBind(); protected abstract Task<Void> onStopBind();
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
@ -522,7 +516,7 @@ public abstract class CameraEngine implements
//region Start & Stop preview //region Start & Stop preview
@NonNull @NonNull
@WorkerThread @EngineThread
private Task<Void> startPreview() { private Task<Void> startPreview() {
LOG.i("startPreview", "canStartPreview:", canStartPreview()); LOG.i("startPreview", "canStartPreview:", canStartPreview());
if (canStartPreview()) { if (canStartPreview()) {
@ -537,7 +531,7 @@ public abstract class CameraEngine implements
} }
@NonNull @NonNull
@WorkerThread @EngineThread
private Task<Void> stopPreview(boolean swallowExceptions) { private Task<Void> stopPreview(boolean swallowExceptions) {
LOG.i("stopPreview", LOG.i("stopPreview",
"needsStopPreview:", needsStopPreview(), "needsStopPreview:", needsStopPreview(),
@ -571,7 +565,7 @@ public abstract class CameraEngine implements
* @return a task * @return a task
*/ */
@NonNull @NonNull
@WorkerThread @EngineThread
protected abstract Task<Void> onStartPreview(); protected abstract Task<Void> onStartPreview();
/** /**
@ -581,7 +575,7 @@ public abstract class CameraEngine implements
* @return a task * @return a task
*/ */
@NonNull @NonNull
@WorkerThread @EngineThread
protected abstract Task<Void> onStopPreview(); protected abstract Task<Void> onStopPreview();
//endregion //endregion
@ -643,7 +637,7 @@ public abstract class CameraEngine implements
* *
* It basically depends on the step at which the preview stream size is actually used. * It basically depends on the step at which the preview stream size is actually used.
*/ */
@WorkerThread @EngineThread
protected abstract void onPreviewStreamSizeChanged(); protected abstract void onPreviewStreamSizeChanged();
@Override @Override
@ -676,7 +670,7 @@ public abstract class CameraEngine implements
* that would cause deadlocks due to us awaiting for {@link #stop()} to return. * that would cause deadlocks due to us awaiting for {@link #stop()} to return.
*/ */
public void destroy() { public void destroy() {
LOG.i("destroy:", "state:", getEngineStateName(), "thread:", Thread.currentThread()); LOG.i("destroy:", "state:", getEngineState(), "thread:", Thread.currentThread());
// Prevent CameraEngine leaks. Don't set to null, or exceptions // Prevent CameraEngine leaks. Don't set to null, or exceptions
// inside the standard stop() method might crash the main thread. // inside the standard stop() method might crash the main thread.
mHandler.getThread().setUncaughtExceptionHandler(new NoOpExceptionHandler()); mHandler.getThread().setUncaughtExceptionHandler(new NoOpExceptionHandler());
@ -710,7 +704,7 @@ public abstract class CameraEngine implements
@NonNull @NonNull
public Task<Void> start() { public Task<Void> start() {
LOG.i("Start:", "posting runnable. State:", getEngineStateName()); LOG.i("Start:", "posting runnable. State:", getEngineState());
final TaskCompletionSource<Void> outTask = new TaskCompletionSource<>(); final TaskCompletionSource<Void> outTask = new TaskCompletionSource<>();
mHandler.run(new Runnable() { mHandler.run(new Runnable() {
@Override @Override
@ -765,7 +759,7 @@ public abstract class CameraEngine implements
@NonNull @NonNull
private Task<Void> stop(final boolean swallowExceptions) { private Task<Void> stop(final boolean swallowExceptions) {
LOG.i("Stop:", "posting runnable. State:", getEngineStateName()); LOG.i("Stop:", "posting runnable. State:", getEngineState());
final TaskCompletionSource<Void> outTask = new TaskCompletionSource<>(); final TaskCompletionSource<Void> outTask = new TaskCompletionSource<>();
mHandler.run(new Runnable() { mHandler.run(new Runnable() {
@Override @Override
@ -1077,6 +1071,7 @@ public abstract class CameraEngine implements
* @param facing the facing value * @param facing the facing value
* @return true if we have one * @return true if we have one
*/ */
@EngineThread
protected abstract boolean collectCameraInfo(@NonNull Facing facing); protected abstract boolean collectCameraInfo(@NonNull Facing facing);
/** /**
@ -1256,6 +1251,7 @@ public abstract class CameraEngine implements
}); });
} }
@EngineThread
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
protected void onStopVideo() { protected void onStopVideo() {
if (mVideoRecorder != null) { if (mVideoRecorder != null) {
@ -1289,19 +1285,19 @@ public abstract class CameraEngine implements
mCallback.dispatchOnVideoRecordingEnd(); mCallback.dispatchOnVideoRecordingEnd();
} }
@WorkerThread @EngineThread
protected abstract void onTakePicture(@NonNull PictureResult.Stub stub, boolean doMetering); protected abstract void onTakePicture(@NonNull PictureResult.Stub stub, boolean doMetering);
@WorkerThread @EngineThread
protected abstract void onTakePictureSnapshot(@NonNull PictureResult.Stub stub, protected abstract void onTakePictureSnapshot(@NonNull PictureResult.Stub stub,
@NonNull AspectRatio outputRatio, @NonNull AspectRatio outputRatio,
boolean doMetering); boolean doMetering);
@WorkerThread @EngineThread
protected abstract void onTakeVideoSnapshot(@NonNull VideoResult.Stub stub, protected abstract void onTakeVideoSnapshot(@NonNull VideoResult.Stub stub,
@NonNull AspectRatio outputRatio); @NonNull AspectRatio outputRatio);
@WorkerThread @EngineThread
protected abstract void onTakeVideo(@NonNull VideoResult.Stub stub); protected abstract void onTakeVideo(@NonNull VideoResult.Stub stub);
//endregion //endregion
@ -1430,9 +1426,11 @@ public abstract class CameraEngine implements
* we can be sure that the camera is available (engineState == STARTED). * we can be sure that the camera is available (engineState == STARTED).
* @return a list of available sizes for preview * @return a list of available sizes for preview
*/ */
@EngineThread
@NonNull @NonNull
protected abstract List<Size> getPreviewStreamAvailableSizes(); protected abstract List<Size> getPreviewStreamAvailableSizes();
@EngineThread
@NonNull @NonNull
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
protected final Size computePreviewStreamSize() { protected final Size computePreviewStreamSize() {

@ -0,0 +1,7 @@
package com.otaliastudios.cameraview.engine;
/**
* Indicates that some action is being executed on the {@link CameraEngine} thread.
*/
@SuppressWarnings("WeakerAccess")
public @interface EngineThread {}

@ -127,7 +127,7 @@ public class OverlayLayout extends FrameLayout implements Overlay {
// to apply some scale (typically > 1). // to apply some scale (typically > 1).
float widthScale = canvas.getWidth() / (float) getWidth(); float widthScale = canvas.getWidth() / (float) getWidth();
float heightScale = canvas.getHeight() / (float) getHeight(); float heightScale = canvas.getHeight() / (float) getHeight();
LOG.i("draw", LOG.v("draw",
"target:", target, "target:", target,
"canvas:", canvas.getWidth() + "x" + canvas.getHeight(), "canvas:", canvas.getWidth() + "x" + canvas.getHeight(),
"view:", getWidth() + "x" + getHeight(), "view:", getWidth() + "x" + getHeight(),

@ -23,6 +23,7 @@ import com.otaliastudios.cameraview.size.AspectRatio;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10; import javax.microedition.khronos.opengles.GL10;
@ -69,8 +70,9 @@ public class GlCameraPreview extends FilterCameraPreview<GLSurfaceView, SurfaceT
private int mOutputTextureId = 0; private int mOutputTextureId = 0;
private SurfaceTexture mInputSurfaceTexture; private SurfaceTexture mInputSurfaceTexture;
private EglViewport mOutputViewport; private EglViewport mOutputViewport;
private final Set<RendererFrameCallback> mRendererFrameCallbacks // A synchronized set was not enough to avoid crashes, probably due to external classes
= Collections.synchronizedSet(new HashSet<RendererFrameCallback>()); // removing the callback while this set is being iterated. CopyOnWriteArraySet solves this.
private final Set<RendererFrameCallback> mRendererFrameCallbacks = new CopyOnWriteArraySet<>();
@VisibleForTesting float mCropScaleX = 1F; @VisibleForTesting float mCropScaleX = 1F;
@VisibleForTesting float mCropScaleY = 1F; @VisibleForTesting float mCropScaleY = 1F;
private View mRootView; private View mRootView;
@ -156,11 +158,8 @@ public class GlCameraPreview extends FilterCameraPreview<GLSurfaceView, SurfaceT
getView().queueEvent(new Runnable() { getView().queueEvent(new Runnable() {
@Override @Override
public void run() { public void run() {
// Need to synchronize when iterating the Collections.synchronizedSet for (RendererFrameCallback callback : mRendererFrameCallbacks) {
synchronized (mRendererFrameCallbacks) { callback.onRendererTextureCreated(mOutputTextureId);
for (RendererFrameCallback callback : mRendererFrameCallbacks) {
callback.onRendererTextureCreated(mOutputTextureId);
}
} }
} }
}); });
@ -225,11 +224,8 @@ public class GlCameraPreview extends FilterCameraPreview<GLSurfaceView, SurfaceT
} }
mOutputViewport.drawFrame(mInputSurfaceTexture.getTimestamp() / 1000L, mOutputViewport.drawFrame(mInputSurfaceTexture.getTimestamp() / 1000L,
mOutputTextureId, mTransformMatrix); mOutputTextureId, mTransformMatrix);
synchronized (mRendererFrameCallbacks) { for (RendererFrameCallback callback : mRendererFrameCallbacks) {
// Need to synchronize when iterating the Collections.synchronizedSet callback.onRendererFrame(mInputSurfaceTexture, mCropScaleX, mCropScaleY);
for (RendererFrameCallback callback : mRendererFrameCallbacks) {
callback.onRendererFrame(mInputSurfaceTexture, mCropScaleX, mCropScaleY);
}
} }
} }
} }
@ -353,12 +349,8 @@ public class GlCameraPreview extends FilterCameraPreview<GLSurfaceView, SurfaceT
if (mOutputViewport != null) { if (mOutputViewport != null) {
mOutputViewport.setFilter(filter); mOutputViewport.setFilter(filter);
} }
for (RendererFrameCallback callback : mRendererFrameCallbacks) {
// Need to synchronize when iterating the Collections.synchronizedSet callback.onRendererFilterChanged(filter);
synchronized (mRendererFrameCallbacks) {
for (RendererFrameCallback callback : mRendererFrameCallbacks) {
callback.onRendererFilterChanged(filter);
}
} }
} }
}); });

@ -241,7 +241,9 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
@Override @Override
public void onEncodingStart() { public void onEncodingStart() {
//do nothing // This would be the most correct place to call dispatchVideoRecordingStart. However,
// after this we'll post the call on the UI thread which can take some time. To compensate
// this, we call dispatchVideoRecordingStart() a bit earlier in this class (onStart()).
} }
@Override @Override

@ -231,11 +231,11 @@ public class AudioMediaEncoder extends MediaEncoder {
} else { } else {
mCurrentReadBytes = mAudioRecord.read(mCurrentBuffer, mConfig.frameSize()); mCurrentReadBytes = mAudioRecord.read(mCurrentBuffer, mConfig.frameSize());
} }
LOG.i("read thread - eos:", endOfStream, "- Read new audio frame. Bytes:", LOG.v("read thread - eos:", endOfStream, "- Read new audio frame. Bytes:",
mCurrentReadBytes); mCurrentReadBytes);
if (mCurrentReadBytes > 0) { // Good read: increase PTS. if (mCurrentReadBytes > 0) { // Good read: increase PTS.
increaseTime(mCurrentReadBytes, endOfStream); increaseTime(mCurrentReadBytes, endOfStream);
LOG.i("read thread - eos:", endOfStream, "- mLastTimeUs:", mLastTimeUs); LOG.v("read thread - eos:", endOfStream, "- mLastTimeUs:", mLastTimeUs);
mCurrentBuffer.limit(mCurrentReadBytes); mCurrentBuffer.limit(mCurrentReadBytes);
enqueue(mCurrentBuffer, mLastTimeUs, endOfStream); enqueue(mCurrentBuffer, mLastTimeUs, endOfStream);
} else if (mCurrentReadBytes == AudioRecord.ERROR_INVALID_OPERATION) { } else if (mCurrentReadBytes == AudioRecord.ERROR_INVALID_OPERATION) {
@ -358,7 +358,7 @@ public class AudioMediaEncoder extends MediaEncoder {
if (mInputBufferQueue.isEmpty()) { if (mInputBufferQueue.isEmpty()) {
skipFrames(2); skipFrames(2);
} else { } else {
LOG.i("encoding thread - performing", mInputBufferQueue.size(), LOG.v("encoding thread - performing", mInputBufferQueue.size(),
"pending operations."); "pending operations.");
InputBuffer inputBuffer; InputBuffer inputBuffer;
while ((inputBuffer = mInputBufferQueue.peek()) != null) { while ((inputBuffer = mInputBufferQueue.peek()) != null) {
@ -408,7 +408,7 @@ public class AudioMediaEncoder extends MediaEncoder {
private void encode(@NonNull InputBuffer buffer) { private void encode(@NonNull InputBuffer buffer) {
long executeStart = System.nanoTime() / 1000000; long executeStart = System.nanoTime() / 1000000;
LOG.i("encoding thread - performing pending operation for timestamp:", LOG.v("encoding thread - performing pending operation for timestamp:",
buffer.timestamp, "- encoding."); buffer.timestamp, "- encoding.");
// NOTE: this copy is prob. the worst part here for performance // NOTE: this copy is prob. the worst part here for performance
buffer.data.put(buffer.source); buffer.data.put(buffer.source);
@ -417,7 +417,7 @@ public class AudioMediaEncoder extends MediaEncoder {
encodeInputBuffer(buffer); encodeInputBuffer(buffer);
boolean eos = buffer.isEndOfStream; boolean eos = buffer.isEndOfStream;
mInputBufferPool.recycle(buffer); mInputBufferPool.recycle(buffer);
LOG.i("encoding thread - performing pending operation for timestamp:", LOG.v("encoding thread - performing pending operation for timestamp:",
buffer.timestamp, "- draining."); buffer.timestamp, "- draining.");
// NOTE: can consider calling this drainOutput on yet another thread, which would let us // NOTE: can consider calling this drainOutput on yet another thread, which would let us
// use an even smaller BUFFER_POOL_MAX_SIZE without losing audio frames. But this way // use an even smaller BUFFER_POOL_MAX_SIZE without losing audio frames. But this way

@ -397,7 +397,7 @@ public abstract class MediaEncoder {
@SuppressLint("LogNotTimber") @SuppressLint("LogNotTimber")
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
protected final void drainOutput(boolean drainAll) { protected final void drainOutput(boolean drainAll) {
LOG.i(mName, "DRAINING - EOS:", drainAll); LOG.v(mName, "DRAINING - EOS:", drainAll);
if (mMediaCodec == null) { if (mMediaCodec == null) {
LOG.e("drain() was called before prepare() or after releasing."); LOG.e("drain() was called before prepare() or after releasing.");
return; return;
@ -459,7 +459,7 @@ public abstract class MediaEncoder {
+ mLastTimeUs - mFirstTimeUs; + mLastTimeUs - mFirstTimeUs;
// Write. // Write.
LOG.i(mName, "DRAINING - About to write(). Adjusted presentation:", LOG.v(mName, "DRAINING - About to write(). Adjusted presentation:",
mBufferInfo.presentationTimeUs); mBufferInfo.presentationTimeUs);
OutputBuffer buffer = mOutputBufferPool.get(); OutputBuffer buffer = mOutputBufferPool.get();
//noinspection ConstantConditions //noinspection ConstantConditions

@ -186,7 +186,7 @@ public class MediaEncoderEngine {
*/ */
@SuppressWarnings("SameParameterValue") @SuppressWarnings("SameParameterValue")
public final void notify(final String event, final Object data) { public final void notify(final String event, final Object data) {
LOG.i("Passing event to encoders:", event); LOG.v("Passing event to encoders:", event);
for (MediaEncoder encoder : mEncoders) { for (MediaEncoder encoder : mEncoders) {
encoder.notify(event, data); encoder.notify(event, data);
} }

@ -124,7 +124,7 @@ public class TextureMediaEncoder extends VideoMediaEncoder<TextureConfig> {
// Always render the first few frames, or muxer fails. // Always render the first few frames, or muxer fails.
return true; return true;
} else if (getPendingEvents(FRAME_EVENT) > 2) { } else if (getPendingEvents(FRAME_EVENT) > 2) {
LOG.w("shouldRenderFrame - Dropping, we already have too many pending events:", LOG.v("shouldRenderFrame - Dropping, we already have too many pending events:",
getPendingEvents(FRAME_EVENT)); getPendingEvents(FRAME_EVENT));
return false; return false;
} else { } else {
@ -177,14 +177,14 @@ public class TextureMediaEncoder extends VideoMediaEncoder<TextureConfig> {
} }
// First, drain any previous data. // First, drain any previous data.
LOG.i("onEvent -", LOG.v("onEvent -",
"frameNumber:", mFrameNumber, "frameNumber:", mFrameNumber,
"timestampUs:", frame.timestampUs(), "timestampUs:", frame.timestampUs(),
"- draining."); "- draining.");
drainOutput(false); drainOutput(false);
// Then draw on the surface. // Then draw on the surface.
LOG.i("onEvent -", LOG.v("onEvent -",
"frameNumber:", mFrameNumber, "frameNumber:", mFrameNumber,
"timestampUs:", frame.timestampUs(), "timestampUs:", frame.timestampUs(),
"- rendering."); "- rendering.");

@ -2,7 +2,6 @@ apply plugin: 'com.android.application'
android { android {
compileSdkVersion rootProject.ext.compileSdkVersion compileSdkVersion rootProject.ext.compileSdkVersion
// buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig { defaultConfig {
applicationId "com.otaliastudios.cameraview.demo" applicationId "com.otaliastudios.cameraview.demo"
@ -23,6 +22,6 @@ android {
dependencies { dependencies {
implementation project(':cameraview') implementation project(':cameraview')
implementation 'androidx.appcompat:appcompat:1.1.0-rc01' implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.google.android.material:material:1.1.0-alpha09' implementation 'com.google.android.material:material:1.1.0-beta01'
} }

@ -190,12 +190,6 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
OptionView view = (OptionView) group.getChildAt(i); OptionView view = (OptionView) group.getChildAt(i);
view.onCameraOpened(camera, options); view.onCameraOpened(camera, options);
} }
camera.addFrameProcessor(new FrameProcessor() {
@Override
public void process(@NonNull Frame frame) {
}
});
} }
@Override @Override

@ -8,6 +8,13 @@ order: 3
New versions are released through GitHub, so the reference page is the [GitHub Releases](https://github.com/natario1/CameraView/releases) page. New versions are released through GitHub, so the reference page is the [GitHub Releases](https://github.com/natario1/CameraView/releases) page.
### v2.3.1
- [Video] Improvement: better timing for `onVideoRecordingStart()` thanks to [@agrawalsuneet][agrawalsuneet] ([#632][632])
- [Video, Camera1] Fix: fixed video errors when starting on specific devices ([#617][617])
- [Video] Fix: fixed crash when closing the app during video snapshots ([#630][630])
- [Preview] Fix: fixed crash when using `GL_SURFACE` ([#630][630])
## v2.3.0 ## v2.3.0
- [Camera2, Metering] New: `startAutoFocus` is much more powerful and does 3A metering (AF, AE, AWB) ([#574][574]) - [Camera2, Metering] New: `startAutoFocus` is much more powerful and does 3A metering (AF, AE, AWB) ([#574][574])
@ -320,3 +327,6 @@ https://github.com/natario1/CameraView/compare/v1.2.3...v1.3.0
[574]: https://github.com/natario1/CameraView/pull/574 [574]: https://github.com/natario1/CameraView/pull/574
[580]: https://github.com/natario1/CameraView/pull/580 [580]: https://github.com/natario1/CameraView/pull/580
[588]: https://github.com/natario1/CameraView/pull/588 [588]: https://github.com/natario1/CameraView/pull/588
[617]: https://github.com/natario1/CameraView/pull/617
[630]: https://github.com/natario1/CameraView/pull/630
[632]: https://github.com/natario1/CameraView/pull/632

@ -24,7 +24,7 @@ allprojects {
Then simply download the latest version: Then simply download the latest version:
```groovy ```groovy
api 'com.otaliastudios:cameraview:2.3.0' api 'com.otaliastudios:cameraview:2.3.1'
``` ```
No other configuration steps are needed. No other configuration steps are needed.
Loading…
Cancel
Save