From 6bbf9dc5ae7ebc85991dcfe3e18e837b12d3f333 Mon Sep 17 00:00:00 2001 From: Igor Murashkin Date: Thu, 5 Sep 2013 12:22:00 -0700 Subject: camera2: Add capture sequences and capture failures - CaptureResult#getRequest is used to tie a result to a request (for convenience) - Add new CaptureFailure class to describe capture failure - Results/frame numbers also return frame numbers, sequence ids - Captures now all return the sequence id - A sequence id onComplete is available in the CaptureListener Bug: 10360518 Change-Id: I9ebaa45698c718a1185b5ae920b7975925fe2f60 --- api/current.txt | 24 +++- .../android/hardware/camera2/CameraDevice.java | 55 ++++++-- .../android/hardware/camera2/CaptureFailure.java | 144 +++++++++++++++++++++ .../android/hardware/camera2/CaptureRequest.java | 47 ++++++- .../android/hardware/camera2/CaptureResult.java | 74 ++++++++++- .../hardware/camera2/impl/CameraDevice.java | 27 ++-- 6 files changed, 338 insertions(+), 33 deletions(-) create mode 100644 core/java/android/hardware/camera2/CaptureFailure.java diff --git a/api/current.txt b/api/current.txt index 779f0f3..5fc7edd 100644 --- a/api/current.txt +++ b/api/current.txt @@ -10846,16 +10846,16 @@ package android.hardware.camera2 { } public abstract interface CameraDevice implements java.lang.AutoCloseable { - method public abstract void capture(android.hardware.camera2.CaptureRequest, android.hardware.camera2.CameraDevice.CaptureListener, android.os.Handler) throws android.hardware.camera2.CameraAccessException; - method public abstract void captureBurst(java.util.List, android.hardware.camera2.CameraDevice.CaptureListener, android.os.Handler) throws android.hardware.camera2.CameraAccessException; + method public abstract int capture(android.hardware.camera2.CaptureRequest, android.hardware.camera2.CameraDevice.CaptureListener, android.os.Handler) throws android.hardware.camera2.CameraAccessException; + method public abstract int captureBurst(java.util.List, android.hardware.camera2.CameraDevice.CaptureListener, android.os.Handler) throws android.hardware.camera2.CameraAccessException; method public abstract void close(); method public abstract void configureOutputs(java.util.List) throws android.hardware.camera2.CameraAccessException; method public abstract android.hardware.camera2.CaptureRequest.Builder createCaptureRequest(int) throws android.hardware.camera2.CameraAccessException; method public abstract void flush() throws android.hardware.camera2.CameraAccessException; method public abstract java.lang.String getId(); method public abstract android.hardware.camera2.CameraProperties getProperties() throws android.hardware.camera2.CameraAccessException; - method public abstract void setRepeatingBurst(java.util.List, android.hardware.camera2.CameraDevice.CaptureListener, android.os.Handler) throws android.hardware.camera2.CameraAccessException; - method public abstract void setRepeatingRequest(android.hardware.camera2.CaptureRequest, android.hardware.camera2.CameraDevice.CaptureListener, android.os.Handler) throws android.hardware.camera2.CameraAccessException; + method public abstract int setRepeatingBurst(java.util.List, android.hardware.camera2.CameraDevice.CaptureListener, android.os.Handler) throws android.hardware.camera2.CameraAccessException; + method public abstract int setRepeatingRequest(android.hardware.camera2.CaptureRequest, android.hardware.camera2.CameraDevice.CaptureListener, android.os.Handler) throws android.hardware.camera2.CameraAccessException; method public abstract void stopRepeating() throws android.hardware.camera2.CameraAccessException; method public abstract void waitUntilIdle() throws android.hardware.camera2.CameraAccessException; field public static final int TEMPLATE_PREVIEW = 1; // 0x1 @@ -10867,7 +10867,8 @@ package android.hardware.camera2 { public static abstract class CameraDevice.CaptureListener { ctor public CameraDevice.CaptureListener(); method public void onCaptureCompleted(android.hardware.camera2.CameraDevice, android.hardware.camera2.CaptureRequest, android.hardware.camera2.CaptureResult); - method public void onCaptureFailed(android.hardware.camera2.CameraDevice, android.hardware.camera2.CaptureRequest); + method public void onCaptureFailed(android.hardware.camera2.CameraDevice, android.hardware.camera2.CaptureRequest, android.hardware.camera2.CaptureFailure); + method public void onCaptureSequenceCompleted(android.hardware.camera2.CameraDevice, int, int); method public void onCaptureStarted(android.hardware.camera2.CameraDevice, android.hardware.camera2.CaptureRequest, long); } @@ -11073,6 +11074,16 @@ package android.hardware.camera2 { field public static final android.hardware.camera2.CameraMetadata.Key TONEMAP_MAX_CURVE_POINTS; } + public class CaptureFailure { + method public int getFrameNumber(); + method public int getReason(); + method public android.hardware.camera2.CaptureRequest getRequest(); + method public int getSequenceId(); + method public boolean wasImageCaptured(); + field public static final int REASON_ERROR = 0; // 0x0 + field public static final int REASON_FLUSHED = 1; // 0x1 + } + public final class CaptureRequest extends android.hardware.camera2.CameraMetadata implements android.os.Parcelable { method public int describeContents(); method public T get(android.hardware.camera2.CameraMetadata.Key); @@ -11139,6 +11150,9 @@ package android.hardware.camera2 { public final class CaptureResult extends android.hardware.camera2.CameraMetadata { method public T get(android.hardware.camera2.CameraMetadata.Key); + method public int getFrameNumber(); + method public android.hardware.camera2.CaptureRequest getRequest(); + method public int getSequenceId(); field public static final android.hardware.camera2.CameraMetadata.Key BLACK_LEVEL_LOCK; field public static final android.hardware.camera2.CameraMetadata.Key COLOR_CORRECTION_GAINS; field public static final android.hardware.camera2.CameraMetadata.Key COLOR_CORRECTION_TRANSFORM; diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index f047b0d..f5fb7ff 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -305,6 +305,9 @@ public interface CameraDevice extends AutoCloseable { * {@code null} to use the current thread's {@link android.os.Looper * looper}. * + * @return int A unique capture sequence ID used by + * {@link CaptureListener#onCaptureSequenceCompleted}. + * * @throws CameraAccessException if the camera device is no longer connected or has * encountered a fatal error * @throws IllegalStateException if the camera is currently busy or unconfigured, @@ -317,7 +320,7 @@ public interface CameraDevice extends AutoCloseable { * @see #setRepeatingRequest * @see #setRepeatingBurst */ - public void capture(CaptureRequest request, CaptureListener listener, Handler handler) + public int capture(CaptureRequest request, CaptureListener listener, Handler handler) throws CameraAccessException; /** @@ -346,6 +349,9 @@ public interface CameraDevice extends AutoCloseable { * {@code null} to use the current thread's {@link android.os.Looper * looper}. * + * @return int A unique capture sequence ID used by + * {@link CaptureListener#onCaptureSequenceCompleted}. + * * @throws CameraAccessException if the camera device is no longer connected or has * encountered a fatal error * @throws IllegalStateException if the camera is currently busy or unconfigured, @@ -358,7 +364,7 @@ public interface CameraDevice extends AutoCloseable { * @see #setRepeatingRequest * @see #setRepeatingBurst */ - public void captureBurst(List requests, CaptureListener listener, + public int captureBurst(List requests, CaptureListener listener, Handler handler) throws CameraAccessException; /** @@ -399,6 +405,9 @@ public interface CameraDevice extends AutoCloseable { * {@code null} to use the current thread's {@link android.os.Looper * looper}. * + * @return int A unique capture sequence ID used by + * {@link CaptureListener#onCaptureSequenceCompleted}. + * * @throws CameraAccessException if the camera device is no longer connected or has * encountered a fatal error * @throws IllegalStateException if the camera is currently busy or unconfigured, @@ -413,7 +422,7 @@ public interface CameraDevice extends AutoCloseable { * @see #stopRepeating * @see #flush */ - public void setRepeatingRequest(CaptureRequest request, CaptureListener listener, + public int setRepeatingRequest(CaptureRequest request, CaptureListener listener, Handler handler) throws CameraAccessException; /** @@ -454,6 +463,9 @@ public interface CameraDevice extends AutoCloseable { * {@code null} to use the current thread's {@link android.os.Looper * looper}. * + * @return int A unique capture sequence ID used by + * {@link CaptureListener#onCaptureSequenceCompleted}. + * * @throws CameraAccessException if the camera device is no longer connected or has * encountered a fatal error * @throws IllegalStateException if the camera is currently busy or unconfigured, @@ -468,7 +480,7 @@ public interface CameraDevice extends AutoCloseable { * @see #stopRepeating * @see #flush */ - public void setRepeatingBurst(List requests, CaptureListener listener, + public int setRepeatingBurst(List requests, CaptureListener listener, Handler handler) throws CameraAccessException; /** @@ -601,7 +613,6 @@ public interface CameraDevice extends AutoCloseable { * @see #captureBurst * @see #setRepeatingRequest * @see #setRepeatingBurst - * */ public static abstract class CaptureListener { @@ -672,8 +683,13 @@ public interface CameraDevice extends AutoCloseable { * *

The default implementation of this method does nothing.

* - * @param camera The CameraDevice sending the callback. - * @param request The request that was given to the CameraDevice + * @param camera + * The CameraDevice sending the callback. + * @param request + * The request that was given to the CameraDevice + * @param failure + * The output failure from the capture, including the failure reason + * and the frame number. * * @see #capture * @see #captureBurst @@ -681,7 +697,30 @@ public interface CameraDevice extends AutoCloseable { * @see #setRepeatingBurst */ public void onCaptureFailed(CameraDevice camera, - CaptureRequest request) { + CaptureRequest request, CaptureFailure failure) { + // default empty implementation + } + + /** + * This method is called independently of the others in CaptureListener, + * when a capture sequence finishes and all {@link CaptureResult} + * or {@link CaptureFailure} for it have been returned via this listener. + * + * @param camera + * The CameraDevice sending the callback. + * @param sequenceId + * A sequence ID returned by the {@link #capture} family of functions. + * @param frameNumber + * The last frame number (returned by {@link CaptureResult#getFrameNumber} + * or {@link CaptureFailure#getFrameNumber}) in the capture sequence. + * + * @see CaptureResult#getFrameNumber() + * @see CaptureFailure#getFrameNumber() + * @see CaptureResult#getSequenceId() + * @see CaptureFailure#getSequenceId() + */ + public void onCaptureSequenceCompleted(CameraDevice camera, + int sequenceId, int frameNumber) { // default empty implementation } } diff --git a/core/java/android/hardware/camera2/CaptureFailure.java b/core/java/android/hardware/camera2/CaptureFailure.java new file mode 100644 index 0000000..3b408cf --- /dev/null +++ b/core/java/android/hardware/camera2/CaptureFailure.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2013 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; + +import android.hardware.camera2.CameraDevice.CaptureListener; + +/** + * A report of failed capture for a single image capture from the image sensor. + * + *

CaptureFailures are produced by a {@link CameraDevice} if processing a + * {@link CaptureRequest} fails, either partially or fully. Use {@link #getReason} + * to determine the specific nature of the failed capture.

+ * + *

Receiving a CaptureFailure means that the metadata associated with that frame number + * has been dropped -- no {@link CaptureResult} with the same frame number will be + * produced.

+ */ +public class CaptureFailure { + /** + * The {@link CaptureResult} has been dropped this frame only due to an error + * in the framework. + * + * @see #getReason() + */ + public static final int REASON_ERROR = 0; + + /** + * The capture has failed due to a {@link CameraDevice#flush} call from the application. + * + * @see #getReason() + */ + public static final int REASON_FLUSHED = 1; + + private final CaptureRequest mRequest; + private final int mReason; + private final boolean mDropped; + private final int mSequenceId; + private final int mFrameNumber; + + /** + * @hide + */ + public CaptureFailure(CaptureRequest request, int reason, boolean dropped, int sequenceId, + int frameNumber) { + mRequest = request; + mReason = reason; + mDropped = dropped; + mSequenceId = sequenceId; + mFrameNumber = frameNumber; + } + + /** + * Get the request associated with this failed capture. + * + *

Whenever a request is unsuccessfully captured, with + * {@link CameraDevice.CaptureListener#onCaptureFailed}, + * the {@code failed capture}'s {@code getRequest()} will return that {@code request}. + *

+ * + *

In particular, + *

cameraDevice.capture(someRequest, new CaptureListener() {
+     *     {@literal @}Override
+     *     void onCaptureFailed(CaptureRequest myRequest, CaptureFailure myFailure) {
+     *         assert(myFailure.getRequest.equals(myRequest) == true);
+     *     }
+     * };
+     * 
+ *

+ * + * @return The request associated with this failed capture. Never {@code null}. + */ + public CaptureRequest getRequest() { + return mRequest; + } + + /** + * Get the frame number associated with this failed capture. + * + *

Whenever a request has been processed, regardless of failed capture or success, + * it gets a unique frame number assigned to its future result/failed capture.

+ * + *

This value monotonically increments, starting with 0, + * for every new result or failure; and the scope is the lifetime of the + * {@link CameraDevice}.

+ * + * @return int frame number + */ + public int getFrameNumber() { + return mFrameNumber; + } + + /** + * Determine why the request was dropped, whether due to an error or to a user + * action. + * + * @return int One of {@code REASON_*} integer constants. + * + * @see #REASON_ERROR + * @see #REASON_FLUSHED + */ + public int getReason() { + return mReason; + } + + /** + * Determine if the image was captured from the camera. + * + *

If the image was not captured, no image buffers will be available. + * If the image was captured, then image buffers may be available.

+ * + * @return boolean True if the image was captured, false otherwise. + */ + public boolean wasImageCaptured() { + return !mDropped; + } + + /** + * The sequence ID for this failed capture that was returned by the + * {@link CameraDevice#capture} family of functions. + * + *

The sequence ID is a unique monotonically increasing value starting from 0, + * incremented every time a new group of requests is submitted to the CameraDevice.

+ * + * @return int The ID for the sequence of requests that this capture failure is the result of + * + * @see CameraDevice.CaptureListener#onCaptureSequenceCompleted + */ + public int getSequenceId() { + return mSequenceId; + } +} diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index 3ec5ca0..f30bcc5 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -17,11 +17,13 @@ package android.hardware.camera2; import android.hardware.camera2.impl.CameraMetadataNative; +import android.hardware.camera2.CameraDevice.CaptureListener; import android.os.Parcel; import android.os.Parcelable; import android.view.Surface; import java.util.HashSet; +import java.util.Objects; /** @@ -62,30 +64,37 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { private Object mUserTag; /** - * Construct empty request - * @hide + * Construct empty request. + * + * Used by Binder to unparcel this object only. */ - public CaptureRequest() { + private CaptureRequest() { mSettings = new CameraMetadataNative(); mSurfaceSet = new HashSet(); } /** - * Clone from source capture request + * Clone from source capture request. + * + * Used by the Builder to create an immutable copy. */ + @SuppressWarnings("unchecked") private CaptureRequest(CaptureRequest source) { mSettings = new CameraMetadataNative(source.mSettings); mSurfaceSet = (HashSet) source.mSurfaceSet.clone(); } /** - * Take ownership of passed-in settings + * Take ownership of passed-in settings. + * + * Used by the Builder to create a mutable CaptureRequest. */ private CaptureRequest(CameraMetadataNative settings) { mSettings = settings; mSurfaceSet = new HashSet(); } + @SuppressWarnings("unchecked") @Override public T get(Key key) { return mSettings.get(key); @@ -108,6 +117,34 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { return mUserTag; } + /** + * Determine whether this CaptureRequest is equal to another CaptureRequest. + * + *

A request is considered equal to another is if it's set of key/values is equal, it's + * list of output surfaces is equal, and the user tag is equal.

+ * + * @param other Another instance of CaptureRequest. + * + * @return True if the requests are the same, false otherwise. + */ + @Override + public boolean equals(Object other) { + return other instanceof CaptureRequest + && equals((CaptureRequest)other); + } + + private boolean equals(CaptureRequest other) { + return other != null + && Objects.equals(mUserTag, other.mUserTag) + && mSurfaceSet.equals(other.mSurfaceSet) + && mSettings.equals(other.mSettings); + } + + @Override + public int hashCode() { + return mSettings.hashCode(); + } + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 377e78a..b82104d 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -37,13 +37,25 @@ import android.hardware.camera2.impl.CameraMetadataNative; public final class CaptureResult extends CameraMetadata { private final CameraMetadataNative mResults; + private final CaptureRequest mRequest; + private final int mSequenceId; /** * Takes ownership of the passed-in properties object * @hide */ - public CaptureResult(CameraMetadataNative results) { + public CaptureResult(CameraMetadataNative results, CaptureRequest parent, int sequenceId) { + if (results == null) { + throw new IllegalArgumentException("results was null"); + } + + if (parent == null) { + throw new IllegalArgumentException("parent was null"); + } + mResults = results; + mRequest = parent; + mSequenceId = sequenceId; } @Override @@ -51,6 +63,61 @@ public final class CaptureResult extends CameraMetadata { return mResults.get(key); } + /** + * Get the request associated with this result. + * + *

Whenever a request is successfully captured, with + * {@link CameraDevice.CaptureListener#onCaptureCompleted}, + * the {@code result}'s {@code getRequest()} will return that {@code request}. + *

+ * + *

In particular, + *

cameraDevice.capture(someRequest, new CaptureListener() {
+     *     {@literal @}Override
+     *     void onCaptureCompleted(CaptureRequest myRequest, CaptureResult myResult) {
+     *         assert(myResult.getRequest.equals(myRequest) == true);
+     *     }
+     * };
+     * 
+ *

+ * + * @return The request associated with this result. Never {@code null}. + */ + public CaptureRequest getRequest() { + return mRequest; + } + + /** + * Get the frame number associated with this result. + * + *

Whenever a request has been processed, regardless of failure or success, + * it gets a unique frame number assigned to its future result/failure.

+ * + *

This value monotonically increments, starting with 0, + * for every new result or failure; and the scope is the lifetime of the + * {@link CameraDevice}.

+ * + * @return int frame number + */ + public int getFrameNumber() { + return get(REQUEST_FRAME_COUNT); + } + + /** + * The sequence ID for this failure that was returned by the + * {@link CameraDevice#capture} family of functions. + * + *

The sequence ID is a unique monotonically increasing value starting from 0, + * incremented every time a new group of requests is submitted to the CameraDevice.

+ * + * @return int The ID for the sequence of requests that this capture result is a part of + * + * @see CameraDevice.CaptureListener#onCaptureSequenceCompleted + */ + public int getSequenceId() { + return mSequenceId; + } + /*@O~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ * The key entries below this point are generated from metadata * definitions in /system/media/camera/docs. Do not modify by hand or @@ -523,8 +590,9 @@ public final class CaptureResult extends CameraMetadata { /** *

- * Number of frames captured since - * open() + * A frame counter set by the framework. This value monotonically + * increases with every new result (that is, each new result has a unique + * frameCount value). *

*

* Reset on release() diff --git a/core/java/android/hardware/camera2/impl/CameraDevice.java b/core/java/android/hardware/camera2/impl/CameraDevice.java index efbd769..c5983a1 100644 --- a/core/java/android/hardware/camera2/impl/CameraDevice.java +++ b/core/java/android/hardware/camera2/impl/CameraDevice.java @@ -179,24 +179,24 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { } @Override - public void capture(CaptureRequest request, CaptureListener listener, Handler handler) + public int capture(CaptureRequest request, CaptureListener listener, Handler handler) throws CameraAccessException { - submitCaptureRequest(request, listener, handler, /*streaming*/false); + return submitCaptureRequest(request, listener, handler, /*streaming*/false); } @Override - public void captureBurst(List requests, CaptureListener listener, + public int captureBurst(List requests, CaptureListener listener, Handler handler) throws CameraAccessException { if (requests.isEmpty()) { Log.w(TAG, "Capture burst request list is empty, do nothing!"); - return; + return -1; } // TODO throw new UnsupportedOperationException("Burst capture implemented yet"); } - private void submitCaptureRequest(CaptureRequest request, CaptureListener listener, + private int submitCaptureRequest(CaptureRequest request, CaptureListener listener, Handler handler, boolean repeating) throws CameraAccessException { // Need a valid handler, or current thread needs to have a looper, if @@ -220,7 +220,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { throw e.asChecked(); } catch (RemoteException e) { // impossible - return; + return -1; } if (listener != null) { mCaptureListenerMap.put(requestId, new CaptureListenerHolder(listener, request, @@ -231,21 +231,22 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { mRepeatingRequestIdStack.add(requestId); } + return requestId; } } @Override - public void setRepeatingRequest(CaptureRequest request, CaptureListener listener, + public int setRepeatingRequest(CaptureRequest request, CaptureListener listener, Handler handler) throws CameraAccessException { - submitCaptureRequest(request, listener, handler, /*streaming*/true); + return submitCaptureRequest(request, listener, handler, /*streaming*/true); } @Override - public void setRepeatingBurst(List requests, CaptureListener listener, + public int setRepeatingBurst(List requests, CaptureListener listener, Handler handler) throws CameraAccessException { if (requests.isEmpty()) { Log.w(TAG, "Set Repeating burst request list is empty, do nothing!"); - return; + return -1; } // TODO throw new UnsupportedOperationException("Burst capture implemented yet"); @@ -429,14 +430,16 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { return; } - final CaptureResult resultAsCapture = new CaptureResult(result); + final CaptureRequest request = holder.getRequest(); + final CaptureResult resultAsCapture = new CaptureResult(result, request, requestId); holder.getHandler().post( new Runnable() { + @Override public void run() { holder.getListener().onCaptureCompleted( CameraDevice.this, - holder.getRequest(), + request, resultAsCapture); } }); -- cgit v1.1