diff --git a/core/java/android/hardware/camera2/legacy/CaptureCollector.java b/core/java/android/hardware/camera2/legacy/CaptureCollector.java index ab31d8ca9ae89..af58a8a5a9f6b 100644 --- a/core/java/android/hardware/camera2/legacy/CaptureCollector.java +++ b/core/java/android/hardware/camera2/legacy/CaptureCollector.java @@ -15,11 +15,12 @@ */ package android.hardware.camera2.legacy; -import android.hardware.camera2.impl.CameraMetadataNative; import android.util.Log; +import android.util.MutableLong; import android.util.Pair; import java.util.ArrayDeque; +import java.util.ArrayList; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; @@ -77,7 +78,7 @@ public class CaptureCollector { if (needsPreview && isPreviewCompleted()) { CaptureCollector.this.onPreviewCompleted(); } - CaptureCollector.this.onRequestCompleted(mRequest, mLegacy, mTimestamp); + CaptureCollector.this.onRequestCompleted(this); } } @@ -176,13 +177,13 @@ public class CaptureCollector { private final ArrayDeque mJpegProduceQueue; private final ArrayDeque mPreviewCaptureQueue; private final ArrayDeque mPreviewProduceQueue; + private final ArrayList mCompletedRequests = new ArrayList<>(); private final ReentrantLock mLock = new ReentrantLock(); private final Condition mIsEmpty; private final Condition mPreviewsEmpty; private final Condition mNotFull; private final CameraDeviceState mDeviceState; - private final LegacyResultMapper mMapper = new LegacyResultMapper(); private int mInFlight = 0; private int mInFlightPreviews = 0; private final int mMaxInFlight; @@ -319,6 +320,54 @@ public class CaptureCollector { } } + /** + * Wait for the specified request to be completed (all buffers available). + * + *

May not wait for the same request more than once, since a successful wait + * will erase the history of that request.

+ * + * @param holder the {@link RequestHolder} for this request. + * @param timeout a timeout to use for this call. + * @param unit the units to use for the timeout. + * @param timestamp the timestamp of the request will be written out to here, in ns + * + * @return {@code false} if this method timed out. + * + * @throws InterruptedException if this thread is interrupted. + */ + public boolean waitForRequestCompleted(RequestHolder holder, long timeout, TimeUnit unit, + MutableLong timestamp) + throws InterruptedException { + long nanos = unit.toNanos(timeout); + final ReentrantLock lock = this.mLock; + lock.lock(); + try { + while (!removeRequestIfCompleted(holder, /*out*/timestamp)) { + if (nanos <= 0) { + return false; + } + nanos = mNotFull.awaitNanos(nanos); + } + return true; + } finally { + lock.unlock(); + } + } + + private boolean removeRequestIfCompleted(RequestHolder holder, MutableLong timestamp) { + int i = 0; + for (CaptureHolder h : mCompletedRequests) { + if (h.mRequest.equals(holder)) { + timestamp.value = h.mTimestamp; + mCompletedRequests.remove(i); + return true; + } + i++; + } + + return false; + } + /** * Called to alert the {@link CaptureCollector} that the jpeg capture has begun. * @@ -431,8 +480,9 @@ public class CaptureCollector { } } - private void onRequestCompleted(RequestHolder request, LegacyRequest legacyHolder, - long timestamp) { + private void onRequestCompleted(CaptureHolder capture) { + RequestHolder request = capture.mRequest; + mInFlight--; if (DEBUG) { Log.d(TAG, "Completed request " + request.getRequestId() + @@ -442,12 +492,12 @@ public class CaptureCollector { throw new IllegalStateException( "More captures completed than requests queued."); } + + mCompletedRequests.add(capture); + mNotFull.signalAll(); if (mInFlight == 0) { mIsEmpty.signalAll(); } - CameraMetadataNative result = mMapper.cachedConvertResultMetadata( - legacyHolder, timestamp); - mDeviceState.setCaptureResult(request, result); } } diff --git a/core/java/android/hardware/camera2/legacy/LegacyFocusStateMapper.java b/core/java/android/hardware/camera2/legacy/LegacyFocusStateMapper.java new file mode 100644 index 0000000000000..e576b43da154b --- /dev/null +++ b/core/java/android/hardware/camera2/legacy/LegacyFocusStateMapper.java @@ -0,0 +1,301 @@ +/* + * 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 android.hardware.camera2.legacy; + +import android.hardware.Camera; +import android.hardware.Camera.Parameters; +import android.hardware.camera2.impl.CameraMetadataNative; +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.CaptureResult; +import android.hardware.camera2.utils.ParamsUtils; +import android.util.Log; + +import java.util.Objects; + +import static android.hardware.camera2.CaptureRequest.*; +import static com.android.internal.util.Preconditions.*; + +/** + * Map capture request data into legacy focus state transitions. + * + *

This object will asynchronously process auto-focus changes, so no interaction + * with it is necessary beyond reading the current state and updating with the latest trigger.

+ */ +@SuppressWarnings("deprecation") +public class LegacyFocusStateMapper { + private static String TAG = "LegacyFocusStateMapper"; + private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); + + private final Camera mCamera; + + private int mAfStatePrevious = CONTROL_AF_STATE_INACTIVE; + private String mAfModePrevious = null; + + /** Guard mAfRun and mAfState */ + private final Object mLock = new Object(); + /** Guard access with mLock */ + private int mAfRun = 0; + /** Guard access with mLock */ + private int mAfState = CONTROL_AF_STATE_INACTIVE; + + /** + * Instantiate a new focus state mapper. + * + * @param camera a non-{@code null} camera1 device + * + * @throws NullPointerException if any of the args were {@code null} + */ + public LegacyFocusStateMapper(Camera camera) { + mCamera = checkNotNull(camera, "camera must not be null"); + } + + /** + * Process the AF triggers from the request as a camera1 autofocus routine. + * + *

This method should be called after the parameters are {@link LegacyRequestMapper mapped} + * with the request.

+ * + *

Callbacks are processed in the background, and the next call to {@link #mapResultTriggers} + * will have the latest AF state as reflected by the camera1 callbacks.

+ * + *

None of the arguments will be mutated.

+ * + * @param captureRequest a non-{@code null} request + * @param parameters a non-{@code null} parameters corresponding to this request (read-only) + */ + public void processRequestTriggers(CaptureRequest captureRequest, + Camera.Parameters parameters) { + checkNotNull(captureRequest, "captureRequest must not be null"); + + /* + * control.afTrigger + */ + int afTrigger = ParamsUtils.getOrDefault(captureRequest, CONTROL_AF_TRIGGER, + CONTROL_AF_TRIGGER_IDLE); + + final String afMode = parameters.getFocusMode(); + + if (!Objects.equals(mAfModePrevious, afMode)) { + if (VERBOSE) { + Log.v(TAG, "processRequestTriggers - AF mode switched from " + mAfModePrevious + + " to " + afMode); + } + + // Switching modes always goes back to INACTIVE; ignore callbacks from previous modes + + synchronized (mLock) { + ++mAfRun; + mAfState = CONTROL_AF_STATE_INACTIVE; + } + mCamera.cancelAutoFocus(); + } + + mAfModePrevious = afMode; + + // Passive AF Scanning + { + final int currentAfRun; + + synchronized (mLock) { + currentAfRun = mAfRun; + } + + mCamera.setAutoFocusMoveCallback(new Camera.AutoFocusMoveCallback() { + @Override + public void onAutoFocusMoving(boolean start, Camera camera) { + synchronized (mLock) { + int latestAfRun = mAfRun; + + if (VERBOSE) { + Log.v(TAG, "onAutoFocusMoving - start " + start + " latest AF run " + + latestAfRun + ", last AF run " + currentAfRun); + } + + if (currentAfRun != latestAfRun) { + Log.d(TAG, + "onAutoFocusMoving - ignoring move callbacks from old af run" + + currentAfRun); + return; + } + + int newAfState = start ? + CONTROL_AF_STATE_PASSIVE_SCAN : + CONTROL_AF_STATE_PASSIVE_FOCUSED; + // We never send CONTROL_AF_STATE_PASSIVE_UNFOCUSED + + switch (afMode) { + case Parameters.FOCUS_MODE_CONTINUOUS_PICTURE: + case Parameters.FOCUS_MODE_CONTINUOUS_VIDEO: + break; + // This callback should never be sent in any other AF mode + default: + Log.w(TAG, "onAutoFocus - got unexpected onAutoFocus in mode " + + afMode); + + } + + mAfState = newAfState; + } + } + }); + } + + // AF Locking + switch (afTrigger) { + case CONTROL_AF_TRIGGER_START: + + int afStateAfterStart; + switch (afMode) { + case Parameters.FOCUS_MODE_AUTO: + case Parameters.FOCUS_MODE_MACRO: + afStateAfterStart = CONTROL_AF_STATE_ACTIVE_SCAN; + break; + case Parameters.FOCUS_MODE_CONTINUOUS_PICTURE: + case Parameters.FOCUS_MODE_CONTINUOUS_VIDEO: + afStateAfterStart = CONTROL_AF_STATE_PASSIVE_SCAN; + default: + // EDOF, INFINITY + afStateAfterStart = CONTROL_AF_STATE_INACTIVE; + } + + final int currentAfRun; + synchronized (mLock) { + currentAfRun = ++mAfRun; + mAfState = afStateAfterStart; + } + + if (VERBOSE) { + Log.v(TAG, "processRequestTriggers - got AF_TRIGGER_START, " + + "new AF run is " + currentAfRun); + } + + mCamera.autoFocus(new Camera.AutoFocusCallback() { + @Override + public void onAutoFocus(boolean success, Camera camera) { + synchronized (mLock) { + int latestAfRun = mAfRun; + + if (VERBOSE) { + Log.v(TAG, "onAutoFocus - success " + success + " latest AF run " + + latestAfRun + ", last AF run " + currentAfRun); + } + + // Ignore old auto-focus results, since another trigger was requested + if (latestAfRun != currentAfRun) { + Log.d(TAG, String.format("onAutoFocus - ignoring AF callback " + + "(old run %d, new run %d)", currentAfRun, latestAfRun)); + + return; + } + + int newAfState = success ? + CONTROL_AF_STATE_FOCUSED_LOCKED : + CONTROL_AF_STATE_NOT_FOCUSED_LOCKED; + + switch (afMode) { + case Parameters.FOCUS_MODE_AUTO: + case Parameters.FOCUS_MODE_CONTINUOUS_PICTURE: + case Parameters.FOCUS_MODE_CONTINUOUS_VIDEO: + case Parameters.FOCUS_MODE_MACRO: + break; + // This callback should never be sent in any other AF mode + default: + Log.w(TAG, "onAutoFocus - got unexpected onAutoFocus in mode " + + afMode); + + } + + mAfState = newAfState; + } + } + }); + + break; + case CONTROL_AF_TRIGGER_CANCEL: + synchronized (mLock) { + int updatedAfRun; + + synchronized (mLock) { + updatedAfRun = ++mAfRun; + mAfState = CONTROL_AF_STATE_INACTIVE; + } + + mCamera.cancelAutoFocus(); + + if (VERBOSE) { + Log.v(TAG, "processRequestTriggers - got AF_TRIGGER_CANCEL, " + + "new AF run is " + updatedAfRun); + } + } + + break; + case CONTROL_AF_TRIGGER_IDLE: + // No action necessary. The callbacks will handle transitions. + break; + default: + Log.w(TAG, "mapTriggers - ignoring unknown control.afTrigger = " + afTrigger); + } + } + + /** + * Update the {@code result} camera metadata map with the new value for the + * {@code control.afState}. + * + *

AF callbacks are processed in the background, and each call to {@link #mapResultTriggers} + * will have the latest AF state as reflected by the camera1 callbacks.

+ * + * @param result a non-{@code null} result + */ + public void mapResultTriggers(CameraMetadataNative result) { + checkNotNull(result, "result must not be null"); + + int newAfState; + synchronized (mLock) { + newAfState = mAfState; + } + + if (VERBOSE && newAfState != mAfStatePrevious) { + Log.v(TAG, String.format("mapResultTriggers - afState changed from %s to %s", + afStateToString(mAfStatePrevious), afStateToString(newAfState))); + } + + result.set(CaptureResult.CONTROL_AF_STATE, newAfState); + + mAfStatePrevious = newAfState; + } + + private static String afStateToString(int afState) { + switch (afState) { + case CONTROL_AF_STATE_ACTIVE_SCAN: + return "ACTIVE_SCAN"; + case CONTROL_AF_STATE_FOCUSED_LOCKED: + return "FOCUSED_LOCKED"; + case CONTROL_AF_STATE_INACTIVE: + return "INACTIVE"; + case CONTROL_AF_STATE_NOT_FOCUSED_LOCKED: + return "NOT_FOCUSED_LOCKED"; + case CONTROL_AF_STATE_PASSIVE_FOCUSED: + return "PASSIVE_FOCUSED"; + case CONTROL_AF_STATE_PASSIVE_SCAN: + return "PASSIVE_SCAN"; + case CONTROL_AF_STATE_PASSIVE_UNFOCUSED: + return "PASSIVE_UNFOCUSED"; + default : + return "UNKNOWN(" + afState + ")"; + } + } +} diff --git a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java index 157c159b51097..cbb8ee2d0c54b 100644 --- a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java +++ b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java @@ -48,6 +48,7 @@ import static android.hardware.camera2.legacy.ParameterUtils.*; * Provide legacy-specific implementations of camera2 metadata for legacy devices, such as the * camera characteristics. */ +@SuppressWarnings("deprecation") public class LegacyMetadataMapper { private static final String TAG = "LegacyMetadataMapper"; private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); @@ -87,8 +88,8 @@ public class LegacyMetadataMapper { */ static final boolean LIE_ABOUT_AE_STATE = false; static final boolean LIE_ABOUT_AE_MAX_REGIONS = false; - static final boolean LIE_ABOUT_AF = true; - static final boolean LIE_ABOUT_AF_MAX_REGIONS = true; + static final boolean LIE_ABOUT_AF = false; + static final boolean LIE_ABOUT_AF_MAX_REGIONS = false; static final boolean LIE_ABOUT_AWB_STATE = false; static final boolean LIE_ABOUT_AWB = true; @@ -161,6 +162,10 @@ public class LegacyMetadataMapper { * control.ae* */ mapControlAe(m, p); + /* + * control.af* + */ + mapControlAf(m, p); /* * control.awb* */ @@ -379,6 +384,54 @@ public class LegacyMetadataMapper { } } + + @SuppressWarnings({"unchecked"}) + private static void mapControlAf(CameraMetadataNative m, Camera.Parameters p) { + /* + * control.afAvailableModes + */ + { + List focusModes = p.getSupportedFocusModes(); + + String[] focusModeStrings = new String[] { + Camera.Parameters.FOCUS_MODE_AUTO, + Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE, + Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, + Camera.Parameters.FOCUS_MODE_EDOF, + Camera.Parameters.FOCUS_MODE_INFINITY, + Camera.Parameters.FOCUS_MODE_MACRO, + Camera.Parameters.FOCUS_MODE_FIXED, + }; + + int[] focusModeInts = new int[] { + CONTROL_AF_MODE_AUTO, + CONTROL_AF_MODE_CONTINUOUS_PICTURE, + CONTROL_AF_MODE_CONTINUOUS_VIDEO, + CONTROL_AF_MODE_EDOF, + CONTROL_AF_MODE_OFF, + CONTROL_AF_MODE_MACRO, + CONTROL_AF_MODE_OFF + }; + + List afAvail = ArrayUtils.convertStringListToIntList( + focusModes, focusModeStrings, focusModeInts); + + // No AF modes supported? That's unpossible! + if (afAvail == null || afAvail.size() == 0) { + Log.w(TAG, "No AF modes supported (HAL bug); defaulting to AF_MODE_OFF only"); + afAvail = new ArrayList(/*capacity*/1); + afAvail.add(CONTROL_AF_MODE_OFF); + } + + m.set(CONTROL_AF_AVAILABLE_MODES, ArrayUtils.toIntArray(afAvail)); + + if (VERBOSE) { + Log.v(TAG, "mapControlAf - control.afAvailableModes set to " + + ListUtils.listToString(afAvail)); + } + } + } + private static void mapControlAwb(CameraMetadataNative m, Camera.Parameters p) { if (!LIE_ABOUT_AWB) { throw new AssertionError("Not implemented yet"); @@ -794,4 +847,55 @@ public class LegacyMetadataMapper { return tags; } + + /** + * Convert the requested AF mode into its equivalent supported parameter. + * + * @param mode {@code CONTROL_AF_MODE} + * @param supportedFocusModes list of camera1's supported focus modes + * @return the stringified af mode, or {@code null} if its not supported + */ + static String convertAfModeToLegacy(int mode, List supportedFocusModes) { + if (supportedFocusModes == null || supportedFocusModes.isEmpty()) { + Log.w(TAG, "No focus modes supported; API1 bug"); + return null; + } + + String param = null; + switch (mode) { + case CONTROL_AF_MODE_AUTO: + param = Parameters.FOCUS_MODE_AUTO; + break; + case CONTROL_AF_MODE_CONTINUOUS_PICTURE: + param = Parameters.FOCUS_MODE_CONTINUOUS_PICTURE; + break; + case CONTROL_AF_MODE_CONTINUOUS_VIDEO: + param = Parameters.FOCUS_MODE_CONTINUOUS_VIDEO; + break; + case CONTROL_AF_MODE_EDOF: + param = Parameters.FOCUS_MODE_EDOF; + break; + case CONTROL_AF_MODE_MACRO: + param = Parameters.FOCUS_MODE_MACRO; + break; + case CONTROL_AF_MODE_OFF: + if (supportedFocusModes.contains(Parameters.FOCUS_MODE_FIXED)) { + param = Parameters.FOCUS_MODE_FIXED; + } else { + param = Parameters.FOCUS_MODE_INFINITY; + } + } + + if (!supportedFocusModes.contains(param)) { + // Weed out bad user input by setting to the first arbitrary focus mode + String defaultMode = supportedFocusModes.get(0); + Log.w(TAG, + String.format( + "convertAfModeToLegacy - ignoring unsupported mode %d, " + + "defaulting to %s", mode, defaultMode)); + param = defaultMode; + } + + return param; + } } diff --git a/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java b/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java index 4a9afa6d08235..fbfc39f0e44ae 100644 --- a/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java +++ b/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java @@ -23,6 +23,7 @@ import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.params.MeteringRectangle; import android.hardware.camera2.utils.ListUtils; +import android.hardware.camera2.utils.ParamsUtils; import android.util.Log; import android.util.Range; import android.util.Size; @@ -38,6 +39,7 @@ import static android.hardware.camera2.CaptureRequest.*; /** * Provide legacy-specific implementations of camera2 CaptureRequest for legacy devices. */ +@SuppressWarnings("deprecation") public class LegacyRequestMapper { private static final String TAG = "LegacyRequestMapper"; private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); @@ -158,7 +160,7 @@ public class LegacyRequestMapper { { Range compensationRange = characteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE); - int compensation = getOrDefault(request, + int compensation = ParamsUtils.getOrDefault(request, CONTROL_AE_EXPOSURE_COMPENSATION, /*defaultValue*/0); @@ -192,6 +194,23 @@ public class LegacyRequestMapper { // control.aeMode, flash.mode mapAeAndFlashMode(request, /*out*/params); + // control.afMode + { + int afMode = ParamsUtils.getOrDefault(request, CONTROL_AF_MODE, + /*defaultValue*/CONTROL_AF_MODE_OFF); + String focusMode = LegacyMetadataMapper.convertAfModeToLegacy(afMode, + params.getSupportedFocusModes()); + + if (focusMode != null) { + params.setFocusMode(focusMode); + } + + if (VERBOSE) { + Log.v(TAG, "convertRequestToMetadata - control.afMode " + + afMode + " mapped to " + focusMode); + } + } + // control.awbLock { Boolean awbLock = getIfSupported(request, CONTROL_AWB_LOCK, /*defaultValue*/false, @@ -204,6 +223,21 @@ public class LegacyRequestMapper { // TODO: Don't add control.awbLock to availableRequestKeys if it's not supported } + + // lens.focusDistance + { + boolean infinityFocusSupported = + ListUtils.listContains(params.getSupportedFocusModes(), + Parameters.FOCUS_MODE_INFINITY); + Float focusDistance = getIfSupported(request, LENS_FOCUS_DISTANCE, + /*defaultValue*/0f, infinityFocusSupported, /*allowedValue*/0f); + + if (focusDistance == null || focusDistance != 0f) { + Log.w(TAG, + "convertRequestToMetadata - Ignoring android.lens.focusDistance " + + infinityFocusSupported + ", only 0.0f is supported"); + } + } } private static List convertMeteringRegionsToLegacy( @@ -253,8 +287,8 @@ public class LegacyRequestMapper { } private static void mapAeAndFlashMode(CaptureRequest r, /*out*/Parameters p) { - int flashMode = getOrDefault(r, FLASH_MODE, FLASH_MODE_OFF); - int aeMode = getOrDefault(r, CONTROL_AE_MODE, CONTROL_AE_MODE_ON); + int flashMode = ParamsUtils.getOrDefault(r, FLASH_MODE, FLASH_MODE_OFF); + int aeMode = ParamsUtils.getOrDefault(r, CONTROL_AE_MODE, CONTROL_AE_MODE_ON); List supportedFlashModes = p.getSupportedFlashModes(); @@ -355,20 +389,6 @@ public class LegacyRequestMapper { return legacyFps; } - /** Return the value set by the key, or the {@code defaultValue} if no value was set. */ - private static T getOrDefault(CaptureRequest r, CaptureRequest.Key key, T defaultValue) { - checkNotNull(r, "r must not be null"); - checkNotNull(key, "key must not be null"); - checkNotNull(defaultValue, "defaultValue must not be null"); - - T value = r.get(key); - if (value == null) { - return defaultValue; - } else { - return value; - } - } - /** * Return {@code null} if the value is not supported, otherwise return the retrieved key's * value from the request (or the default value if it wasn't set). @@ -382,7 +402,7 @@ public class LegacyRequestMapper { private static T getIfSupported( CaptureRequest r, CaptureRequest.Key key, T defaultValue, boolean isSupported, T allowedValue) { - T val = getOrDefault(r, key, defaultValue); + T val = ParamsUtils.getOrDefault(r, key, defaultValue); if (!isSupported) { if (!Objects.equals(val, allowedValue)) { diff --git a/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java b/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java index 88f95e163a645..58c3984176a58 100644 --- a/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java +++ b/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java @@ -34,11 +34,13 @@ import android.util.Size; import java.util.ArrayList; import java.util.List; +import static com.android.internal.util.Preconditions.*; import static android.hardware.camera2.CaptureResult.*; /** * Provide legacy-specific implementations of camera2 CaptureResult for legacy devices. */ +@SuppressWarnings("deprecation") public class LegacyResultMapper { private static final String TAG = "LegacyResultMapper"; private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); @@ -55,22 +57,53 @@ public class LegacyResultMapper { * * @param legacyRequest a non-{@code null} legacy request containing the latest parameters * @param timestamp the timestamp to use for this result in nanoseconds. + * @param frameCounter a monotonically increasing frame counter for the result * * @return {@link CameraMetadataNative} object containing result metadata. */ public CameraMetadataNative cachedConvertResultMetadata( - LegacyRequest legacyRequest, long timestamp) { - if (mCachedRequest != null && legacyRequest.parameters.same(mCachedRequest.parameters)) { - CameraMetadataNative newResult = new CameraMetadataNative(mCachedResult); + LegacyRequest legacyRequest, long timestamp, long frameCounter) { + CameraMetadataNative result; + boolean cached; - // sensor.timestamp - newResult.set(CaptureResult.SENSOR_TIMESTAMP, timestamp); - return newResult; + /* + * Attempt to look up the result from the cache if the parameters haven't changed + */ + if (mCachedRequest != null && legacyRequest.parameters.same(mCachedRequest.parameters)) { + result = new CameraMetadataNative(mCachedResult); + cached = true; + } else { + result = convertResultMetadata(legacyRequest, timestamp); + cached = false; + + // Always cache a *copy* of the metadata result, + // since api2's client side takes ownership of it after it receives a result + mCachedRequest = legacyRequest; + mCachedResult = new CameraMetadataNative(result); } - mCachedRequest = legacyRequest; - mCachedResult = convertResultMetadata(mCachedRequest, timestamp); - return mCachedResult; + /* + * Unconditionally set fields that change in every single frame + */ + { + // request.frameCounter + result.set(REQUEST_FRAME_COUNT, (int)frameCounter); + // TODO: fix CaptureResult#getFrameNumber not to need this key + + // sensor.timestamp + result.set(SENSOR_TIMESTAMP, timestamp); + } + + if (VERBOSE) { + Log.v(TAG, "cachedConvertResultMetadata - cached? " + cached + + " frameCounter = " + frameCounter + " timestamp = " + timestamp); + + Log.v(TAG, "----- beginning of result dump ------"); + result.dumpToLog(); + Log.v(TAG, "----- end of result dump ------"); + } + + return result; } /** @@ -81,7 +114,7 @@ public class LegacyResultMapper { * * @return a {@link CameraMetadataNative} object containing result metadata. */ - public static CameraMetadataNative convertResultMetadata(LegacyRequest legacyRequest, + private static CameraMetadataNative convertResultMetadata(LegacyRequest legacyRequest, long timestamp) { CameraCharacteristics characteristics = legacyRequest.characteristics; CaptureRequest request = legacyRequest.captureRequest; @@ -98,17 +131,15 @@ public class LegacyResultMapper { /* * control */ - // control.afState - if (LegacyMetadataMapper.LIE_ABOUT_AF) { - // TODO: Implement autofocus state machine - result.set(CaptureResult.CONTROL_AF_MODE, request.get(CaptureRequest.CONTROL_AF_MODE)); - } /* * control.ae* */ mapAe(result, characteristics, request, activeArraySize, zoomData, /*out*/params); + // control.afMode + result.set(CaptureResult.CONTROL_AF_MODE, convertLegacyAfMode(params.getFocusMode())); + // control.awbLock result.set(CaptureResult.CONTROL_AWB_LOCK, params.getAutoWhiteBalanceLock()); @@ -137,9 +168,23 @@ public class LegacyResultMapper { /* * lens */ + // lens.focusDistance + { + if (Parameters.FOCUS_MODE_INFINITY.equals(params.getFocusMode())) { + result.set(CaptureResult.LENS_FOCUS_DISTANCE, 0.0f); + } + } + // lens.focalLength result.set(CaptureResult.LENS_FOCAL_LENGTH, params.getFocalLength()); + /* + * request + */ + // request.pipelineDepth + result.set(REQUEST_PIPELINE_DEPTH, + characteristics.get(CameraCharacteristics.REQUEST_PIPELINE_MAX_DEPTH)); + /* * scaler */ @@ -148,8 +193,6 @@ public class LegacyResultMapper { /* * sensor */ - // sensor.timestamp - result.set(CaptureResult.SENSOR_TIMESTAMP, timestamp); // TODO: Remaining result metadata tags conversions. return result; @@ -301,6 +344,33 @@ public class LegacyResultMapper { m.set(CONTROL_AE_MODE, aeMode); } + private static int convertLegacyAfMode(String mode) { + if (mode == null) { + Log.w(TAG, "convertLegacyAfMode - no AF mode, default to OFF"); + return CONTROL_AF_MODE_OFF; + } + + switch (mode) { + case Parameters.FOCUS_MODE_AUTO: + return CONTROL_AF_MODE_AUTO; + case Parameters.FOCUS_MODE_CONTINUOUS_PICTURE: + return CONTROL_AF_MODE_CONTINUOUS_PICTURE; + case Parameters.FOCUS_MODE_CONTINUOUS_VIDEO: + return CONTROL_AF_MODE_CONTINUOUS_VIDEO; + case Parameters.FOCUS_MODE_EDOF: + return CONTROL_AF_MODE_EDOF; + case Parameters.FOCUS_MODE_MACRO: + return CONTROL_AF_MODE_MACRO; + case Parameters.FOCUS_MODE_FIXED: + return CONTROL_AF_MODE_OFF; + case Parameters.FOCUS_MODE_INFINITY: + return CONTROL_AF_MODE_OFF; + default: + Log.w(TAG, "convertLegacyAfMode - unknown mode " + mode + " , ignoring"); + return CONTROL_AF_MODE_OFF; + } + } + /** Map results for scaler.* */ private static void mapScaler(CameraMetadataNative m, ZoomData zoomData, diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java index 066b416f05e26..bcee315395acc 100644 --- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java +++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java @@ -28,6 +28,7 @@ import android.os.Handler; import android.os.Message; import android.os.SystemClock; import android.util.Log; +import android.util.MutableLong; import android.util.Pair; import android.util.Size; import android.view.Surface; @@ -65,6 +66,7 @@ public class RequestThreadManager { private final CameraDeviceState mDeviceState; private final CaptureCollector mCaptureCollector; + private final LegacyFocusStateMapper mFocusStateMapper; private static final int MSG_CONFIGURE_OUTPUTS = 1; private static final int MSG_SUBMIT_CAPTURE_REQUEST = 2; @@ -74,6 +76,7 @@ public class RequestThreadManager { private static final int PREVIEW_FRAME_TIMEOUT = 300; // ms private static final int JPEG_FRAME_TIMEOUT = 3000; // ms (same as CTS for API2) + private static final int REQUEST_COMPLETE_TIMEOUT = 3000; // ms (same as JPEG timeout) private static final float ASPECT_RATIO_TOLERANCE = 0.01f; private boolean mPreviewRunning = false; @@ -510,7 +513,7 @@ public class RequestThreadManager { private final Handler.Callback mRequestHandlerCb = new Handler.Callback() { private boolean mCleanup = false; - private LegacyResultMapper mMapper = new LegacyResultMapper(); + private final LegacyResultMapper mMapper = new LegacyResultMapper(); @Override public boolean handleMessage(Message msg) { @@ -586,6 +589,8 @@ public class RequestThreadManager { CaptureRequest request = holder.getRequest(); boolean paramsChanged = false; + + // Lazily process the rest of the request if (mLastRequest == null || mLastRequest.captureRequest != request) { // The intermediate buffer is sometimes null, but we always need @@ -608,6 +613,10 @@ public class RequestThreadManager { } } + // Unconditionally process AF triggers, since they're non-idempotent + // - must be done after setting the most-up-to-date AF mode + mFocusStateMapper.processRequestTriggers(request, mParams); + try { boolean success = mCaptureCollector.queueRequest(holder, mLastRequest, JPEG_FRAME_TIMEOUT, TimeUnit.MILLISECONDS); @@ -649,6 +658,27 @@ public class RequestThreadManager { // Update parameters to the latest that we think the camera is using mLastRequest.setParameters(mParams); } + + MutableLong timestampMutable = new MutableLong(/*value*/0L); + try { + boolean success = mCaptureCollector.waitForRequestCompleted(holder, + REQUEST_COMPLETE_TIMEOUT, TimeUnit.MILLISECONDS, + /*out*/timestampMutable); + + if (!success) { + Log.e(TAG, "Timed out while waiting for request to complete."); + } + } catch (InterruptedException e) { + // TODO: report error to CameraDevice + Log.e(TAG, "Interrupted during request completition.", e); + } + + CameraMetadataNative result = mMapper.cachedConvertResultMetadata( + mLastRequest, timestampMutable.value, holder.getFrameNumber()); + // Update AF state + mFocusStateMapper.mapResultTriggers(result); + + mDeviceState.setCaptureResult(holder, result); } if (DEBUG) { long totalTime = SystemClock.elapsedRealtimeNanos() - startTime; @@ -700,6 +730,7 @@ public class RequestThreadManager { String name = String.format("RequestThread-%d", cameraId); TAG = name; mDeviceState = checkNotNull(deviceState, "deviceState must not be null"); + mFocusStateMapper = new LegacyFocusStateMapper(mCamera); mCaptureCollector = new CaptureCollector(MAX_IN_FLIGHT_REQUESTS, mDeviceState); mRequestThread = new RequestHandlerThread(name, mRequestHandlerCb); } diff --git a/core/java/android/hardware/camera2/utils/ArrayUtils.java b/core/java/android/hardware/camera2/utils/ArrayUtils.java index c5a56cdae969a..24c85d0d36914 100644 --- a/core/java/android/hardware/camera2/utils/ArrayUtils.java +++ b/core/java/android/hardware/camera2/utils/ArrayUtils.java @@ -67,6 +67,36 @@ public class ArrayUtils { return null; } + List convertedList = convertStringListToIntList(list, convertFrom, convertTo); + + int[] returnArray = new int[convertedList.size()]; + for (int i = 0; i < returnArray.length; ++i) { + returnArray[i] = convertedList.get(i); + } + + return returnArray; + } + + /** + * Create an {@code List} from the {@code List<>} by using {@code convertFrom} and + * {@code convertTo} as a one-to-one map (via the index). + * + *

Strings not appearing in {@code convertFrom} are ignored (with a logged warning); + * strings appearing in {@code convertFrom} but not {@code convertTo} are silently + * dropped.

+ * + * @param list Source list of strings + * @param convertFrom Conversion list of strings + * @param convertTo Conversion list of ints + * @return A list of ints where the values correspond to the ones in {@code convertTo} + * or {@code null} if {@code list} was {@code null} + */ + public static List convertStringListToIntList( + List list, String[] convertFrom, int[] convertTo) { + if (list == null) { + return null; + } + List convertedList = new ArrayList<>(list.size()); for (String str : list) { @@ -84,12 +114,33 @@ public class ArrayUtils { } } - int[] returnArray = new int[convertedList.size()]; - for (int i = 0; i < returnArray.length; ++i) { - returnArray[i] = convertedList.get(i); + return convertedList; + } + + /** + * Convert the list of integers in {@code list} to an {@code int} array. + * + *

Every element in {@code list} must be non-{@code null}.

+ * + * @param list a list of non-{@code null} integers + * + * @return a new int array containing all the elements from {@code list} + * + * @throws NullPointerException if any of the elements in {@code list} were {@code null} + */ + public static int[] toIntArray(List list) { + if (list == null) { + return null; } - return returnArray; + int[] arr = new int[list.size()]; + int i = 0; + for (int elem : list) { + arr[i] = elem; + i++; + } + + return arr; } private ArrayUtils() { diff --git a/core/java/android/hardware/camera2/utils/ParamsUtils.java b/core/java/android/hardware/camera2/utils/ParamsUtils.java index 232a4f6642ccb..976fa2e38007f 100644 --- a/core/java/android/hardware/camera2/utils/ParamsUtils.java +++ b/core/java/android/hardware/camera2/utils/ParamsUtils.java @@ -19,6 +19,7 @@ package android.hardware.camera2.utils; import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.RectF; +import android.hardware.camera2.CaptureRequest; import android.util.Rational; import android.util.Size; @@ -175,6 +176,24 @@ public class ParamsUtils { destination.top = source.top; } + /** + * Return the value set by the key, or the {@code defaultValue} if no value was set. + * + * @throws NullPointerException if any of the args were {@code null} + */ + public static T getOrDefault(CaptureRequest r, CaptureRequest.Key key, T defaultValue) { + checkNotNull(r, "r must not be null"); + checkNotNull(key, "key must not be null"); + checkNotNull(defaultValue, "defaultValue must not be null"); + + T value = r.get(key); + if (value == null) { + return defaultValue; + } else { + return value; + } + } + private ParamsUtils() { throw new AssertionError(); }