diff options
Diffstat (limited to 'camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java')
-rw-r--r-- | camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java | 575 |
1 files changed, 394 insertions, 181 deletions
diff --git a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java index d139c62..2fc4ad3 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java @@ -65,6 +65,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { private final DispatchThread mDispatchThread; private final CameraManager mCameraManager; private final MediaActionSound mNoisemaker; + private CameraExceptionHandler mExceptionHandler; /** * Number of camera devices. The length of {@code mCameraDevices} does not reveal this @@ -86,6 +87,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { mCameraHandlerThread = new HandlerThread("Camera2 Handler Thread"); mCameraHandlerThread.start(); mCameraHandler = new Camera2Handler(mCameraHandlerThread.getLooper()); + mExceptionHandler = new CameraExceptionHandler(mCameraHandler); mCameraState = new AndroidCamera2StateHolder(); mDispatchThread = new DispatchThread(mCameraHandler, mCameraHandlerThread); mDispatchThread.start(); @@ -134,11 +136,6 @@ class AndroidCamera2AgentImpl extends CameraAgent { // TODO: Implement @Override - public void setCameraDefaultExceptionCallback(CameraExceptionCallback callback, - Handler handler) {} - - // TODO: Implement - @Override public void recycle() {} // TODO: Some indices may now be invalid; ensure everyone can handle that and update the docs @@ -159,8 +156,23 @@ class AndroidCamera2AgentImpl extends CameraAgent { return mDispatchThread; } + @Override + protected CameraStateHolder getCameraState() { + return mCameraState; + } + + @Override + protected CameraExceptionHandler getCameraExceptionHandler() { + return mExceptionHandler; + } + + @Override + public void setCameraExceptionHandler(CameraExceptionHandler exceptionHandler) { + mExceptionHandler = exceptionHandler; + } + private static abstract class CaptureAvailableListener - extends CameraCaptureSession.CaptureListener + extends CameraCaptureSession.CaptureCallback implements ImageReader.OnImageAvailableListener {}; private class Camera2Handler extends HistoryHandler { @@ -168,6 +180,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { private CameraOpenCallback mOpenCallback; private int mCameraIndex; private String mCameraId; + private int mCancelAfPending = 0; // Available in CAMERA_UNCONFIGURED state and above: private CameraDevice mCamera; @@ -198,6 +211,9 @@ class AndroidCamera2AgentImpl extends CameraAgent { // Available whenever setAutoFocusMoveCallback() was last invoked with a non-null argument: private CameraAFMoveCallback mPassiveAfCallback; + // Gets reset on every state change + private int mCurrentAeState = CaptureResult.CONTROL_AE_STATE_INACTIVE; + Camera2Handler(Looper looper) { super(looper); } @@ -205,14 +221,16 @@ class AndroidCamera2AgentImpl extends CameraAgent { @Override public void handleMessage(final Message msg) { super.handleMessage(msg); + Log.v(TAG, "handleMessage - action = '" + CameraActions.stringify(msg.what) + "'"); + int cameraAction = msg.what; try { - switch(msg.what) { + switch (cameraAction) { case CameraActions.OPEN_CAMERA: case CameraActions.RECONNECT: { CameraOpenCallback openCallback = (CameraOpenCallback) msg.obj; int cameraIndex = msg.arg1; - if (mCameraState.getState() != AndroidCamera2StateHolder.CAMERA_UNOPENED) { + if (mCameraState.getState() > AndroidCamera2StateHolder.CAMERA_UNOPENED) { openCallback.onDeviceOpenedAlready(cameraIndex, generateHistoryString(cameraIndex)); break; @@ -228,7 +246,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { mOpenCallback.onCameraDisabled(msg.arg1); break; } - mCameraManager.openCamera(mCameraId, mCameraDeviceStateListener, this); + mCameraManager.openCamera(mCameraId, mCameraDeviceStateCallback, this); break; } @@ -263,7 +281,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { mPhotoSize = null; mCameraIndex = 0; mCameraId = null; - mCameraState.setState(AndroidCamera2StateHolder.CAMERA_UNOPENED); + changeState(AndroidCamera2StateHolder.CAMERA_UNOPENED); break; } @@ -289,19 +307,23 @@ class AndroidCamera2AgentImpl extends CameraAgent { } mOneshotPreviewingCallback = (CameraStartPreviewCallback) msg.obj; - mCameraState.setState(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE); + changeState(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE); try { mSession.setRepeatingRequest( mPersistentSettings.createRequest(mCamera, CameraDevice.TEMPLATE_PREVIEW, mPreviewSurface), - /*listener*/mCameraFocusStateListener, /*handler*/this); + /*listener*/mCameraResultStateCallback, /*handler*/this); } catch(CameraAccessException ex) { Log.w(TAG, "Unable to start preview", ex); - mCameraState.setState(AndroidCamera2StateHolder.CAMERA_PREVIEW_READY); + changeState(AndroidCamera2StateHolder.CAMERA_PREVIEW_READY); } break; } + // FIXME: We need to tear down the CameraCaptureSession here + // (and unlock the CameraSettings object from our + // CameraProxy) so that the preview/photo sizes can be + // changed again while no preview is running. case CameraActions.STOP_PREVIEW: { if (mCameraState.getState() < AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE) { @@ -310,7 +332,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { } mSession.stopRepeating(); - mCameraState.setState(AndroidCamera2StateHolder.CAMERA_PREVIEW_READY); + changeState(AndroidCamera2StateHolder.CAMERA_PREVIEW_READY); break; } @@ -353,6 +375,11 @@ class AndroidCamera2AgentImpl extends CameraAgent { } case CameraActions.AUTO_FOCUS: { + if (mCancelAfPending > 0) { + Log.v(TAG, "handleMessage - Ignored AUTO_FOCUS because there was " + + mCancelAfPending + " pending CANCEL_AUTO_FOCUS messages"); + break; // ignore AF because a CANCEL_AF is queued after this + } // We only support locking the focus while a preview is being displayed. // However, it can be requested multiple times in succession; the effect of // the subsequent invocations is determined by the focus mode defined in the @@ -368,19 +395,39 @@ class AndroidCamera2AgentImpl extends CameraAgent { } // The earliest we can reliably tell whether the autofocus has locked in - // response to our latest request is when our one-time capture completes. + // response to our latest request is when our one-time capture progresses. // However, it will probably take longer than that, so once that happens, // just start checking the repeating preview requests as they complete. final CameraAFCallback callback = (CameraAFCallback) msg.obj; - CameraCaptureSession.CaptureListener deferredCallbackSetter = - new CameraCaptureSession.CaptureListener() { + CameraCaptureSession.CaptureCallback deferredCallbackSetter = + new CameraCaptureSession.CaptureCallback() { + private boolean mAlreadyDispatched = false; + + @Override + public void onCaptureProgressed(CameraCaptureSession session, + CaptureRequest request, + CaptureResult result) { + checkAfState(result); + } + @Override public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) { - // Now our mCameraFocusStateListener will invoke the callback the - // first time it finds the focus motor to be locked. - mOneshotAfCallback = callback; + checkAfState(result); + } + + private void checkAfState(CaptureResult result) { + if (result.get(CaptureResult.CONTROL_AF_STATE) != null && + !mAlreadyDispatched) { + // Now our mCameraResultStateCallback will invoke the callback + // the first time it finds the focus motor to be locked. + mAlreadyDispatched = true; + mOneshotAfCallback = callback; + // This is an optimization: check the AF state of this frame + // instead of simply waiting for the next. + mCameraResultStateCallback.monitorControlStates(result); + } } @Override @@ -392,7 +439,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { }}; // Send a one-time capture to trigger the camera driver to lock focus. - mCameraState.setState(AndroidCamera2StateHolder.CAMERA_FOCUS_LOCKED); + changeState(AndroidCamera2StateHolder.CAMERA_FOCUS_LOCKED); Camera2RequestSettingsSet trigger = new Camera2RequestSettingsSet(mPersistentSettings); trigger.set(CaptureRequest.CONTROL_AF_TRIGGER, @@ -404,12 +451,15 @@ class AndroidCamera2AgentImpl extends CameraAgent { /*listener*/deferredCallbackSetter, /*handler*/ this); } catch(CameraAccessException ex) { Log.e(TAG, "Unable to lock autofocus", ex); - mCameraState.setState(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE); + changeState(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE); } break; } case CameraActions.CANCEL_AUTO_FOCUS: { + // Ignore all AFs that were already queued until we see + // a CANCEL_AUTO_FOCUS_FINISH + mCancelAfPending++; // Why would you want to unlock the lens if it isn't already locked? if (mCameraState.getState() < AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE) { @@ -418,7 +468,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { } // Send a one-time capture to trigger the camera driver to resume scanning. - mCameraState.setState(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE); + changeState(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE); Camera2RequestSettingsSet cancel = new Camera2RequestSettingsSet(mPersistentSettings); cancel.set(CaptureRequest.CONTROL_AF_TRIGGER, @@ -430,12 +480,18 @@ class AndroidCamera2AgentImpl extends CameraAgent { /*listener*/null, /*handler*/this); } catch(CameraAccessException ex) { Log.e(TAG, "Unable to cancel autofocus", ex); - mCameraState.setState( - AndroidCamera2StateHolder.CAMERA_FOCUS_LOCKED); + changeState(AndroidCamera2StateHolder.CAMERA_FOCUS_LOCKED); } break; } + case CameraActions.CANCEL_AUTO_FOCUS_FINISH: { + // Stop ignoring AUTO_FOCUS messages unless there are additional + // CANCEL_AUTO_FOCUSes that were added + mCancelAfPending--; + break; + } + case CameraActions.SET_AUTO_FOCUS_MOVE_CALLBACK: { mPassiveAfCallback = (CameraAFMoveCallback) msg.obj; break; @@ -467,12 +523,18 @@ class AndroidCamera2AgentImpl extends CameraAgent { case CameraActions.SET_DISPLAY_ORIENTATION: { // Only set the JPEG capture orientation if requested to do so; otherwise, - // capture in the sensor's physical orientation + // capture in the sensor's physical orientation. (e.g., JPEG rotation is + // necessary in auto-rotate mode. mPersistentSettings.set(CaptureRequest.JPEG_ORIENTATION, msg.arg2 > 0 ? mCameraProxy.getCharacteristics().getJpegOrientation(msg.arg1) : 0); break; } + case CameraActions.SET_JPEG_ORIENTATION: { + mPersistentSettings.set(CaptureRequest.JPEG_ORIENTATION, msg.arg1); + break; + } + case CameraActions.CAPTURE_PHOTO: { if (mCameraState.getState() < AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE) { @@ -486,8 +548,21 @@ class AndroidCamera2AgentImpl extends CameraAgent { final CaptureAvailableListener listener = (CaptureAvailableListener) msg.obj; - if (mLegacyDevice) { - // Just snap the shot + if (mLegacyDevice || + (mCurrentAeState == CaptureResult.CONTROL_AE_STATE_CONVERGED && + !mPersistentSettings.matches(CaptureRequest.CONTROL_AE_MODE, + CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH) && + !mPersistentSettings.matches(CaptureRequest.FLASH_MODE, + CaptureRequest.FLASH_MODE_SINGLE))) + { + // Legacy devices don't support the precapture state keys and instead + // perform autoexposure convergence automatically upon capture. + + // On other devices, as long as it has already converged, it determined + // that flash was not required, and we're not going to invalidate the + // current exposure levels by forcing the force on, we can save + // significant capture time by not forcing a recalculation. + Log.i(TAG, "Skipping pre-capture autoexposure convergence"); mCaptureReader.setOnImageAvailableListener(listener, /*handler*/this); try { mSession.capture( @@ -496,17 +571,42 @@ class AndroidCamera2AgentImpl extends CameraAgent { mCaptureReader.getSurface()), listener, /*handler*/this); } catch (CameraAccessException ex) { - Log.e(TAG, "Unable to initiate legacy capture", ex); + Log.e(TAG, "Unable to initiate immediate capture", ex); } } else { - // Not a legacy device, so we need to let AE converge before capturing - CameraCaptureSession.CaptureListener deferredCallbackSetter = - new CameraCaptureSession.CaptureListener() { + // We need to let AE converge before capturing. Once our one-time + // trigger capture has made it into the pipeline, we'll start checking + // for the completion of that convergence, capturing when that happens. + Log.i(TAG, "Forcing pre-capture autoexposure convergence"); + CameraCaptureSession.CaptureCallback deferredCallbackSetter = + new CameraCaptureSession.CaptureCallback() { + private boolean mAlreadyDispatched = false; + + @Override + public void onCaptureProgressed(CameraCaptureSession session, + CaptureRequest request, + CaptureResult result) { + checkAeState(result); + } + @Override public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) { - mOneshotCaptureCallback = listener; + checkAeState(result); + } + + private void checkAeState(CaptureResult result) { + if (result.get(CaptureResult.CONTROL_AE_STATE) != null && + !mAlreadyDispatched) { + // Now our mCameraResultStateCallback will invoke the + // callback once the autoexposure routine has converged. + mAlreadyDispatched = true; + mOneshotCaptureCallback = listener; + // This is an optimization: check the AE state of this frame + // instead of simply waiting for the next. + mCameraResultStateCallback.monitorControlStates(result); + } } @Override @@ -541,12 +641,12 @@ class AndroidCamera2AgentImpl extends CameraAgent { } } } catch (final Exception ex) { - if (msg.what != CameraActions.RELEASE && mCamera != null) { + if (cameraAction != CameraActions.RELEASE && mCamera != null) { // TODO: Handle this better mCamera.close(); mCamera = null; } else if (mCamera == null) { - if (msg.what == CameraActions.OPEN_CAMERA) { + if (cameraAction == CameraActions.OPEN_CAMERA) { if (mOpenCallback != null) { mOpenCallback.onDeviceOpenFailure(mCameraIndex, generateHistoryString(mCameraIndex)); @@ -558,12 +658,12 @@ class AndroidCamera2AgentImpl extends CameraAgent { } if (ex instanceof RuntimeException) { - post(new Runnable() { - @Override - public void run() { - sCameraExceptionCallback.onCameraException((RuntimeException) ex); - }}); + String commandHistory = generateHistoryString(Integer.parseInt(mCameraId)); + mExceptionHandler.onCameraException((RuntimeException) ex, commandHistory, + cameraAction, mCameraState.getState()); } + } finally { + WaitDoneBundle.unblockSyncWaiters(msg); } } @@ -599,13 +699,13 @@ class AndroidCamera2AgentImpl extends CameraAgent { mSession.setRepeatingRequest( mPersistentSettings.createRequest(mCamera, CameraDevice.TEMPLATE_PREVIEW, mPreviewSurface), - /*listener*/mCameraFocusStateListener, /*handler*/this); + /*listener*/mCameraResultStateCallback, /*handler*/this); } catch (CameraAccessException ex) { Log.e(TAG, "Failed to apply updated request settings", ex); } } else if (mCameraState.getState() < AndroidCamera2StateHolder.CAMERA_PREVIEW_READY) { // If we're already ready to preview, this doesn't regress our state - mCameraState.setState(AndroidCamera2StateHolder.CAMERA_CONFIGURED); + changeState(AndroidCamera2StateHolder.CAMERA_CONFIGURED); } } @@ -646,7 +746,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { try { mCamera.createCaptureSession( Arrays.asList(mPreviewSurface, mCaptureReader.getSurface()), - mCameraPreviewStateListener, this); + mCameraPreviewStateCallback, this); } catch (CameraAccessException ex) { Log.e(TAG, "Failed to create camera capture session", ex); } @@ -659,12 +759,22 @@ class AndroidCamera2AgentImpl extends CameraAgent { } catch (CameraAccessException ex) { Log.e(TAG, "Failed to close existing camera capture session", ex); } - mCameraState.setState(AndroidCamera2StateHolder.CAMERA_CONFIGURED); + changeState(AndroidCamera2StateHolder.CAMERA_CONFIGURED); } - // This listener monitors our connection to and disconnection from camera devices. - private CameraDevice.StateListener mCameraDeviceStateListener = - new CameraDevice.StateListener() { + private void changeState(int newState) { + if (mCameraState.getState() != newState) { + mCameraState.setState(newState); + if (newState < AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE) { + mCurrentAeState = CaptureResult.CONTROL_AE_STATE_INACTIVE; + mCameraResultStateCallback.resetState(); + } + } + } + + // This callback monitors our connection to and disconnection from camera devices. + private CameraDevice.StateCallback mCameraDeviceStateCallback = + new CameraDevice.StateCallback() { @Override public void onOpened(CameraDevice camera) { mCamera = camera; @@ -672,15 +782,17 @@ class AndroidCamera2AgentImpl extends CameraAgent { try { CameraCharacteristics props = mCameraManager.getCameraCharacteristics(mCameraId); - mCameraProxy = new AndroidCamera2ProxyImpl(mCameraIndex, mCamera, - getCameraDeviceInfo().getCharacteristics(mCameraIndex), props); + CameraDeviceInfo.Characteristics characteristics = + getCameraDeviceInfo().getCharacteristics(mCameraIndex); + mCameraProxy = new AndroidCamera2ProxyImpl(AndroidCamera2AgentImpl.this, + mCameraIndex, mCamera, characteristics, props); mPersistentSettings = new Camera2RequestSettingsSet(); mActiveArray = props.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); mLegacyDevice = props.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY; - mCameraState.setState(AndroidCamera2StateHolder.CAMERA_UNCONFIGURED); + changeState(AndroidCamera2StateHolder.CAMERA_UNCONFIGURED); mOpenCallback.onCameraOpened(mCameraProxy); } catch (CameraAccessException ex) { mOpenCallback.onDeviceOpenFailure(mCameraIndex, @@ -704,13 +816,13 @@ class AndroidCamera2AgentImpl extends CameraAgent { } }}; - // This listener monitors our camera session (i.e. our transition into and out of preview). - private CameraCaptureSession.StateListener mCameraPreviewStateListener = - new CameraCaptureSession.StateListener() { + // This callback monitors our camera session (i.e. our transition into and out of preview). + private CameraCaptureSession.StateCallback mCameraPreviewStateCallback = + new CameraCaptureSession.StateCallback() { @Override public void onConfigured(CameraCaptureSession session) { mSession = session; - mCameraState.setState(AndroidCamera2StateHolder.CAMERA_PREVIEW_READY); + changeState(AndroidCamera2StateHolder.CAMERA_PREVIEW_READY); } @Override @@ -728,49 +840,76 @@ class AndroidCamera2AgentImpl extends CameraAgent { } }}; - // This listener monitors requested captures and notifies any relevant callbacks. - private CameraCaptureSession.CaptureListener mCameraFocusStateListener = - new CameraCaptureSession.CaptureListener() { + private abstract class CameraResultStateCallback + extends CameraCaptureSession.CaptureCallback { + public abstract void monitorControlStates(CaptureResult result); + + public abstract void resetState(); + } + + // This callback monitors requested captures and notifies any relevant callbacks. + private CameraResultStateCallback mCameraResultStateCallback = + new CameraResultStateCallback() { private int mLastAfState = -1; + private long mLastAfFrameNumber = -1; + private long mLastAeFrameNumber = -1; + + @Override + public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request, + CaptureResult result) { + monitorControlStates(result); + } @Override public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) { + monitorControlStates(result); + } + + @Override + public void monitorControlStates(CaptureResult result) { Integer afStateMaybe = result.get(CaptureResult.CONTROL_AF_STATE); if (afStateMaybe != null) { int afState = afStateMaybe; - boolean afStateChanged = false; - if (afState != mLastAfState) { + // Since we handle both partial and total results for multiple frames here, we + // might get the final callbacks for an earlier frame after receiving one or + // more that correspond to the next one. To prevent our data from oscillating, + // we never consider AF states that are older than the last one we've seen. + if (result.getFrameNumber() > mLastAfFrameNumber) { + boolean afStateChanged = afState != mLastAfState; mLastAfState = afState; - afStateChanged = true; - } - - switch (afState) { - case CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN: - case CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED: - case CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED: { - if (afStateChanged && mPassiveAfCallback != null) { - // A CameraAFMoveCallback is attached. If we just started to scan, - // the motor is moving; otherwise, it has settled. - mPassiveAfCallback.onAutoFocusMoving( - afState == CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN, - mCameraProxy); + mLastAfFrameNumber = result.getFrameNumber(); + + switch (afState) { + case CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN: + case CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED: + case CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED: { + if (afStateChanged && mPassiveAfCallback != null) { + // A CameraAFMoveCallback is attached. If we just started to + // scan, the motor is moving; otherwise, it has settled. + mPassiveAfCallback.onAutoFocusMoving( + afState == CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN, + mCameraProxy); + } + break; } - break; - } - case CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED: - case CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED: { - if (mOneshotAfCallback != null) { - // A call to autoFocus() was just made to request a focus lock. - // Notify the caller that the lens is now indefinitely fixed, and - // report whether the image we're now stuck with is in focus. - mOneshotAfCallback.onAutoFocus( - afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED, - mCameraProxy); - mOneshotAfCallback = null; + case CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED: + case CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED: { + // This check must be made regardless of whether the focus state has + // changed recently to avoid infinite waiting during autoFocus() + // when the algorithm has already either converged or failed to. + if (mOneshotAfCallback != null) { + // A call to autoFocus() was just made to request a focus lock. + // Notify the caller that the lens is now indefinitely fixed, + // and report whether the image we're stuck with is in focus. + mOneshotAfCallback.onAutoFocus( + afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED, + mCameraProxy); + mOneshotAfCallback = null; + } + break; } - break; } } } @@ -778,36 +917,55 @@ class AndroidCamera2AgentImpl extends CameraAgent { Integer aeStateMaybe = result.get(CaptureResult.CONTROL_AE_STATE); if (aeStateMaybe != null) { int aeState = aeStateMaybe; - - switch (aeState) { - case CaptureResult.CONTROL_AE_STATE_CONVERGED: - case CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED: - case CaptureResult.CONTROL_AE_STATE_LOCKED: { - if (mOneshotCaptureCallback != null) { - // A call to takePicture() was just made, and autoexposure converged - // so it's time to initiate the capture! - mCaptureReader.setOnImageAvailableListener(mOneshotCaptureCallback, - /*handler*/Camera2Handler.this); - try { - mSession.capture( - mPersistentSettings.createRequest(mCamera, - CameraDevice.TEMPLATE_STILL_CAPTURE, - mCaptureReader.getSurface()), + // Since we handle both partial and total results for multiple frames here, we + // might get the final callbacks for an earlier frame after receiving one or + // more that correspond to the next one. To prevent our data from oscillating, + // we never consider AE states that are older than the last one we've seen. + if (result.getFrameNumber() > mLastAeFrameNumber) { + mCurrentAeState = aeStateMaybe; + mLastAeFrameNumber = result.getFrameNumber(); + + switch (aeState) { + case CaptureResult.CONTROL_AE_STATE_CONVERGED: + case CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED: + case CaptureResult.CONTROL_AE_STATE_LOCKED: { + // This check must be made regardless of whether the exposure state + // has changed recently to avoid infinite waiting during + // takePicture() when the algorithm has already converged. + if (mOneshotCaptureCallback != null) { + // A call to takePicture() was just made, and autoexposure + // converged so it's time to initiate the capture! + mCaptureReader.setOnImageAvailableListener( /*listener*/mOneshotCaptureCallback, /*handler*/Camera2Handler.this); - } catch (CameraAccessException ex) { - Log.e(TAG, "Unable to initiate capture", ex); - } finally { - mOneshotCaptureCallback = null; + try { + mSession.capture( + mPersistentSettings.createRequest(mCamera, + CameraDevice.TEMPLATE_STILL_CAPTURE, + mCaptureReader.getSurface()), + /*callback*/mOneshotCaptureCallback, + /*handler*/Camera2Handler.this); + } catch (CameraAccessException ex) { + Log.e(TAG, "Unable to initiate capture", ex); + } finally { + mOneshotCaptureCallback = null; + } } + break; } - break; } } } } @Override + public void resetState() { + mLastAfState = -1; + mLastAfFrameNumber = -1; + mLastAeFrameNumber = -1; + } + + @Override public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request, CaptureFailure failure) { Log.e(TAG, "Capture attempt failed with reason " + failure.getReason()); @@ -815,18 +973,27 @@ class AndroidCamera2AgentImpl extends CameraAgent { } private class AndroidCamera2ProxyImpl extends CameraAgent.CameraProxy { + private final AndroidCamera2AgentImpl mCameraAgent; private final int mCameraIndex; private final CameraDevice mCamera; private final CameraDeviceInfo.Characteristics mCharacteristics; private final AndroidCamera2Capabilities mCapabilities; + private CameraSettings mLastSettings; + private boolean mShutterSoundEnabled; - public AndroidCamera2ProxyImpl(int cameraIndex, CameraDevice camera, + public AndroidCamera2ProxyImpl( + AndroidCamera2AgentImpl agent, + int cameraIndex, + CameraDevice camera, CameraDeviceInfo.Characteristics characteristics, CameraCharacteristics properties) { + mCameraAgent = agent; mCameraIndex = cameraIndex; mCamera = camera; mCharacteristics = characteristics; mCapabilities = new AndroidCamera2Capabilities(properties); + mLastSettings = null; + mShutterSoundEnabled = true; } // TODO: Implement @@ -848,10 +1015,34 @@ class AndroidCamera2AgentImpl extends CameraAgent { return mCapabilities; } + public CameraAgent getAgent() { + return mCameraAgent; + } + private AndroidCamera2Capabilities getSpecializedCapabilities() { return mCapabilities; } + // FIXME: Unlock the sizes in stopPreview(), as per the corresponding + // explanation on the STOP_PREVIEW case in the handler. + @Override + public void setPreviewTexture(SurfaceTexture surfaceTexture) { + // Once the Surface has been selected, we configure the session and + // are no longer able to change the sizes. + getSettings().setSizesLocked(true); + super.setPreviewTexture(surfaceTexture); + } + + // FIXME: Unlock the sizes in stopPreview(), as per the corresponding + // explanation on the STOP_PREVIEW case in the handler. + @Override + public void setPreviewTextureSync(SurfaceTexture surfaceTexture) { + // Once the Surface has been selected, we configure the session and + // are no longer able to change the sizes. + getSettings().setSizesLocked(true); + super.setPreviewTexture(surfaceTexture); + } + // TODO: Implement @Override public void setPreviewDataCallback(Handler handler, CameraPreviewDataCallback cb) {} @@ -870,53 +1061,67 @@ class AndroidCamera2AgentImpl extends CameraAgent { @Override public void autoFocus(final Handler handler, final CameraAFCallback cb) { - mDispatchThread.runJob(new Runnable() { - @Override - public void run() { - CameraAFCallback cbForward = null; - if (cb != null) { - cbForward = new CameraAFCallback() { - @Override - public void onAutoFocus(final boolean focused, - final CameraProxy camera) { - handler.post(new Runnable() { - @Override - public void run() { - cb.onAutoFocus(focused, camera); - }}); - }}; - } + try { + mDispatchThread.runJob(new Runnable() { + @Override + public void run() { + CameraAFCallback cbForward = null; + if (cb != null) { + cbForward = new CameraAFCallback() { + @Override + public void onAutoFocus(final boolean focused, + final CameraProxy camera) { + handler.post(new Runnable() { + @Override + public void run() { + cb.onAutoFocus(focused, camera); + } + }); + } + }; + } - mCameraState.waitForStates(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE | - AndroidCamera2StateHolder.CAMERA_FOCUS_LOCKED); - mCameraHandler.obtainMessage(CameraActions.AUTO_FOCUS, cbForward) - .sendToTarget(); - }}); + mCameraState.waitForStates(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE | + AndroidCamera2StateHolder.CAMERA_FOCUS_LOCKED); + mCameraHandler.obtainMessage(CameraActions.AUTO_FOCUS, cbForward) + .sendToTarget(); + } + }); + } catch (RuntimeException ex) { + mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex); + } } @TargetApi(Build.VERSION_CODES.JELLY_BEAN) @Override public void setAutoFocusMoveCallback(final Handler handler, final CameraAFMoveCallback cb) { - mDispatchThread.runJob(new Runnable() { - @Override - public void run() { - CameraAFMoveCallback cbForward = null; - if (cb != null) { - cbForward = new CameraAFMoveCallback() { - @Override - public void onAutoFocusMoving(final boolean moving, - final CameraProxy camera) { - handler.post(new Runnable() { - @Override - public void run() { - cb.onAutoFocusMoving(moving, camera); - }}); - }}; - } + try { + mDispatchThread.runJob(new Runnable() { + @Override + public void run() { + CameraAFMoveCallback cbForward = null; + if (cb != null) { + cbForward = new CameraAFMoveCallback() { + @Override + public void onAutoFocusMoving(final boolean moving, + final CameraProxy camera) { + handler.post(new Runnable() { + @Override + public void run() { + cb.onAutoFocusMoving(moving, camera); + } + }); + } + }; + } - mCameraHandler.obtainMessage(CameraActions.SET_AUTO_FOCUS_MOVE_CALLBACK, - cbForward).sendToTarget(); - }}); + mCameraHandler.obtainMessage(CameraActions.SET_AUTO_FOCUS_MOVE_CALLBACK, + cbForward).sendToTarget(); + } + }); + } catch (RuntimeException ex) { + mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex); + } } @Override @@ -930,12 +1135,14 @@ class AndroidCamera2AgentImpl extends CameraAgent { new CaptureAvailableListener() { @Override public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request, - long timestamp) { + long timestamp, long frameNumber) { if (shutter != null) { handler.post(new Runnable() { @Override public void run() { - mNoisemaker.play(MediaActionSound.SHUTTER_CLICK); + if (mShutterSoundEnabled) { + mNoisemaker.play(MediaActionSound.SHUTTER_CLICK); + } shutter.onShutter(AndroidCamera2ProxyImpl.this); }}); } @@ -956,14 +1163,20 @@ class AndroidCamera2AgentImpl extends CameraAgent { } } }}; - mDispatchThread.runJob(new Runnable() { - @Override - public void run() { - mCameraState.waitForStates(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE | - AndroidCamera2StateHolder.CAMERA_FOCUS_LOCKED); - mCameraHandler.obtainMessage(CameraActions.CAPTURE_PHOTO, picListener) - .sendToTarget(); - }}); + try { + mDispatchThread.runJob(new Runnable() { + @Override + public void run() { + // Wait until PREVIEW_ACTIVE or better + mCameraState.waitForStates( + ~(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE - 1)); + mCameraHandler.obtainMessage(CameraActions.CAPTURE_PHOTO, picListener) + .sendToTarget(); + } + }); + } catch (RuntimeException ex) { + mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex); + } } // TODO: Implement @@ -985,10 +1198,6 @@ class AndroidCamera2AgentImpl extends CameraAgent { // TODO: Implement @Override - public void setErrorCallback(Handler handler, CameraErrorCallback cb) {} - - // TODO: Implement - @Override public void setParameters(android.hardware.Camera.Parameters params) {} // TODO: Implement @@ -997,7 +1206,10 @@ class AndroidCamera2AgentImpl extends CameraAgent { @Override public CameraSettings getSettings() { - return mCameraHandler.buildSettings(mCapabilities); + if (mLastSettings == null) { + mLastSettings = mCameraHandler.buildSettings(mCapabilities); + } + return mLastSettings; } @Override @@ -1011,9 +1223,17 @@ class AndroidCamera2AgentImpl extends CameraAgent { return false; } - return applySettingsHelper(settings, AndroidCamera2StateHolder.CAMERA_UNCONFIGURED | - AndroidCamera2StateHolder.CAMERA_CONFIGURED | - AndroidCamera2StateHolder.CAMERA_PREVIEW_READY); + // Wait for any state that isn't OPENED + if (applySettingsHelper(settings, ~AndroidCamera2StateHolder.CAMERA_UNOPENED)) { + mLastSettings = settings; + return true; + } + return false; + } + + @Override + public void enableShutterSound(boolean enable) { + mShutterSoundEnabled = enable; } // TODO: Implement @@ -1040,19 +1260,22 @@ class AndroidCamera2AgentImpl extends CameraAgent { private static class AndroidCamera2StateHolder extends CameraStateHolder { // Usage flow: openCamera() -> applySettings() -> setPreviewTexture() -> startPreview() -> // autoFocus() -> takePicture() + // States are mutually exclusive, but must be separate bits so that they can be used with + // the StateHolder#waitForStates() and StateHolder#waitToAvoidStates() methods. + // Do not set the state to be a combination of these values! /* Camera states */ /** No camera device is opened. */ - public static final int CAMERA_UNOPENED = 1; + public static final int CAMERA_UNOPENED = 1 << 0; /** A camera is opened, but no settings have been provided. */ - public static final int CAMERA_UNCONFIGURED = 2; + public static final int CAMERA_UNCONFIGURED = 1 << 1; /** The open camera has been configured by providing it with settings. */ - public static final int CAMERA_CONFIGURED = 3; + public static final int CAMERA_CONFIGURED = 1 << 2; /** A capture session is ready to stream a preview, but still has no repeating request. */ - public static final int CAMERA_PREVIEW_READY = 4; + public static final int CAMERA_PREVIEW_READY = 1 << 3; /** A preview is currently being streamed. */ - public static final int CAMERA_PREVIEW_ACTIVE = 5; + public static final int CAMERA_PREVIEW_ACTIVE = 1 << 4; /** The lens is locked on a particular region. */ - public static final int CAMERA_FOCUS_LOCKED = 6; + public static final int CAMERA_FOCUS_LOCKED = 1 << 5; public AndroidCamera2StateHolder() { this(CAMERA_UNOPENED); @@ -1173,9 +1396,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { @Override public boolean canDisableShutterSound() { - // The new API doesn't support this operation, so don't encourage people to try it. - // TODO: What kind of assumptions have callers made about this result's meaning? - return false; + return true; } private static float[] convertRectToPoly(RectF rf) { @@ -1196,12 +1417,4 @@ class AndroidCamera2AgentImpl extends CameraAgent { } } } - - private static final CameraExceptionCallback sCameraExceptionCallback = - new CameraExceptionCallback() { - @Override - public synchronized void onCameraException(RuntimeException e) { - throw e; - } - }; } |