diff options
9 files changed, 233 insertions, 25 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 c4eea50..913a575 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java @@ -215,7 +215,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { CameraOpenCallback openCallback = (CameraOpenCallback) msg.obj; int cameraIndex = msg.arg1; - if (mCameraState.getState() != AndroidCamera2StateHolder.CAMERA_UNOPENED) { + if (mCameraState.getState() > AndroidCamera2StateHolder.CAMERA_UNOPENED) { openCallback.onDeviceOpenedAlready(cameraIndex, generateHistoryString(cameraIndex)); break; @@ -305,6 +305,10 @@ class AndroidCamera2AgentImpl extends CameraAgent { break; } + // FIXME: We need to tear down the CameraCaptureSession here + // (and unlock the CameraSettings object from our + // CameraProxy) so that the preview/photo sizes can be + // changed again while no preview is running. case CameraActions.STOP_PREVIEW: { if (mCameraState.getState() < AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE) { @@ -936,6 +940,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { private final CameraDeviceInfo.Characteristics mCharacteristics; private final AndroidCamera2Capabilities mCapabilities; private CameraSettings mLastSettings; + private boolean mShutterSoundEnabled; public AndroidCamera2ProxyImpl(int cameraIndex, CameraDevice camera, CameraDeviceInfo.Characteristics characteristics, @@ -945,6 +950,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { mCharacteristics = characteristics; mCapabilities = new AndroidCamera2Capabilities(properties); mLastSettings = null; + mShutterSoundEnabled = true; } // TODO: Implement @@ -970,6 +976,26 @@ class AndroidCamera2AgentImpl extends CameraAgent { return mCapabilities; } + // FIXME: Unlock the sizes in stopPreview(), as per the corresponding + // explanation on the STOP_PREVIEW case in the handler. + @Override + public void setPreviewTexture(SurfaceTexture surfaceTexture) { + // Once the Surface has been selected, we configure the session and + // are no longer able to change the sizes. + getSettings().setSizesLocked(true); + super.setPreviewTexture(surfaceTexture); + } + + // FIXME: Unlock the sizes in stopPreview(), as per the corresponding + // explanation on the STOP_PREVIEW case in the handler. + @Override + public void setPreviewTextureSync(SurfaceTexture surfaceTexture) { + // Once the Surface has been selected, we configure the session and + // are no longer able to change the sizes. + getSettings().setSizesLocked(true); + super.setPreviewTexture(surfaceTexture); + } + // TODO: Implement @Override public void setPreviewDataCallback(Handler handler, CameraPreviewDataCallback cb) {} @@ -1053,7 +1079,9 @@ class AndroidCamera2AgentImpl extends CameraAgent { handler.post(new Runnable() { @Override public void run() { - mNoisemaker.play(MediaActionSound.SHUTTER_CLICK); + if (mShutterSoundEnabled) { + mNoisemaker.play(MediaActionSound.SHUTTER_CLICK); + } shutter.onShutter(AndroidCamera2ProxyImpl.this); }}); } @@ -1077,8 +1105,9 @@ class AndroidCamera2AgentImpl extends CameraAgent { mDispatchThread.runJob(new Runnable() { @Override public void run() { - mCameraState.waitForStates(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE | - AndroidCamera2StateHolder.CAMERA_FOCUS_LOCKED); + // Wait until PREVIEW_ACTIVE or better + mCameraState.waitForStates( + ~(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE - 1)); mCameraHandler.obtainMessage(CameraActions.CAPTURE_PHOTO, picListener) .sendToTarget(); }}); @@ -1132,15 +1161,19 @@ class AndroidCamera2AgentImpl extends CameraAgent { return false; } - if (applySettingsHelper(settings, AndroidCamera2StateHolder.CAMERA_UNCONFIGURED | - AndroidCamera2StateHolder.CAMERA_CONFIGURED | - AndroidCamera2StateHolder.CAMERA_PREVIEW_READY)) { + // Wait for any state that isn't OPENED + if (applySettingsHelper(settings, ~AndroidCamera2StateHolder.CAMERA_UNOPENED)) { mLastSettings = settings; return true; } return false; } + @Override + public void enableShutterSound(boolean enable) { + mShutterSoundEnabled = enable; + } + // TODO: Implement @Override public String dumpDeviceSettings() { return null; } @@ -1165,19 +1198,22 @@ class AndroidCamera2AgentImpl extends CameraAgent { private static class AndroidCamera2StateHolder extends CameraStateHolder { // Usage flow: openCamera() -> applySettings() -> setPreviewTexture() -> startPreview() -> // autoFocus() -> takePicture() + // States are mutually exclusive, but must be separate bits so that they can be used with + // the StateHolder#waitForStates() and StateHolder#waitToAvoidStates() methods. + // Do not set the state to be a combination of these values! /* Camera states */ /** No camera device is opened. */ - public static final int CAMERA_UNOPENED = 1; + public static final int CAMERA_UNOPENED = 1 << 0; /** A camera is opened, but no settings have been provided. */ - public static final int CAMERA_UNCONFIGURED = 2; + public static final int CAMERA_UNCONFIGURED = 1 << 1; /** The open camera has been configured by providing it with settings. */ - public static final int CAMERA_CONFIGURED = 3; + public static final int CAMERA_CONFIGURED = 1 << 2; /** A capture session is ready to stream a preview, but still has no repeating request. */ - public static final int CAMERA_PREVIEW_READY = 4; + public static final int CAMERA_PREVIEW_READY = 1 << 3; /** A preview is currently being streamed. */ - public static final int CAMERA_PREVIEW_ACTIVE = 5; + public static final int CAMERA_PREVIEW_ACTIVE = 1 << 4; /** The lens is locked on a particular region. */ - public static final int CAMERA_FOCUS_LOCKED = 6; + public static final int CAMERA_FOCUS_LOCKED = 1 << 5; public AndroidCamera2StateHolder() { this(CAMERA_UNOPENED); @@ -1298,9 +1334,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { @Override public boolean canDisableShutterSound() { - // The new API doesn't support this operation, so don't encourage people to try it. - // TODO: What kind of assumptions have callers made about this result's meaning? - return false; + return true; } private static float[] convertRectToPoly(RectF rf) { 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 bd610cc..8001a37 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Capabilities.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Capabilities.java @@ -204,7 +204,6 @@ public class AndroidCamera2Capabilities extends CameraCapabilities { 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: @@ -226,6 +225,11 @@ public class AndroidCamera2Capabilities extends CameraCapabilities { return SceneMode.THEATRE; // TODO: We cannot expose FACE_PRIORITY, or HIGH_SPEED_VIDEO } + + if (sm == LegacyVendorTags.CONTROL_SCENE_MODE_HDR) { + return SceneMode.HDR; + } + Log.w(TAG, "Unable to convert from API 2 scene mode: " + sm); 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 7f6cffe..d668f85 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Settings.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Settings.java @@ -18,7 +18,9 @@ package com.android.ex.camera2.portability; import static android.hardware.camera2.CaptureRequest.*; +import android.graphics.Matrix; import android.graphics.Rect; +import android.graphics.RectF; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.params.MeteringRectangle; @@ -43,8 +45,12 @@ public class AndroidCamera2Settings extends CameraSettings { private final Builder mTemplateSettings; private final Camera2RequestSettingsSet mRequestSettings; + /** Sensor's active array bounds. */ private final Rect mActiveArray; + /** Crop rectangle for digital zoom (measured WRT the active array). */ private final Rect mCropRectangle; + /** Bounds of visible preview portion (measured WRT the active array). */ + private Rect mVisiblePreviewRectangle; /** * Create a settings representation that answers queries of unspecified @@ -84,6 +90,8 @@ public class AndroidCamera2Settings extends CameraSettings { mActiveArray = activeArray; mCropRectangle = new Rect(0, 0, activeArray.width(), activeArray.height()); + mSizesLocked = false; + Range<Integer> previewFpsRange = mTemplateSettings.get(CONTROL_AE_TARGET_FPS_RANGE); if (previewFpsRange != null) { setPreviewFpsRange(previewFpsRange.getLower(), previewFpsRange.getUpper()); @@ -177,6 +185,8 @@ public class AndroidCamera2Settings extends CameraSettings { @Override public void setZoomRatio(float ratio) { super.setZoomRatio(ratio); + + // Compute the crop rectangle to be passed to the framework mCropRectangle.set(0, 0, toIntConstrained( mActiveArray.width() / mCurrentZoomRatio, 0, mActiveArray.width()), @@ -184,6 +194,10 @@ public class AndroidCamera2Settings extends CameraSettings { mActiveArray.height() / mCurrentZoomRatio, 0, mActiveArray.height())); mCropRectangle.offsetTo((mActiveArray.width() - mCropRectangle.width()) / 2, (mActiveArray.height() - mCropRectangle.height()) / 2); + + // Compute the effective crop rectangle to be used for computing focus/metering coordinates + mVisiblePreviewRectangle = + effectiveCropRectFromRequested(mCropRectangle, mCurrentPreviewSize); } private boolean matchesTemplateDefault(Key<?> setting) { @@ -405,7 +419,10 @@ public class AndroidCamera2Settings extends CameraSettings { mode = CONTROL_SCENE_MODE_FIREWORKS; break; } - // TODO: We cannot support HDR + case HDR: { + mode = LegacyVendorTags.CONTROL_SCENE_MODE_HDR; + break; + } case LANDSCAPE: { mode = CONTROL_SCENE_MODE_LANDSCAPE; break; @@ -512,4 +529,49 @@ public class AndroidCamera2Settings extends CameraSettings { mRequestSettings.set(JPEG_GPS_LOCATION, location); } } + + /** + * Calculate the effective crop rectangle for this preview viewport; + * assumes the preview is centered to the sensor and scaled to fit across one of the dimensions + * without skewing. + * + * <p>Assumes the zoom level of the provided desired crop rectangle.</p> + * + * @param requestedCrop Desired crop rectangle, in active array space. + * @param previewSize Size of the preview buffer render target, in pixels (not in sensor space). + * @return A rectangle that serves as the preview stream's effective crop region (unzoomed), in + * sensor space. + * + * @throws NullPointerException + * If any of the args were {@code null}. + */ + private static Rect effectiveCropRectFromRequested(Rect requestedCrop, Size previewSize) { + float aspectRatioArray = requestedCrop.width() * 1.0f / requestedCrop.height(); + float aspectRatioPreview = previewSize.width() * 1.0f / previewSize.height(); + + float cropHeight, cropWidth; + if (aspectRatioPreview < aspectRatioArray) { + // The new width must be smaller than the height, so scale the width by AR + cropHeight = requestedCrop.height(); + cropWidth = cropHeight * aspectRatioPreview; + } else { + // The new height must be smaller (or equal) than the width, so scale the height by AR + cropWidth = requestedCrop.width(); + cropHeight = cropWidth / aspectRatioPreview; + } + + Matrix translateMatrix = new Matrix(); + RectF cropRect = new RectF(/*left*/0, /*top*/0, cropWidth, cropHeight); + + // Now center the crop rectangle so its center is in the center of the active array + translateMatrix.setTranslate(requestedCrop.exactCenterX(), requestedCrop.exactCenterY()); + translateMatrix.postTranslate(-cropRect.centerX(), -cropRect.centerY()); + + translateMatrix.mapRect(/*inout*/cropRect); + + // Round the rect corners towards the nearest integer values + Rect result = new Rect(); + cropRect.roundOut(result); + return result; + } } 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 f8a2e38..358d5f6 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java @@ -400,6 +400,7 @@ class AndroidCameraAgentImpl extends CameraAgent { break; } + // TODO: Lock the CameraSettings object's sizes case CameraActions.SET_PREVIEW_TEXTURE_ASYNC: { setPreviewTexture(msg.obj); break; @@ -424,6 +425,7 @@ class AndroidCameraAgentImpl extends CameraAgent { break; } + // TODO: Unlock the CameraSettings object's sizes case CameraActions.STOP_PREVIEW: { mCamera.stopPreview(); break; @@ -612,11 +614,15 @@ class AndroidCameraAgentImpl extends CameraAgent { if (mCapabilities.supports(CameraCapabilities.Feature.FOCUS_AREA)) { if (settings.getFocusAreas().size() != 0) { parameters.setFocusAreas(settings.getFocusAreas()); + } else { + parameters.setFocusAreas(null); } } if (mCapabilities.supports(CameraCapabilities.Feature.METERING_AREA)) { if (settings.getMeteringAreas().size() != 0) { parameters.setMeteringAreas(settings.getMeteringAreas()); + } else { + parameters.setMeteringAreas(null); } } if (settings.getCurrentFlashMode() != CameraCapabilities.FlashMode.NO_FLASH) { 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 f5421d6..ee69b54 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraSettings.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraSettings.java @@ -28,6 +28,8 @@ public class AndroidCameraSettings extends CameraSettings { public AndroidCameraSettings(CameraCapabilities capabilities, Camera.Parameters params) { CameraCapabilities.Stringifier stringifier = capabilities.getStringifier(); + setSizesLocked(false); + // Preview Camera.Size paramPreviewSize = params.getPreviewSize(); setPreviewSize(new Size(paramPreviewSize.width, paramPreviewSize.height)); 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 df94a41..b624b47 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java @@ -436,8 +436,24 @@ public abstract class CameraAgent { /** * Sets the {@link android.graphics.SurfaceTexture} for preview. * + * <p>Note that, once this operation has been performed, it is no longer + * possible to change the preview or photo sizes in the + * {@link CameraSettings} instance for this camera, and the mutators for + * these fields are allowed to ignore all further invocations until the + * preview is stopped with {@link #stopPreview}.</p> + * * @param surfaceTexture The {@link SurfaceTexture} for preview. - */ + * + * @see CameraSettings#setPhotoSize + * @see CameraSettings#setPreviewSize + */ + // XXX: Despite the above documentation about locking the sizes, the API + // 1 implementation doesn't currently enforce this at all, although the + // Camera class warns that preview sizes shouldn't be changed while a + // preview is running. Furthermore, the API 2 implementation doesn't yet + // unlock the sizes when stopPreview() is invoked (see related FIXME on + // the STOP_PREVIEW case in its handler; in the meantime, changing API 2 + // sizes would require closing and reopening the camera. public void setPreviewTexture(final SurfaceTexture surfaceTexture) { getDispatchThread().runJob(new Runnable() { @Override @@ -452,7 +468,15 @@ public abstract class CameraAgent { * Blocks until a {@link android.graphics.SurfaceTexture} has been set * for preview. * + * <p>Note that, once this operation has been performed, it is no longer + * possible to change the preview or photo sizes in the + * {@link CameraSettings} instance for this camera, and the mutators for + * these fields are allowed to ignore all further invocations.</p> + * * @param surfaceTexture The {@link SurfaceTexture} for preview. + * + * @see CameraSettings#setPhotoSize + * @see CameraSettings#setPreviewSize */ public void setPreviewTextureSync(final SurfaceTexture surfaceTexture) { final WaitDoneBundle bundle = new WaitDoneBundle(); 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 0d7c302..60c8cb2 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/CameraCapabilities.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraCapabilities.java @@ -184,7 +184,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 + // Note: Supported as a vendor tag on the Camera2 API for some LEGACY devices. HDR, /** * Take pictures on distant objects. 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 d0600e8..87e9adf 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/CameraSettings.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraSettings.java @@ -38,6 +38,7 @@ public abstract class CameraSettings { protected final Map<String, String> mGeneralSetting = new TreeMap<>(); protected final List<Camera.Area> mMeteringAreas = new ArrayList<>(); protected final List<Camera.Area> mFocusAreas = new ArrayList<>(); + protected boolean mSizesLocked; protected int mPreviewFpsRangeMin; protected int mPreviewFpsRangeMax; protected int mPreviewFrameRate; @@ -116,6 +117,7 @@ public abstract class CameraSettings { mGeneralSetting.putAll(src.mGeneralSetting); mMeteringAreas.addAll(src.mMeteringAreas); mFocusAreas.addAll(src.mFocusAreas); + mSizesLocked = src.mSizesLocked; mPreviewFpsRangeMin = src.mPreviewFpsRangeMin; mPreviewFpsRangeMax = src.mPreviewFpsRangeMax; mPreviewFrameRate = src.mPreviewFrameRate; @@ -151,6 +153,19 @@ public abstract class CameraSettings { mGeneralSetting.put(key, value); } + /** + * Changes whether classes outside this class are allowed to set the preview + * and photo capture sizes. + * + * @param locked Whether to prevent changes to these fields. + * + * @see #setPhotoSize + * @see #setPreviewSize + */ + /*package*/ void setSizesLocked(boolean locked) { + mSizesLocked = locked; + } + /** Preview **/ /** @@ -212,9 +227,16 @@ public abstract class CameraSettings { /** * @param previewSize The size to use for preview. + * @return Whether the operation was allowed (i.e. the sizes are unlocked). */ - public void setPreviewSize(Size previewSize) { + public boolean setPreviewSize(Size previewSize) { + if (mSizesLocked) { + Log.w(TAG, "Attempt to change preview size while locked"); + return false; + } + mCurrentPreviewSize = new Size(previewSize); + return true; } /** @@ -245,12 +267,17 @@ public abstract class CameraSettings { } /** - * Sets the size for the photo. - * - * @param photoSize The photo size. + * @param photoSize The size to use for preview. + * @return Whether the operation was allowed (i.e. the sizes are unlocked). */ - public void setPhotoSize(Size photoSize) { + public boolean setPhotoSize(Size photoSize) { + if (mSizesLocked) { + Log.w(TAG, "Attempt to change photo size while locked"); + return false; + } + mCurrentPhotoSize = new Size(photoSize); + return true; } /** diff --git a/camera2/portability/src/com/android/ex/camera2/portability/LegacyVendorTags.java b/camera2/portability/src/com/android/ex/camera2/portability/LegacyVendorTags.java new file mode 100644 index 0000000..e55748d --- /dev/null +++ b/camera2/portability/src/com/android/ex/camera2/portability/LegacyVendorTags.java @@ -0,0 +1,49 @@ +/* + * 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.camera2.CameraCharacteristics; + +import java.lang.ExceptionInInitializerError; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + * Vendor tag declarations for the Legacy Camera2 API implementation. + */ +public class LegacyVendorTags { + + /** + * Hidden enum for scene modes supported only by the Camera1 API. + */ + public static final int CONTROL_SCENE_MODE_HDR; + + static { + try { + CONTROL_SCENE_MODE_HDR = + Class.forName("android.hardware.camera2.CameraCharacteristics"). + getField("CONTROL_SCENE_MODE_HDR").getInt(null); + } catch (Exception e) { + throw new ExceptionInInitializerError( + "Error while reflecting on LegacyVendorTags: " + e); + } + } + + private LegacyVendorTags() { + throw new AssertionError(); + } +}
\ No newline at end of file |