Rename some methods, have controllers implement onSurfaceDestroyed, revisit controller lifecycle

v2
Mattia Iavarone 6 years ago
parent 7df47cdf45
commit 470ad7783f
  1. 4
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraPreviewTest.java
  2. 4
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/TextureCameraPreviewTest.java
  3. 118
      cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java
  4. 20
      cameraview/src/main/views/com/otaliastudios/cameraview/CameraPreview.java
  5. 65
      cameraview/src/main/views/com/otaliastudios/cameraview/GLCameraPreview.java
  6. 7
      cameraview/src/main/views/com/otaliastudios/cameraview/SurfaceCameraPreview.java
  7. 6
      cameraview/src/main/views/com/otaliastudios/cameraview/TextureCameraPreview.java

@ -79,7 +79,7 @@ public abstract class CameraPreviewTest extends BaseTest {
@Test
public void testDefaults() {
ensureAvailable();
assertTrue(preview.isReady());
assertTrue(preview.hasSurface());
assertNotNull(preview.getView());
assertNotNull(preview.getOutputClass());
}
@ -128,7 +128,7 @@ public abstract class CameraPreviewTest extends BaseTest {
// Since desired is 'desired', let's fake a new view size that is consistent with it.
// Ensure crop is not happening anymore.
preview.mCropTask.listen();
preview.onSurfaceSizeChanged((int) (50f * desired), 50); // Wait...
preview.dispatchOnOutputSurfaceSizeChanged((int) (50f * desired), 50); // Wait...
preview.mCropTask.await();
assertEquals(desired, getViewAspectRatioWithScale(), 0.01f);
assertFalse(preview.isCropping());

@ -22,7 +22,7 @@ public class TextureCameraPreviewTest extends CameraPreviewTest {
if (isHardwareAccelerated()) {
super.ensureAvailable();
} else {
preview.onSurfaceAvailable(
preview.dispatchOnOutputSurfaceAvailable(
surfaceSize.getWidth(),
surfaceSize.getHeight());
}
@ -33,7 +33,7 @@ public class TextureCameraPreviewTest extends CameraPreviewTest {
super.ensureDestroyed();
if (!isHardwareAccelerated()) {
// Ensure it is called.
preview.onSurfaceDestroyed();
preview.dispatchOnOutputSurfaceDestroyed();
}
}

@ -12,7 +12,6 @@ import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import android.util.Log;
import android.support.media.ExifInterface;
import android.view.SurfaceHolder;
@ -77,6 +76,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
public void run() {
LOG.i("onSurfaceAvailable:", "Inside handler. About to bind.");
if (shouldBindToSurface()) bindToSurface();
if (shouldStartPreview()) startPreview("onSurfaceAvailable");
}
});
}
@ -99,13 +99,25 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
LOG.i("onSurfaceChanged:", "Computed a new preview size. Going on.");
mPreviewSize = newSize;
mCamera.stopPreview();
applySizesAndStartPreview("onSurfaceChanged:");
startPreview("onSurfaceChanged:");
}
});
}
@Override
public void onSurfaceDestroyed() {
LOG.i("onSurfaceDestroyed");
schedule(null, true, new Runnable() {
@Override
public void run() {
stopPreview();
if (mIsBound) unbindFromSurface();
}
});
}
private boolean shouldBindToSurface() {
return isCameraAvailable() && mPreview != null && mPreview.isReady() && !mIsBound;
return isCameraAvailable() && mPreview != null && mPreview.hasSurface() && !mIsBound;
}
// The act of binding an "open" camera to a "ready" preview.
@ -121,18 +133,37 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
mCamera.setPreviewTexture((SurfaceTexture) output);
}
} catch (IOException e) {
Log.e("bindToSurface:", "Failed to bind.", e);
LOG.e("bindToSurface:", "Failed to bind.", e);
throw new CameraException(e, CameraException.REASON_FAILED_TO_START_PREVIEW);
}
mCaptureSize = computeCaptureSize();
mPreviewSize = computePreviewSize(sizesFromList(mCamera.getParameters().getSupportedPreviewSizes()));
applySizesAndStartPreview("bindToSurface:");
mIsBound = true;
}
@WorkerThread
private void unbindFromSurface() {
mIsBound = false;
mPreviewSize = null;
mCaptureSize = null;
try {
if (mPreview.getOutputClass() == SurfaceHolder.class) {
mCamera.setPreviewDisplay(null);
} else {
mCamera.setPreviewTexture(null);
}
} catch (IOException e) {
LOG.e("unbindFromSurface", "Could not release surface", e);
}
}
private boolean shouldStartPreview() {
return isCameraAvailable() && mIsBound;
}
// To be called when the preview size is setup or changed.
private void applySizesAndStartPreview(String log) {
private void startPreview(String log) {
LOG.i(log, "Dispatching onCameraPreviewSizeChanged.");
mCameraCallbacks.onCameraPreviewSizeChanged();
@ -159,24 +190,28 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
LOG.i(log, "Started preview.");
}
@WorkerThread
@Override
void onStart() {
if (isCameraAvailable()) {
LOG.w("onStart:", "Camera not available. Should not happen.");
onStop(); // Should not happen.
private void stopPreview() {
mPreviewFormat = 0;
mFrameManager.release();
mCamera.setPreviewCallbackWithBuffer(null); // Release anything left
try {
mCamera.stopPreview();
} catch (Exception e) {
LOG.e("stopPreview", "Could not stop preview", e);
}
if (collectCameraId()) {
}
private void createCamera() {
try {
mCamera = Camera.open(mCameraId);
} catch (Exception e) {
LOG.e("onStart:", "Failed to connect. Maybe in use by another app?");
LOG.e("createCamera:", "Failed to connect. Maybe in use by another app?");
throw new CameraException(e, CameraException.REASON_FAILED_TO_CONNECT);
}
mCamera.setErrorCallback(this);
// Set parameters that might have been set before the camera was opened.
LOG.i("onStart:", "Applying default parameters.");
LOG.i("createCamera:", "Applying default parameters.");
Camera.Parameters params = mCamera.getParameters();
mCameraOptions = new CameraOptions(params, flip(REF_SENSOR, REF_VIEW));
applyDefaultFocus(params);
@ -187,10 +222,32 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
mergePlaySound(mPlaySounds);
params.setRecordingHint(mMode == Mode.VIDEO);
mCamera.setParameters(params);
// Try starting preview.
mCamera.setDisplayOrientation(offset(REF_SENSOR, REF_VIEW)); // <- not allowed during preview
}
private void destroyCamera() {
try {
LOG.i("destroyCamera:", "Clean up.", "Releasing camera.");
mCamera.release();
LOG.i("destroyCamera:", "Clean up.", "Released camera.");
} catch (Exception e) {
LOG.w("destroyCamera:", "Clean up.", "Exception while releasing camera.", e);
}
mCamera = null;
mCameraOptions = null;
}
@WorkerThread
@Override
void onStart() {
if (isCameraAvailable()) {
LOG.w("onStart:", "Camera not available. Should not happen.");
onStop(); // Should not happen.
}
if (collectCameraId()) {
createCamera();
if (shouldBindToSurface()) bindToSurface();
if (shouldStartPreview()) startPreview("onStart");
LOG.i("onStart:", "Ended");
}
}
@ -200,31 +257,14 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
void onStop() {
LOG.i("onStop:", "About to clean up.");
mHandler.get().removeCallbacks(mPostFocusResetRunnable);
mFrameManager.release();
if (mCamera != null) {
LOG.i("onStop:", "Clean up.", "Ending video. mVideoRecorder is null?", mVideoRecorder == null);
if (mVideoRecorder != null) {
mVideoRecorder.stop();
mVideoRecorder = null;
}
try {
LOG.i("onStop:", "Clean up.", "Stopping preview.");
mCamera.setPreviewCallbackWithBuffer(null);
mCamera.stopPreview();
LOG.i("onStop:", "Clean up.", "Stopped preview.");
} catch (Exception e) {
LOG.w("onStop:", "Clean up.", "Exception while stopping preview.", e);
}
try {
LOG.i("onStop:", "Clean up.", "Releasing camera.");
mCamera.release();
LOG.i("onStop:", "Clean up.", "Released camera.");
} catch (Exception e) {
LOG.w("onStop:", "Clean up.", "Exception while releasing camera.", e);
}
if (mCamera != null) {
stopPreview();
unbindFromSurface();
destroyCamera();
}
mCameraOptions = null;
mCamera = null;
@ -767,7 +807,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
void startAutoFocus(@Nullable final Gesture gesture, final PointF point) {
// Must get width and height from the UI thread.
int viewWidth = 0, viewHeight = 0;
if (mPreview != null && mPreview.isReady()) {
if (mPreview != null && mPreview.hasSurface()) {
viewWidth = mPreview.getView().getWidth();
viewHeight = mPreview.getView().getHeight();
}

@ -17,6 +17,7 @@ abstract class CameraPreview<T extends View, Output> {
interface SurfaceCallback {
void onSurfaceAvailable();
void onSurfaceChanged();
void onSurfaceDestroyed();
}
private SurfaceCallback mSurfaceCallback;
@ -55,8 +56,10 @@ abstract class CameraPreview<T extends View, Output> {
LOG.i("setInputStreamSize:", "desiredW=", width, "desiredH=", height);
mInputStreamWidth = width;
mInputStreamHeight = height;
if (mInputStreamWidth > 0 && mInputStreamHeight > 0) {
crop();
}
}
final Size getInputStreamSize() {
return new Size(mInputStreamWidth, mInputStreamHeight);
@ -75,30 +78,35 @@ abstract class CameraPreview<T extends View, Output> {
}
protected final void onSurfaceAvailable(int width, int height) {
LOG.i("onSurfaceAvailable:", "w=", width, "h=", height);
protected final void dispatchOnOutputSurfaceAvailable(int width, int height) {
LOG.i("dispatchOnOutputSurfaceAvailable:", "w=", width, "h=", height);
mOutputSurfaceWidth = width;
mOutputSurfaceHeight = height;
if (mOutputSurfaceWidth > 0 && mOutputSurfaceHeight > 0) {
crop();
}
mSurfaceCallback.onSurfaceAvailable();
}
// As far as I can see, these are the view/surface dimensions.
// This is called by subclasses.
protected final void onSurfaceSizeChanged(int width, int height) {
LOG.i("onSurfaceSizeChanged:", "w=", width, "h=", height);
protected final void dispatchOnOutputSurfaceSizeChanged(int width, int height) {
LOG.i("dispatchOnOutputSurfaceSizeChanged:", "w=", width, "h=", height);
if (width != mOutputSurfaceWidth || height != mOutputSurfaceHeight) {
mOutputSurfaceWidth = width;
mOutputSurfaceHeight = height;
if (width > 0 && height > 0) {
crop();
}
mSurfaceCallback.onSurfaceChanged();
}
}
protected final void onSurfaceDestroyed() {
protected final void dispatchOnOutputSurfaceDestroyed() {
mOutputSurfaceWidth = 0;
mOutputSurfaceHeight = 0;
mSurfaceCallback.onSurfaceDestroyed();
}
void onResume() {}
@ -107,7 +115,7 @@ abstract class CameraPreview<T extends View, Output> {
void onDestroy() {}
final boolean isReady() {
final boolean hasSurface() {
return mOutputSurfaceWidth > 0 && mOutputSurfaceHeight > 0;
}

@ -69,7 +69,6 @@ class GLCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture> imple
glView.setEGLContextClientVersion(2);
glView.setRenderer(this);
glView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
// glView.getHolder().setFixedSize(600, 300);
glView.getHolder().addCallback(new SurfaceHolder.Callback() {
public void surfaceCreated(SurfaceHolder holder) {}
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
@ -78,7 +77,7 @@ class GLCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture> imple
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
onSurfaceDestroyed();
dispatchOnOutputSurfaceDestroyed();
}
});
return glView;
@ -99,7 +98,12 @@ class GLCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture> imple
@Override
void onDestroy() {
super.onDestroy();
releaseInputSurfaceTexture();
}
private void releaseInputSurfaceTexture() {
if (mInputSurfaceTexture != null) {
mInputSurfaceTexture.setOnFrameAvailableListener(null);
mInputSurfaceTexture.release();
mInputSurfaceTexture = null;
}
@ -109,9 +113,7 @@ class GLCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture> imple
}
}
// Renderer thread
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
private void createInputSurfaceTexture() {
mOutputViewport = new GLViewport();
mOutputTextureId = mOutputViewport.createTexture();
mInputSurfaceTexture = new SurfaceTexture(mOutputTextureId);
@ -129,18 +131,52 @@ class GLCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture> imple
// Renderer thread
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
createInputSurfaceTexture();
}
// Renderer thread
@Override
public void onSurfaceChanged(GL10 gl, final int width, final int height) {
if (!mDispatched) {
onSurfaceAvailable(width, height);
dispatchOnOutputSurfaceAvailable(width, height);
mDispatched = true;
} else {
onSurfaceSizeChanged(width, height);
} else if (mOutputSurfaceWidth != width || mOutputSurfaceHeight != height) {
// It did actually change. (Got some cases where onSurfaceChanged is called with same values). Go on.
// Since surface size changed does not work, we try to destroy and recreate.
// dispatchOnOutputSurfaceSizeChanged(width, height);
// Recreate the texture...
releaseInputSurfaceTexture();
// Tell the controller that the output surface was destroyed..
// And recreated.
dispatchOnOutputSurfaceDestroyed();
getView().post(new Runnable() {
@Override
public void run() {
// getView().setPreserveEGLContextOnPause(true);
getView().onPause();
getView().onResume();
// getView().setPreserveEGLContextOnPause(false);
getView().queueEvent(new Runnable() {
@Override
public void run() {
createInputSurfaceTexture();
dispatchOnOutputSurfaceAvailable(width, height);
}
});
}
});
}
}
@Override
public void onDrawFrame(GL10 gl) {
if (mInputSurfaceTexture == null) return;
// Latch the latest frame. If there isn't anything new,
// we'll just re-use whatever was there before.
mInputSurfaceTexture.updateTexImage();
@ -151,10 +187,19 @@ class GLCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture> imple
// Draw the video frame.
mInputSurfaceTexture.getTransformMatrix(mTransformMatrix);
if (isCropping()) {
Matrix.scaleM(mTransformMatrix, 0, mScaleX, mScaleY, 1);
}
if (mOutputViewport == null) return;
mOutputViewport.drawFrame(mOutputTextureId, mTransformMatrix);
}
@Override
void setInputStreamSize(int width, int height) {
// mInputSurfaceTexture.setDefaultBufferSize(width, height);
super.setInputStreamSize(width, height);
}
@Override
Class<SurfaceTexture> getOutputClass() {
return SurfaceTexture.class;
@ -165,7 +210,6 @@ class GLCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture> imple
return mInputSurfaceTexture;
}
@Override
boolean supportsCropping() {
return true;
@ -200,6 +244,7 @@ class GLCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture> imple
// We must increase width.
scaleX = target.toFloat() / current.toFloat();
}
mCropping = scaleX > 1.02f || scaleY > 1.02f;
mScaleX = 1F / scaleX;
mScaleY = 1F / scaleY;
getView().requestRender();

@ -3,7 +3,6 @@ package com.otaliastudios.cameraview;
import android.content.Context;
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
@ -46,17 +45,17 @@ class SurfaceCameraPreview extends CameraPreview<View, SurfaceHolder> {
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
LOG.i("callback:", "surfaceChanged", "w:", width, "h:", height, "firstTime:", mFirstTime);
if (mFirstTime) {
onSurfaceAvailable(width, height);
dispatchOnOutputSurfaceAvailable(width, height);
mFirstTime = false;
} else {
onSurfaceSizeChanged(width, height);
dispatchOnOutputSurfaceSizeChanged(width, height);
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
LOG.i("callback:", "surfaceDestroyed");
onSurfaceDestroyed();
dispatchOnOutputSurfaceDestroyed();
mFirstTime = true;
}
});

@ -25,17 +25,17 @@ class TextureCameraPreview extends CameraPreview<TextureView, SurfaceTexture> {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
onSurfaceAvailable(width, height);
dispatchOnOutputSurfaceAvailable(width, height);
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
onSurfaceSizeChanged(width, height);
dispatchOnOutputSurfaceSizeChanged(width, height);
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
onSurfaceDestroyed();
dispatchOnOutputSurfaceDestroyed();
return true;
}

Loading…
Cancel
Save