am 6a80927a: Merge "camera2: refactor LEGACY mode error handling." into lmp-dev

* commit '6a80927aef62f74a4801180adf68fe8781181060':
  camera2: refactor LEGACY mode error handling.
This commit is contained in:
Ruben Brunk
2014-09-21 19:25:56 +00:00
committed by Android Git Automerger
11 changed files with 553 additions and 130 deletions

View File

@@ -51,7 +51,6 @@ import java.util.TreeSet;
* HAL2.1+ implementation of CameraDevice. Use CameraManager#open to instantiate
*/
public class CameraDeviceImpl extends CameraDevice {
private final String TAG;
private final boolean DEBUG;
@@ -1136,7 +1135,6 @@ public class CameraDeviceImpl extends CameraDevice {
}
public class CameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
//
// Constants below need to be kept up-to-date with
// frameworks/av/include/camera/camera2/ICameraDeviceCallbacks.h
@@ -1149,34 +1147,29 @@ public class CameraDeviceImpl extends CameraDevice {
/**
* Camera has been disconnected
*/
static final int ERROR_CAMERA_DISCONNECTED = 0;
public static final int ERROR_CAMERA_DISCONNECTED = 0;
/**
* Camera has encountered a device-level error
* Matches CameraDevice.StateCallback#ERROR_CAMERA_DEVICE
*/
static final int ERROR_CAMERA_DEVICE = 1;
public static final int ERROR_CAMERA_DEVICE = 1;
/**
* Camera has encountered a service-level error
* Matches CameraDevice.StateCallback#ERROR_CAMERA_SERVICE
*/
static final int ERROR_CAMERA_SERVICE = 2;
public static final int ERROR_CAMERA_SERVICE = 2;
/**
* Camera has encountered an error processing a single request.
*/
static final int ERROR_CAMERA_REQUEST = 3;
public static final int ERROR_CAMERA_REQUEST = 3;
/**
* Camera has encountered an error producing metadata for a single capture
*/
static final int ERROR_CAMERA_RESULT = 4;
public static final int ERROR_CAMERA_RESULT = 4;
/**
* Camera has encountered an error producing an image buffer for a single capture
*/
static final int ERROR_CAMERA_BUFFER = 5;
public static final int ERROR_CAMERA_BUFFER = 5;
@Override
public IBinder asBinder() {
@@ -1187,9 +1180,9 @@ public class CameraDeviceImpl extends CameraDevice {
public void onDeviceError(final int errorCode, CaptureResultExtras resultExtras) {
if (DEBUG) {
Log.d(TAG, String.format(
"Device error received, code %d, frame number %d, request ID %d, subseq ID %d",
errorCode, resultExtras.getFrameNumber(), resultExtras.getRequestId(),
resultExtras.getSubsequenceId()));
"Device error received, code %d, frame number %d, request ID %d, subseq ID %d",
errorCode, resultExtras.getFrameNumber(), resultExtras.getRequestId(),
resultExtras.getSubsequenceId()));
}
synchronized(mInterfaceLock) {

View File

@@ -49,6 +49,9 @@ public class CameraDeviceState {
private static final int STATE_IDLE = 3;
private static final int STATE_CAPTURING = 4;
private static final String[] sStateNames = { "ERROR", "UNCONFIGURED", "CONFIGURING", "IDLE",
"CAPTURING"};
private int mCurrentState = STATE_UNCONFIGURED;
private int mCurrentError = CameraBinderDecorator.NO_ERROR;
@@ -57,6 +60,11 @@ public class CameraDeviceState {
private Handler mCurrentHandler = null;
private CameraDeviceStateListener mCurrentListener = null;
/**
* Error code used by {@link #setCaptureStart} and {@link #setCaptureResult} to indicate that no
* error has occurred.
*/
public static final int NO_CAPTURE_ERROR = -1;
/**
* CameraDeviceStateListener callbacks to be called after state transitions.
@@ -126,11 +134,15 @@ public class CameraDeviceState {
*
* @param request A {@link RequestHolder} containing the request for the current capture.
* @param timestamp The timestamp of the capture start in nanoseconds.
* @param captureError Report a recoverable error for a single request using a valid
* error code for {@code ICameraDeviceCallbacks}, or
* {@link #NO_CAPTURE_ERROR}
* @return {@link CameraBinderDecorator#NO_ERROR}, or an error if one has occurred.
*/
public synchronized int setCaptureStart(final RequestHolder request, long timestamp) {
public synchronized int setCaptureStart(final RequestHolder request, long timestamp,
int captureError) {
mCurrentRequest = request;
doStateTransition(STATE_CAPTURING, timestamp);
doStateTransition(STATE_CAPTURING, timestamp, captureError);
return mCurrentError;
}
@@ -144,12 +156,16 @@ public class CameraDeviceState {
* the {@code ERROR} state,
* </p>
*
* @param request the {@link RequestHolder} request that created this result.
* @param result the {@link CameraMetadataNative} result to set.
* @param request The {@link RequestHolder} request that created this result.
* @param result The {@link CameraMetadataNative} result to set.
* @param captureError Report a recoverable error for a single buffer or result using a valid
* error code for {@code ICameraDeviceCallbacks}, or
* {@link #NO_CAPTURE_ERROR}.
* @return {@link CameraBinderDecorator#NO_ERROR}, or an error if one has occurred.
*/
public synchronized int setCaptureResult(final RequestHolder request,
final CameraMetadataNative result) {
final CameraMetadataNative result,
final int captureError) {
if (mCurrentState != STATE_CAPTURING) {
Log.e(TAG, "Cannot receive result while in state: " + mCurrentState);
mCurrentError = CameraBinderDecorator.INVALID_OPERATION;
@@ -158,12 +174,21 @@ public class CameraDeviceState {
}
if (mCurrentHandler != null && mCurrentListener != null) {
mCurrentHandler.post(new Runnable() {
@Override
public void run() {
mCurrentListener.onCaptureResult(result, request);
}
});
if (captureError != NO_CAPTURE_ERROR) {
mCurrentHandler.post(new Runnable() {
@Override
public void run() {
mCurrentListener.onError(captureError, request);
}
});
} else {
mCurrentHandler.post(new Runnable() {
@Override
public void run() {
mCurrentListener.onCaptureResult(result, request);
}
});
}
}
return mCurrentError;
}
@@ -181,14 +206,16 @@ public class CameraDeviceState {
}
private void doStateTransition(int newState) {
doStateTransition(newState, /*timestamp*/0);
doStateTransition(newState, /*timestamp*/0, CameraBinderDecorator.NO_ERROR);
}
private void doStateTransition(int newState, final long timestamp) {
if (DEBUG) {
if (newState != mCurrentState) {
Log.d(TAG, "Transitioning to state " + newState);
private void doStateTransition(int newState, final long timestamp, final int error) {
if (newState != mCurrentState) {
String stateName = "UNKNOWN";
if (newState >= 0 && newState < sStateNames.length) {
stateName = sStateNames[newState];
}
Log.i(TAG, "Legacy camera service transitioning to state " + stateName);
}
switch(newState) {
case STATE_ERROR:
@@ -251,13 +278,23 @@ public class CameraDeviceState {
doStateTransition(STATE_ERROR);
break;
}
if (mCurrentHandler != null && mCurrentListener != null) {
mCurrentHandler.post(new Runnable() {
@Override
public void run() {
mCurrentListener.onCaptureStarted(mCurrentRequest, timestamp);
}
});
if (error != NO_CAPTURE_ERROR) {
mCurrentHandler.post(new Runnable() {
@Override
public void run() {
mCurrentListener.onError(error, mCurrentRequest);
}
});
} else {
mCurrentHandler.post(new Runnable() {
@Override
public void run() {
mCurrentListener.onCaptureStarted(mCurrentRequest, timestamp);
}
});
}
}
mCurrentState = STATE_CAPTURING;
break;

View File

@@ -341,6 +341,10 @@ public class CameraDeviceUserShim implements ICameraDeviceUser {
Log.d(TAG, "disconnect called.");
}
if (mLegacyDevice.isClosed()) {
Log.w(TAG, "Cannot disconnect, device has already been closed.");
}
try {
mLegacyDevice.close();
} finally {
@@ -355,6 +359,11 @@ public class CameraDeviceUserShim implements ICameraDeviceUser {
if (DEBUG) {
Log.d(TAG, "submitRequest called.");
}
if (mLegacyDevice.isClosed()) {
Log.e(TAG, "Cannot submit request, device has been closed.");
return CameraBinderDecorator.ENODEV;
}
synchronized(mConfigureLock) {
if (mConfiguring) {
Log.e(TAG, "Cannot submit request, configuration change in progress.");
@@ -370,6 +379,11 @@ public class CameraDeviceUserShim implements ICameraDeviceUser {
if (DEBUG) {
Log.d(TAG, "submitRequestList called.");
}
if (mLegacyDevice.isClosed()) {
Log.e(TAG, "Cannot submit request list, device has been closed.");
return CameraBinderDecorator.ENODEV;
}
synchronized(mConfigureLock) {
if (mConfiguring) {
Log.e(TAG, "Cannot submit request, configuration change in progress.");
@@ -384,6 +398,11 @@ public class CameraDeviceUserShim implements ICameraDeviceUser {
if (DEBUG) {
Log.d(TAG, "cancelRequest called.");
}
if (mLegacyDevice.isClosed()) {
Log.e(TAG, "Cannot cancel request, device has been closed.");
return CameraBinderDecorator.ENODEV;
}
synchronized(mConfigureLock) {
if (mConfiguring) {
Log.e(TAG, "Cannot cancel request, configuration change in progress.");
@@ -400,6 +419,11 @@ public class CameraDeviceUserShim implements ICameraDeviceUser {
if (DEBUG) {
Log.d(TAG, "beginConfigure called.");
}
if (mLegacyDevice.isClosed()) {
Log.e(TAG, "Cannot begin configure, device has been closed.");
return CameraBinderDecorator.ENODEV;
}
synchronized(mConfigureLock) {
if (mConfiguring) {
Log.e(TAG, "Cannot begin configure, configuration change already in progress.");
@@ -415,6 +439,11 @@ public class CameraDeviceUserShim implements ICameraDeviceUser {
if (DEBUG) {
Log.d(TAG, "endConfigure called.");
}
if (mLegacyDevice.isClosed()) {
Log.e(TAG, "Cannot end configure, device has been closed.");
return CameraBinderDecorator.ENODEV;
}
ArrayList<Surface> surfaces = null;
synchronized(mConfigureLock) {
if (!mConfiguring) {
@@ -438,6 +467,11 @@ public class CameraDeviceUserShim implements ICameraDeviceUser {
if (DEBUG) {
Log.d(TAG, "deleteStream called.");
}
if (mLegacyDevice.isClosed()) {
Log.e(TAG, "Cannot delete stream, device has been closed.");
return CameraBinderDecorator.ENODEV;
}
synchronized(mConfigureLock) {
if (!mConfiguring) {
Log.e(TAG, "Cannot delete stream, beginConfigure hasn't been called yet.");
@@ -458,6 +492,11 @@ public class CameraDeviceUserShim implements ICameraDeviceUser {
if (DEBUG) {
Log.d(TAG, "createStream called.");
}
if (mLegacyDevice.isClosed()) {
Log.e(TAG, "Cannot create stream, device has been closed.");
return CameraBinderDecorator.ENODEV;
}
synchronized(mConfigureLock) {
if (!mConfiguring) {
Log.e(TAG, "Cannot create stream, beginConfigure hasn't been called yet.");
@@ -474,6 +513,10 @@ public class CameraDeviceUserShim implements ICameraDeviceUser {
if (DEBUG) {
Log.d(TAG, "createDefaultRequest called.");
}
if (mLegacyDevice.isClosed()) {
Log.e(TAG, "Cannot create default request, device has been closed.");
return CameraBinderDecorator.ENODEV;
}
CameraMetadataNative template;
try {
@@ -503,6 +546,11 @@ public class CameraDeviceUserShim implements ICameraDeviceUser {
if (DEBUG) {
Log.d(TAG, "waitUntilIdle called.");
}
if (mLegacyDevice.isClosed()) {
Log.e(TAG, "Cannot wait until idle, device has been closed.");
return CameraBinderDecorator.ENODEV;
}
synchronized(mConfigureLock) {
if (mConfiguring) {
Log.e(TAG, "Cannot wait until idle, configuration change in progress.");
@@ -518,13 +566,21 @@ public class CameraDeviceUserShim implements ICameraDeviceUser {
if (DEBUG) {
Log.d(TAG, "flush called.");
}
if (mLegacyDevice.isClosed()) {
Log.e(TAG, "Cannot flush, device has been closed.");
return CameraBinderDecorator.ENODEV;
}
synchronized(mConfigureLock) {
if (mConfiguring) {
Log.e(TAG, "Cannot flush, configuration change in progress.");
return CameraBinderDecorator.INVALID_OPERATION;
}
}
// TODO: implement flush.
long lastFrame = mLegacyDevice.flush();
if (lastFrameNumber != null) {
lastFrameNumber.setNumber(lastFrame);
}
return CameraBinderDecorator.NO_ERROR;
}

View File

@@ -15,12 +15,14 @@
*/
package android.hardware.camera2.legacy;
import android.hardware.camera2.impl.CameraDeviceImpl;
import android.util.Log;
import android.util.MutableLong;
import android.util.Pair;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
@@ -44,7 +46,7 @@ public class CaptureCollector {
private static final int MAX_JPEGS_IN_FLIGHT = 1;
private class CaptureHolder {
private class CaptureHolder implements Comparable<CaptureHolder>{
private final RequestHolder mRequest;
private final LegacyRequest mLegacy;
public final boolean needsJpeg;
@@ -53,6 +55,10 @@ public class CaptureCollector {
private long mTimestamp = 0;
private int mReceivedFlags = 0;
private boolean mHasStarted = false;
private boolean mFailedJpeg = false;
private boolean mFailedPreview = false;
private boolean mCompleted = false;
private boolean mPreviewCompleted = false;
public CaptureHolder(RequestHolder request, LegacyRequest legacyHolder) {
mRequest = request;
@@ -74,11 +80,43 @@ public class CaptureCollector {
}
public void tryComplete() {
if (isCompleted()) {
if (needsPreview && isPreviewCompleted()) {
CaptureCollector.this.onPreviewCompleted();
if (!mPreviewCompleted && needsPreview && isPreviewCompleted()) {
CaptureCollector.this.onPreviewCompleted();
mPreviewCompleted = true;
}
if (isCompleted() && !mCompleted) {
if (mFailedPreview || mFailedJpeg) {
if (!mHasStarted) {
// Send a request error if the capture has not yet started.
mRequest.failRequest();
CaptureCollector.this.mDeviceState.setCaptureStart(mRequest, mTimestamp,
CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_REQUEST);
} else {
// Send buffer dropped errors for each pending buffer if the request has
// started.
if (mFailedPreview) {
Log.w(TAG, "Preview buffers dropped for request: " +
mRequest.getRequestId());
for (int i = 0; i < mRequest.numPreviewTargets(); i++) {
CaptureCollector.this.mDeviceState.setCaptureResult(mRequest,
/*result*/null,
CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_BUFFER);
}
}
if (mFailedJpeg) {
Log.w(TAG, "Jpeg buffers dropped for request: " +
mRequest.getRequestId());
for (int i = 0; i < mRequest.numJpegTargets(); i++) {
CaptureCollector.this.mDeviceState.setCaptureResult(mRequest,
/*result*/null,
CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_BUFFER);
}
}
}
}
CaptureCollector.this.onRequestCompleted(this);
CaptureCollector.this.onRequestCompleted(CaptureHolder.this);
mCompleted = true;
}
}
@@ -103,7 +141,8 @@ public class CaptureCollector {
if (!mHasStarted) {
mHasStarted = true;
CaptureCollector.this.mDeviceState.setCaptureStart(mRequest, mTimestamp);
CaptureCollector.this.mDeviceState.setCaptureStart(mRequest, mTimestamp,
CameraDeviceState.NO_CAPTURE_ERROR);
}
tryComplete();
@@ -126,6 +165,20 @@ public class CaptureCollector {
tryComplete();
}
public void setJpegFailed() {
if (DEBUG) {
Log.d(TAG, "setJpegFailed - called for request " + mRequest.getRequestId());
}
if (!needsJpeg || isJpegCompleted()) {
return;
}
mFailedJpeg = true;
mReceivedFlags |= FLAG_RECEIVED_JPEG;
mReceivedFlags |= FLAG_RECEIVED_JPEG_TS;
tryComplete();
}
public void setPreviewTimestamp(long timestamp) {
if (DEBUG) {
Log.d(TAG, "setPreviewTimestamp - called for request " + mRequest.getRequestId());
@@ -148,7 +201,8 @@ public class CaptureCollector {
if (!needsJpeg) {
if (!mHasStarted) {
mHasStarted = true;
CaptureCollector.this.mDeviceState.setCaptureStart(mRequest, mTimestamp);
CaptureCollector.this.mDeviceState.setCaptureStart(mRequest, mTimestamp,
CameraDeviceState.NO_CAPTURE_ERROR);
}
}
@@ -171,8 +225,37 @@ public class CaptureCollector {
mReceivedFlags |= FLAG_RECEIVED_PREVIEW;
tryComplete();
}
public void setPreviewFailed() {
if (DEBUG) {
Log.d(TAG, "setPreviewFailed - called for request " + mRequest.getRequestId());
}
if (!needsPreview || isPreviewCompleted()) {
return;
}
mFailedPreview = true;
mReceivedFlags |= FLAG_RECEIVED_PREVIEW;
mReceivedFlags |= FLAG_RECEIVED_PREVIEW_TS;
tryComplete();
}
// Comparison and equals based on frame number.
@Override
public int compareTo(CaptureHolder captureHolder) {
return (mRequest.getFrameNumber() > captureHolder.mRequest.getFrameNumber()) ? 1 :
((mRequest.getFrameNumber() == captureHolder.mRequest.getFrameNumber()) ? 0 :
-1);
}
// Comparison and equals based on frame number.
@Override
public boolean equals(Object o) {
return o instanceof CaptureHolder && compareTo((CaptureHolder) o) == 0;
}
}
private final TreeSet<CaptureHolder> mActiveRequests;
private final ArrayDeque<CaptureHolder> mJpegCaptureQueue;
private final ArrayDeque<CaptureHolder> mJpegProduceQueue;
private final ArrayDeque<CaptureHolder> mPreviewCaptureQueue;
@@ -200,6 +283,7 @@ public class CaptureCollector {
mJpegProduceQueue = new ArrayDeque<>(MAX_JPEGS_IN_FLIGHT);
mPreviewCaptureQueue = new ArrayDeque<>(mMaxInFlight);
mPreviewProduceQueue = new ArrayDeque<>(mMaxInFlight);
mActiveRequests = new TreeSet<>();
mIsEmpty = mLock.newCondition();
mNotFull = mLock.newCondition();
mPreviewsEmpty = mLock.newCondition();
@@ -263,7 +347,7 @@ public class CaptureCollector {
mPreviewProduceQueue.add(h);
mInFlightPreviews++;
}
mActiveRequests.add(h);
mInFlight++;
return true;
@@ -440,7 +524,9 @@ public class CaptureCollector {
try {
CaptureHolder h = mPreviewCaptureQueue.poll();
if (h == null) {
Log.w(TAG, "previewCaptured called with no preview request on queue!");
if (DEBUG) {
Log.d(TAG, "previewCaptured called with no preview request on queue!");
}
return null;
}
h.setPreviewTimestamp(timestamp);
@@ -471,6 +557,81 @@ public class CaptureCollector {
}
}
/**
* Called to alert the {@link CaptureCollector} that the next pending preview capture has failed.
*/
public void failNextPreview() {
final ReentrantLock lock = this.mLock;
lock.lock();
try {
CaptureHolder h1 = mPreviewCaptureQueue.peek();
CaptureHolder h2 = mPreviewProduceQueue.peek();
// Find the request with the lowest frame number.
CaptureHolder h = (h1 == null) ? h2 :
((h2 == null) ? h1 :
((h1.compareTo(h2) <= 0) ? h1 :
h2));
if (h != null) {
mPreviewCaptureQueue.remove(h);
mPreviewProduceQueue.remove(h);
mActiveRequests.remove(h);
h.setPreviewFailed();
}
} finally {
lock.unlock();
}
}
/**
* Called to alert the {@link CaptureCollector} that the next pending jpeg capture has failed.
*/
public void failNextJpeg() {
final ReentrantLock lock = this.mLock;
lock.lock();
try {
CaptureHolder h1 = mJpegCaptureQueue.peek();
CaptureHolder h2 = mJpegProduceQueue.peek();
// Find the request with the lowest frame number.
CaptureHolder h = (h1 == null) ? h2 :
((h2 == null) ? h1 :
((h1.compareTo(h2) <= 0) ? h1 :
h2));
if (h != null) {
mJpegCaptureQueue.remove(h);
mJpegProduceQueue.remove(h);
mActiveRequests.remove(h);
h.setJpegFailed();
}
} finally {
lock.unlock();
}
}
/**
* Called to alert the {@link CaptureCollector} all pending captures have failed.
*/
public void failAll() {
final ReentrantLock lock = this.mLock;
lock.lock();
try {
CaptureHolder h;
while ((h = mActiveRequests.pollFirst()) != null) {
h.setPreviewFailed();
h.setJpegFailed();
}
mPreviewCaptureQueue.clear();
mPreviewProduceQueue.clear();
mJpegCaptureQueue.clear();
mJpegProduceQueue.clear();
} finally {
lock.unlock();
}
}
private void onPreviewCompleted() {
mInFlightPreviews--;
if (mInFlightPreviews < 0) {
@@ -496,6 +657,7 @@ public class CaptureCollector {
}
mCompletedRequests.add(capture);
mActiveRequests.remove(capture);
mNotFull.signalAll();
if (mInFlight == 0) {

View File

@@ -23,6 +23,9 @@ import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.impl.CaptureResultExtras;
import android.hardware.camera2.ICameraDeviceCallbacks;
import android.hardware.camera2.params.StreamConfiguration;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.hardware.camera2.utils.ArrayUtils;
import android.hardware.camera2.utils.LongParcelable;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.utils.CameraRuntimeException;
@@ -35,6 +38,7 @@ import android.util.Size;
import android.view.Surface;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
@@ -56,11 +60,13 @@ public class LegacyCameraDevice implements AutoCloseable {
public static final String DEBUG_PROP = "HAL1ShimLogging";
private final String TAG;
private static final boolean DEBUG = false;
private static final boolean DEBUG = Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.DEBUG);
private final int mCameraId;
private final CameraCharacteristics mStaticCharacteristics;
private final ICameraDeviceCallbacks mDeviceCallbacks;
private final CameraDeviceState mDeviceState = new CameraDeviceState();
private List<Surface> mConfiguredSurfaces;
private boolean mClosed = false;
private final ConditionVariable mIdle = new ConditionVariable(/*open*/true);
@@ -87,14 +93,15 @@ public class LegacyCameraDevice implements AutoCloseable {
private final CameraDeviceState.CameraDeviceStateListener mStateListener =
new CameraDeviceState.CameraDeviceStateListener() {
@Override
public void onError(final int errorCode, RequestHolder holder) {
public void onError(final int errorCode, final RequestHolder holder) {
mIdle.open();
final CaptureResultExtras extras = getExtrasFromRequest(holder);
mResultHandler.post(new Runnable() {
@Override
public void run() {
if (DEBUG) {
Log.d(TAG, "doing onError callback.");
Log.d(TAG, "doing onError callback for request " + holder.getRequestId() +
", with error code " + errorCode);
}
try {
mDeviceCallbacks.onDeviceError(errorCode, extras);
@@ -135,14 +142,15 @@ public class LegacyCameraDevice implements AutoCloseable {
}
@Override
public void onCaptureStarted(RequestHolder holder, final long timestamp) {
public void onCaptureStarted(final RequestHolder holder, final long timestamp) {
final CaptureResultExtras extras = getExtrasFromRequest(holder);
mResultHandler.post(new Runnable() {
@Override
public void run() {
if (DEBUG) {
Log.d(TAG, "doing onCaptureStarted callback.");
Log.d(TAG, "doing onCaptureStarted callback for request " +
holder.getRequestId());
}
try {
mDeviceCallbacks.onCaptureStarted(extras, timestamp);
@@ -155,14 +163,15 @@ public class LegacyCameraDevice implements AutoCloseable {
}
@Override
public void onCaptureResult(final CameraMetadataNative result, RequestHolder holder) {
public void onCaptureResult(final CameraMetadataNative result, final RequestHolder holder) {
final CaptureResultExtras extras = getExtrasFromRequest(holder);
mResultHandler.post(new Runnable() {
@Override
public void run() {
if (DEBUG) {
Log.d(TAG, "doing onCaptureResult callback.");
Log.d(TAG, "doing onCaptureResult callback for request " +
holder.getRequestId());
}
try {
mDeviceCallbacks.onResultReceived(result, extras);
@@ -216,6 +225,7 @@ public class LegacyCameraDevice implements AutoCloseable {
mCallbackHandlerThread.start();
mCallbackHandler = new Handler(mCallbackHandlerThread.getLooper());
mDeviceState.setCameraDeviceCallbacks(mCallbackHandler, mStateListener);
mStaticCharacteristics = characteristics;
mRequestThreadManager =
new RequestThreadManager(cameraId, camera, characteristics, mDeviceState);
mRequestThreadManager.start();
@@ -239,6 +249,42 @@ public class LegacyCameraDevice implements AutoCloseable {
Log.e(TAG, "configureOutputs - null outputs are not allowed");
return BAD_VALUE;
}
StreamConfigurationMap streamConfigurations = mStaticCharacteristics.
get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
// Validate surface size and format.
try {
Size s = getSurfaceSize(output);
int surfaceType = detectSurfaceType(output);
Size[] sizes = streamConfigurations.getOutputSizes(surfaceType);
if (sizes == null) {
// WAR: Override default format to IMPLEMENTATION_DEFINED for b/9487482
if ((surfaceType >= LegacyMetadataMapper.HAL_PIXEL_FORMAT_RGBA_8888 &&
surfaceType <= LegacyMetadataMapper.HAL_PIXEL_FORMAT_BGRA_8888)) {
// YUV_420_888 is always present in LEGACY for all IMPLEMENTATION_DEFINED
// output sizes, and is publicly visible in the API (i.e.
// {@code #getOutputSizes} works here).
sizes = streamConfigurations.getOutputSizes(ImageFormat.YUV_420_888);
} else if (surfaceType == LegacyMetadataMapper.HAL_PIXEL_FORMAT_BLOB) {
sizes = streamConfigurations.getOutputSizes(ImageFormat.JPEG);
}
}
if (!ArrayUtils.contains(sizes, s)) {
String reason = (sizes == null) ? "format is invalid." :
("size not in valid set: " + Arrays.toString(sizes));
Log.e(TAG, String.format("Surface with size (w=%d, h=%d) and format 0x%x is"
+ " not valid, %s", s.getWidth(), s.getHeight(), surfaceType,
reason));
return BAD_VALUE;
}
} catch (BufferQueueAbandonedException e) {
Log.e(TAG, "Surface bufferqueue is abandoned, cannot configure as output: ", e);
return BAD_VALUE;
}
}
}
@@ -248,7 +294,6 @@ public class LegacyCameraDevice implements AutoCloseable {
error = mDeviceState.setIdle();
}
// TODO: May also want to check the surfaces more deeply (e.g. state, formats, sizes..)
if (error == NO_ERROR) {
mConfiguredSurfaces = outputs != null ? new ArrayList<>(outputs) : null;
}
@@ -342,6 +387,24 @@ public class LegacyCameraDevice implements AutoCloseable {
mIdle.block();
}
/**
* Flush any pending requests.
*
* @return the last frame number.
*/
public long flush() {
long lastFrame = mRequestThreadManager.flush();
waitUntilIdle();
return lastFrame;
}
/**
* Return {@code true} if the device has been closed.
*/
public boolean isClosed() {
return mClosed;
}
@Override
public void close() {
mRequestThreadManager.quit();
@@ -362,7 +425,7 @@ public class LegacyCameraDevice implements AutoCloseable {
mResultThread.getName(), mResultThread.getId()));
}
// TODO: throw IllegalStateException in every method after close has been called
mClosed = true;
}
@Override

View File

@@ -17,6 +17,7 @@
package android.hardware.camera2.legacy;
import android.graphics.ImageFormat;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.hardware.Camera;
import android.hardware.Camera.CameraInfo;
@@ -61,8 +62,10 @@ public class LegacyMetadataMapper {
private static final long NS_PER_MS = 1000000;
// from graphics.h
private static final int HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED = 0x22;
private static final int HAL_PIXEL_FORMAT_BLOB = 0x21;
public static final int HAL_PIXEL_FORMAT_RGBA_8888 = PixelFormat.RGBA_8888;
public static final int HAL_PIXEL_FORMAT_BGRA_8888 = 0x5;
public static final int HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED = 0x22;
public static final int HAL_PIXEL_FORMAT_BLOB = 0x21;
// for metadata
private static final float LENS_INFO_MINIMUM_FOCUS_DISTANCE_FIXED_FOCUS = 0.0f;
@@ -1170,7 +1173,7 @@ public class LegacyMetadataMapper {
Rect activeArray = c.get(SENSOR_INFO_ACTIVE_ARRAY_SIZE);
MeteringRectangle[] activeRegions = new MeteringRectangle[] {
new MeteringRectangle(/*x*/0, /*y*/0, /*width*/activeArray.width() - 1,
/*height*/activeArray.height() - 1,/*weight*/1)};
/*height*/activeArray.height() - 1,/*weight*/0)};
m.set(CaptureRequest.CONTROL_AE_REGIONS, activeRegions);
m.set(CaptureRequest.CONTROL_AWB_REGIONS, activeRegions);
m.set(CaptureRequest.CONTROL_AF_REGIONS, activeRegions);
@@ -1274,6 +1277,11 @@ public class LegacyMetadataMapper {
// flash.mode
m.set(CaptureRequest.FLASH_MODE, FLASH_MODE_OFF);
/*
* noiseReduction.*
*/
m.set(CaptureRequest.NOISE_REDUCTION_MODE, NOISE_REDUCTION_MODE_FAST);
/*
* lens.*
*/

View File

@@ -26,7 +26,9 @@ import java.util.Collection;
import static com.android.internal.util.Preconditions.*;
/**
* Immutable container for a single capture request and associated information.
* Semi-immutable container for a single capture request and associated information,
* the only mutable characteristic of this container is whether or not is has been
* marked as "failed" using {@code #failRequest}.
*/
public class RequestHolder {
private static final String TAG = "RequestHolder";
@@ -36,8 +38,9 @@ public class RequestHolder {
private final int mRequestId;
private final int mSubsequeceId;
private final long mFrameNumber;
private final boolean mHasJpegTargets;
private final boolean mHasPreviewTargets;
private final int mNumJpegTargets;
private final int mNumPreviewTargets;
private volatile boolean mFailed = false;
/**
* Returns true if the given surface requires jpeg buffers.
@@ -71,36 +74,37 @@ public class RequestHolder {
}
/**
* Returns true if any of the surfaces targeted by the contained request require jpeg buffers.
* Returns the number of surfaces targeted by the request that require jpeg buffers.
*/
private static boolean requestContainsJpegTargets(CaptureRequest request) {
private static int numJpegTargets(CaptureRequest request) {
int count = 0;
for (Surface s : request.getTargets()) {
try {
if (jpegType(s)) {
return true;
++count;
}
} catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
Log.w(TAG, "Surface abandoned, skipping...", e);
}
}
return false;
return count;
}
/**
* Returns true if any of the surfaces targeted by the contained request require a
* non-jpeg buffer type.
* Returns the number of surfaces targeted by the request that require non-jpeg buffers.
*/
private static boolean requestContainsPreviewTargets(CaptureRequest request) {
private static int numPreviewTargets(CaptureRequest request) {
int count = 0;
for (Surface s : request.getTargets()) {
try {
if (previewType(s)) {
return true;
++count;
}
} catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
Log.w(TAG, "Surface abandoned, skipping...", e);
}
}
return false;
return count;
}
/**
@@ -115,8 +119,8 @@ public class RequestHolder {
private final int mSubsequenceId;
private final CaptureRequest mRequest;
private final boolean mRepeating;
private final boolean mHasJpegTargets;
private final boolean mHasPreviewTargets;
private final int mNumJpegTargets;
private final int mNumPreviewTargets;
/**
* Construct a new {@link Builder} to generate {@link RequestHolder} objects.
@@ -134,8 +138,8 @@ public class RequestHolder {
mSubsequenceId = subsequenceId;
mRequest = request;
mRepeating = repeating;
mHasJpegTargets = requestContainsJpegTargets(mRequest);
mHasPreviewTargets = requestContainsPreviewTargets(mRequest);
mNumJpegTargets = numJpegTargets(mRequest);
mNumPreviewTargets = numPreviewTargets(mRequest);
}
/**
@@ -147,20 +151,20 @@ public class RequestHolder {
*/
public RequestHolder build(long frameNumber) {
return new RequestHolder(mRequestId, mSubsequenceId, mRequest, mRepeating, frameNumber,
mHasJpegTargets, mHasPreviewTargets);
mNumJpegTargets, mNumPreviewTargets);
}
}
private RequestHolder(int requestId, int subsequenceId, CaptureRequest request,
boolean repeating, long frameNumber, boolean hasJpegTargets,
boolean hasPreviewTargets) {
boolean repeating, long frameNumber, int numJpegTargets,
int numPreviewTargets) {
mRepeating = repeating;
mRequest = request;
mRequestId = requestId;
mSubsequeceId = subsequenceId;
mFrameNumber = frameNumber;
mHasJpegTargets = hasJpegTargets;
mHasPreviewTargets = hasPreviewTargets;
mNumJpegTargets = numJpegTargets;
mNumPreviewTargets = numPreviewTargets;
}
/**
@@ -209,7 +213,7 @@ public class RequestHolder {
* Returns true if any of the surfaces targeted by the contained request require jpeg buffers.
*/
public boolean hasJpegTargets() {
return mHasJpegTargets;
return mNumJpegTargets > 0;
}
/**
@@ -217,7 +221,36 @@ public class RequestHolder {
* non-jpeg buffer type.
*/
public boolean hasPreviewTargets(){
return mHasPreviewTargets;
return mNumPreviewTargets > 0;
}
/**
* Return the number of jpeg-type surfaces targeted by this request.
*/
public int numJpegTargets() {
return mNumJpegTargets;
}
/**
* Return the number of non-jpeg-type surfaces targeted by this request.
*/
public int numPreviewTargets() {
return mNumPreviewTargets;
}
/**
* Mark this request as failed.
*/
public void failRequest() {
Log.w(TAG, "Capture failed for request: " + getRequestId());
mFailed = true;
}
/**
* Return {@code true} if this request failed.
*/
public boolean requestFailed() {
return mFailed;
}
}

View File

@@ -80,12 +80,27 @@ public class RequestQueue {
ret = (mCurrentRepeatingFrameNumber == INVALID_FRAME) ? INVALID_FRAME :
mCurrentRepeatingFrameNumber - 1;
mCurrentRepeatingFrameNumber = INVALID_FRAME;
Log.i(TAG, "Repeating capture request cancelled.");
} else {
Log.e(TAG, "cancel failed: no repeating request exists for request id: " + requestId);
}
return ret;
}
/**
* Cancel a repeating request.
*
* @return the last frame to be returned from the HAL for the given repeating request, or
* {@code INVALID_FRAME} if none exists.
*/
public synchronized long stopRepeating() {
if (mRepeatingRequest == null) {
Log.e(TAG, "cancel failed: no repeating request exists.");
return INVALID_FRAME;
}
return stopRepeating(mRepeatingRequest.getRequestId());
}
/**
* Add a the given burst to the queue.
*
@@ -105,6 +120,7 @@ public class RequestQueue {
BurstHolder burst = new BurstHolder(requestId, repeating, requests);
long ret = INVALID_FRAME;
if (burst.isRepeating()) {
Log.i(TAG, "Repeating capture request set.");
if (mRepeatingRequest != null) {
ret = (mCurrentRepeatingFrameNumber == INVALID_FRAME) ? INVALID_FRAME :
mCurrentRepeatingFrameNumber - 1;

View File

@@ -20,6 +20,7 @@ import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.impl.CameraDeviceImpl;
import android.hardware.camera2.utils.LongParcelable;
import android.hardware.camera2.utils.SizeAreaComparator;
import android.hardware.camera2.impl.CameraMetadataNative;
@@ -33,7 +34,6 @@ import android.util.Pair;
import android.util.Size;
import android.view.Surface;
import java.io.IOError;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
@@ -284,10 +284,9 @@ public class RequestThreadManager {
startPreview();
}
private void configureOutputs(Collection<Surface> outputs) throws IOException {
private void configureOutputs(Collection<Surface> outputs) {
if (DEBUG) {
String outputsStr = outputs == null ? "null" : (outputs.size() + " surfaces");
Log.d(TAG, "configureOutputs with " + outputsStr);
}
@@ -297,7 +296,11 @@ public class RequestThreadManager {
* using a different one; this also reduces the likelihood of getting into a deadlock
* when disconnecting from the old previous texture at a later time.
*/
mCamera.setPreviewTexture(/*surfaceTexture*/null);
try {
mCamera.setPreviewTexture(/*surfaceTexture*/null);
} catch (IOException e) {
Log.w(TAG, "Failed to clear prior SurfaceTexture, may cause GL deadlock: ", e);
}
if (mGLThreadManager != null) {
mGLThreadManager.waitUntilStarted();
@@ -568,26 +571,23 @@ public class RequestThreadManager {
case MSG_CONFIGURE_OUTPUTS:
ConfigureHolder config = (ConfigureHolder) msg.obj;
int sizes = config.surfaces != null ? config.surfaces.size() : 0;
Log.i(TAG, "Configure outputs: " + sizes +
" surfaces configured.");
Log.i(TAG, "Configure outputs: " + sizes + " surfaces configured.");
try {
boolean success = mCaptureCollector.waitForEmpty(JPEG_FRAME_TIMEOUT,
TimeUnit.MILLISECONDS);
if (!success) {
Log.e(TAG, "Timed out while queueing configure request.");
mCaptureCollector.failAll();
}
} catch (InterruptedException e) {
// TODO: report error to CameraDevice
Log.e(TAG, "Interrupted while waiting for requests to complete.");
mDeviceState.setError(
CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
break;
}
try {
configureOutputs(config.surfaces);
} catch (IOException e) {
// TODO: report error to CameraDevice
throw new IOError(e);
}
configureOutputs(config.surfaces);
config.condition.open();
if (DEBUG) {
long totalTime = SystemClock.elapsedRealtimeNanos() - startTime;
@@ -599,16 +599,23 @@ public class RequestThreadManager {
// Get the next burst from the request queue.
Pair<BurstHolder, Long> nextBurst = mRequestQueue.getNext();
if (nextBurst == null) {
// If there are no further requests queued, wait for any currently executing
// requests to complete, then switch to idle state.
try {
boolean success = mCaptureCollector.waitForEmpty(JPEG_FRAME_TIMEOUT,
TimeUnit.MILLISECONDS);
if (!success) {
Log.e(TAG, "Timed out while waiting for empty.");
Log.e(TAG,
"Timed out while waiting for prior requests to complete.");
mCaptureCollector.failAll();
}
} catch (InterruptedException e) {
// TODO: report error to CameraDevice
Log.e(TAG, "Interrupted while waiting for requests to complete.");
Log.e(TAG, "Interrupted while waiting for requests to complete: ", e);
mDeviceState.setError(
CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
break;
}
mDeviceState.setIdle();
break;
@@ -625,27 +632,39 @@ public class RequestThreadManager {
boolean paramsChanged = false;
// Lazily process the rest of the request
// Only update parameters if the request has changed
if (mLastRequest == null || mLastRequest.captureRequest != request) {
// The intermediate buffer is sometimes null, but we always need
// the camera1's configured preview size
// the Camera1 API configured preview size
Size previewSize = ParameterUtils.convertSize(mParams.getPreviewSize());
LegacyRequest legacyRequest = new LegacyRequest(
mCharacteristics, request, previewSize,
mParams); // params are copied
LegacyRequest legacyRequest = new LegacyRequest(mCharacteristics,
request, previewSize, mParams); // params are copied
mLastRequest = legacyRequest;
// Parameters are mutated as a side-effect
LegacyMetadataMapper.convertRequestMetadata(/*inout*/legacyRequest);
// If the parameters have changed, set them in the Camera1 API.
if (!mParams.same(legacyRequest.parameters)) {
mParams = legacyRequest.parameters;
mCamera.setParameters(mParams);
try {
mCamera.setParameters(legacyRequest.parameters);
} catch (RuntimeException e) {
// If setting the parameters failed, report a request error to
// the camera client, and skip any further work for this request
Log.e(TAG, "Exception while setting camera parameters: ", e);
holder.failRequest();
mDeviceState.setCaptureStart(holder, /*timestamp*/0,
CameraDeviceImpl.CameraDeviceCallbacks.
ERROR_CAMERA_REQUEST);
continue;
}
paramsChanged = true;
mParams = legacyRequest.parameters;
}
mLastRequest = legacyRequest;
}
try {
@@ -653,19 +672,27 @@ public class RequestThreadManager {
mLastRequest, JPEG_FRAME_TIMEOUT, TimeUnit.MILLISECONDS);
if (!success) {
// Report a request error if we timed out while queuing this.
Log.e(TAG, "Timed out while queueing capture request.");
holder.failRequest();
mDeviceState.setCaptureStart(holder, /*timestamp*/0,
CameraDeviceImpl.CameraDeviceCallbacks.
ERROR_CAMERA_REQUEST);
continue;
}
// Starting the preview needs to happen before enabling
// face detection or auto focus
if (holder.hasPreviewTargets()) {
doPreviewCapture(holder);
}
if (holder.hasJpegTargets()) {
success = mCaptureCollector.
waitForPreviewsEmpty(PREVIEW_FRAME_TIMEOUT *
MAX_IN_FLIGHT_REQUESTS, TimeUnit.MILLISECONDS);
if (!success) {
Log.e(TAG, "Timed out waiting for prior requests to complete.");
while(!mCaptureCollector.waitForPreviewsEmpty(PREVIEW_FRAME_TIMEOUT,
TimeUnit.MILLISECONDS)) {
// Fail preview requests until the queue is empty.
Log.e(TAG, "Timed out while waiting for preview requests to " +
"complete.");
mCaptureCollector.failNextPreview();
}
mReceivedJpeg.close();
doJpegCapturePrepare(holder);
@@ -686,17 +713,21 @@ public class RequestThreadManager {
if (holder.hasJpegTargets()) {
doJpegCapture(holder);
if (!mReceivedJpeg.block(JPEG_FRAME_TIMEOUT)) {
// TODO: report error to CameraDevice
Log.e(TAG, "Hit timeout for jpeg callback!");
mCaptureCollector.failNextJpeg();
}
}
} catch (IOException e) {
// TODO: report error to CameraDevice
throw new IOError(e);
Log.e(TAG, "Received device exception: ", e);
mDeviceState.setError(
CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
break;
} catch (InterruptedException e) {
// TODO: report error to CameraDevice
Log.e(TAG, "Interrupted during capture.", e);
Log.e(TAG, "Interrupted during capture: ", e);
mDeviceState.setError(
CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
break;
}
if (paramsChanged) {
@@ -717,10 +748,13 @@ public class RequestThreadManager {
if (!success) {
Log.e(TAG, "Timed out while waiting for request to complete.");
mCaptureCollector.failAll();
}
} catch (InterruptedException e) {
// TODO: report error to CameraDevice
Log.e(TAG, "Interrupted during request completition.", e);
Log.e(TAG, "Interrupted waiting for request completion: ", e);
mDeviceState.setError(
CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
break;
}
CameraMetadataNative result = mMapper.cachedConvertResultMetadata(
@@ -736,7 +770,10 @@ public class RequestThreadManager {
// Update face-related results
mFaceDetectMapper.mapResultFaces(result, mLastRequest);
mDeviceState.setCaptureResult(holder, result);
if (!holder.requestFailed()) {
mDeviceState.setCaptureResult(holder, result,
CameraDeviceState.NO_CAPTURE_ERROR);
}
}
if (DEBUG) {
long totalTime = SystemClock.elapsedRealtimeNanos() - startTime;
@@ -751,10 +788,12 @@ public class RequestThreadManager {
TimeUnit.MILLISECONDS);
if (!success) {
Log.e(TAG, "Timed out while queueing cleanup request.");
mCaptureCollector.failAll();
}
} catch (InterruptedException e) {
// TODO: report error to CameraDevice
Log.e(TAG, "Interrupted while waiting for requests to complete.");
Log.e(TAG, "Interrupted while waiting for requests to complete: ", e);
mDeviceState.setError(
CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
}
if (mGLThreadManager != null) {
mGLThreadManager.quit();
@@ -802,11 +841,15 @@ public class RequestThreadManager {
}
/**
* Flush the pending requests.
* Flush any pending requests.
*
* @return the last frame number.
*/
public void flush() {
// TODO: Implement flush.
Log.e(TAG, "flush not yet implemented.");
public long flush() {
Log.i(TAG, "Flushing all pending requests.");
long lastFrame = mRequestQueue.stopRepeating();
mCaptureCollector.failAll();
return lastFrame;
}
/**
@@ -856,7 +899,6 @@ public class RequestThreadManager {
return mRequestQueue.stopRepeating(requestId);
}
/**
* Configure with the current list of output Surfaces.
*

View File

@@ -652,7 +652,9 @@ public class SurfaceTextureRenderer {
// No preview request queued, drop frame.
if (captureHolder == null) {
Log.w(TAG, "Dropping preview frame.");
if (DEBUG) {
Log.d(TAG, "Dropping preview frame.");
}
if (doTiming) {
endGlTiming();
}

View File

@@ -32,7 +32,7 @@ public class ArrayUtils {
/** Return the index of {@code needle} in the {@code array}, or else {@code -1} */
public static <T> int getArrayIndex(T[] array, T needle) {
if (needle == null) {
if (array == null) {
return -1;
}
@@ -167,6 +167,17 @@ public class ArrayUtils {
return getArrayIndex(array, elem) != -1;
}
/**
* Returns true if the given {@code array} contains the given element.
*
* @param array {@code array} to check for {@code elem}
* @param elem {@code elem} to test for
* @return {@code true} if the given element is contained
*/
public static <T> boolean contains(T[] array, T elem) {
return getArrayIndex(array, elem) != -1;
}
private ArrayUtils() {
throw new AssertionError();
}