summaryrefslogtreecommitdiffstats
path: root/camera2/portability/src/com/android/ex
diff options
context:
space:
mode:
authorSol Boucher <solb@google.com>2014-07-22 18:39:32 -0700
committerSol Boucher <solb@google.com>2014-08-07 15:02:05 -0700
commitde48004068f8c16f9a56c60b0ed2485a67687b4b (patch)
treecc584bbc1756d0f5f00367f39565288679728809 /camera2/portability/src/com/android/ex
parentd4e5286bb4145e0371b783158fc8411565429c9b (diff)
downloadandroid_frameworks_ex-de48004068f8c16f9a56c60b0ed2485a67687b4b.tar.gz
android_frameworks_ex-de48004068f8c16f9a56c60b0ed2485a67687b4b.tar.bz2
android_frameworks_ex-de48004068f8c16f9a56c60b0ed2485a67687b4b.zip
camera2-portability: Support photo capture using camera2 API
This implements JPEG capture, including an autoexposure precapture sequence. There are many changes to AndroidCamera2Capabilities and AndroidCamera2Settings to support the representation of modes (e.g. flash modes) whose flags do not map trivially between the API implementations. Part of this work is the conversion of AndroidCamera2AgentImpl to use and store a Camera2RequestSettingsSet instead of a bare API 2 CaptureRequest.Builder. Change-Id: I03f9f98c954a7b0c140ac8d80161878c92ef65d2
Diffstat (limited to 'camera2/portability/src/com/android/ex')
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java299
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Capabilities.java391
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Settings.java455
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java12
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraSettings.java12
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java29
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/CameraCapabilities.java3
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/CameraDeviceInfo.java100
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/CameraSettings.java40
9 files changed, 936 insertions, 405 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 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&mdash;and, optionally,
+ * JPEG&mdash;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;
}