am c5b6a0c8: am 44e5ce82: am 6dc35134: Merge changes I104c8243,I82811142 into lmp-dev

* commit 'c5b6a0c8ba9f1c89ec58670e374039fe7e88df2a':
  camera2: legacy: fix cached results bug and fix focus modes
  camera2: legacy: Add focus support
This commit is contained in:
Igor Murashkin
2014-07-30 14:27:54 +00:00
committed by Android Git Automerger
8 changed files with 696 additions and 50 deletions

View File

@@ -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<CaptureHolder> mJpegProduceQueue;
private final ArrayDeque<CaptureHolder> mPreviewCaptureQueue;
private final ArrayDeque<CaptureHolder> mPreviewProduceQueue;
private final ArrayList<CaptureHolder> 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).
*
* <p>May not wait for the same request more than once, since a successful wait
* will erase the history of that request.</p>
*
* @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);
}
}

View File

@@ -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.
*
* <p>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.</p>
*/
@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.
*
* <p>This method should be called after the parameters are {@link LegacyRequestMapper mapped}
* with the request.</p>
*
* <p>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.</p>
*
* <p>None of the arguments will be mutated.</p>
*
* @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}.
*
* <p>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.</p>
*
* @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 + ")";
}
}
}

View File

@@ -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<String> 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<Integer> 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<Integer>(/*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<String> 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;
}
}

View File

@@ -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<Integer> 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<Camera.Area> 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<String> 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> T getOrDefault(CaptureRequest r, CaptureRequest.Key<T> 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> T getIfSupported(
CaptureRequest r, CaptureRequest.Key<T> 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)) {

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -67,6 +67,36 @@ public class ArrayUtils {
return null;
}
List<Integer> 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<Integer>} from the {@code List<>} by using {@code convertFrom} and
* {@code convertTo} as a one-to-one map (via the index).
*
* <p>Strings not appearing in {@code convertFrom} are ignored (with a logged warning);
* strings appearing in {@code convertFrom} but not {@code convertTo} are silently
* dropped.</p>
*
* @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<Integer> convertStringListToIntList(
List<String> list, String[] convertFrom, int[] convertTo) {
if (list == null) {
return null;
}
List<Integer> 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.
*
* <p>Every element in {@code list} must be non-{@code null}.</p>
*
* @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<Integer> 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() {

View File

@@ -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> T getOrDefault(CaptureRequest r, CaptureRequest.Key<T> 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();
}