Add synchronization in SnapshotVideoRecorder. Fixes #642

pull/651/head
Mattia Iavarone 6 years ago
parent 96444d11c1
commit 68b993d2c7
  1. 56
      cameraview/src/main/java/com/otaliastudios/cameraview/video/SnapshotVideoRecorder.java
  2. 42
      cameraview/src/main/java/com/otaliastudios/cameraview/video/VideoRecorder.java

@ -51,6 +51,7 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
private static final int STATE_NOT_RECORDING = 1; private static final int STATE_NOT_RECORDING = 1;
private MediaEncoderEngine mEncoderEngine; private MediaEncoderEngine mEncoderEngine;
private final Object mEncoderEngineLock = new Object();
private GlCameraPreview mPreview; private GlCameraPreview mPreview;
private int mCurrentState = STATE_NOT_RECORDING; private int mCurrentState = STATE_NOT_RECORDING;
@ -82,14 +83,21 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
dispatchVideoRecordingStart(); dispatchVideoRecordingStart();
} }
// Can be called different threads
@Override @Override
protected void onStop(boolean isCameraShutdown) { protected void onStop(boolean isCameraShutdown) {
if (isCameraShutdown) { if (isCameraShutdown) {
// The renderer callback might never be called. From my tests, it's not. // The renderer callback might never be called. From my tests, it's not,
// so we can't wait for that callback to stop the encoder engine.
LOG.i("Stopping the encoder engine from isCameraShutdown."); LOG.i("Stopping the encoder engine from isCameraShutdown.");
mDesiredState = STATE_NOT_RECORDING; mDesiredState = STATE_NOT_RECORDING;
mCurrentState = STATE_NOT_RECORDING; mCurrentState = STATE_NOT_RECORDING;
mEncoderEngine.stop(); synchronized (mEncoderEngineLock) {
if (mEncoderEngine != null) {
mEncoderEngine.stop();
mEncoderEngine = null;
}
}
} else { } else {
mDesiredState = STATE_NOT_RECORDING; mDesiredState = STATE_NOT_RECORDING;
} }
@ -104,12 +112,15 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
} }
} }
@RendererThread
@Override @Override
public void onRendererFilterChanged(@NonNull Filter filter) { public void onRendererFilterChanged(@NonNull Filter filter) {
mCurrentFilter = filter.copy(); mCurrentFilter = filter.copy();
if (mEncoderEngine != null) { mCurrentFilter.setSize(mResult.size.getWidth(), mResult.size.getHeight());
mCurrentFilter.setSize(mResult.size.getWidth(), mResult.size.getHeight()); synchronized (mEncoderEngineLock) {
mEncoderEngine.notify(TextureMediaEncoder.FILTER_EVENT, mCurrentFilter); if (mEncoderEngine != null) {
mEncoderEngine.notify(TextureMediaEncoder.FILTER_EVENT, mCurrentFilter);
}
} }
} }
@ -206,14 +217,16 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
} }
// Engine // Engine
mEncoderEngine = new MediaEncoderEngine(mResult.file, synchronized (mEncoderEngineLock) {
videoEncoder, mEncoderEngine = new MediaEncoderEngine(mResult.file,
audioEncoder, videoEncoder,
mResult.maxDuration, audioEncoder,
mResult.maxSize, mResult.maxDuration,
SnapshotVideoRecorder.this); mResult.maxSize,
mEncoderEngine.notify(TextureMediaEncoder.FILTER_EVENT, mCurrentFilter); SnapshotVideoRecorder.this);
mEncoderEngine.start(); mEncoderEngine.notify(TextureMediaEncoder.FILTER_EVENT, mCurrentFilter);
mEncoderEngine.start();
}
mCurrentState = STATE_RECORDING; mCurrentState = STATE_RECORDING;
} }
@ -226,15 +239,22 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
// NOTE: this is an approximation but it seems to work: // NOTE: this is an approximation but it seems to work:
frame.timestampMillis = System.currentTimeMillis(); frame.timestampMillis = System.currentTimeMillis();
surfaceTexture.getTransformMatrix(frame.transform); surfaceTexture.getTransformMatrix(frame.transform);
if (mEncoderEngine != null) { // Can happen on teardown. At least it used to. synchronized (mEncoderEngineLock) {
mEncoderEngine.notify(TextureMediaEncoder.FRAME_EVENT, frame); if (mEncoderEngine != null) { // Can be null on teardown.
mEncoderEngine.notify(TextureMediaEncoder.FRAME_EVENT, frame);
}
} }
} }
if (mCurrentState == STATE_RECORDING && mDesiredState == STATE_NOT_RECORDING) { if (mCurrentState == STATE_RECORDING && mDesiredState == STATE_NOT_RECORDING) {
LOG.i("Stopping the encoder engine."); LOG.i("Stopping the encoder engine.");
mCurrentState = STATE_NOT_RECORDING; mCurrentState = STATE_NOT_RECORDING;
mEncoderEngine.stop(); synchronized (mEncoderEngineLock) {
if (mEncoderEngine != null) {
mEncoderEngine.stop();
mEncoderEngine = null;
}
}
} }
} }
@ -280,7 +300,9 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
mOverlayDrawer.release(); mOverlayDrawer.release();
mOverlayDrawer = null; mOverlayDrawer = null;
} }
mEncoderEngine = null; synchronized (mEncoderEngineLock) {
mEncoderEngine = null;
}
dispatchResult(); dispatchResult();
} }
} }

@ -50,6 +50,7 @@ public abstract class VideoRecorder {
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
protected Exception mError; protected Exception mError;
private int mState; private int mState;
private final Object mStateLock = new Object();
/** /**
* Creates a new video recorder. * Creates a new video recorder.
@ -66,12 +67,14 @@ public abstract class VideoRecorder {
* @param stub the video stub * @param stub the video stub
*/ */
public final void start(@NonNull VideoResult.Stub stub) { public final void start(@NonNull VideoResult.Stub stub) {
if (mState != STATE_IDLE) { synchronized (mStateLock) {
LOG.e("start:", "called twice, or while stopping! " + if (mState != STATE_IDLE) {
"Ignoring. state:", mState); LOG.e("start:", "called twice, or while stopping! " +
return; "Ignoring. state:", mState);
return;
}
mState = STATE_RECORDING;
} }
mState = STATE_RECORDING;
mResult = stub; mResult = stub;
onStart(); onStart();
} }
@ -81,12 +84,15 @@ public abstract class VideoRecorder {
* @param isCameraShutdown whether this is a full shutdown, camera is being closed * @param isCameraShutdown whether this is a full shutdown, camera is being closed
*/ */
public final void stop(boolean isCameraShutdown) { public final void stop(boolean isCameraShutdown) {
if (mState == STATE_IDLE) { synchronized (mStateLock) {
LOG.e("stop:", "called twice, or called before start! " + if (mState == STATE_IDLE) {
"Ignoring. isCameraShutdown:", isCameraShutdown); // Do not check for STOPPING! See onStop().
return; LOG.e("stop:", "called twice, or called before start! " +
"Ignoring. isCameraShutdown:", isCameraShutdown);
return;
}
mState = STATE_STOPPING;
} }
mState = STATE_STOPPING;
onStop(isCameraShutdown); onStop(isCameraShutdown);
} }
@ -96,11 +102,19 @@ public abstract class VideoRecorder {
*/ */
public boolean isRecording() { public boolean isRecording() {
// true if not idle. // true if not idle.
return mState != STATE_IDLE; synchronized (mStateLock) {
return mState != STATE_IDLE;
}
} }
protected abstract void onStart(); protected abstract void onStart();
/**
* Should stop recording as fast as possible. This can be called twice because the
* shutdown boolean might be different.
*
* @param isCameraShutdown whether camera is shutting down
*/
protected abstract void onStop(boolean isCameraShutdown); protected abstract void onStop(boolean isCameraShutdown);
/** /**
@ -108,8 +122,10 @@ public abstract class VideoRecorder {
* either with some error (null result) or with the actual stub, filled. * either with some error (null result) or with the actual stub, filled.
*/ */
protected final void dispatchResult() { protected final void dispatchResult() {
if (!isRecording()) return; synchronized (mStateLock) {
mState = STATE_IDLE; if (!isRecording()) return;
mState = STATE_IDLE;
}
onDispatchResult(); onDispatchResult();
if (mListener != null) { if (mListener != null) {
mListener.onVideoResult(mResult, mError); mListener.onVideoResult(mResult, mError);

Loading…
Cancel
Save