diff options
13 files changed, 784 insertions, 124 deletions
diff --git a/api/current.txt b/api/current.txt index 7f4b8f0..199182b 100644 --- a/api/current.txt +++ b/api/current.txt @@ -12962,6 +12962,8 @@ package android.hardware.camera2 { method public abstract int captureBurst(java.util.List<android.hardware.camera2.CaptureRequest>, android.hardware.camera2.CameraCaptureSession.CaptureCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException; method public abstract void close(); method public abstract android.hardware.camera2.CameraDevice getDevice(); + method public abstract android.view.Surface getInputSurface(); + method public abstract boolean isReprocessible(); method public abstract int setRepeatingBurst(java.util.List<android.hardware.camera2.CaptureRequest>, android.hardware.camera2.CameraCaptureSession.CaptureCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException; method public abstract int setRepeatingRequest(android.hardware.camera2.CaptureRequest, android.hardware.camera2.CameraCaptureSession.CaptureCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException; method public abstract void stopRepeating() throws android.hardware.camera2.CameraAccessException; @@ -13073,6 +13075,8 @@ package android.hardware.camera2 { method public abstract void close(); method public abstract android.hardware.camera2.CaptureRequest.Builder createCaptureRequest(int) throws android.hardware.camera2.CameraAccessException; method public abstract void createCaptureSession(java.util.List<android.view.Surface>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException; + method public abstract android.hardware.camera2.CaptureRequest.Builder createReprocessCaptureRequest(android.hardware.camera2.TotalCaptureResult) throws android.hardware.camera2.CameraAccessException; + method public abstract void createReprocessibleCaptureSession(android.hardware.camera2.params.InputConfiguration, java.util.List<android.view.Surface>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException; method public abstract java.lang.String getId(); field public static final int TEMPLATE_MANUAL = 6; // 0x6 field public static final int TEMPLATE_PREVIEW = 1; // 0x1 @@ -13323,6 +13327,7 @@ package android.hardware.camera2 { method public int describeContents(); method public T get(android.hardware.camera2.CaptureRequest.Key<T>); method public java.lang.Object getTag(); + method public boolean isReprocess(); method public void writeToParcel(android.os.Parcel, int); field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Boolean> BLACK_LEVEL_LOCK; field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> COLOR_CORRECTION_ABERRATION_MODE; @@ -13523,6 +13528,13 @@ package android.hardware.camera2.params { field public static final int SCORE_MIN = 1; // 0x1 } + public final class InputConfiguration { + ctor public InputConfiguration(int, int, int); + method public int getFormat(); + method public int getHeight(); + method public int getWidth(); + } + public final class LensShadingMap { method public void copyGainFactors(float[], int); method public int getColumnCount(); diff --git a/api/system-current.txt b/api/system-current.txt index 12ebc17..612e1ab 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -13254,6 +13254,8 @@ package android.hardware.camera2 { method public abstract int captureBurst(java.util.List<android.hardware.camera2.CaptureRequest>, android.hardware.camera2.CameraCaptureSession.CaptureCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException; method public abstract void close(); method public abstract android.hardware.camera2.CameraDevice getDevice(); + method public abstract android.view.Surface getInputSurface(); + method public abstract boolean isReprocessible(); method public abstract int setRepeatingBurst(java.util.List<android.hardware.camera2.CaptureRequest>, android.hardware.camera2.CameraCaptureSession.CaptureCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException; method public abstract int setRepeatingRequest(android.hardware.camera2.CaptureRequest, android.hardware.camera2.CameraCaptureSession.CaptureCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException; method public abstract void stopRepeating() throws android.hardware.camera2.CameraAccessException; @@ -13365,6 +13367,8 @@ package android.hardware.camera2 { method public abstract void close(); method public abstract android.hardware.camera2.CaptureRequest.Builder createCaptureRequest(int) throws android.hardware.camera2.CameraAccessException; method public abstract void createCaptureSession(java.util.List<android.view.Surface>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException; + method public abstract android.hardware.camera2.CaptureRequest.Builder createReprocessCaptureRequest(android.hardware.camera2.TotalCaptureResult) throws android.hardware.camera2.CameraAccessException; + method public abstract void createReprocessibleCaptureSession(android.hardware.camera2.params.InputConfiguration, java.util.List<android.view.Surface>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException; method public abstract java.lang.String getId(); field public static final int TEMPLATE_MANUAL = 6; // 0x6 field public static final int TEMPLATE_PREVIEW = 1; // 0x1 @@ -13615,6 +13619,7 @@ package android.hardware.camera2 { method public int describeContents(); method public T get(android.hardware.camera2.CaptureRequest.Key<T>); method public java.lang.Object getTag(); + method public boolean isReprocess(); method public void writeToParcel(android.os.Parcel, int); field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Boolean> BLACK_LEVEL_LOCK; field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> COLOR_CORRECTION_ABERRATION_MODE; @@ -13815,6 +13820,13 @@ package android.hardware.camera2.params { field public static final int SCORE_MIN = 1; // 0x1 } + public final class InputConfiguration { + ctor public InputConfiguration(int, int, int); + method public int getFormat(); + method public int getHeight(); + method public int getWidth(); + } + public final class LensShadingMap { method public void copyGainFactors(float[], int); method public int getColumnCount(); diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java index ce83028..6b6f026 100644 --- a/core/java/android/hardware/camera2/CameraCaptureSession.java +++ b/core/java/android/hardware/camera2/CameraCaptureSession.java @@ -17,21 +17,31 @@ package android.hardware.camera2; import android.os.Handler; +import android.view.Surface; import java.util.List; + /** - * A configured capture session for a {@link CameraDevice}, used for capturing - * images from the camera. + * A configured capture session for a {@link CameraDevice}, used for capturing images from the + * camera or reprocessing images captured from the camera in the same session previously. * * <p>A CameraCaptureSession is created by providing a set of target output surfaces to - * {@link CameraDevice#createCaptureSession createCaptureSession}. Once created, the session is - * active until a new session is created by the camera device, or the camera device is closed.</p> + * {@link CameraDevice#createCaptureSession createCaptureSession}, or by providing an + * {@link android.hardware.camera2.params.InputConfiguration} and a set of target output surfaces to + * {@link CameraDevice#createReprocessibleCaptureSession createReprocessibleCaptureSession} for a + * reprocessible capture session. Once created, the session is active until a new session is + * created by the camera device, or the camera device is closed.</p> + * + * <p>All capture sessions can be used for capturing images from the camera but only reprocessible + * capture sessions can reprocess images captured from the camera in the same session previously. + * </p> * * <p>Creating a session is an expensive operation and can take several hundred milliseconds, since * it requires configuring the camera device's internal pipelines and allocating memory buffers for * sending images to the desired targets. Therefore the setup is done asynchronously, and - * {@link CameraDevice#createCaptureSession createCaptureSession} will send the ready-to-use - * CameraCaptureSession to the provided listener's + * {@link CameraDevice#createCaptureSession createCaptureSession} and + * {@link CameraDevice#createReprocessibleCaptureSession createReprocessibleCaptureSession} will + * send the ready-to-use CameraCaptureSession to the provided listener's * {@link CameraCaptureSession.StateCallback#onConfigured onConfigured} callback. If configuration * cannot be completed, then the * {@link CameraCaptureSession.StateCallback#onConfigureFailed onConfigureFailed} is called, and the @@ -77,6 +87,12 @@ public abstract class CameraCaptureSession implements AutoCloseable { * {@link #setRepeatingBurst}, and will be processed as soon as the current * repeat/repeatBurst processing completes.</p> * + * <p>All capture sessions can be used for capturing images from the camera but only capture + * sessions created by + * {@link CameraDevice#createReprocessibleCaptureSession createReprocessibleCaptureSession} + * can submit reprocess capture requests. Submitting a reprocess request to a regular capture + * session will result in an {@link IllegalArgumentException}.</p> + * * @param request the settings for this capture * @param listener The callback object to notify once this request has been * processed. If null, no metadata will be produced for this capture, @@ -94,7 +110,9 @@ public abstract class CameraCaptureSession implements AutoCloseable { * was explicitly closed, a new session has been created * or the camera device has been closed. * @throws IllegalArgumentException if the request targets no Surfaces or Surfaces that are not - * configured as outputs for this session. Or if the handler is + * configured as outputs for this session. Or if a reprocess + * capture request is submitted in a non-reprocessible capture + * session. Or if the handler is * null, the listener is not null, and the calling thread has * no looper. * @@ -102,6 +120,7 @@ public abstract class CameraCaptureSession implements AutoCloseable { * @see #setRepeatingRequest * @see #setRepeatingBurst * @see #abortCaptures + * @see CameraDevice#createReprocessibleCaptureSession */ public abstract int capture(CaptureRequest request, CaptureCallback listener, Handler handler) throws CameraAccessException; @@ -121,6 +140,13 @@ public abstract class CameraCaptureSession implements AutoCloseable { * {@link #capture} repeatedly is that this method guarantees that no * other requests will be interspersed with the burst.</p> * + * <p>All capture sessions can be used for capturing images from the camera but only capture + * sessions created by + * {@link CameraDevice#createReprocessibleCaptureSession createReprocessibleCaptureSession} + * can submit reprocess capture requests. The list of requests must all be capturing images from + * the camera or all be reprocess capture requests. Submitting a reprocess request to a regular + * capture session will result in an {@link IllegalArgumentException}.</p> + * * @param requests the list of settings for this burst capture * @param listener The callback object to notify each time one of the * requests in the burst has been processed. If null, no metadata will be @@ -138,9 +164,13 @@ public abstract class CameraCaptureSession implements AutoCloseable { * @throws IllegalStateException if this session is no longer active, either because the session * was explicitly closed, a new session has been created * or the camera device has been closed. - * @throws IllegalArgumentException If the requests target no Surfaces or Surfaces not currently - * configured as outputs. Or if the handler is null, the - * listener is not null, and the calling thread has no looper. + * @throws IllegalArgumentException If the requests target no Surfaces, or target Surfaces not + * currently configured as outputs. Or if a reprocess + * capture request is submitted in a non-reprocessible capture + * session. Or if the list of requests contains both requests + * to capture images from the camera and reprocess capture + * requests. Or if the handler is null, the listener is not + * null, and the calling thread has no looper. * * @see #capture * @see #setRepeatingRequest @@ -175,6 +205,14 @@ public abstract class CameraCaptureSession implements AutoCloseable { * in-progress burst will be completed before the new repeat request will be * used.</p> * + * <p>This method does not support reprocess capture requests because each reprocess + * {@link CaptureRequest} must be created from the {@link TotalCaptureResult} that matches + * the input image to be reprocessed. This is either the {@link TotalCaptureResult} of capture + * that is sent for reprocessing, or one of the {@link TotalCaptureResult TotalCaptureResults} + * of a set of captures, when data from the whole set is combined by the application into a + * single reprocess input image. The request must be capturing images from the camera. If a + * reprocess capture request is submitted, this method will throw IllegalArgumentException.</p> + * * @param request the request to repeat indefinitely * @param listener The callback object to notify every time the * request finishes processing. If null, no metadata will be @@ -193,9 +231,10 @@ public abstract class CameraCaptureSession implements AutoCloseable { * was explicitly closed, a new session has been created * or the camera device has been closed. * @throws IllegalArgumentException If the requests reference no Surfaces or Surfaces that are - * not currently configured as outputs. Or if the handler is - * null, the listener is not null, and the calling thread has - * no looper. Or if no requests were passed in. + * not currently configured as outputs. Or if the request is + * a reprocess capture request. Or if the handler is null, the + * listener is not null, and the calling thread has no looper. + * Or if no requests were passed in. * * @see #capture * @see #captureBurst @@ -235,6 +274,14 @@ public abstract class CameraCaptureSession implements AutoCloseable { * in-progress burst will be completed before the new repeat burst will be * used.</p> * + * <p>This method does not support reprocess capture requests because each reprocess + * {@link CaptureRequest} must be created from the {@link TotalCaptureResult} that matches + * the input image to be reprocessed. This is either the {@link TotalCaptureResult} of capture + * that is sent for reprocessing, or one of the {@link TotalCaptureResult TotalCaptureResults} + * of a set of captures, when data from the whole set is combined by the application into a + * single reprocess input image. The request must be capturing images from the camera. If a + * reprocess capture request is submitted, this method will throw IllegalArgumentException.</p> + * * @param requests the list of requests to cycle through indefinitely * @param listener The callback object to notify each time one of the * requests in the repeating bursts has finished processing. If null, no @@ -253,7 +300,8 @@ public abstract class CameraCaptureSession implements AutoCloseable { * was explicitly closed, a new session has been created * or the camera device has been closed. * @throws IllegalArgumentException If the requests reference no Surfaces or Surfaces not - * currently configured as outputs. Or if the handler is null, + * currently configured as outputs. Or if one of the requests + * is a reprocess capture request. Or if the handler is null, * the listener is not null, and the calling thread has no * looper. Or if no requests were passed in. * @@ -298,9 +346,10 @@ public abstract class CameraCaptureSession implements AutoCloseable { * request or a repeating burst is set, it will be cleared.</p> * * <p>This method is the fastest way to switch the camera device to a new session with - * {@link CameraDevice#createCaptureSession}, at the cost of discarding in-progress work. It - * must be called before the new session is created. Once all pending requests are either - * completed or thrown away, the {@link StateCallback#onReady} callback will be called, + * {@link CameraDevice#createCaptureSession} or + * {@link CameraDevice#createReprocessibleCaptureSession}, at the cost of discarding in-progress + * work. It must be called before the new session is created. Once all pending requests are + * either completed or thrown away, the {@link StateCallback#onReady} callback will be called, * if the session has not been closed. Otherwise, the {@link StateCallback#onClosed} * callback will be fired when a new session is created by the camera device.</p> * @@ -321,10 +370,39 @@ public abstract class CameraCaptureSession implements AutoCloseable { * @see #setRepeatingRequest * @see #setRepeatingBurst * @see CameraDevice#createCaptureSession + * @see CameraDevice#createReprocessibleCaptureSession */ public abstract void abortCaptures() throws CameraAccessException; /** + * Return if the application can submit reprocess capture requests with this camera capture + * session. + * + * @return {@code true} if the application can submit reprocess capture requests with this + * camera capture session. {@code false} otherwise. + * + * @see CameraDevice#createReprocessibleCaptureSession + */ + public abstract boolean isReprocessible(); + + /** + * Get the input Surface associated with a reprocessible capture session. + * + * <p>Each reprocessible capture session has an input {@link Surface} where the reprocess + * capture requests get the input images from, rather than the camera device. The application + * can create a {@link android.media.ImageWriter} with this input {@link Surface} and use it to + * provide input images for reprocess capture requests.</p> + * + * @return The {@link Surface} where reprocessing capture requests get the input images from. If + * this is not a reprocess capture session, {@code null} will be returned. + * + * @see CameraDevice#createReprocessibleCaptureSession + * @see android.media.ImageWriter + * @see android.media.ImageReader + */ + public abstract Surface getInputSurface(); + + /** * Close this capture session asynchronously. * * <p>Closing a session frees up the target output Surfaces of the session for reuse with either diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index fd4cf3c..51b326b 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -16,6 +16,7 @@ package android.hardware.camera2; +import android.hardware.camera2.params.InputConfiguration; import android.hardware.camera2.params.StreamConfigurationMap; import android.hardware.camera2.params.OutputConfiguration; import android.os.Handler; @@ -135,7 +136,7 @@ public abstract class CameraDevice implements AutoCloseable { * * <p>The active capture session determines the set of potential output Surfaces for * the camera device for each capture request. A given request may use all - * or a only some of the outputs. Once the CameraCaptureSession is created, requests can be + * or only some of the outputs. Once the CameraCaptureSession is created, requests can be * can be submitted with {@link CameraCaptureSession#capture capture}, * {@link CameraCaptureSession#captureBurst captureBurst}, * {@link CameraCaptureSession#setRepeatingRequest setRepeatingRequest}, or @@ -393,6 +394,75 @@ public abstract class CameraDevice implements AutoCloseable { List<OutputConfiguration> outputConfigurations, CameraCaptureSession.StateCallback callback, Handler handler) throws CameraAccessException; + /** + * Create a new reprocessible camera capture session by providing the desired reprocessing + * input Surface configuration and the target output set of Surfaces to the camera device. + * + * <p>If a camera device supports YUV reprocessing + * ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING}) or OPAQUE + * reprocessing + * ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_OPAQUE_REPROCESSING}), besides + * the capture session created via {@link #createCaptureSession}, the application can also + * create a reprocessible capture session to submit reprocess capture requests in addition to + * regular capture requests. A reprocess capture request takes the next available buffer from + * the session's input Surface, and sends it through the camera device's processing pipeline + * again, to produce buffers for the request's target output Surfaces. No new image data is + * captured for a reprocess request. However the input buffer provided by + * the application must be captured previously by the same camera device in the same session + * directly (e.g. for Zero-Shutter-Lag use case) or indirectly (e.g. combining multiple output + * images).</p> + * + * <p>The active reprocessible capture session determines an input {@link Surface} and the set + * of potential output Surfaces for the camera devices for each capture request. The application + * can use {@link #createCaptureRequest} to create regular capture requests to capture new + * images from the camera device, and use {@link #createReprocessCaptureRequest} to create + * reprocess capture requests to process buffers from the input {@link Surface}. A request may + * use all or only some of the outputs. All the output Surfaces in one capture request will come + * from the same source, either from a new capture by the camera device, or from the input + * Surface depending on if the request is a reprocess capture request.</p> + * + * <p>Input formats and sizes supported by the camera device can be queried via + * {@link StreamConfigurationMap#getInputFormats} and + * {@link StreamConfigurationMap#getInputSizes}. For each supported input format, the camera + * device supports a set of output formats and sizes for reprocessing that can be queried via + * {@link StreamConfigurationMap#getValidOutputFormatsForInput} and + * {@link StreamConfigurationMap#getOutputSizes}. While output Surfaces with formats that + * aren't valid reprocess output targets for the input configuration can be part of a session, + * they cannot be used as targets for a reprocessing request.</p> + * + * <p>Since the application cannot access {@link android.graphics.ImageFormat#PRIVATE} images + * directly, an output Surface created by {@link android.media.ImageReader#newOpaqueInstance} + * will be considered as intended to be used for reprocessing input and thus the + * {@link android.media.ImageReader} size must match one of the supported input sizes for + * {@link android.graphics.ImageFormat#PRIVATE} format. Otherwise, creating a reprocessible + * capture session will fail.</p> + * + * @param inputConfig The configuration for the input {@link Surface} + * @param outputs The new set of Surfaces that should be made available as + * targets for captured image data. + * @param callback The callback to notify about the status of the new capture session. + * @param handler The handler on which the callback should be invoked, or {@code null} to use + * the current thread's {@link android.os.Looper looper}. + * + * @throws IllegalArgumentException if the input configuration is null or not supported, the set + * of output Surfaces do not meet the requirements, the + * callback is null, or the handler is null but the current + * thread has no looper. + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @throws IllegalStateException if the camera device has been closed + * + * @see CameraCaptureSession + * @see StreamConfigurationMap#getInputFormats + * @see StreamConfigurationMap#getInputSizes + * @see StreamConfigurationMap#getValidOutputFormatsForInput + * @see StreamConfigurationMap#getOutputSizes + * @see android.media.ImageWriter + * @see android.media.ImageReader + */ + public abstract void createReprocessibleCaptureSession(InputConfiguration inputConfig, + List<Surface> outputs, CameraCaptureSession.StateCallback callback, Handler handler) + throws CameraAccessException; /** * <p>Create a {@link CaptureRequest.Builder} for new capture requests, @@ -423,6 +493,36 @@ public abstract class CameraDevice implements AutoCloseable { throws CameraAccessException; /** + * <p>Create a {@link CaptureRequest.Builder} for a new reprocess {@link CaptureRequest} from a + * {@link TotalCaptureResult}. + * + * <p>Each reprocess {@link CaptureRequest} processes one buffer from + * {@link CameraCaptureSession}'s input {@link Surface} to all output {@link Surface Surfaces} + * included in the reprocess capture request. The reprocess input images must be generated from + * one or multiple output images captured from the same camera device. The application can + * provide input images to camera device via + * {{@link android.media.ImageWriter#queueInputImage ImageWriter#queueInputImage}}. + * The application must use the capture result of one of those output images to create a + * reprocess capture request so that the camera device can use the information to achieve + * optimal reprocess image quality. + * + * @param inputResult The capture result of the output image or one of the output images used + * to generate the reprocess input image for this capture request. + * + * @throws IllegalArgumentException if inputResult is null. + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @throws IllegalStateException if the camera device has been closed + * + * @see CaptureRequest.Builder + * @see TotalCaptureResult + * @see CameraDevice#createReprocessibleCaptureSession + * @see android.media.ImageWriter + */ + public abstract CaptureRequest.Builder createReprocessCaptureRequest( + TotalCaptureResult inputResult) throws CameraAccessException; + + /** * Close the connection to this camera device as quickly as possible. * * <p>Immediately after this call, all calls to the camera device or active session interface diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index b513379..1a00a05 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -148,7 +148,7 @@ public final class CameraManager { * new one provided.</p> * * <p>The first time a callback is registered, it is immediately called - * with the torch mode status of all currently known camera devices.</p> + * with the torch mode status of all currently known camera devices with a flash unit.</p> * * <p>Since this callback will be registered with the camera service, remember to unregister it * once it is no longer needed; otherwise the callback will continue to receive events @@ -524,7 +524,7 @@ public final class CameraManager { * A callback for camera flash torch modes becoming unavailable, disabled, or enabled. * * <p>The torch mode becomes unavailable when the camera device it belongs to becomes - * unavailable or other camera resouces it needs become busy due to other higher priority + * unavailable or other camera resources it needs become busy due to other higher priority * camera activities. The torch mode becomes disabled when it was turned off or when the camera * device it belongs to is no longer in use and other camera resources it needs are no longer * busy. A camera's torch mode is turned off when an application calls {@link #setTorchMode} to diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index b8fb8e7..35727e8 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -157,6 +157,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> private final HashSet<Surface> mSurfaceSet; private final CameraMetadataNative mSettings; + private boolean mIsReprocess; private Object mUserTag; @@ -168,6 +169,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> private CaptureRequest() { mSettings = new CameraMetadataNative(); mSurfaceSet = new HashSet<Surface>(); + mIsReprocess = false; } /** @@ -179,6 +181,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> private CaptureRequest(CaptureRequest source) { mSettings = new CameraMetadataNative(source.mSettings); mSurfaceSet = (HashSet<Surface>) source.mSurfaceSet.clone(); + mIsReprocess = source.mIsReprocess; mUserTag = source.mUserTag; } @@ -187,9 +190,10 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * * Used by the Builder to create a mutable CaptureRequest. */ - private CaptureRequest(CameraMetadataNative settings) { + private CaptureRequest(CameraMetadataNative settings, boolean isReprocess) { mSettings = CameraMetadataNative.move(settings); mSurfaceSet = new HashSet<Surface>(); + mIsReprocess = isReprocess; } /** @@ -257,10 +261,27 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> } /** + * Determine if this is a reprocess capture request. + * + * <p>A reprocess capture request produces output images from an input buffer from the + * {@link CameraCaptureSession}'s input {@link Surface}. A reprocess capture request can be + * created by {@link CameraDevice#createReprocessCaptureRequest}.</p> + * + * @return {@code true} if this is a reprocess capture request. {@code false} if this is not a + * reprocess capture request. + * + * @see CameraDevice#createReprocessCaptureRequest + */ + public boolean isReprocess() { + return mIsReprocess; + } + + /** * Determine whether this CaptureRequest is equal to another CaptureRequest. * * <p>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.</p> + * list of output surfaces is equal, the user tag is equal, and the return values of + * isReprocess() are equal.</p> * * @param other Another instance of CaptureRequest. * @@ -276,7 +297,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> return other != null && Objects.equals(mUserTag, other.mUserTag) && mSurfaceSet.equals(other.mSurfaceSet) - && mSettings.equals(other.mSettings); + && mSettings.equals(other.mSettings) + && mIsReprocess == other.mIsReprocess; } @Override @@ -323,6 +345,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> Surface s = (Surface) p; mSurfaceSet.add(s); } + + mIsReprocess = (in.readInt() == 0) ? false : true; } @Override @@ -334,6 +358,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> public void writeToParcel(Parcel dest, int flags) { mSettings.writeToParcel(dest, flags); dest.writeParcelableArray(mSurfaceSet.toArray(new Surface[mSurfaceSet.size()]), flags); + dest.writeInt(mIsReprocess ? 1 : 0); } /** @@ -374,8 +399,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * * @hide */ - public Builder(CameraMetadataNative template) { - mRequest = new CaptureRequest(template); + public Builder(CameraMetadataNative template, boolean reprocess) { + mRequest = new CaptureRequest(template, reprocess); } /** diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index e346dc2..4341869 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -295,11 +295,18 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>Whenever a request has been processed, regardless of failure or success, * it gets a unique frame number assigned to its future result/failure.</p> * - * <p>This value monotonically increments, starting with 0, - * for every new result or failure; and the scope is the lifetime of the - * {@link CameraDevice}.</p> + * <p>For the same type of request (capturing from the camera device or reprocessing), this + * value monotonically increments, starting with 0, for every new result or failure and the + * scope is the lifetime of the {@link CameraDevice}. Between different types of requests, + * the frame number may not monotonically increment. For example, the frame number of a newer + * reprocess result may be smaller than the frame number of an older result of capturing new + * images from the camera device, but the frame number of a newer reprocess result will never be + * smaller than the frame number of an older reprocess result.</p> * * @return The frame number + * + * @see CameraDevice#createCaptureRequest + * @see CameraDevice#createReprocessCaptureRequest */ public long getFrameNumber() { return mFrameNumber; diff --git a/core/java/android/hardware/camera2/ICameraDeviceUser.aidl b/core/java/android/hardware/camera2/ICameraDeviceUser.aidl index 01f2396..23bfa66 100644 --- a/core/java/android/hardware/camera2/ICameraDeviceUser.aidl +++ b/core/java/android/hardware/camera2/ICameraDeviceUser.aidl @@ -16,11 +16,11 @@ package android.hardware.camera2; -import android.hardware.camera2.params.OutputConfiguration; -import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.CaptureRequest; - +import android.hardware.camera2.impl.CameraMetadataNative; +import android.hardware.camera2.params.OutputConfiguration; import android.hardware.camera2.utils.LongParcelable; +import android.view.Surface; /** @hide */ interface ICameraDeviceUser @@ -68,6 +68,29 @@ interface ICameraDeviceUser // non-negative value is the stream ID. negative value is status_t int createStream(in OutputConfiguration outputConfiguration); + /** + * Create an input stream + * + * <p>Create an input stream of width, height, and format</p> + * + * @param width Width of the input buffers + * @param height Height of the input buffers + * @param format Format of the input buffers. One of HAL_PIXEL_FORMAT_*. + * + * @return stream ID if it's a non-negative value. status_t if it's a negative value. + */ + int createInputStream(int width, int height, int format); + + /** + * Get the surface of the input stream. + * + * <p>It's valid to call this method only after a stream configuration is completed + * successfully and the stream configuration includes a input stream.</p> + * + * @param surface An output argument for the surface of the input stream buffer queue. + */ + int getInputSurface(out Surface surface); + int createDefaultRequest(int templateId, out CameraMetadataNative request); int getCameraInfo(out CameraMetadataNative info); diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java index e87a2f8..fb5b13c 100644 --- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java @@ -44,6 +44,8 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { private final int mId; private final String mIdString; + /** Input surface configured by native camera framework based on user-specified configuration */ + private final Surface mInput; /** User-specified set of surfaces used as the configuration outputs */ private final List<Surface> mOutputs; /** @@ -85,7 +87,7 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { * There must be no pending actions * (e.g. no pending captures, no repeating requests, no flush).</p> */ - CameraCaptureSessionImpl(int id, List<Surface> outputs, + CameraCaptureSessionImpl(int id, Surface input, List<Surface> outputs, CameraCaptureSession.StateCallback callback, Handler stateHandler, android.hardware.camera2.impl.CameraDeviceImpl deviceImpl, Handler deviceStateHandler, boolean configureSuccess) { @@ -100,6 +102,7 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { // TODO: extra verification of outputs mOutputs = outputs; + mInput = input; mStateHandler = checkHandler(stateHandler); mStateCallback = createUserStateCallbackProxy(mStateHandler, callback); @@ -145,8 +148,12 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { Handler handler) throws CameraAccessException { if (request == null) { throw new IllegalArgumentException("request must not be null"); + } else if (request.isReprocess() && !isReprocessible()) { + throw new IllegalArgumentException("this capture session cannot handle reprocess " + + "requests"); } + checkNotClosed(); handler = checkHandler(handler, callback); @@ -169,6 +176,19 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { throw new IllegalArgumentException("requests must have at least one element"); } + boolean reprocess = requests.get(0).isReprocess(); + if (reprocess && !isReprocessible()) { + throw new IllegalArgumentException("this capture session cannot handle reprocess " + + "requests"); + } + + for (int i = 1; i < requests.size(); i++) { + if (requests.get(i).isReprocess() != reprocess) { + throw new IllegalArgumentException("cannot mix regular and reprocess capture " + + " requests"); + } + } + checkNotClosed(); handler = checkHandler(handler, callback); @@ -188,8 +208,11 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { Handler handler) throws CameraAccessException { if (request == null) { throw new IllegalArgumentException("request must not be null"); + } else if (request.isReprocess()) { + throw new IllegalArgumentException("repeating reprocess requests are not supported"); } + checkNotClosed(); handler = checkHandler(handler, callback); @@ -212,6 +235,13 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { throw new IllegalArgumentException("requests must have at least one element"); } + for (CaptureRequest r : requests) { + if (r.isReprocess()) { + throw new IllegalArgumentException("repeating reprocess burst requests are not " + + "supported"); + } + } + checkNotClosed(); handler = checkHandler(handler, callback); @@ -257,6 +287,16 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { // The next BUSY -> IDLE set of transitions will mark the end of the abort. } + @Override + public boolean isReprocessible() { + return mInput != null; + } + + @Override + public Surface getInputSurface() { + return mInput; + } + /** * Replace this session with another session. * @@ -658,8 +698,8 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { mUnconfigureDrainer.taskStarted(); try { - mDeviceImpl - .configureOutputsChecked(null); // begin transition to unconfigured + // begin transition to unconfigured + mDeviceImpl.configureStreamsChecked(null, null); } catch (CameraAccessException e) { // OK: do not throw checked exceptions. Log.e(TAG, mIdString + "Exception while configuring outputs: ", e); diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index 38f8e39..91388c3 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -28,7 +28,10 @@ import android.hardware.camera2.CaptureFailure; import android.hardware.camera2.ICameraDeviceCallbacks; import android.hardware.camera2.ICameraDeviceUser; import android.hardware.camera2.TotalCaptureResult; +import android.hardware.camera2.params.InputConfiguration; import android.hardware.camera2.params.OutputConfiguration; +import android.hardware.camera2.params.ReprocessFormatsMap; +import android.hardware.camera2.params.StreamConfigurationMap; import android.hardware.camera2.utils.CameraBinderDecorator; import android.hardware.camera2.utils.CameraRuntimeException; import android.hardware.camera2.utils.LongParcelable; @@ -37,6 +40,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.util.Log; +import android.util.Size; import android.util.SparseArray; import android.view.Surface; @@ -46,7 +50,8 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.TreeSet; +import java.util.LinkedList; +import java.util.TreeMap; /** * HAL2.1+ implementation of CameraDevice. Use CameraManager#open to instantiate @@ -78,9 +83,11 @@ public class CameraDeviceImpl extends CameraDevice { private int mRepeatingRequestId = REQUEST_ID_NONE; private final ArrayList<Integer> mRepeatingRequestIdDeletedList = new ArrayList<Integer>(); - // Map stream IDs to Surfaces + // Map stream IDs to input/output configurations + private SimpleEntry<Integer, InputConfiguration> mConfiguredInput = + new SimpleEntry<>(REQUEST_ID_NONE, null); private final SparseArray<OutputConfiguration> mConfiguredOutputs = - new SparseArray<OutputConfiguration>(); + new SparseArray<>(); private final String mCameraId; private final CameraCharacteristics mCharacteristics; @@ -320,38 +327,48 @@ public class CameraDeviceImpl extends CameraDevice { for (Surface s : outputs) { outputConfigs.add(new OutputConfiguration(s)); } - configureOutputsChecked(outputConfigs); + configureStreamsChecked(/*inputConfig*/null, outputConfigs); + } /** - * Attempt to configure the outputs; the device goes to idle and then configures the - * new outputs if possible. + * Attempt to configure the input and outputs; the device goes to idle and then configures the + * new input and outputs if possible. * - * <p>The configuration may gracefully fail, if there are too many outputs, if the formats - * are not supported, or if the sizes for that format is not supported. In this case this - * function will return {@code false} and the unconfigured callback will be fired.</p> + * <p>The configuration may gracefully fail, if input configuration is not supported, + * if there are too many outputs, if the formats are not supported, or if the sizes for that + * format is not supported. In this case this function will return {@code false} and the + * unconfigured callback will be fired.</p> * - * <p>If the configuration succeeds (with 1 or more outputs), then the idle callback is fired. - * Unconfiguring the device always fires the idle callback.</p> + * <p>If the configuration succeeds (with 1 or more outputs with or without an input), + * then the idle callback is fired. Unconfiguring the device always fires the idle callback.</p> * + * @param inputConfig input configuration or {@code null} for no input * @param outputs a list of one or more surfaces, or {@code null} to unconfigure * @return whether or not the configuration was successful * * @throws CameraAccessException if there were any unexpected problems during configuration */ - public boolean configureOutputsChecked(List<OutputConfiguration> outputs) - throws CameraAccessException { + public boolean configureStreamsChecked(InputConfiguration inputConfig, + List<OutputConfiguration> outputs) throws CameraAccessException { // Treat a null input the same an empty list if (outputs == null) { outputs = new ArrayList<OutputConfiguration>(); } + if (outputs.size() == 0 && inputConfig != null) { + throw new IllegalArgumentException("cannot configure an input stream without " + + "any output streams"); + } + + checkInputConfiguration(inputConfig); + boolean success = false; synchronized(mInterfaceLock) { checkIfCameraClosedOrInError(); // Streams to create HashSet<OutputConfiguration> addSet = new HashSet<OutputConfiguration>(outputs); - // Streams to delete + // Streams to delete List<Integer> deleteList = new ArrayList<Integer>(); // Determine which streams need to be created, which to be deleted @@ -373,6 +390,24 @@ public class CameraDeviceImpl extends CameraDevice { waitUntilIdle(); mRemoteDevice.beginConfigure(); + + // reconfigure the input stream if the input configuration is different. + InputConfiguration currentInputConfig = mConfiguredInput.getValue(); + if (inputConfig != currentInputConfig && + (inputConfig == null || !inputConfig.equals(currentInputConfig))) { + if (currentInputConfig != null) { + mRemoteDevice.deleteStream(mConfiguredInput.getKey()); + mConfiguredInput = new SimpleEntry<Integer, InputConfiguration>( + REQUEST_ID_NONE, null); + } + if (inputConfig != null) { + int streamId = mRemoteDevice.createInputStream(inputConfig.getWidth(), + inputConfig.getHeight(), inputConfig.getFormat()); + mConfiguredInput = new SimpleEntry<Integer, InputConfiguration>( + streamId, inputConfig); + } + } + // Delete all streams first (to free up HW resources) for (Integer streamId : deleteList) { mRemoteDevice.deleteStream(streamId); @@ -429,7 +464,7 @@ public class CameraDeviceImpl extends CameraDevice { for (Surface surface : outputs) { outConfigurations.add(new OutputConfiguration(surface)); } - createCaptureSessionByOutputConfiguration(outConfigurations, callback, handler); + createCaptureSessionInternal(null, outConfigurations, callback, handler); } @Override @@ -437,9 +472,39 @@ public class CameraDeviceImpl extends CameraDevice { List<OutputConfiguration> outputConfigurations, CameraCaptureSession.StateCallback callback, Handler handler) throws CameraAccessException { + if (DEBUG) { + Log.d(TAG, "createCaptureSessionByOutputConfiguration"); + } + + createCaptureSessionInternal(null, outputConfigurations, callback, handler); + } + + @Override + public void createReprocessibleCaptureSession(InputConfiguration inputConfig, + List<Surface> outputs, CameraCaptureSession.StateCallback callback, Handler handler) + throws CameraAccessException { + if (DEBUG) { + Log.d(TAG, "createReprocessibleCaptureSession"); + } + + if (inputConfig == null) { + throw new IllegalArgumentException("inputConfig cannot be null when creating a " + + "reprocessible capture session"); + } + List<OutputConfiguration> outConfigurations = new ArrayList<>(outputs.size()); + for (Surface surface : outputs) { + outConfigurations.add(new OutputConfiguration(surface)); + } + createCaptureSessionInternal(inputConfig, outConfigurations, callback, handler); + } + + private void createCaptureSessionInternal(InputConfiguration inputConfig, + List<OutputConfiguration> outputConfigurations, + CameraCaptureSession.StateCallback callback, Handler handler) + throws CameraAccessException { synchronized(mInterfaceLock) { if (DEBUG) { - Log.d(TAG, "createCaptureSession"); + Log.d(TAG, "createCaptureSessionInternal"); } checkIfCameraClosedOrInError(); @@ -453,15 +518,24 @@ public class CameraDeviceImpl extends CameraDevice { // TODO: dont block for this boolean configureSuccess = true; CameraAccessException pendingException = null; + Surface input = null; try { - // configure outputs and then block until IDLE - configureSuccess = configureOutputsChecked(outputConfigurations); + // configure streams and then block until IDLE + configureSuccess = configureStreamsChecked(inputConfig, outputConfigurations); + if (inputConfig != null) { + input = new Surface(); + mRemoteDevice.getInputSurface(/*out*/input); + } } catch (CameraAccessException e) { configureSuccess = false; pendingException = e; + input = null; if (DEBUG) { Log.v(TAG, "createCaptureSession - failed with exception ", e); } + } catch (RemoteException e) { + // impossible + return; } List<Surface> outSurfaces = new ArrayList<>(outputConfigurations.size()); @@ -470,7 +544,7 @@ public class CameraDeviceImpl extends CameraDevice { } // Fire onConfigured if configureOutputs succeeded, fire onConfigureFailed otherwise. CameraCaptureSessionImpl newSession = - new CameraCaptureSessionImpl(mNextSessionId++, + new CameraCaptureSessionImpl(mNextSessionId++, input, outSurfaces, callback, handler, this, mDeviceHandler, configureSuccess); @@ -512,12 +586,25 @@ public class CameraDeviceImpl extends CameraDevice { } CaptureRequest.Builder builder = - new CaptureRequest.Builder(templatedRequest); + new CaptureRequest.Builder(templatedRequest, /*reprocess*/false); return builder; } } + @Override + public CaptureRequest.Builder createReprocessCaptureRequest(TotalCaptureResult inputResult) + throws CameraAccessException { + synchronized(mInterfaceLock) { + checkIfCameraClosedOrInError(); + + CameraMetadataNative resultMetadata = new + CameraMetadataNative(inputResult.getNativeCopy()); + + return new CaptureRequest.Builder(resultMetadata, /*reprocess*/true); + } + } + public int capture(CaptureRequest request, CaptureCallback callback, Handler handler) throws CameraAccessException { if (DEBUG) { @@ -810,6 +897,40 @@ public class CameraDeviceImpl extends CameraDevice { } } + private void checkInputConfiguration(InputConfiguration inputConfig) { + if (inputConfig != null) { + StreamConfigurationMap configMap = mCharacteristics.get( + CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + + int[] inputFormats = configMap.getInputFormats(); + boolean validFormat = false; + for (int format : inputFormats) { + if (format == inputConfig.getFormat()) { + validFormat = true; + } + } + + if (validFormat == false) { + throw new IllegalArgumentException("input format " + inputConfig.getFormat() + + " is not valid"); + } + + boolean validSize = false; + Size[] inputSizes = configMap.getInputSizes(inputConfig.getFormat()); + for (Size s : inputSizes) { + if (inputConfig.getWidth() == s.getWidth() && + inputConfig.getHeight() == s.getHeight()) { + validSize = true; + } + } + + if (validSize == false) { + throw new IllegalArgumentException("input size " + inputConfig.getWidth() + "x" + + inputConfig.getHeight() + " is not valid"); + } + } + } + /** * <p>A callback for tracking the progress of a {@link CaptureRequest} * submitted to the camera device.</p> @@ -996,19 +1117,46 @@ public class CameraDeviceImpl extends CameraDevice { public class FrameNumberTracker { private long mCompletedFrameNumber = -1; - private final TreeSet<Long> mFutureErrorSet = new TreeSet<Long>(); + private long mCompletedReprocessFrameNumber = -1; + /** the skipped frame numbers that belong to regular results */ + private final LinkedList<Long> mSkippedRegularFrameNumbers = new LinkedList<Long>(); + /** the skipped frame numbers that belong to reprocess results */ + private final LinkedList<Long> mSkippedReprocessFrameNumbers = new LinkedList<Long>(); + /** frame number -> is reprocess */ + private final TreeMap<Long, Boolean> mFutureErrorMap = new TreeMap<Long, Boolean>(); /** Map frame numbers to list of partial results */ private final HashMap<Long, List<CaptureResult>> mPartialResults = new HashMap<>(); private void update() { - Iterator<Long> iter = mFutureErrorSet.iterator(); + Iterator iter = mFutureErrorMap.entrySet().iterator(); while (iter.hasNext()) { - long errorFrameNumber = iter.next(); - if (errorFrameNumber == mCompletedFrameNumber + 1) { - mCompletedFrameNumber++; - iter.remove(); + TreeMap.Entry pair = (TreeMap.Entry)iter.next(); + Long errorFrameNumber = (Long)pair.getKey(); + Boolean reprocess = (Boolean)pair.getValue(); + Boolean removeError = true; + if (reprocess) { + if (errorFrameNumber == mCompletedReprocessFrameNumber + 1) { + mCompletedReprocessFrameNumber = errorFrameNumber; + } else if (mSkippedReprocessFrameNumbers.isEmpty() != true && + errorFrameNumber == mSkippedReprocessFrameNumbers.element()) { + mCompletedReprocessFrameNumber = errorFrameNumber; + mSkippedReprocessFrameNumbers.remove(); + } else { + removeError = false; + } } else { - break; + if (errorFrameNumber == mCompletedFrameNumber + 1) { + mCompletedFrameNumber = errorFrameNumber; + } else if (mSkippedRegularFrameNumbers.isEmpty() != true && + errorFrameNumber == mSkippedRegularFrameNumbers.element()) { + mCompletedFrameNumber = errorFrameNumber; + mSkippedRegularFrameNumbers.remove(); + } else { + removeError = false; + } + } + if (removeError) { + iter.remove(); } } } @@ -1017,25 +1165,21 @@ public class CameraDeviceImpl extends CameraDevice { * This function is called every time when a result or an error is received. * @param frameNumber the frame number corresponding to the result or error * @param isError true if it is an error, false if it is not an error + * @param isReprocess true if it is a reprocess result, false if it is a regular result. */ - public void updateTracker(long frameNumber, boolean isError) { + public void updateTracker(long frameNumber, boolean isError, boolean isReprocess) { if (isError) { - mFutureErrorSet.add(frameNumber); + mFutureErrorMap.put(frameNumber, isReprocess); } else { - /** - * HAL cannot send an OnResultReceived for frame N unless it knows for - * sure that all frames prior to N have either errored out or completed. - * So if the current frame is not an error, then all previous frames - * should have arrived. The following line checks whether this holds. - */ - if (frameNumber != mCompletedFrameNumber + 1) { - Log.e(TAG, String.format( - "result frame number %d comes out of order, should be %d + 1", - frameNumber, mCompletedFrameNumber)); - // Continue on to set the completed frame number to this frame anyway, - // to be robust to lower-level errors and allow for clean shutdowns. + try { + if (isReprocess) { + updateCompletedReprocessFrameNumber(frameNumber); + } else { + updateCompletedFrameNumber(frameNumber); + } + } catch (IllegalArgumentException e) { + Log.e(TAG, e.getMessage()); } - mCompletedFrameNumber = frameNumber; } update(); } @@ -1049,12 +1193,13 @@ public class CameraDeviceImpl extends CameraDevice { * @param frameNumber the frame number corresponding to the result * @param result the total or partial result * @param partial {@true} if the result is partial, {@code false} if total + * @param isReprocess true if it is a reprocess result, false if it is a regular result. */ - public void updateTracker(long frameNumber, CaptureResult result, boolean partial) { - + public void updateTracker(long frameNumber, CaptureResult result, boolean partial, + boolean isReprocess) { if (!partial) { // Update the total result's frame status as being successful - updateTracker(frameNumber, /*isError*/false); + updateTracker(frameNumber, /*isError*/false, isReprocess); // Don't keep a list of total results, we don't need to track them return; } @@ -1093,28 +1238,112 @@ public class CameraDeviceImpl extends CameraDevice { return mCompletedFrameNumber; } + public long getCompletedReprocessFrameNumber() { + return mCompletedReprocessFrameNumber; + } + + /** + * Update the completed frame number for regular results. + * + * It validates that all previous frames have arrived except for reprocess frames. + * + * If there is a gap since previous regular frame number, assume the frames in the gap are + * reprocess frames and store them in the skipped reprocess frame number queue to check + * against when reprocess frames arrive. + */ + private void updateCompletedFrameNumber(long frameNumber) throws IllegalArgumentException { + if (frameNumber <= mCompletedFrameNumber) { + throw new IllegalArgumentException("frame number " + frameNumber + " is a repeat"); + } else if (frameNumber <= mCompletedReprocessFrameNumber) { + // if frame number is smaller than completed reprocess frame number, + // it must be the head of mSkippedRegularFrameNumbers + if (mSkippedRegularFrameNumbers.isEmpty() == true || + frameNumber < mSkippedRegularFrameNumbers.element()) { + throw new IllegalArgumentException("frame number " + frameNumber + + " is a repeat"); + } else if (frameNumber > mSkippedRegularFrameNumbers.element()) { + throw new IllegalArgumentException("frame number " + frameNumber + + " comes out of order. Expecting " + + mSkippedRegularFrameNumbers.element()); + } + // frame number matches the head of the skipped frame number queue. + mSkippedRegularFrameNumbers.remove(); + } else { + // there is a gap of unseen frame numbers which should belong to reprocess result + // put all the skipped frame numbers in the queue + for (long i = Math.max(mCompletedFrameNumber, mCompletedReprocessFrameNumber) + 1; + i < frameNumber; i++) { + mSkippedReprocessFrameNumbers.add(i); + } + } + + mCompletedFrameNumber = frameNumber; + } + + /** + * Update the completed frame number for reprocess results. + * + * It validates that all previous frames have arrived except for regular frames. + * + * If there is a gap since previous reprocess frame number, assume the frames in the gap are + * regular frames and store them in the skipped regular frame number queue to check + * against when regular frames arrive. + */ + private void updateCompletedReprocessFrameNumber(long frameNumber) + throws IllegalArgumentException { + if (frameNumber < mCompletedReprocessFrameNumber) { + throw new IllegalArgumentException("frame number " + frameNumber + " is a repeat"); + } else if (frameNumber < mCompletedFrameNumber) { + // if reprocess frame number is smaller than completed regular frame number, + // it must be the head of the skipped reprocess frame number queue. + if (mSkippedReprocessFrameNumbers.isEmpty() == true || + frameNumber < mSkippedReprocessFrameNumbers.element()) { + throw new IllegalArgumentException("frame number " + frameNumber + + " is a repeat"); + } else if (frameNumber > mSkippedReprocessFrameNumbers.element()) { + throw new IllegalArgumentException("frame number " + frameNumber + + " comes out of order. Expecting " + + mSkippedReprocessFrameNumbers.element()); + } + // frame number matches the head of the skipped frame number queue. + mSkippedReprocessFrameNumbers.remove(); + } else { + // put all the skipped frame numbers in the queue + for (long i = Math.max(mCompletedFrameNumber, mCompletedReprocessFrameNumber) + 1; + i < frameNumber; i++) { + mSkippedRegularFrameNumbers.add(i); + } + } + mCompletedReprocessFrameNumber = frameNumber; + } } private void checkAndFireSequenceComplete() { long completedFrameNumber = mFrameNumberTracker.getCompletedFrameNumber(); + long completedReprocessFrameNumber = mFrameNumberTracker.getCompletedReprocessFrameNumber(); + boolean isReprocess = false; Iterator<SimpleEntry<Long, Integer> > iter = mFrameNumberRequestPairs.iterator(); while (iter.hasNext()) { final SimpleEntry<Long, Integer> frameNumberRequestPair = iter.next(); - if (frameNumberRequestPair.getKey() <= completedFrameNumber) { - - // remove request from mCaptureCallbackMap - final int requestId = frameNumberRequestPair.getValue(); - final CaptureCallbackHolder holder; - synchronized(mInterfaceLock) { - if (mRemoteDevice == null) { - Log.w(TAG, "Camera closed while checking sequences"); - return; - } + boolean sequenceCompleted = false; + final int requestId = frameNumberRequestPair.getValue(); + final CaptureCallbackHolder holder; + synchronized(mInterfaceLock) { + if (mRemoteDevice == null) { + Log.w(TAG, "Camera closed while checking sequences"); + return; + } - int index = mCaptureCallbackMap.indexOfKey(requestId); - holder = (index >= 0) ? mCaptureCallbackMap.valueAt(index) - : null; - if (holder != null) { + int index = mCaptureCallbackMap.indexOfKey(requestId); + holder = (index >= 0) ? + mCaptureCallbackMap.valueAt(index) : null; + if (holder != null) { + isReprocess = holder.getRequest().isReprocess(); + // check if it's okay to remove request from mCaptureCallbackMap + if ((isReprocess && frameNumberRequestPair.getKey() <= + completedReprocessFrameNumber) || (!isReprocess && + frameNumberRequestPair.getKey() <= completedFrameNumber)) { + sequenceCompleted = true; mCaptureCallbackMap.removeAt(index); if (DEBUG) { Log.v(TAG, String.format( @@ -1125,36 +1354,40 @@ public class CameraDeviceImpl extends CameraDevice { } } } + } + + // If no callback is registered for this requestId or sequence completed, remove it + // from the frame number->request pair because it's not needed anymore. + if (holder == null || sequenceCompleted) { iter.remove(); + } - // Call onCaptureSequenceCompleted - if (holder != null) { - Runnable resultDispatch = new Runnable() { - @Override - public void run() { - if (!CameraDeviceImpl.this.isClosed()){ - if (DEBUG) { - Log.d(TAG, String.format( - "fire sequence complete for request %d", - requestId)); - } + // Call onCaptureSequenceCompleted + if (sequenceCompleted) { + Runnable resultDispatch = new Runnable() { + @Override + public void run() { + if (!CameraDeviceImpl.this.isClosed()){ + if (DEBUG) { + Log.d(TAG, String.format( + "fire sequence complete for request %d", + requestId)); + } - long lastFrameNumber = frameNumberRequestPair.getKey(); - if (lastFrameNumber < Integer.MIN_VALUE - || lastFrameNumber > Integer.MAX_VALUE) { - throw new AssertionError(lastFrameNumber - + " cannot be cast to int"); - } - holder.getCallback().onCaptureSequenceCompleted( - CameraDeviceImpl.this, - requestId, - lastFrameNumber); + long lastFrameNumber = frameNumberRequestPair.getKey(); + if (lastFrameNumber < Integer.MIN_VALUE + || lastFrameNumber > Integer.MAX_VALUE) { + throw new AssertionError(lastFrameNumber + + " cannot be cast to int"); } + holder.getCallback().onCaptureSequenceCompleted( + CameraDeviceImpl.this, + requestId, + lastFrameNumber); } - }; - holder.getHandler().post(resultDispatch); - } - + } + }; + holder.getHandler().post(resultDispatch); } } } @@ -1319,9 +1552,11 @@ public class CameraDeviceImpl extends CameraDevice { final CaptureCallbackHolder holder = CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId); + final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId()); boolean isPartialResult = (resultExtras.getPartialResultCount() < mTotalPartialCount); + boolean isReprocess = request.isReprocess(); // Check if we have a callback for this if (holder == null) { @@ -1331,7 +1566,8 @@ public class CameraDeviceImpl extends CameraDevice { + frameNumber); } - mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult); + mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult, + isReprocess); return; } @@ -1343,11 +1579,11 @@ public class CameraDeviceImpl extends CameraDevice { + frameNumber); } - mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult); + mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult, + isReprocess); return; } - final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId()); Runnable resultDispatch = null; @@ -1398,7 +1634,7 @@ public class CameraDeviceImpl extends CameraDevice { holder.getHandler().post(resultDispatch); // Collect the partials for a total result; or mark the frame as totally completed - mFrameNumberTracker.updateTracker(frameNumber, finalResult, isPartialResult); + mFrameNumberTracker.updateTracker(frameNumber, finalResult, isPartialResult, isReprocess); // Fire onCaptureSequenceCompleted if (!isPartialResult) { @@ -1460,7 +1696,7 @@ public class CameraDeviceImpl extends CameraDevice { if (DEBUG) { Log.v(TAG, String.format("got error frame %d", frameNumber)); } - mFrameNumberTracker.updateTracker(frameNumber, /*error*/true); + mFrameNumberTracker.updateTracker(frameNumber, /*error*/true, request.isReprocess()); checkAndFireSequenceComplete(); } diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java index 70f3463..4cd9414 100644 --- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java +++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java @@ -530,6 +530,18 @@ public class CameraDeviceUserShim implements ICameraDeviceUser { } @Override + public int createInputStream(int width, int height, int format) { + Log.e(TAG, "creating input stream is not supported on legacy devices"); + return CameraBinderDecorator.INVALID_OPERATION; + } + + @Override + public int getInputSurface(/*out*/ Surface surface) { + Log.e(TAG, "getting input surface is not supported on legacy devices"); + return CameraBinderDecorator.INVALID_OPERATION; + } + + @Override public int createDefaultRequest(int templateId, /*out*/CameraMetadataNative request) { if (DEBUG) { Log.d(TAG, "createDefaultRequest called."); diff --git a/core/java/android/hardware/camera2/params/InputConfiguration.java b/core/java/android/hardware/camera2/params/InputConfiguration.java new file mode 100644 index 0000000..dea1c5c --- /dev/null +++ b/core/java/android/hardware/camera2/params/InputConfiguration.java @@ -0,0 +1,115 @@ +/* + * Copyright 2015 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.params; + +import android.hardware.camera2.utils.HashCodeHelpers; + +/** + * Immutable class to store an input configuration that is used to create a reprocessible capture + * session. + * + * @see CameraDevice#createReprocessibleCaptureSession + * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP + */ +public final class InputConfiguration { + + private final int mWidth; + private final int mHeight; + private final int mFormat; + + /** + * Create an input configration with the width, height, and user-defined format. + * + * <p>Images of an user-defined format are accessible by applications. Use + * {@link android.hardware.camera2.CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP} + * to query supported input formats</p> + * + * @param width Width of the input buffers. + * @param height Height of the input buffers. + * @param format Format of the input buffers. One of ImageFormat or PixelFormat constants. + * + * @see android.graphics.ImageFormat + * @see android.graphics.PixelFormat + * @see android.hardware.camera2.CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP + */ + public InputConfiguration(int width, int height, int format) { + mWidth = width; + mHeight = height; + mFormat = format; + } + + /** + * Get the width of this input configration. + * + * @return width of this input configuration. + */ + public int getWidth() { + return mWidth; + } + + /** + * Get the height of this input configration. + * + * @return height of this input configuration. + */ + public int getHeight() { + return mHeight; + } + + /** + * Get the format of this input configration. + * + * @return format of this input configuration. + */ + public int getFormat() { + return mFormat; + } + + /** + * Check if this InputConfiguration is equal to another InputConfiguration. + * + * <p>Two input configurations are equal if and only if they have the same widths, heights, and + * formats.</p> + * + * @param obj the object to compare this instance with. + * + * @return {@code true} if the objects were equal, {@code false} otherwise. + */ + @Override + public boolean equals(Object obj) { + if (!(obj instanceof InputConfiguration)) { + return false; + } + + InputConfiguration otherInputConfig = (InputConfiguration) obj; + + if (otherInputConfig.getWidth() == mWidth && + otherInputConfig.getHeight() == mHeight && + otherInputConfig.getFormat() == mFormat) { + return true; + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return HashCodeHelpers.hashCode(mWidth, mHeight, mFormat); + } +} diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java index e05e1fc..0466540 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java @@ -160,7 +160,7 @@ public class CameraDeviceBinderTest extends AndroidTestCase { assertEquals(CameraBinderTestUtils.NO_ERROR, status); assertFalse(metadata.isEmpty()); - CaptureRequest.Builder request = new CaptureRequest.Builder(metadata); + CaptureRequest.Builder request = new CaptureRequest.Builder(metadata, /*reprocess*/false); assertFalse(request.isEmpty()); assertFalse(metadata.isEmpty()); if (needStream) { |
