diff options
15 files changed, 1137 insertions, 560 deletions
diff --git a/camera2/portability/portability.mk b/camera2/portability/portability.mk index a86c511..2ecc1df 100644 --- a/camera2/portability/portability.mk +++ b/camera2/portability/portability.mk @@ -19,5 +19,6 @@ LOCAL_MODULE := android-ex-camera2-portability LOCAL_MODULE_TAGS := optional LOCAL_SDK_VERSION := current LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_STATIC_JAVA_LIBRARIES := android-ex-camera2-utils include $(BUILD_STATIC_JAVA_LIBRARY) 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 74e76a3..65047f1 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java @@ -18,6 +18,7 @@ package com.android.ex.camera2.portability; import android.annotation.TargetApi; import android.content.Context; +import android.graphics.ImageFormat; import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.hardware.camera2.CameraAccessException; @@ -30,6 +31,8 @@ import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureResult; import android.hardware.camera2.TotalCaptureResult; import android.hardware.camera2.params.MeteringRectangle; +import android.media.Image; +import android.media.ImageReader; import android.os.Build; import android.os.Handler; import android.os.HandlerThread; @@ -38,7 +41,9 @@ import android.os.Message; import android.view.Surface; import com.android.ex.camera2.portability.debug.Log; +import com.android.ex.camera2.utils.Camera2RequestSettingsSet; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -148,6 +153,10 @@ class AndroidCamera2AgentImpl extends CameraAgent { return mDispatchThread; } + private static abstract class CaptureAvailableListener + extends CameraCaptureSession.CaptureListener + implements ImageReader.OnImageAvailableListener {}; + private class Camera2Handler extends HistoryHandler { // Caller-provided when leaving CAMERA_UNOPENED state: private CameraOpenCallback mOpenCallback; @@ -157,8 +166,9 @@ class AndroidCamera2AgentImpl extends CameraAgent { // Available in CAMERA_UNCONFIGURED state and above: private CameraDevice mCamera; private AndroidCamera2ProxyImpl mCameraProxy; - private CaptureRequest.Builder mPersistentRequestBuilder; + private Camera2RequestSettingsSet mPersistentSettings; private Rect mActiveArray; + private boolean mLegacyDevice; // Available in CAMERA_CONFIGURED state and above: private Size mPreviewSize; @@ -168,6 +178,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { private SurfaceTexture mPreviewTexture; private Surface mPreviewSurface; private CameraCaptureSession mSession; + private ImageReader mCaptureReader; // Available from the beginning of PREVIEW_ACTIVE until the first preview frame arrives: private CameraStartPreviewCallback mOneshotPreviewingCallback; @@ -175,6 +186,9 @@ class AndroidCamera2AgentImpl extends CameraAgent { // Available in FOCUS_LOCKED between AF trigger receipt and whenever the lens stops moving: private CameraAFCallback mOneshotAfCallback; + // Available when taking picture between AE trigger receipt and autoexposure convergence + private CaptureAvailableListener mOneshotCaptureCallback; + // Available whenever setAutoFocusMoveCallback() was last invoked with a non-null argument: private CameraAFMoveCallback mPassiveAfCallback; @@ -226,13 +240,17 @@ class AndroidCamera2AgentImpl extends CameraAgent { mCamera = null; } mCameraProxy = null; - mPersistentRequestBuilder = null; + mPersistentSettings = null; mActiveArray = null; if (mPreviewSurface != null) { mPreviewSurface.release(); mPreviewSurface = null; } mPreviewTexture = null; + if (mCaptureReader != null) { + mCaptureReader.close(); + mCaptureReader = null; + } mPreviewSize = null; mPhotoSize = null; mCameraIndex = 0; @@ -265,8 +283,10 @@ class AndroidCamera2AgentImpl extends CameraAgent { mOneshotPreviewingCallback = (CameraStartPreviewCallback) msg.obj; mCameraState.setState(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE); try { - mSession.setRepeatingRequest(mPersistentRequestBuilder.build(), - /*listener*/mCameraFocusStateListener, /*handler*/this); + mSession.setRepeatingRequest( + mPersistentSettings.createRequest(mCamera, + CameraDevice.TEMPLATE_PREVIEW, mPreviewSurface), + /*listener*/mCameraFocusStateListener, /*handler*/this); } catch(CameraAccessException ex) { Log.w(TAG, "Unable to start preview", ex); mCameraState.setState(AndroidCamera2StateHolder.CAMERA_PREVIEW_READY); @@ -319,7 +339,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { }*/ case CameraActions.APPLY_SETTINGS: { - CameraSettings settings = (CameraSettings) msg.obj; + AndroidCamera2Settings settings = (AndroidCamera2Settings) msg.obj; applyToRequest(settings); break; } @@ -365,17 +385,19 @@ class AndroidCamera2AgentImpl extends CameraAgent { // Send a one-time capture to trigger the camera driver to lock focus. mCameraState.setState(AndroidCamera2StateHolder.CAMERA_FOCUS_LOCKED); - mPersistentRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, + Camera2RequestSettingsSet trigger = + new Camera2RequestSettingsSet(mPersistentSettings); + trigger.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START); try { - mSession.capture(mPersistentRequestBuilder.build(), + mSession.capture( + trigger.createRequest(mCamera, CameraDevice.TEMPLATE_PREVIEW, + mPreviewSurface), /*listener*/deferredCallbackSetter, /*handler*/ this); } catch(CameraAccessException ex) { Log.e(TAG, "Unable to lock autofocus", ex); mCameraState.setState(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE); } - mPersistentRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, - CaptureRequest.CONTROL_AF_TRIGGER_IDLE); break; } @@ -389,18 +411,20 @@ class AndroidCamera2AgentImpl extends CameraAgent { // Send a one-time capture to trigger the camera driver to resume scanning. mCameraState.setState(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE); - mPersistentRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, + Camera2RequestSettingsSet cancel = + new Camera2RequestSettingsSet(mPersistentSettings); + cancel.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_CANCEL); try { - mSession.capture(mPersistentRequestBuilder.build(), + mSession.capture( + cancel.createRequest(mCamera, CameraDevice.TEMPLATE_PREVIEW, + mPreviewSurface), /*listener*/null, /*handler*/this); } catch(CameraAccessException ex) { Log.e(TAG, "Unable to cancel autofocus", ex); mCameraState.setState( AndroidCamera2StateHolder.CAMERA_FOCUS_LOCKED); } - mPersistentRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, - CaptureRequest.CONTROL_AF_TRIGGER_IDLE); break; } @@ -431,15 +455,78 @@ class AndroidCamera2AgentImpl extends CameraAgent { case CameraActions.ENABLE_SHUTTER_SOUND: { break; - } + }*/ case CameraActions.SET_DISPLAY_ORIENTATION: { + // TODO: Need to handle preview in addition to capture + // Only set the JPEG capture orientation if requested to do so; otherwise, + // capture in the sensor's physical orientation + mPersistentSettings.set(CaptureRequest.JPEG_ORIENTATION, msg.arg2 > 0 ? + mCameraProxy.getCharacteristics().getJpegOrientation(msg.arg1) : 0); break; } case CameraActions.CAPTURE_PHOTO: { + if (mCameraState.getState() < + AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE) { + Log.e(TAG, "Photos may only be taken when a preview is active"); + break; + } + if (mCameraState.getState() != + AndroidCamera2StateHolder.CAMERA_FOCUS_LOCKED) { + Log.w(TAG, "Taking a (likely blurry) photo without the lens locked"); + } + + final CaptureAvailableListener listener = + (CaptureAvailableListener) msg.obj; + if (mLegacyDevice) { + // Just snap the shot + mCaptureReader.setOnImageAvailableListener(listener, /*handler*/this); + try { + mSession.capture( + mPersistentSettings.createRequest(mCamera, + CameraDevice.TEMPLATE_STILL_CAPTURE, + mCaptureReader.getSurface()), + listener, /*handler*/this); + } catch (CameraAccessException ex) { + Log.e(TAG, "Unable to initiate legacy capture", ex); + } + } else { + // Not a legacy device, so we need to let AE converge before capturing + CameraCaptureSession.CaptureListener deferredCallbackSetter = + new CameraCaptureSession.CaptureListener() { + @Override + public void onCaptureCompleted(CameraCaptureSession session, + CaptureRequest request, + TotalCaptureResult result) { + mOneshotCaptureCallback = listener; + } + + @Override + public void onCaptureFailed(CameraCaptureSession session, + CaptureRequest request, + CaptureFailure failure) { + Log.e(TAG, "Autoexposure and capture failed with reason " + + failure.getReason()); + // TODO: Make an error callback? + }}; + + // Set a one-time capture to trigger the camera driver's autoexposure: + Camera2RequestSettingsSet expose = + new Camera2RequestSettingsSet(mPersistentSettings); + expose.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, + CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START); + try { + mSession.capture( + expose.createRequest(mCamera, CameraDevice.TEMPLATE_PREVIEW, + mPreviewSurface), + /*listener*/deferredCallbackSetter, /*handler*/this); + } catch (CameraAccessException ex) { + Log.e(TAG, "Unable to run autoexposure and perform capture", ex); + } + } break; - }*/ + } default: { // TODO: Rephrase once everything has been implemented @@ -474,8 +561,13 @@ class AndroidCamera2AgentImpl extends CameraAgent { } public CameraSettings buildSettings(AndroidCamera2Capabilities caps) { - return new AndroidCamera2Settings(caps, mPersistentRequestBuilder, mPreviewSize, - mPhotoSize); + try { + return new AndroidCamera2Settings(mCamera, CameraDevice.TEMPLATE_PREVIEW, + mActiveArray, mPreviewSize, mPhotoSize); + } catch (CameraAccessException ex) { + Log.e(TAG, "Unable to query camera device to build settings representation"); + return null; + } } /** @@ -487,34 +579,19 @@ class AndroidCamera2AgentImpl extends CameraAgent { * * @param settings The new/updated settings */ - // TODO: Finish implementation to add support for all settings - private void applyToRequest(CameraSettings settings) { + private void applyToRequest(AndroidCamera2Settings settings) { // TODO: If invoked when in PREVIEW_READY state, a new preview size will not take effect - AndroidCamera2Capabilities.IntegralStringifier intifier = - mCameraProxy.getSpecializedCapabilities().getIntegralStringifier(); + + mPersistentSettings.union(settings.getRequestSettings()); mPreviewSize = settings.getCurrentPreviewSize(); mPhotoSize = settings.getCurrentPhotoSize(); - mPersistentRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, - intifier.intify(settings.getCurrentFocusMode())); - - mPersistentRequestBuilder.set(CaptureRequest.CONTROL_AF_REGIONS, - legacyAreasToMeteringRectangles(settings.getFocusAreas())); - mPersistentRequestBuilder.set(CaptureRequest.CONTROL_AE_REGIONS, - legacyAreasToMeteringRectangles(settings.getMeteringAreas())); - - if (settings.getCurrentFlashMode() != CameraCapabilities.FlashMode.NO_FLASH) { - mPersistentRequestBuilder.set(CaptureRequest.FLASH_MODE, - intifier.intify(settings.getCurrentFlashMode())); - } - if (settings.getCurrentSceneMode() != CameraCapabilities.SceneMode.NO_SCENE_MODE) { - mPersistentRequestBuilder.set(CaptureRequest.CONTROL_SCENE_MODE, - intifier.intify(settings.getCurrentSceneMode())); - } if (mCameraState.getState() >= AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE) { // If we're already previewing, reflect most settings immediately try { - mSession.setRepeatingRequest(mPersistentRequestBuilder.build(), + mSession.setRepeatingRequest( + mPersistentSettings.createRequest(mCamera, + CameraDevice.TEMPLATE_PREVIEW, mPreviewSurface), /*listener*/mCameraFocusStateListener, /*handler*/this); } catch (CameraAccessException ex) { Log.e(TAG, "Failed to apply updated request settings", ex); @@ -525,46 +602,6 @@ class AndroidCamera2AgentImpl extends CameraAgent { } } - private MeteringRectangle[] legacyAreasToMeteringRectangles( - List<android.hardware.Camera.Area> reference) { - MeteringRectangle[] transformed = null; - if (reference.size() > 0) { - - transformed = new MeteringRectangle[reference.size()]; - for (int index = 0; index < reference.size(); ++index) { - android.hardware.Camera.Area source = reference.get(index); - Rect rectangle = source.rect; - - // Old API coordinates were [-1000,1000]; new ones are [0,ACTIVE_ARRAY_SIZE). - double oldLeft = (rectangle.left + 1000) / 2000.0; - double oldTop = (rectangle.top + 1000) / 2000.0; - double oldRight = (rectangle.right + 1000) / 2000.0; - double oldBottom = (rectangle.bottom + 1000) / 2000.0; - int left = toIntConstrained( - mActiveArray.width() * oldLeft + mActiveArray.left, - 0, mActiveArray.width() - 1); - int top = toIntConstrained( - mActiveArray.height() * oldTop + mActiveArray.top, - 0, mActiveArray.height() - 1); - int right = toIntConstrained( - mActiveArray.width() * oldRight + mActiveArray.left, - 0, mActiveArray.width() - 1); - int bottom = toIntConstrained( - mActiveArray.height() * oldBottom + mActiveArray.top, - 0, mActiveArray.height() - 1); - transformed[index] = new MeteringRectangle(left, top, - right - left, bottom - top, source.weight); - } - } - return transformed; - } - - private int toIntConstrained(double original, int min, int max) { - original = Math.max(original, min); - original = Math.min(original, max); - return (int) original; - } - private void setPreviewTexture(SurfaceTexture surfaceTexture) { // TODO: Must be called after providing a .*Settings populated with sizes // TODO: We don't technically offer a selection of sizes tailored to SurfaceTextures! @@ -589,14 +626,19 @@ class AndroidCamera2AgentImpl extends CameraAgent { surfaceTexture.setDefaultBufferSize(mPreviewSize.width(), mPreviewSize.height()); if (mPreviewSurface != null) { - mPersistentRequestBuilder.removeTarget(mPreviewSurface); mPreviewSurface.release(); } mPreviewSurface = new Surface(surfaceTexture); - mPersistentRequestBuilder.addTarget(mPreviewSurface); + + if (mCaptureReader != null) { + mCaptureReader.close(); + } + mCaptureReader = ImageReader.newInstance( + mPhotoSize.width(), mPhotoSize.height(), ImageFormat.JPEG, 1); try { - mCamera.createCaptureSession(Arrays.asList(mPreviewSurface), + mCamera.createCaptureSession( + Arrays.asList(mPreviewSurface, mCaptureReader.getSurface()), mCameraPreviewStateListener, this); } catch (CameraAccessException ex) { Log.e(TAG, "Failed to create camera capture session", ex); @@ -625,10 +667,12 @@ class AndroidCamera2AgentImpl extends CameraAgent { mCameraManager.getCameraCharacteristics(mCameraId); mCameraProxy = new AndroidCamera2ProxyImpl(mCameraIndex, mCamera, getCameraDeviceInfo().getCharacteristics(mCameraIndex), props); - mPersistentRequestBuilder = - camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); + 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); mOpenCallback.onCameraOpened(mCameraProxy); } catch (CameraAccessException ex) { @@ -723,6 +767,37 @@ 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()), + /*listener*/mOneshotCaptureCallback, + /*handler*/Camera2Handler.this); + } catch (CameraAccessException ex) { + Log.e(TAG, "Unable to initiate capture", ex); + } finally { + mOneshotCaptureCallback = null; + } + } + break; + } + } + } } @Override @@ -836,15 +911,50 @@ class AndroidCamera2AgentImpl extends CameraAgent { // TODO: Implement @Override - public void takePicture(Handler handler, - CameraShutterCallback shutter, + public void takePicture(final Handler handler, + final CameraShutterCallback shutter, CameraPictureCallback raw, CameraPictureCallback postview, - CameraPictureCallback jpeg) {} + final CameraPictureCallback jpeg) { + // TODO: We never call raw or postview + final CaptureAvailableListener picListener = + new CaptureAvailableListener() { + @Override + public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request, + long timestamp) { + if (shutter != null) { + handler.post(new Runnable() { + @Override + public void run() { + shutter.onShutter(AndroidCamera2ProxyImpl.this); + }}); + } + } - // TODO: Remove this method override once we handle the message - @Override - public void setDisplayOrientation(int degrees) {} + @Override + public void onImageAvailable(ImageReader reader) { + try (Image image = reader.acquireNextImage()) { + if (jpeg != null) { + ByteBuffer buffer = image.getPlanes()[0].getBuffer(); + final byte[] pixels = new byte[buffer.remaining()]; + buffer.get(pixels); + handler.post(new Runnable() { + @Override + public void run() { + jpeg.onPictureTaken(pixels, AndroidCamera2ProxyImpl.this); + }}); + } + } + }}; + 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(); + }}); + } // TODO: Implement @Override @@ -882,6 +992,15 @@ class AndroidCamera2AgentImpl extends CameraAgent { @Override public boolean applySettings(CameraSettings settings) { + if (settings == null) { + Log.w(TAG, "null parameters in applySettings()"); + return false; + } + if (!(settings instanceof AndroidCamera2Settings)) { + Log.e(TAG, "Provided settings not compatible with the backing framework API"); + return false; + } + return applySettingsHelper(settings, AndroidCamera2StateHolder.CAMERA_UNCONFIGURED | AndroidCamera2StateHolder.CAMERA_CONFIGURED | AndroidCamera2StateHolder.CAMERA_PREVIEW_READY); @@ -995,7 +1114,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { return mFirstFrontCameraId; } - private static class AndroidCharacteristics2 implements Characteristics { + private static class AndroidCharacteristics2 extends Characteristics { private CameraCharacteristics mCameraInfo; AndroidCharacteristics2(CameraCharacteristics cameraInfo) { diff --git a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Capabilities.java b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Capabilities.java index b81e098..4bdbe64 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Capabilities.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Capabilities.java @@ -38,11 +38,8 @@ import java.util.Arrays; public class AndroidCamera2Capabilities extends CameraCapabilities { private static Log.Tag TAG = new Log.Tag("AndCam2Capabs"); - private IntegralStringifier mIntStringifier; - AndroidCamera2Capabilities(CameraCharacteristics p) { - super(new IntegralStringifier()); - mIntStringifier = (IntegralStringifier) getStringifier(); + super(new Stringifier()); StreamConfigurationMap s = p.get(SCALER_STREAM_CONFIGURATION_MAP); @@ -99,18 +96,18 @@ public class AndroidCamera2Capabilities extends CameraCapabilities { if (mMaxNumOfMeteringArea > 0) { mSupportedFeatures.add(Feature.METERING_AREA); } - } - public IntegralStringifier getIntegralStringifier() { - return mIntStringifier; + // TODO: Detect other features } private void buildSceneModes(CameraCharacteristics p) { - for (int scene : p.get(CONTROL_AVAILABLE_SCENE_MODES)) { - SceneMode equiv = mIntStringifier.sceneModeFromInt(scene); - if (equiv != SceneMode.NO_SCENE_MODE) { - // equiv isn't a default generated because we couldn't handle this mode, so add it - mSupportedSceneModes.add(equiv); + int[] scenes = p.get(CONTROL_AVAILABLE_SCENE_MODES); + if (scenes != null) { + for (int scene : scenes) { + SceneMode equiv = sceneModeFromInt(scene); + if (equiv != null) { + mSupportedSceneModes.add(equiv); + } } } } @@ -118,275 +115,145 @@ public class AndroidCamera2Capabilities extends CameraCapabilities { private void buildFlashModes(CameraCharacteristics p) { mSupportedFlashModes.add(FlashMode.OFF); if (p.get(FLASH_INFO_AVAILABLE)) { + mSupportedFlashModes.add(FlashMode.AUTO); mSupportedFlashModes.add(FlashMode.ON); mSupportedFlashModes.add(FlashMode.TORCH); - // TODO: New modes aren't represented here + for (int expose : p.get(CONTROL_AE_AVAILABLE_MODES)) { + if (expose == CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE) { + mSupportedFlashModes.add(FlashMode.RED_EYE); + } + } } } private void buildFocusModes(CameraCharacteristics p) { - for (int focus : p.get(CONTROL_AF_AVAILABLE_MODES)) { - FocusMode equiv = mIntStringifier.focusModeFromInt(focus); - if (equiv != FocusMode.AUTO || focus == CONTROL_AF_MODE_AUTO) { - // equiv isn't a default generated because we couldn't handle this mode, so add it - mSupportedFocusModes.add(equiv); + int[] focuses = p.get(CONTROL_AF_AVAILABLE_MODES); + if (focuses != null) { + for (int focus : focuses) { + FocusMode equiv = focusModeFromInt(focus); + if (equiv != null) { + mSupportedFocusModes.add(equiv); + } } } } private void buildWhiteBalances(CameraCharacteristics p) { - for (int bal : p.get(CONTROL_AWB_AVAILABLE_MODES)) { - WhiteBalance equiv = mIntStringifier.whiteBalanceFromInt(bal); - if (equiv != WhiteBalance.AUTO || bal == CONTROL_AWB_MODE_AUTO) { - // equiv isn't a default generated because we couldn't handle this mode, so add it - mSupportedWhiteBalances.add(equiv); + int[] bals = p.get(CONTROL_AWB_AVAILABLE_MODES); + if (bals != null) { + for (int bal : bals) { + WhiteBalance equiv = whiteBalanceFromInt(bal); + if (equiv != null) { + mSupportedWhiteBalances.add(equiv); + } } } } - public static class IntegralStringifier extends Stringifier { - /** - * Converts the focus mode to API-related integer representation. - * - * @param fm The focus mode to convert. - * @return The corresponding {@code int} used by the camera framework - * API, or {@link CONTROL_AF_MODE_AUTO} if that fails. - */ - public int intify(FocusMode fm) { - switch (fm) { - case AUTO: - return CONTROL_AF_MODE_AUTO; - case CONTINUOUS_PICTURE: - return CONTROL_AF_MODE_CONTINUOUS_PICTURE; - case CONTINUOUS_VIDEO: - return CONTROL_AF_MODE_CONTINUOUS_VIDEO; - case EXTENDED_DOF: - return CONTROL_AF_MODE_EDOF; - case FIXED: - return CONTROL_AF_MODE_OFF; - case MACRO: - return CONTROL_AF_MODE_MACRO; - // TODO: New modes aren't represented here - } - return CONTROL_AF_MODE_AUTO; - } - - /** - * Converts the API-related integer representation of the focus mode to - * the abstract representation. - * - * @param val The integral representation. - * @return The mode represented by the input integer, or the focus mode - * with the lowest ordinal if it cannot be converted. - */ - public FocusMode focusModeFromInt(int fm) { - switch (fm) { - case CONTROL_AF_MODE_AUTO: - return FocusMode.AUTO; - case CONTROL_AF_MODE_CONTINUOUS_PICTURE: - return FocusMode.CONTINUOUS_PICTURE; - case CONTROL_AF_MODE_CONTINUOUS_VIDEO: - return FocusMode.CONTINUOUS_VIDEO; - case CONTROL_AF_MODE_EDOF: - return FocusMode.EXTENDED_DOF; - case CONTROL_AF_MODE_OFF: - return FocusMode.FIXED; - case CONTROL_AF_MODE_MACRO: - return FocusMode.MACRO; - // TODO: New modes aren't represented here - } - return FocusMode.values()[0]; - } - - /** - * Converts the flash mode to API-related integer representation. - * - * @param fm The flash mode to convert. - * @return The corresponding {@code int} used by the camera framework - * API, or {@link CONTROL_AF_MODE_AUTO} if that fails. - */ - public int intify(FlashMode flm) { - switch (flm) { - case OFF: - return FLASH_MODE_OFF; - case ON: - return FLASH_MODE_SINGLE; - case TORCH: - return FLASH_MODE_TORCH; - // TODO: New modes aren't represented here - } - return FLASH_MODE_OFF; - } - - /** - * Converts the API-related integer representation of the flash mode to - * the abstract representation. - * - * @param flm The integral representation. - * @return The mode represented by the input integer, or the flash mode - * with the lowest ordinal if it cannot be converted. - */ - public FlashMode flashModeFromInt(int flm) { - switch (flm) { - case FLASH_MODE_OFF: - return FlashMode.OFF; - case FLASH_MODE_SINGLE: - return FlashMode.ON; - case FLASH_MODE_TORCH: - return FlashMode.TORCH; - // TODO: New modes aren't represented here - } - return FlashMode.values()[0]; - } - - /** - * Converts the scene mode to API-related integer representation. - * - * @param fm The scene mode to convert. - * @return The corresponding {@code int} used by the camera framework - * API, or {@link CONTROL_SCENE_MODE_DISABLED} if that fails. - */ - public int intify(SceneMode sm) { - switch (sm) { - case AUTO: - return CONTROL_SCENE_MODE_DISABLED; - case ACTION: - return CONTROL_SCENE_MODE_ACTION; - case BARCODE: - return CONTROL_SCENE_MODE_BARCODE; - case BEACH: - return CONTROL_SCENE_MODE_BEACH; - case CANDLELIGHT: - return CONTROL_SCENE_MODE_CANDLELIGHT; - case FIREWORKS: - return CONTROL_SCENE_MODE_FIREWORKS; - case LANDSCAPE: - return CONTROL_SCENE_MODE_LANDSCAPE; - case NIGHT: - return CONTROL_SCENE_MODE_NIGHT; - case PARTY: - return CONTROL_SCENE_MODE_PARTY; - case PORTRAIT: - return CONTROL_SCENE_MODE_PORTRAIT; - case SNOW: - return CONTROL_SCENE_MODE_SNOW; - case SPORTS: - return CONTROL_SCENE_MODE_SPORTS; - case STEADYPHOTO: - return CONTROL_SCENE_MODE_STEADYPHOTO; - case SUNSET: - return CONTROL_SCENE_MODE_SUNSET; - case THEATRE: - return CONTROL_SCENE_MODE_THEATRE; - // TODO: New modes aren't represented here - } - return CONTROL_SCENE_MODE_DISABLED; - } - - /** - * Converts the API-related integer representation of the scene mode to - * the abstract representation. - * - * @param sm The integral representation. - * @return The mode represented by the input integer, or the scene mode - * with the lowest ordinal if it cannot be converted. - */ - public SceneMode sceneModeFromInt(int sm) { - switch (sm) { - case CONTROL_SCENE_MODE_DISABLED: - return SceneMode.AUTO; - case CONTROL_SCENE_MODE_ACTION: - return SceneMode.ACTION; - case CONTROL_SCENE_MODE_BARCODE: - return SceneMode.BARCODE; - case CONTROL_SCENE_MODE_BEACH: - return SceneMode.BEACH; - case CONTROL_SCENE_MODE_CANDLELIGHT: - return SceneMode.CANDLELIGHT; - case CONTROL_SCENE_MODE_FIREWORKS: - return SceneMode.FIREWORKS; - case CONTROL_SCENE_MODE_LANDSCAPE: - return SceneMode.LANDSCAPE; - case CONTROL_SCENE_MODE_NIGHT: - return SceneMode.NIGHT; - case CONTROL_SCENE_MODE_PARTY: - return SceneMode.PARTY; - case CONTROL_SCENE_MODE_PORTRAIT: - return SceneMode.PORTRAIT; - case CONTROL_SCENE_MODE_SNOW: - return SceneMode.SNOW; - case CONTROL_SCENE_MODE_SPORTS: - return SceneMode.SPORTS; - case CONTROL_SCENE_MODE_STEADYPHOTO: - return SceneMode.STEADYPHOTO; - case CONTROL_SCENE_MODE_SUNSET: - return SceneMode.SUNSET; - case CONTROL_SCENE_MODE_THEATRE: - return SceneMode.THEATRE; - // TODO: New modes aren't represented here - } - return SceneMode.values()[0]; + /** + * Converts the API-related integer representation of the focus mode to the + * abstract representation. + * + * @param fm The integral representation. + * @return The mode represented by the input integer, or {@code null} if it + * cannot be converted. + */ + public static FocusMode focusModeFromInt(int fm) { + switch (fm) { + case CONTROL_AF_MODE_AUTO: + return FocusMode.AUTO; + case CONTROL_AF_MODE_CONTINUOUS_PICTURE: + return FocusMode.CONTINUOUS_PICTURE; + case CONTROL_AF_MODE_CONTINUOUS_VIDEO: + return FocusMode.CONTINUOUS_VIDEO; + case CONTROL_AF_MODE_EDOF: + return FocusMode.EXTENDED_DOF; + case CONTROL_AF_MODE_OFF: + return FocusMode.FIXED; + // TODO: We cannot support INFINITY + case CONTROL_AF_MODE_MACRO: + return FocusMode.MACRO; } + Log.w(TAG, "Unable to convert from API 2 focus mode: " + fm); + return null; + } - /** - * Converts the white balance to API-related integer representation. - * - * @param fm The white balance to convert. - * @return The corresponding {@code int} used by the camera framework - * API, or {@link CONTROL_SCENE_MODE_DISABLED} if that fails. - */ - public int intify(WhiteBalance wb) { - switch (wb) { - case AUTO: - return CONTROL_AWB_MODE_AUTO; - case CLOUDY_DAYLIGHT: - return CONTROL_AWB_MODE_CLOUDY_DAYLIGHT; - case DAYLIGHT: - return CONTROL_AWB_MODE_DAYLIGHT; - case FLUORESCENT: - return CONTROL_AWB_MODE_FLUORESCENT; - case INCANDESCENT: - return CONTROL_AWB_MODE_INCANDESCENT; - case SHADE: - return CONTROL_AWB_MODE_SHADE; - case TWILIGHT: - return CONTROL_AWB_MODE_TWILIGHT; - case WARM_FLUORESCENT: - return CONTROL_AWB_MODE_WARM_FLUORESCENT; - // TODO: New modes aren't represented here - } - return CONTROL_AWB_MODE_AUTO; + /** + * Converts the API-related integer representation of the scene mode to the + * abstract representation. + * + * @param sm The integral representation. + * @return The mode represented by the input integer, or {@code null} if it + * cannot be converted. + */ + public static SceneMode sceneModeFromInt(int sm) { + switch (sm) { + case CONTROL_SCENE_MODE_DISABLED: + return SceneMode.AUTO; + case CONTROL_SCENE_MODE_ACTION: + return SceneMode.ACTION; + case CONTROL_SCENE_MODE_BARCODE: + return SceneMode.BARCODE; + case CONTROL_SCENE_MODE_BEACH: + return SceneMode.BEACH; + case CONTROL_SCENE_MODE_CANDLELIGHT: + return SceneMode.CANDLELIGHT; + case CONTROL_SCENE_MODE_FIREWORKS: + return SceneMode.FIREWORKS; + // TODO: We cannot support HDR + case CONTROL_SCENE_MODE_LANDSCAPE: + return SceneMode.LANDSCAPE; + case CONTROL_SCENE_MODE_NIGHT: + return SceneMode.NIGHT; + // TODO: We cannot support NIGHT_PORTRAIT + case CONTROL_SCENE_MODE_PARTY: + return SceneMode.PARTY; + case CONTROL_SCENE_MODE_PORTRAIT: + return SceneMode.PORTRAIT; + case CONTROL_SCENE_MODE_SNOW: + return SceneMode.SNOW; + case CONTROL_SCENE_MODE_SPORTS: + return SceneMode.SPORTS; + case CONTROL_SCENE_MODE_STEADYPHOTO: + return SceneMode.STEADYPHOTO; + case CONTROL_SCENE_MODE_SUNSET: + return SceneMode.SUNSET; + case CONTROL_SCENE_MODE_THEATRE: + return SceneMode.THEATRE; + // TODO: We cannot expose FACE_PRIORITY, or HIGH_SPEED_VIDEO } + Log.w(TAG, "Unable to convert from API 2 scene mode: " + sm); + return null; + } - /** - * Converts the API-related integer representation of the white balance - * to the abstract representation. - * - * @param wb The integral representation. - * @return The balance represented by the input integer, or the white - * balance with the lowest ordinal if it cannot be converted. - */ - public WhiteBalance whiteBalanceFromInt(int wb) { - switch (wb) { - case CONTROL_AWB_MODE_AUTO: - return WhiteBalance.AUTO; - case CONTROL_AWB_MODE_CLOUDY_DAYLIGHT: - return WhiteBalance.CLOUDY_DAYLIGHT; - case CONTROL_AWB_MODE_DAYLIGHT: - return WhiteBalance.DAYLIGHT; - case CONTROL_AWB_MODE_FLUORESCENT: - return WhiteBalance.FLUORESCENT; - case CONTROL_AWB_MODE_INCANDESCENT: - return WhiteBalance.INCANDESCENT; - case CONTROL_AWB_MODE_SHADE: - return WhiteBalance.SHADE; - case CONTROL_AWB_MODE_TWILIGHT: - return WhiteBalance.TWILIGHT; - case CONTROL_AWB_MODE_WARM_FLUORESCENT: - return WhiteBalance.WARM_FLUORESCENT; - // TODO: New modes aren't represented here - } - return WhiteBalance.values()[0]; + /** + * Converts the API-related integer representation of the white balance to + * the abstract representation. + * + * @param wb The integral representation. + * @return The balance represented by the input integer, or {@code null} if + * it cannot be converted. + */ + public static WhiteBalance whiteBalanceFromInt(int wb) { + switch (wb) { + case CONTROL_AWB_MODE_AUTO: + return WhiteBalance.AUTO; + case CONTROL_AWB_MODE_CLOUDY_DAYLIGHT: + return WhiteBalance.CLOUDY_DAYLIGHT; + case CONTROL_AWB_MODE_DAYLIGHT: + return WhiteBalance.DAYLIGHT; + case CONTROL_AWB_MODE_FLUORESCENT: + return WhiteBalance.FLUORESCENT; + case CONTROL_AWB_MODE_INCANDESCENT: + return WhiteBalance.INCANDESCENT; + case CONTROL_AWB_MODE_SHADE: + return WhiteBalance.SHADE; + case CONTROL_AWB_MODE_TWILIGHT: + return WhiteBalance.TWILIGHT; + case CONTROL_AWB_MODE_WARM_FLUORESCENT: + return WhiteBalance.WARM_FLUORESCENT; } + Log.w(TAG, "Unable to convert from API 2 white balance: " + wb); + return null; } } diff --git a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Settings.java b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Settings.java index e21e177..288ded7 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Settings.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Settings.java @@ -16,32 +16,449 @@ package com.android.ex.camera2.portability; -import android.hardware.camera2.CaptureRequest; +import static android.hardware.camera2.CaptureRequest.*; + +import android.graphics.Rect; +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraDevice; +import android.hardware.camera2.params.MeteringRectangle; +import android.util.Range; + +import com.android.ex.camera2.portability.CameraCapabilities.FlashMode; +import com.android.ex.camera2.portability.CameraCapabilities.FocusMode; +import com.android.ex.camera2.portability.CameraCapabilities.SceneMode; +import com.android.ex.camera2.portability.CameraCapabilities.WhiteBalance; +import com.android.ex.camera2.portability.debug.Log; +import com.android.ex.camera2.utils.Camera2RequestSettingsSet; + +import java.util.List; +import java.util.Objects; /** * The subclass of {@link CameraSettings} for Android Camera 2 API. */ public class AndroidCamera2Settings extends CameraSettings { - // TODO: Implement more completely - public AndroidCamera2Settings(AndroidCamera2Capabilities capabilities, - CaptureRequest.Builder request, - Size preview, Size photo) { - // TODO: Support zoom - setZoomRatio(1.0f); - setZoomIndex(0); - - // TODO: Set exposure compensation - - AndroidCamera2Capabilities.IntegralStringifier stringifier = - capabilities.getIntegralStringifier(); - setFocusMode(stringifier.focusModeFromInt(request.get(CaptureRequest.CONTROL_AF_MODE))); - setFlashMode(stringifier.flashModeFromInt(request.get(CaptureRequest.FLASH_MODE))); - setSceneMode(stringifier.sceneModeFromInt(request.get(CaptureRequest.CONTROL_SCENE_MODE))); - - // This is mutability-safe because those setters copy the Size objects + private static final Log.Tag TAG = new Log.Tag("AndCam2Set"); + + private final Builder mTemplateSettings; + private final Rect mActiveArray; + private final Camera2RequestSettingsSet mRequestSettings; + + /** + * Create a settings representation that answers queries of unspecified + * options in the same way as the provided template would. + * + * <p>The default settings provided by the given template are only ever used + * for reporting back to the client app (i.e. when it queries an option + * it didn't explicitly set first). {@link Camera2RequestSettingsSet}s + * generated by an instance of this class will have any settings not + * modified using one of that instance's mutators forced to default, so that + * their effective values when submitting a capture request will be those of + * the template that is provided to the camera framework at that time.</p> + * + * @param camera Device from which to draw default settings. + * @param template Specific template to use for the defaults. + * @param activeArray Boundary coordinates of the sensor's active array. + * @param preview Dimensions of preview streams. + * @param photo Dimensions of captured images. + * + * @throws CameraAccessException Upon internal framework/driver failure. + */ + public AndroidCamera2Settings(CameraDevice camera, int template, Rect activeArray, + Size preview, Size photo) throws CameraAccessException { + mTemplateSettings = camera.createCaptureRequest(template); + mActiveArray = activeArray; + mRequestSettings = new Camera2RequestSettingsSet(); + + Range<Integer> previewFpsRange = mTemplateSettings.get(CONTROL_AE_TARGET_FPS_RANGE); + if (previewFpsRange != null) { + setPreviewFpsRange(previewFpsRange.getLower(), previewFpsRange.getUpper()); + } setPreviewSize(preview); + // TODO: mCurrentPreviewFormat setPhotoSize(photo); + mJpegCompressQuality = queryTemplateDefaultOrMakeOneUp(JPEG_QUALITY, (byte) 0); + // TODO: mCurrentPhotoFormat + // TODO: mCurrentZoomRatio + mCurrentZoomRatio = 1.0f; + // TODO: mCurrentZoomIndex + mExposureCompensationIndex = + queryTemplateDefaultOrMakeOneUp(CONTROL_AE_EXPOSURE_COMPENSATION, 0); + mCurrentFlashMode = flashModeFromRequest(); + mCurrentFocusMode = AndroidCamera2Capabilities.focusModeFromInt( + mTemplateSettings.get(CONTROL_AF_MODE)); + mCurrentSceneMode = AndroidCamera2Capabilities.sceneModeFromInt( + mTemplateSettings.get(CONTROL_SCENE_MODE)); + mWhiteBalance = AndroidCamera2Capabilities.whiteBalanceFromInt( + mTemplateSettings.get(CONTROL_AWB_MODE)); + mVideoStabilizationEnabled = queryTemplateDefaultOrMakeOneUp( + CONTROL_VIDEO_STABILIZATION_MODE, CONTROL_VIDEO_STABILIZATION_MODE_OFF) == + CONTROL_VIDEO_STABILIZATION_MODE_ON; + mAutoExposureLocked = queryTemplateDefaultOrMakeOneUp(CONTROL_AE_LOCK, false); + mAutoWhiteBalanceLocked = queryTemplateDefaultOrMakeOneUp(CONTROL_AWB_LOCK, false); + // TODO: mRecordingHintEnabled + // TODO: mGpsData + android.util.Size exifThumbnailSize = mTemplateSettings.get(JPEG_THUMBNAIL_SIZE); + if (exifThumbnailSize != null) { + mExifThumbnailSize = + new Size(exifThumbnailSize.getWidth(), exifThumbnailSize.getHeight()); + } + } + + public AndroidCamera2Settings(AndroidCamera2Settings other) { + super(other); + mTemplateSettings = other.mTemplateSettings; + mActiveArray = other.mActiveArray; + mRequestSettings = new Camera2RequestSettingsSet(other.mRequestSettings); + } + + @Override + public CameraSettings copy() { + return new AndroidCamera2Settings(this); + } + + private <T> T queryTemplateDefaultOrMakeOneUp(Key<T> key, T defaultDefault) { + T val = mTemplateSettings.get(key); + if (val != null) { + return val; + } else { + // Spoof the default so matchesTemplateDefault excludes this key from generated sets. + // This approach beats a simple sentinel because it provides basic boolean support. + mTemplateSettings.set(key, defaultDefault); + return defaultDefault; + } + } + + private FlashMode flashModeFromRequest() { + Integer autoExposure = mTemplateSettings.get(CONTROL_AE_MODE); + if (autoExposure != null) { + switch (autoExposure) { + case CONTROL_AE_MODE_ON: + return FlashMode.OFF; + case CONTROL_AE_MODE_ON_AUTO_FLASH: + return FlashMode.AUTO; + case CONTROL_AE_MODE_ON_ALWAYS_FLASH: { + if (mTemplateSettings.get(FLASH_MODE) == FLASH_MODE_TORCH) { + return FlashMode.TORCH; + } else { + return FlashMode.ON; + } + } + case CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE: + return FlashMode.RED_EYE; + } + } + return null; + } + + private boolean matchesTemplateDefault(Key<?> setting) { + if (setting == CONTROL_AE_REGIONS) { + return mMeteringAreas.size() == 0; + } else if (setting == CONTROL_AF_REGIONS) { + return mFocusAreas.size() == 0; + } else if (setting == CONTROL_AE_TARGET_FPS_RANGE) { + Range defaultFpsRange = mTemplateSettings.get(CONTROL_AE_TARGET_FPS_RANGE); + return (mPreviewFpsRangeMin == 0 && mPreviewFpsRangeMax == 0) || + (defaultFpsRange != null && mPreviewFpsRangeMin == defaultFpsRange.getLower() && + mPreviewFpsRangeMax == defaultFpsRange.getUpper()); + } else if (setting == JPEG_QUALITY) { + return Objects.equals(mJpegCompressQuality, + mTemplateSettings.get(JPEG_QUALITY)); + } else if (setting == CONTROL_AE_EXPOSURE_COMPENSATION) { + return Objects.equals(mExposureCompensationIndex, + mTemplateSettings.get(CONTROL_AE_EXPOSURE_COMPENSATION)); + } else if (setting == CONTROL_VIDEO_STABILIZATION_MODE) { + Integer videoStabilization = mTemplateSettings.get(CONTROL_VIDEO_STABILIZATION_MODE); + return (videoStabilization != null && + (mVideoStabilizationEnabled && videoStabilization == + CONTROL_VIDEO_STABILIZATION_MODE_ON) || + (!mVideoStabilizationEnabled && videoStabilization == + CONTROL_VIDEO_STABILIZATION_MODE_OFF)); + } else if (setting == CONTROL_AE_LOCK) { + return Objects.equals(mAutoExposureLocked, mTemplateSettings.get(CONTROL_AE_LOCK)); + } else if (setting == CONTROL_AWB_LOCK) { + return Objects.equals(mAutoWhiteBalanceLocked, mTemplateSettings.get(CONTROL_AWB_LOCK)); + } else if (setting == JPEG_THUMBNAIL_SIZE) { + android.util.Size defaultThumbnailSize = mTemplateSettings.get(JPEG_THUMBNAIL_SIZE); + return (mExifThumbnailSize.width() == 0 && mExifThumbnailSize.height() == 0) || + (defaultThumbnailSize != null && + mExifThumbnailSize.width() == defaultThumbnailSize.getWidth() && + mExifThumbnailSize.height() == defaultThumbnailSize.getHeight()); + } + Log.w(TAG, "Settings implementation checked default of unhandled option key"); + // Since this class isn't equipped to handle it, claim it matches the default to prevent + // updateRequestSettingOrForceToDefault from going with the user-provided preference + return true; + } + + private <T> void updateRequestSettingOrForceToDefault(Key<T> setting, T possibleChoice) { + mRequestSettings.set(setting, matchesTemplateDefault(setting) ? null : possibleChoice); + } + + public Camera2RequestSettingsSet getRequestSettings() { + updateRequestSettingOrForceToDefault(CONTROL_AE_REGIONS, + legacyAreasToMeteringRectangles(mMeteringAreas)); + updateRequestSettingOrForceToDefault(CONTROL_AF_REGIONS, + legacyAreasToMeteringRectangles(mFocusAreas)); + updateRequestSettingOrForceToDefault(CONTROL_AE_TARGET_FPS_RANGE, + new Range(mPreviewFpsRangeMin, mPreviewFpsRangeMax)); + // TODO: mCurrentPreviewFormat + updateRequestSettingOrForceToDefault(JPEG_QUALITY, mJpegCompressQuality); + // TODO: mCurrentPhotoFormat + // TODO: mCurrentZoomRatio + // TODO: mCurrentZoomIndex + updateRequestSettingOrForceToDefault(CONTROL_AE_EXPOSURE_COMPENSATION, + mExposureCompensationIndex); + updateRequestFlashMode(); + updateRequestFocusMode(); + updateRequestSceneMode(); + updateRequestWhiteBalance(); + updateRequestSettingOrForceToDefault(CONTROL_VIDEO_STABILIZATION_MODE, + mVideoStabilizationEnabled ? + CONTROL_VIDEO_STABILIZATION_MODE_ON : CONTROL_VIDEO_STABILIZATION_MODE_OFF); + // OIS shouldn't be on if software video stabilization is. + mRequestSettings.set(LENS_OPTICAL_STABILIZATION_MODE, + mVideoStabilizationEnabled ? LENS_OPTICAL_STABILIZATION_MODE_OFF : + null); + updateRequestSettingOrForceToDefault(CONTROL_AE_LOCK, mAutoExposureLocked); + updateRequestSettingOrForceToDefault(CONTROL_AWB_LOCK, mAutoWhiteBalanceLocked); + // TODO: mRecordingHintEnabled + // TODO: mGpsData + updateRequestSettingOrForceToDefault(JPEG_THUMBNAIL_SIZE, + new android.util.Size( + mExifThumbnailSize.width(), mExifThumbnailSize.height())); + + return mRequestSettings; + } + + private MeteringRectangle[] legacyAreasToMeteringRectangles( + List<android.hardware.Camera.Area> reference) { + MeteringRectangle[] transformed = null; + if (reference.size() > 0) { + + transformed = new MeteringRectangle[reference.size()]; + for (int index = 0; index < reference.size(); ++index) { + android.hardware.Camera.Area source = reference.get(index); + Rect rectangle = source.rect; + + // Old API coordinates were [-1000,1000]; new ones are [0,ACTIVE_ARRAY_SIZE). + double oldLeft = (rectangle.left + 1000) / 2000.0; + double oldTop = (rectangle.top + 1000) / 2000.0; + double oldRight = (rectangle.right + 1000) / 2000.0; + double oldBottom = (rectangle.bottom + 1000) / 2000.0; + int left = toIntConstrained( mActiveArray.width() * oldLeft + mActiveArray.left, + 0, mActiveArray.width() - 1); + int top = toIntConstrained( mActiveArray.height() * oldTop + mActiveArray.top, + 0, mActiveArray.height() - 1); + int right = toIntConstrained( mActiveArray.width() * oldRight + mActiveArray.left, + 0, mActiveArray.width() - 1); + int bottom = toIntConstrained( mActiveArray.height() * oldBottom + mActiveArray.top, + 0, mActiveArray.height() - 1); + transformed[index] = new MeteringRectangle(left, top, right - left, bottom - top, + source.weight); + } + } + return transformed; + } + + private int toIntConstrained(double original, int min, int max) { + original = Math.max(original, min); + original = Math.min(original, max); + return (int) original; + } + + private void updateRequestFlashMode() { + Integer aeMode = null; + Integer flashMode = null; + if (mCurrentFlashMode != null) { + switch (mCurrentFlashMode) { + case AUTO: { + aeMode = CONTROL_AE_MODE_ON_AUTO_FLASH; + break; + } + case OFF: { + aeMode = CONTROL_AE_MODE_ON; + flashMode = FLASH_MODE_OFF; + break; + } + case ON: { + aeMode = CONTROL_AE_MODE_ON_ALWAYS_FLASH; + flashMode = FLASH_MODE_SINGLE; + break; + } + case TORCH: { + flashMode = FLASH_MODE_TORCH; + break; + } + case RED_EYE: { + aeMode = CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE; + break; + } + default: { + Log.w(TAG, "Unable to convert to API 2 flash mode: " + mCurrentFlashMode); + break; + } + } + } + mRequestSettings.set(CONTROL_AE_MODE, aeMode); + mRequestSettings.set(FLASH_MODE, flashMode); + } + + private void updateRequestFocusMode() { + Integer mode = null; + if (mCurrentFocusMode != null) { + switch (mCurrentFocusMode) { + case AUTO: { + mode = CONTROL_AF_MODE_AUTO; + break; + } + case CONTINUOUS_PICTURE: { + mode = CONTROL_AF_MODE_CONTINUOUS_PICTURE; + break; + } + case CONTINUOUS_VIDEO: { + mode = CONTROL_AF_MODE_CONTINUOUS_VIDEO; + break; + } + case EXTENDED_DOF: { + mode = CONTROL_AF_MODE_EDOF; + break; + } + case FIXED: { + mode = CONTROL_AF_MODE_OFF; + break; + } + // TODO: We cannot support INFINITY + case MACRO: { + mode = CONTROL_AF_MODE_MACRO; + break; + } + default: { + Log.w(TAG, "Unable to convert to API 2 focus mode: " + mCurrentFocusMode); + break; + } + } + } + mRequestSettings.set(CONTROL_AF_MODE, mode); + } + + private void updateRequestSceneMode() { + Integer mode = null; + if (mCurrentSceneMode != null) { + switch (mCurrentSceneMode) { + case AUTO: { + mode = CONTROL_SCENE_MODE_DISABLED; + break; + } + case ACTION: { + mode = CONTROL_SCENE_MODE_ACTION; + break; + } + case BARCODE: { + mode = CONTROL_SCENE_MODE_BARCODE; + break; + } + case BEACH: { + mode = CONTROL_SCENE_MODE_BEACH; + break; + } + case CANDLELIGHT: { + mode = CONTROL_SCENE_MODE_CANDLELIGHT; + break; + } + case FIREWORKS: { + mode = CONTROL_SCENE_MODE_FIREWORKS; + break; + } + // TODO: We cannot support HDR + case LANDSCAPE: { + mode = CONTROL_SCENE_MODE_LANDSCAPE; + break; + } + case NIGHT: { + mode = CONTROL_SCENE_MODE_NIGHT; + break; + } + // TODO: We cannot support NIGHT_PORTRAIT + case PARTY: { + mode = CONTROL_SCENE_MODE_PARTY; + break; + } + case PORTRAIT: { + mode = CONTROL_SCENE_MODE_PORTRAIT; + break; + } + case SNOW: { + mode = CONTROL_SCENE_MODE_SNOW; + break; + } + case SPORTS: { + mode = CONTROL_SCENE_MODE_SPORTS; + break; + } + case STEADYPHOTO: { + mode = CONTROL_SCENE_MODE_STEADYPHOTO; + break; + } + case SUNSET: { + mode = CONTROL_SCENE_MODE_SUNSET; + break; + } + case THEATRE: { + mode = CONTROL_SCENE_MODE_THEATRE; + break; + } + default: { + Log.w(TAG, "Unable to convert to API 2 scene mode: " + mCurrentSceneMode); + break; + } + } + } + mRequestSettings.set(CONTROL_SCENE_MODE, mode); + } - // TODO: Initialize formats, too + private void updateRequestWhiteBalance() { + Integer mode = null; + if (mWhiteBalance != null) { + switch (mWhiteBalance) { + case AUTO: { + mode = CONTROL_AWB_MODE_AUTO; + break; + } + case CLOUDY_DAYLIGHT: { + mode = CONTROL_AWB_MODE_CLOUDY_DAYLIGHT; + break; + } + case DAYLIGHT: { + mode = CONTROL_AWB_MODE_DAYLIGHT; + break; + } + case FLUORESCENT: { + mode = CONTROL_AWB_MODE_FLUORESCENT; + break; + } + case INCANDESCENT: { + mode = CONTROL_AWB_MODE_INCANDESCENT; + break; + } + case SHADE: { + mode = CONTROL_AWB_MODE_SHADE; + break; + } + case TWILIGHT: { + mode = CONTROL_AWB_MODE_TWILIGHT; + break; + } + case WARM_FLUORESCENT: { + mode = CONTROL_AWB_MODE_WARM_FLUORESCENT; + break; + } + default: { + Log.w(TAG, "Unable to convert to API 2 white balance: " + mWhiteBalance); + break; + } + } + } + mRequestSettings.set(CONTROL_AWB_MODE, mode); } } diff --git a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java index 9bbbb7a..95f0320 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java @@ -175,7 +175,7 @@ class AndroidCameraAgentImpl extends CameraAgent { return mFirstFrontCameraId; } - private static class AndroidCharacteristics implements Characteristics { + private static class AndroidCharacteristics extends Characteristics { private Camera.CameraInfo mCameraInfo; AndroidCharacteristics(Camera.CameraInfo cameraInfo) { @@ -437,7 +437,14 @@ class AndroidCameraAgentImpl extends CameraAgent { } case CameraActions.SET_DISPLAY_ORIENTATION: { - mCamera.setDisplayOrientation(msg.arg1); + // Update preview orientation + mCamera.setDisplayOrientation( + mCharacteristics.getPreviewOrientation(msg.arg1)); + // Only set the JPEG capture orientation if requested to do so; otherwise, + // capture in the sensor's physical orientation + mParamsToSet.setRotation( + msg.arg2 > 0 ? mCharacteristics.getJpegOrientation(msg.arg1) : 0); + mCamera.setParameters(mParamsToSet); break; } @@ -566,7 +573,6 @@ class AndroidCameraAgentImpl extends CameraAgent { // Should use settings.getCurrentZoomRatio() instead here. mParamsToSet.setZoom(settings.getCurrentZoomIndex()); } - mParamsToSet.setRotation((int) settings.getCurrentPhotoRotationDegrees()); mParamsToSet.setExposureCompensation(settings.getExposureCompensationIndex()); if (mCapabilities.supports(CameraCapabilities.Feature.AUTO_EXPOSURE_LOCK)) { mParamsToSet.setAutoExposureLock(settings.isAutoExposureLocked()); diff --git a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraSettings.java b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraSettings.java index a4039bd..ceab7fe 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraSettings.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraSettings.java @@ -57,11 +57,19 @@ public class AndroidCameraSettings extends CameraSettings { } setRecordingHintEnabled(TRUE.equals(params.get(RECORDING_HINT))); - // Output: Photo size, compression quality, rotation. - setPhotoRotationDegrees(0f); + // Output: Photo size, compression quality setPhotoJpegCompressionQuality(params.getJpegQuality()); Camera.Size paramPictureSize = params.getPictureSize(); setPhotoSize(new Size(paramPictureSize.width, paramPictureSize.height)); setPhotoFormat(params.getPictureFormat()); } + + public AndroidCameraSettings(AndroidCameraSettings other) { + super(other); + } + + @Override + public CameraSettings copy() { + return new AndroidCameraSettings(this); + } } diff --git a/camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java b/camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java index d875cea..dd4f77c 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java @@ -618,16 +618,32 @@ public abstract class CameraAgent { CameraPictureCallback jpeg); /** - * Sets the display orientation for camera to adjust the preview orientation. + * Sets the display orientation for camera to adjust the preview and JPEG orientation. * - * @param degrees The rotation in degrees. Should be 0, 90, 180 or 270. + * @param degrees The counterclockwise rotation in degrees, relative to the device's natural + * orientation. Should be 0, 90, 180 or 270. */ public void setDisplayOrientation(final int degrees) { + setDisplayOrientation(degrees, true); + } + + /** + * Sets the display orientation for camera to adjust the preview—and, optionally, + * JPEG—orientations. + * <p>If capture rotation is not requested, future captures will be returned in the sensor's + * physical rotation, which does not necessarily match the device's natural orientation.</p> + * + * @param degrees The counterclockwise rotation in degrees, relative to the device's natural + * orientation. Should be 0, 90, 180 or 270. + * @param capture Whether to adjust the JPEG capture orientation as well as the preview one. + */ + public void setDisplayOrientation(final int degrees, final boolean capture) { getDispatchThread().runJob(new Runnable() { @Override public void run() { getCameraHandler() - .obtainMessage(CameraActions.SET_DISPLAY_ORIENTATION, degrees, 0) + .obtainMessage(CameraActions.SET_DISPLAY_ORIENTATION, degrees, + capture ? 1 : 0) .sendToTarget(); }}); } @@ -713,7 +729,7 @@ public abstract class CameraAgent { * @param statesToAwait Bitwise OR of the required camera states. * @return Whether the settings can be applied. */ - protected boolean applySettingsHelper(final CameraSettings settings, + protected boolean applySettingsHelper(CameraSettings settings, final int statesToAwait) { if (settings == null) { Log.v(TAG, "null parameters in applySettings()"); @@ -723,15 +739,14 @@ public abstract class CameraAgent { return false; } - final CameraSettings copyOfSettings = new CameraSettings(settings); + final CameraSettings copyOfSettings = settings.copy(); getDispatchThread().runJob(new Runnable() { @Override public void run() { getCameraState().waitForStates(statesToAwait); getCameraHandler().obtainMessage(CameraActions.APPLY_SETTINGS, copyOfSettings) .sendToTarget(); - } - }); + }}); return true; } diff --git a/camera2/portability/src/com/android/ex/camera2/portability/CameraCapabilities.java b/camera2/portability/src/com/android/ex/camera2/portability/CameraCapabilities.java index 9b7ca6e..16d70f9 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/CameraCapabilities.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraCapabilities.java @@ -97,6 +97,7 @@ public class CameraCapabilities { * Focus is set at infinity. * @see {@link android.hardware.Camera.Parameters#FOCUS_MODE_INFINITY}. */ + // TODO: Unsupported on API 2 INFINITY, /** * Macro (close-up) focus mode. @@ -182,6 +183,7 @@ public class CameraCapabilities { * Capture a scene using high dynamic range imaging techniques. * @see {@link android.hardware.Camera.Parameters#SCENE_MODE_HDR}. */ + // TODO: Unsupported on API 2 HDR, /** * Take pictures on distant objects. @@ -197,6 +199,7 @@ public class CameraCapabilities { * Take people pictures at night. * @see {@link android.hardware.Camera.Parameters#SCENE_MODE_NIGHT_PORTRAIT}. */ + // TODO: Unsupported on API 2 NIGHT_PORTRAIT, /** * Take indoor low-light shot. diff --git a/camera2/portability/src/com/android/ex/camera2/portability/CameraDeviceInfo.java b/camera2/portability/src/com/android/ex/camera2/portability/CameraDeviceInfo.java index ada1f29..a657170 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/CameraDeviceInfo.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraDeviceInfo.java @@ -1,7 +1,25 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.ex.camera2.portability; import android.hardware.Camera; +import com.android.ex.camera2.portability.debug.Log; + /** * The device info for all attached cameras. */ @@ -35,27 +53,97 @@ public interface CameraDeviceInfo { /** * Device characteristics for a single camera. */ - public interface Characteristics { + public abstract class Characteristics { + private static final Log.Tag TAG = new Log.Tag("CamDvcInfChar"); + /** * @return Whether the camera faces the back of the device. */ - boolean isFacingBack(); + public abstract boolean isFacingBack(); /** * @return Whether the camera faces the device's screen. */ - boolean isFacingFront(); + public abstract boolean isFacingFront(); /** * @return The camera image orientation, or the clockwise rotation angle * that must be applied to display it in its natural orientation - * (in degrees, and always a multiple of 90). + * (in degrees, always a multiple of 90, and between [90,270]). + */ + public abstract int getSensorOrientation(); + + /** + * @param currentDisplayOrientation + * The current display orientation, as measured clockwise from + * the device's natural orientation (in degrees, always a + * multiple of 90, and between 0 and 270, inclusive). + * @return + * The relative preview image orientation, or the clockwise + * rotation angle that must be applied to display preview + * frames in the matching orientation, accounting for implicit + * mirroring, if applicable (in degrees, always a multiple of + * 90, and between 0 and 270, inclusive). */ - int getSensorOrientation(); + public int getPreviewOrientation(int currentDisplayOrientation) { + // Drivers tend to mirror the image during front camera preview. + return getRelativeImageOrientation(currentDisplayOrientation, true); + } + + /** + * @param currentDisplayOrientation + * The current display orientation, as measured clockwise from + * the device's natural orientation (in degrees, always a + * multiple of 90, and between 0 and 270, inclusive). + * @return + * The relative capture image orientation, or the clockwise + * rotation angle that must be applied to display these frames + * in the matching orientation (in degrees, always a multiple + * of 90, and between 0 and 270, inclusive). + */ + public int getJpegOrientation(int currentDisplayOrientation) { + // Don't mirror during capture! + return getRelativeImageOrientation(currentDisplayOrientation, false); + } + + /** + * @param currentDisplayOrientaiton + * {@link #getPreviewOrientation}, {@link #getJpegOrientation} + * @param compensateForMirroring + * Whether to account for mirroring in the case of front-facing + * cameras, which is necessary iff the OS/driver is + * automatically reflecting the image. + * @return + * {@link #getPreviewOrientation}, {@link #getJpegOrientation} + * + * @see android.hardware.Camera.setDisplayOrientation + */ + protected int getRelativeImageOrientation(int currentDisplayOrientation, + boolean compensateForMirroring) { + if (currentDisplayOrientation % 90 != 0) { + Log.e(TAG, "Provided display orientation is not divisible by 90"); + } + if (currentDisplayOrientation < 0 || currentDisplayOrientation > 270) { + Log.e(TAG, "Provided display orientation is outside expected range"); + } + + int result = 0; + if (isFacingFront()) { + result = (getSensorOrientation() + currentDisplayOrientation) % 360; + if (compensateForMirroring) { + result = (360 - result) % 360; + } + } else if (isFacingBack()) { + result = (getSensorOrientation() - currentDisplayOrientation + 360) % 360; + } else { + Log.e(TAG, "Camera is facing unhandled direction"); + } + return result; + } /** * @return Whether the shutter sound can be disabled. */ - boolean canDisableShutterSound(); + public abstract boolean canDisableShutterSound(); } } diff --git a/camera2/portability/src/com/android/ex/camera2/portability/CameraSettings.java b/camera2/portability/src/com/android/ex/camera2/portability/CameraSettings.java index 3948b2e..26d0f85 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/CameraSettings.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraSettings.java @@ -18,6 +18,8 @@ package com.android.ex.camera2.portability; import android.hardware.Camera; +import com.android.ex.camera2.portability.debug.Log; + import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -26,7 +28,12 @@ import java.util.TreeMap; /** * A class which stores the camera settings. */ -public class CameraSettings { +public abstract class CameraSettings { + private static final Log.Tag TAG = new Log.Tag("CamSet"); + + // Attempts to provide a value outside this range will be ignored. + private static final int MIN_JPEG_COMPRESSION_QUALITY = 1; + private static final int MAX_JPEG_COMPRESSION_QUALITY = 100; protected final Map<String, String> mGeneralSetting = new TreeMap<>(); protected final List<Camera.Area> mMeteringAreas = new ArrayList<>(); @@ -37,11 +44,10 @@ public class CameraSettings { protected Size mCurrentPreviewSize; private int mCurrentPreviewFormat; protected Size mCurrentPhotoSize; - protected int mJpegCompressQuality; + protected byte mJpegCompressQuality; protected int mCurrentPhotoFormat; protected float mCurrentZoomRatio; protected int mCurrentZoomIndex; - protected float mPhotoRotationDegrees; protected int mExposureCompensationIndex; protected CameraCapabilities.FlashMode mCurrentFlashMode; protected CameraCapabilities.FocusMode mCurrentFocusMode; @@ -96,7 +102,7 @@ public class CameraSettings { * @param src The source settings. * @return The copy of the source. */ - public CameraSettings(CameraSettings src) { + protected CameraSettings(CameraSettings src) { mGeneralSetting.putAll(src.mGeneralSetting); mMeteringAreas.addAll(src.mMeteringAreas); mFocusAreas.addAll(src.mFocusAreas); @@ -112,7 +118,6 @@ public class CameraSettings { mCurrentPhotoFormat = src.mCurrentPhotoFormat; mCurrentZoomRatio = src.mCurrentZoomRatio; mCurrentZoomIndex = src.mCurrentZoomIndex; - mPhotoRotationDegrees = src.mPhotoRotationDegrees; mExposureCompensationIndex = src.mExposureCompensationIndex; mCurrentFlashMode = src.mCurrentFlashMode; mCurrentFocusMode = src.mCurrentFocusMode; @@ -126,6 +131,11 @@ public class CameraSettings { mExifThumbnailSize = src.mExifThumbnailSize; } + /** + * @return A copy of this object, as an instance of the implementing class. + */ + public abstract CameraSettings copy(); + /** General setting **/ @Deprecated public void setSetting(String key, String value) { @@ -258,7 +268,12 @@ public class CameraSettings { * @param quality The quality for JPEG. */ public void setPhotoJpegCompressionQuality(int quality) { - mJpegCompressQuality = quality; + if (quality < MIN_JPEG_COMPRESSION_QUALITY || quality > MAX_JPEG_COMPRESSION_QUALITY) { + Log.w(TAG, "Ignoring JPEG quality that falls outside the expected range"); + return; + } + // This is safe because the positive numbers go up to 127. + mJpegCompressQuality = (byte) quality; } public int getPhotoJpegCompressionQuality() { @@ -296,22 +311,15 @@ public class CameraSettings { mCurrentZoomIndex = index; } - /** Transformation **/ - - public void setPhotoRotationDegrees(float photoRotationDegrees) { - mPhotoRotationDegrees = photoRotationDegrees; - } - - public float getCurrentPhotoRotationDegrees() { - return mPhotoRotationDegrees; - } - /** Exposure **/ public void setExposureCompensationIndex(int index) { mExposureCompensationIndex = index; } + /** + * @return The exposure compensation, with 0 meaning unadjusted. + */ public int getExposureCompensationIndex() { return mExposureCompensationIndex; } diff --git a/camera2/portability/tests/Android.mk b/camera2/portability/tests/Android.mk index 5bb09ef..f0b24e0 100644 --- a/camera2/portability/tests/Android.mk +++ b/camera2/portability/tests/Android.mk @@ -18,7 +18,8 @@ include $(CLEAR_VARS) LOCAL_PACKAGE_NAME := android-ex-camera2-portability-tests LOCAL_MODULE_TAGS := tests LOCAL_SDK_VERSION := current -LOCAL_SRC_FILES := $(call all-java-files-under,src) -LOCAL_STATIC_JAVA_LIBRARIES := android-ex-camera2-portability android-support-test +LOCAL_SRC_FILES := $(call all-java-files-under,src) $(call all-java-files-under,../../utils/tests) +LOCAL_STATIC_JAVA_LIBRARIES := android-ex-camera2-portability android-ex-camera2-utils \ + android-support-test mockito-target include $(BUILD_PACKAGE) diff --git a/camera2/portability/tests/AndroidManifest.xml b/camera2/portability/tests/AndroidManifest.xml index 2e6a38b..65cf709 100644 --- a/camera2/portability/tests/AndroidManifest.xml +++ b/camera2/portability/tests/AndroidManifest.xml @@ -19,8 +19,8 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.ex.camera2.portability.tests"> <uses-permission android:name="android.permission.CAMERA" /> - <application android:label="CameraToo"> - <!--<uses-library android:name="android.test.runner" />--> + <application> + <uses-library android:name="android.test.runner" /> </application> <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" android:targetPackage="com.android.ex.camera2.portability.tests" /> diff --git a/camera2/portability/tests/src/com/android/ex/camera2/portability/Camera2PortabilityTest.java b/camera2/portability/tests/src/com/android/ex/camera2/portability/Camera2PortabilityTest.java index b421b98..034fac7 100644 --- a/camera2/portability/tests/src/com/android/ex/camera2/portability/Camera2PortabilityTest.java +++ b/camera2/portability/tests/src/com/android/ex/camera2/portability/Camera2PortabilityTest.java @@ -16,45 +16,49 @@ package com.android.ex.camera2.portability; -import static android.hardware.camera2.CameraCharacteristics.*; +import static android.hardware.camera2.CaptureRequest.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; -import com.android.ex.camera2.portability.AndroidCamera2Capabilities.IntegralStringifier; +import android.content.Context; +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraDevice; +import android.hardware.camera2.CameraManager; +import android.hardware.camera2.CaptureRequest; +import android.support.test.InjectContext; + import com.android.ex.camera2.portability.CameraCapabilities.FlashMode; import com.android.ex.camera2.portability.CameraCapabilities.FocusMode; import com.android.ex.camera2.portability.CameraCapabilities.SceneMode; import com.android.ex.camera2.portability.CameraCapabilities.Stringifier; import com.android.ex.camera2.portability.CameraCapabilities.WhiteBalance; +import com.android.ex.camera2.utils.Camera2DeviceTester; import org.junit.Test; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; -public class Camera2PortabilityTest { - private <E> void cameraCapabilitiesStringifierEach(Class<E> classy, - Stringifier strfy, - String call) throws Exception { - for(E val : (E[]) classy.getMethod("values").invoke(null)) { - String valString = - (String) Stringifier.class.getMethod("stringify", classy).invoke(strfy, val); - assertEquals(val, - Stringifier.class.getMethod(call, String.class).invoke(strfy, valString)); - } - } - +public class Camera2PortabilityTest extends Camera2DeviceTester { @Test - public void cameraCapabilitiesStringifier() throws Exception { + public void cameraCapabilitiesStringifier() { Stringifier strfy = new Stringifier(); - cameraCapabilitiesStringifierEach(FocusMode.class, strfy, "focusModeFromString"); - cameraCapabilitiesStringifierEach(FlashMode.class, strfy, "flashModeFromString"); - cameraCapabilitiesStringifierEach(SceneMode.class, strfy, "sceneModeFromString"); - cameraCapabilitiesStringifierEach(WhiteBalance.class, strfy, "whiteBalanceFromString"); + for(FocusMode val : FocusMode.values()) { + assertEquals(val, strfy.focusModeFromString(strfy.stringify(val))); + } + for(FlashMode val : FlashMode.values()) { + assertEquals(val, strfy.flashModeFromString(strfy.stringify(val))); + } + for(SceneMode val : SceneMode.values()) { + assertEquals(val, strfy.sceneModeFromString(strfy.stringify(val))); + } + for(WhiteBalance val : WhiteBalance.values()) { + assertEquals(val, strfy.whiteBalanceFromString(strfy.stringify(val))); + } } @Test - public void cameraCapabilitiesStringifierNull() throws Exception { + public void cameraCapabilitiesStringifierNull() { Stringifier strfy = new Stringifier(); assertEquals(strfy.focusModeFromString(null), FocusMode.AUTO); assertEquals(strfy.flashModeFromString(null), FlashMode.NO_FLASH); @@ -63,7 +67,7 @@ public class Camera2PortabilityTest { } @Test - public void cameraCapabilitiesStringifierInvalid() throws Exception { + public void cameraCapabilitiesStringifierInvalid() { Stringifier strfy = new Stringifier(); assertEquals(strfy.focusModeFromString("crap"), FocusMode.AUTO); assertEquals(strfy.flashModeFromString("crap"), FlashMode.NO_FLASH); @@ -71,72 +75,93 @@ public class Camera2PortabilityTest { assertEquals(strfy.whiteBalanceFromString("crap"), WhiteBalance.AUTO); } - private void cameraCapabilitiesIntifierEach(int apiVal, - IntegralStringifier intfy, - String call) throws Exception { - Method toCall = IntegralStringifier.class.getMethod(call, int.class); - Class<?> returnType = toCall.getReturnType(); - Object returnVal = toCall.invoke(intfy, apiVal); - assertEquals(apiVal, - IntegralStringifier.class.getMethod("intify", returnType).invoke(intfy, returnVal)); + private CameraCharacteristics buildFrameworkCharacteristics() throws CameraAccessException { + CameraManager manager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE); + String id = manager.getCameraIdList()[0]; + return manager.getCameraCharacteristics(id); + } + + private void camera2SettingsCheckSingleOption(AndroidCamera2Settings setts, + Key<?> apiKey, int apiVal) { + assertEquals(apiVal, setts.getRequestSettings().get(apiKey)); } @Test - public void cameraCapabilitiesIntifier() throws Exception { - IntegralStringifier intstr = new IntegralStringifier(); + public void camera2SettingsSetOptionsAndGetRequestSettings() throws CameraAccessException { + AndroidCamera2Settings set = new AndroidCamera2Settings( + mCamera, CameraDevice.TEMPLATE_PREVIEW, null, null, null); // Focus modes - cameraCapabilitiesIntifierEach(CONTROL_AF_MODE_AUTO, intstr, "focusModeFromInt"); - cameraCapabilitiesIntifierEach(CONTROL_AF_MODE_CONTINUOUS_PICTURE, intstr, - "focusModeFromInt"); - cameraCapabilitiesIntifierEach(CONTROL_AF_MODE_CONTINUOUS_VIDEO, intstr, - "focusModeFromInt"); - cameraCapabilitiesIntifierEach(CONTROL_AF_MODE_EDOF, intstr, "focusModeFromInt"); - cameraCapabilitiesIntifierEach(CONTROL_AF_MODE_OFF, intstr, "focusModeFromInt"); - cameraCapabilitiesIntifierEach(CONTROL_AF_MODE_MACRO, intstr, "focusModeFromInt"); - - // Flash modes - cameraCapabilitiesIntifierEach(FLASH_MODE_OFF, intstr, "flashModeFromInt"); - cameraCapabilitiesIntifierEach(FLASH_MODE_SINGLE, intstr, "flashModeFromInt"); - cameraCapabilitiesIntifierEach(FLASH_MODE_TORCH, intstr, "flashModeFromInt"); + set.setFocusMode(FocusMode.AUTO); + camera2SettingsCheckSingleOption(set, CONTROL_AF_MODE, CONTROL_AF_MODE_AUTO); + set.setFocusMode(FocusMode.CONTINUOUS_PICTURE); + camera2SettingsCheckSingleOption(set, CONTROL_AF_MODE, CONTROL_AF_MODE_CONTINUOUS_PICTURE); + set.setFocusMode(FocusMode.CONTINUOUS_VIDEO); + camera2SettingsCheckSingleOption(set, CONTROL_AF_MODE, CONTROL_AF_MODE_CONTINUOUS_VIDEO); + set.setFocusMode(FocusMode.EXTENDED_DOF); + camera2SettingsCheckSingleOption(set, CONTROL_AF_MODE, CONTROL_AF_MODE_EDOF); + set.setFocusMode(FocusMode.FIXED); + camera2SettingsCheckSingleOption(set, CONTROL_AF_MODE, CONTROL_AF_MODE_OFF); + set.setFocusMode(FocusMode.MACRO); + camera2SettingsCheckSingleOption(set, CONTROL_AF_MODE, CONTROL_AF_MODE_MACRO); // Scene modes - cameraCapabilitiesIntifierEach(CONTROL_SCENE_MODE_DISABLED, intstr, "sceneModeFromInt"); - cameraCapabilitiesIntifierEach(CONTROL_SCENE_MODE_ACTION, intstr, "sceneModeFromInt"); - cameraCapabilitiesIntifierEach(CONTROL_SCENE_MODE_BARCODE, intstr, "sceneModeFromInt"); - cameraCapabilitiesIntifierEach(CONTROL_SCENE_MODE_BEACH, intstr, "sceneModeFromInt"); - cameraCapabilitiesIntifierEach(CONTROL_SCENE_MODE_CANDLELIGHT, intstr, "sceneModeFromInt"); - cameraCapabilitiesIntifierEach(CONTROL_SCENE_MODE_FIREWORKS, intstr, "sceneModeFromInt"); - cameraCapabilitiesIntifierEach(CONTROL_SCENE_MODE_LANDSCAPE, intstr, "sceneModeFromInt"); - cameraCapabilitiesIntifierEach(CONTROL_SCENE_MODE_NIGHT, intstr, "sceneModeFromInt"); - cameraCapabilitiesIntifierEach(CONTROL_SCENE_MODE_PARTY, intstr, "sceneModeFromInt"); - cameraCapabilitiesIntifierEach(CONTROL_SCENE_MODE_PORTRAIT, intstr, "sceneModeFromInt"); - cameraCapabilitiesIntifierEach(CONTROL_SCENE_MODE_SNOW, intstr, "sceneModeFromInt"); - cameraCapabilitiesIntifierEach(CONTROL_SCENE_MODE_SPORTS, intstr, "sceneModeFromInt"); - cameraCapabilitiesIntifierEach(CONTROL_SCENE_MODE_STEADYPHOTO, intstr, "sceneModeFromInt"); - cameraCapabilitiesIntifierEach(CONTROL_SCENE_MODE_SUNSET, intstr, "sceneModeFromInt"); - cameraCapabilitiesIntifierEach(CONTROL_SCENE_MODE_THEATRE, intstr, "sceneModeFromInt"); + set.setSceneMode(SceneMode.AUTO); + camera2SettingsCheckSingleOption(set, CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_DISABLED); + set.setSceneMode(SceneMode.ACTION); + camera2SettingsCheckSingleOption(set, CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_ACTION); + set.setSceneMode(SceneMode.BARCODE); + camera2SettingsCheckSingleOption(set, CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_BARCODE); + set.setSceneMode(SceneMode.BEACH); + camera2SettingsCheckSingleOption(set, CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_BEACH); + set.setSceneMode(SceneMode.CANDLELIGHT); + camera2SettingsCheckSingleOption(set, CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_CANDLELIGHT); + set.setSceneMode(SceneMode.FIREWORKS); + camera2SettingsCheckSingleOption(set, CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_FIREWORKS); + set.setSceneMode(SceneMode.LANDSCAPE); + camera2SettingsCheckSingleOption(set, CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_LANDSCAPE); + set.setSceneMode(SceneMode.NIGHT); + camera2SettingsCheckSingleOption(set, CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_NIGHT); + set.setSceneMode(SceneMode.PARTY); + camera2SettingsCheckSingleOption(set, CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_PARTY); + set.setSceneMode(SceneMode.PORTRAIT); + camera2SettingsCheckSingleOption(set, CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_PORTRAIT); + set.setSceneMode(SceneMode.SNOW); + camera2SettingsCheckSingleOption(set, CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_SNOW); + set.setSceneMode(SceneMode.SPORTS); + camera2SettingsCheckSingleOption(set, CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_SPORTS); + set.setSceneMode(SceneMode.STEADYPHOTO); + camera2SettingsCheckSingleOption(set, CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_STEADYPHOTO); + set.setSceneMode(SceneMode.SUNSET); + camera2SettingsCheckSingleOption(set, CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_SUNSET); + set.setSceneMode(SceneMode.THEATRE); + camera2SettingsCheckSingleOption(set, CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_THEATRE); // White balances - cameraCapabilitiesIntifierEach(CONTROL_AWB_MODE_AUTO, intstr, "whiteBalanceFromInt"); - cameraCapabilitiesIntifierEach(CONTROL_AWB_MODE_CLOUDY_DAYLIGHT, intstr, - "whiteBalanceFromInt"); - cameraCapabilitiesIntifierEach(CONTROL_AWB_MODE_DAYLIGHT, intstr, "whiteBalanceFromInt"); - cameraCapabilitiesIntifierEach(CONTROL_AWB_MODE_FLUORESCENT, intstr, - "whiteBalanceFromInt"); - cameraCapabilitiesIntifierEach(CONTROL_AWB_MODE_INCANDESCENT, intstr, - "whiteBalanceFromInt"); - cameraCapabilitiesIntifierEach(CONTROL_AWB_MODE_SHADE, intstr, "whiteBalanceFromInt"); - cameraCapabilitiesIntifierEach(CONTROL_AWB_MODE_TWILIGHT, intstr, "whiteBalanceFromInt"); - cameraCapabilitiesIntifierEach(CONTROL_AWB_MODE_WARM_FLUORESCENT, intstr, - "whiteBalanceFromInt"); + set.setWhiteBalance(WhiteBalance.AUTO); + camera2SettingsCheckSingleOption(set, CONTROL_AWB_MODE, CONTROL_AWB_MODE_AUTO); + set.setWhiteBalance(WhiteBalance.CLOUDY_DAYLIGHT); + camera2SettingsCheckSingleOption(set, CONTROL_AWB_MODE, CONTROL_AWB_MODE_CLOUDY_DAYLIGHT); + set.setWhiteBalance(WhiteBalance.DAYLIGHT); + camera2SettingsCheckSingleOption(set, CONTROL_AWB_MODE, CONTROL_AWB_MODE_DAYLIGHT); + set.setWhiteBalance(WhiteBalance.FLUORESCENT); + camera2SettingsCheckSingleOption(set, CONTROL_AWB_MODE, CONTROL_AWB_MODE_FLUORESCENT); + set.setWhiteBalance(WhiteBalance.INCANDESCENT); + camera2SettingsCheckSingleOption(set, CONTROL_AWB_MODE, CONTROL_AWB_MODE_INCANDESCENT); + set.setWhiteBalance(WhiteBalance.SHADE); + camera2SettingsCheckSingleOption(set, CONTROL_AWB_MODE, CONTROL_AWB_MODE_SHADE); + set.setWhiteBalance(WhiteBalance.TWILIGHT); + camera2SettingsCheckSingleOption(set, CONTROL_AWB_MODE, CONTROL_AWB_MODE_TWILIGHT); + set.setWhiteBalance(WhiteBalance.WARM_FLUORESCENT); + camera2SettingsCheckSingleOption(set, CONTROL_AWB_MODE, CONTROL_AWB_MODE_WARM_FLUORESCENT); } - // TODO: Add a test checking whether stringification matches API representation + // TODO: Add a test checking whether stringification matches API 1 representation @Test - public void cameraCapabilitiesIntsMatchApi2Representations() throws Exception { - IntegralStringifier intstr = new IntegralStringifier(); + public void camera2CapabilitiesFocusModeFromInt() throws CameraAccessException { + CameraCharacteristics chars = buildFrameworkCharacteristics(); + AndroidCamera2Capabilities intstr = new AndroidCamera2Capabilities(chars); // Focus modes assertEquals(intstr.focusModeFromInt(CONTROL_AF_MODE_AUTO), FocusMode.AUTO); @@ -148,18 +173,12 @@ public class Camera2PortabilityTest { assertEquals(intstr.focusModeFromInt(CONTROL_AF_MODE_OFF), FocusMode.FIXED); assertEquals(intstr.focusModeFromInt(CONTROL_AF_MODE_MACRO), FocusMode.MACRO); - // Flash modes - assertEquals(intstr.flashModeFromInt(FLASH_MODE_OFF), FlashMode.OFF); - assertEquals(intstr.flashModeFromInt(FLASH_MODE_SINGLE), FlashMode.ON); - assertEquals(intstr.flashModeFromInt(FLASH_MODE_TORCH), FlashMode.TORCH); - // Scene modes assertEquals(intstr.sceneModeFromInt(CONTROL_SCENE_MODE_DISABLED), SceneMode.AUTO); assertEquals(intstr.sceneModeFromInt(CONTROL_SCENE_MODE_ACTION), SceneMode.ACTION); assertEquals(intstr.sceneModeFromInt(CONTROL_SCENE_MODE_BARCODE), SceneMode.BARCODE); assertEquals(intstr.sceneModeFromInt(CONTROL_SCENE_MODE_BEACH), SceneMode.BEACH); - assertEquals(intstr.sceneModeFromInt(CONTROL_SCENE_MODE_CANDLELIGHT), - SceneMode.CANDLELIGHT); + assertEquals(intstr.sceneModeFromInt(CONTROL_SCENE_MODE_CANDLELIGHT), SceneMode.CANDLELIGHT); assertEquals(intstr.sceneModeFromInt(CONTROL_SCENE_MODE_FIREWORKS), SceneMode.FIREWORKS); assertEquals(intstr.sceneModeFromInt(CONTROL_SCENE_MODE_LANDSCAPE), SceneMode.LANDSCAPE); assertEquals(intstr.sceneModeFromInt(CONTROL_SCENE_MODE_NIGHT), SceneMode.NIGHT); diff --git a/camera2/utils/tests/src/com/android/ex/camera2/utils/Camera2DeviceTester.java b/camera2/utils/tests/src/com/android/ex/camera2/utils/Camera2DeviceTester.java new file mode 100644 index 0000000..4db6dfb --- /dev/null +++ b/camera2/utils/tests/src/com/android/ex/camera2/utils/Camera2DeviceTester.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ex.camera2.utils; + +import android.content.Context; +import android.hardware.camera2.CameraDevice; +import android.hardware.camera2.CameraManager; +import android.os.Handler; +import android.os.HandlerThread; +import android.support.test.InjectContext; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; + +/** + * Subclasses of this have an {@code mCamera} instance variable representing the first camera. + */ +public class Camera2DeviceTester { + private static HandlerThread sThread; + + private static Handler sHandler; + + @BeforeClass + public static void setupBackgroundHandler() { + sThread = new HandlerThread("CameraFramework"); + sThread.start(); + sHandler = new Handler(sThread.getLooper()); + } + + @AfterClass + public static void teardownBackgroundHandler() throws Exception { + sThread.quitSafely(); + sThread.join(); + } + + @InjectContext + public Context mContext; + + private class DeviceCapturer extends CameraDevice.StateListener { + private CameraDevice mCamera; + + public CameraDevice captureCameraDevice() throws Exception { + CameraManager manager = + (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE); + String id = manager.getCameraIdList()[0]; + synchronized (this) { + manager.openCamera(id, this, sHandler); + wait(); + } + return mCamera; + } + + @Override + public synchronized void onOpened(CameraDevice camera) { + mCamera = camera; + notify(); + } + + @Override + public void onDisconnected(CameraDevice camera) {} + + @Override + public void onError(CameraDevice camera, int error) {} + } + + protected CameraDevice mCamera; + + @Before + public void obtainCameraCaptureRequestBuilderFactory() throws Exception { + mCamera = new DeviceCapturer().captureCameraDevice(); + } + + @After + public void releaseCameraCaptureRequestBuilderFactory() { + mCamera.close(); + } +} diff --git a/camera2/utils/tests/src/com/android/ex/camera2/utils/Camera2UtilsTest.java b/camera2/utils/tests/src/com/android/ex/camera2/utils/Camera2UtilsTest.java index dd9566b..bb23e37 100644 --- a/camera2/utils/tests/src/com/android/ex/camera2/utils/Camera2UtilsTest.java +++ b/camera2/utils/tests/src/com/android/ex/camera2/utils/Camera2UtilsTest.java @@ -23,24 +23,15 @@ import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import android.content.Context; import android.hardware.camera2.CameraCaptureSession.CaptureListener; import android.hardware.camera2.CameraDevice; -import android.hardware.camera2.CameraManager; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureRequest.Key; -import android.os.Handler; -import android.os.HandlerThread; -import android.support.test.InjectContext; import android.view.Surface; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; import org.junit.Test; -public class Camera2UtilsTest { +public class Camera2UtilsTest extends Camera2DeviceTester { private void captureListenerSplitterAllCallbacksReceived(CaptureListener splitter, CaptureListener... terminals) { splitter.onCaptureCompleted(null, null, null); @@ -152,65 +143,6 @@ public class Camera2UtilsTest { assertFalse(setUp.contains(CaptureRequest.CONTROL_AE_LOCK)); } - private static HandlerThread sThread; - - private static Handler sHandler; - - @BeforeClass - public static void setupBackgroundHandler() { - sThread = new HandlerThread("CameraFramework"); - sThread.start(); - sHandler = new Handler(sThread.getLooper()); - } - - @AfterClass - public static void teardownBackgroundHandler() throws Exception { - sThread.quitSafely(); - sThread.join(); - } - - @InjectContext - public Context mContext; - - public class DeviceCapturer extends CameraDevice.StateListener { - private CameraDevice mCamera; - - public CameraDevice captureCameraDevice() throws Exception { - CameraManager manager = - (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE); - String id = manager.getCameraIdList()[0]; - synchronized (this) { - manager.openCamera(id, this, sHandler); - wait(); - } - return mCamera; - } - - @Override - public synchronized void onOpened(CameraDevice camera) { - mCamera = camera; - notify(); - } - - @Override - public void onDisconnected(CameraDevice camera) {} - - @Override - public void onError(CameraDevice camera, int error) {} - } - - private CameraDevice mCamera; - - @Before - public void obtainCameraCaptureRequestBuilderFactory() throws Exception { - mCamera = new DeviceCapturer().captureCameraDevice(); - } - - @After - public void releaseCameraCaptureRequestBuilderFactory() { - mCamera.close(); - } - @Test public void requestSettingsSetStartsWithoutChanges() { Camera2RequestSettingsSet setUp = new Camera2RequestSettingsSet(); |