pull/696/head
Mattia Iavarone 6 years ago
parent ba68d9cc29
commit 5468b4d92e
  1. 54
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/CameraIntegrationTest.java
  2. 20
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java

@ -193,22 +193,26 @@ public abstract class CameraIntegrationTest<E extends CameraEngine> extends Base
@SuppressWarnings("UnusedReturnValue") @SuppressWarnings("UnusedReturnValue")
@Nullable @Nullable
private VideoResult waitForVideoResult(boolean expectSuccess) { private VideoResult waitForVideoResult(boolean expectSuccess) {
// CountDownLatch for onVideoRecordingEnd. Op<Boolean> wait1 = new Op<>();
CountDownLatch onVideoRecordingEnd = new CountDownLatch(1); Op<VideoResult> wait2 = new Op<>();
doCountDown(onVideoRecordingEnd).when(listener).onVideoRecordingEnd(); doEndOp(wait1, true).when(listener).onVideoRecordingEnd();
doEndOp(wait1, false).when(listener).onCameraError(argThat(new ArgumentMatcher<CameraException>() {
// Op for onVideoTaken. @Override
final Op<VideoResult> video = new Op<>(); public boolean matches(CameraException argument) {
doEndOp(video, 0).when(listener).onVideoTaken(any(VideoResult.class)); return argument.getReason() == CameraException.REASON_VIDEO_FAILED;
doEndOp(video, null).when(listener).onCameraError(argThat(new ArgumentMatcher<CameraException>() { }
}));
doEndOp(wait2, 0).when(listener).onVideoTaken(any(VideoResult.class));
doEndOp(wait2, null).when(listener).onCameraError(argThat(new ArgumentMatcher<CameraException>() {
@Override @Override
public boolean matches(CameraException argument) { public boolean matches(CameraException argument) {
return argument.getReason() == CameraException.REASON_VIDEO_FAILED; return argument.getReason() == CameraException.REASON_VIDEO_FAILED;
} }
})); }));
// Wait for onVideoTaken and check. // First wait for onVideoRecordingEnd().
VideoResult result = video.await(DELAY); LOG.i("[WAIT VIDEO]", "Waiting for onVideoRecordingEnd()...");
Boolean wait1Result = wait1.await(DELAY);
// It seems that when running all the tests together, the device can go in some // It seems that when running all the tests together, the device can go in some
// power saving mode which makes the CPU extremely slow. This is especially problematic // power saving mode which makes the CPU extremely slow. This is especially problematic
@ -217,24 +221,26 @@ public abstract class CameraIntegrationTest<E extends CameraEngine> extends Base
if (expectSuccess && camera.isTakingVideo()) { if (expectSuccess && camera.isTakingVideo()) {
while (camera.isTakingVideo()) { while (camera.isTakingVideo()) {
LOG.w("[WAIT VIDEO]", "Waiting extra", DELAY, "milliseconds..."); LOG.w("[WAIT VIDEO]", "Waiting extra", DELAY, "milliseconds...");
video.listen(); wait1.listen();
result = video.await(DELAY); wait1Result = wait1.await(DELAY);
} }
// Sleep another 1000, because camera.isTakingVideo() might return false even
// if the result still has to be dispatched. Rare but could happen.
try { Thread.sleep(1000); } catch (InterruptedException ignore) {}
} }
// Now we should be OK. // Now wait for onVideoResult().
LOG.i("[WAIT VIDEO]", "Waiting for onVideoTaken()...");
VideoResult wait2Result = wait2.await(DELAY);
// Assert.
if (expectSuccess) { if (expectSuccess) {
LOG.i("[WAIT VIDEO]", "Expecting success."); assertNotNull("Should call onVideoRecordingEnd", wait1Result);
assertEquals("Should call onVideoRecordingEnd", 0, onVideoRecordingEnd.getCount()); assertTrue("Should call onVideoRecordingEnd", wait1Result);
assertNotNull("Should end video", result); assertNotNull("Should call onVideoTaken", wait2Result);
} else { } else {
LOG.i("[WAIT VIDEO]", "Expecting failure."); assertTrue("Should not call onVideoRecordingEnd",
assertNull("Should not end video", result); wait1Result == null || !wait1Result);
assertNull("Should not call onVideoTaken", wait2Result);
} }
return result; return wait2Result;
} }
@Nullable @Nullable
@ -635,7 +641,7 @@ public abstract class CameraIntegrationTest<E extends CameraEngine> extends Base
// Assuming video frame rate is 20... // Assuming video frame rate is 20...
//noinspection ConstantConditions //noinspection ConstantConditions
camera.setVideoBitRate((int) estimateVideoBitRate(camera.getVideoSize(), 20)); camera.setVideoBitRate((int) estimateVideoBitRate(camera.getVideoSize(), 20));
camera.setVideoMaxSize(estimateVideoBytes(camera.getVideoBitRate(), 6000)); camera.setVideoMaxSize(estimateVideoBytes(camera.getVideoBitRate(), 5000));
takeVideoSync(true); takeVideoSync(true);
waitForVideoResult(true); waitForVideoResult(true);
} }
@ -651,7 +657,7 @@ public abstract class CameraIntegrationTest<E extends CameraEngine> extends Base
//noinspection ConstantConditions //noinspection ConstantConditions
camera.setVideoBitRate((int) estimateVideoBitRate(camera.getSnapshotSize(), camera.setVideoBitRate((int) estimateVideoBitRate(camera.getSnapshotSize(),
(int) camera.getPreviewFrameRate())); (int) camera.getPreviewFrameRate()));
camera.setVideoMaxSize(estimateVideoBytes(camera.getVideoBitRate(), 6000)); camera.setVideoMaxSize(estimateVideoBytes(camera.getVideoBitRate(), 5000));
takeVideoSnapshotSync(true); takeVideoSnapshotSync(true);
waitForVideoResult(true); waitForVideoResult(true);
} }

@ -277,9 +277,7 @@ public abstract class CameraEngine implements
// (at least in Camera1, e.g. onError). // (at least in Camera1, e.g. onError).
if (fromExceptionHandler) { if (fromExceptionHandler) {
LOG.e("EXCEPTION:", "Handler thread is gone. Replacing."); LOG.e("EXCEPTION:", "Handler thread is gone. Replacing.");
thread.interrupt(); recreateHandler();
mHandler = WorkerHandler.get("CameraViewEngine");
mHandler.getThread().setUncaughtExceptionHandler(new CrashExceptionHandler());
} }
// 2. Depending on the exception, we must destroy(false|true) to release resources, and // 2. Depending on the exception, we must destroy(false|true) to release resources, and
@ -311,6 +309,12 @@ public abstract class CameraEngine implements
}); });
} }
private void recreateHandler() {
mHandler.getThread().interrupt();
mHandler = WorkerHandler.get("CameraViewEngine");
mHandler.getThread().setUncaughtExceptionHandler(new CrashExceptionHandler());
}
//endregion //endregion
//region State management //region State management
@ -348,10 +352,6 @@ public abstract class CameraEngine implements
// 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());
} }
// Instead of waiting forever, it's better to wait a fixed amount of time and loop.
// This frees the thread in between the loop, and possibly solves deadlock issues
// in the internal camera implementation (see Camera1Engine.onStopEngine() and
// Camera2Engine.onStopEngine()).
// Cannot use Tasks.await() because we might be on the UI thread. // Cannot use Tasks.await() because we might be on the UI thread.
final CountDownLatch latch = new CountDownLatch(1); final CountDownLatch latch = new CountDownLatch(1);
stop(true).addOnCompleteListener( stop(true).addOnCompleteListener(
@ -365,10 +365,14 @@ public abstract class CameraEngine implements
try { try {
boolean success = latch.await(6, TimeUnit.SECONDS); boolean success = latch.await(6, TimeUnit.SECONDS);
if (!success) { if (!success) {
// This thread is likely stuck. The reason might be deadlock issues in the internal
// camera implementation, at least in emulators: see Camera1Engine and Camera2Engine
// onStopEngine() implementation and comments.
LOG.w("DESTROY: Could not destroy synchronously after 6 seconds.", LOG.w("DESTROY: Could not destroy synchronously after 6 seconds.",
"Current thread:", Thread.currentThread(), "Current thread:", Thread.currentThread(),
"Handler thread: ", mHandler.getThread(), "Handler thread: ", mHandler.getThread(),
"Trying again..."); "Trying again on a new thread...");
recreateHandler();
destroy(unrecoverably); destroy(unrecoverably);
} }
} catch (InterruptedException ignore) {} } catch (InterruptedException ignore) {}

Loading…
Cancel
Save