Camera: Initial support for Camera2 extension
Add the necessary APIs that will allow Camera2 clients to access
and work with existing CameraX extensions.
Bug: 170481080
Test: Camera CTS
Change-Id: Iaa22fea69ebe01599b3f2e66c1b46ca92015cdc5
diff --git a/core/api/current.txt b/core/api/current.txt
index 892ddf4..55521d3 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -17645,6 +17645,7 @@
method public void createCaptureSession(android.hardware.camera2.params.SessionConfiguration) throws android.hardware.camera2.CameraAccessException;
method @Deprecated public abstract void createCaptureSessionByOutputConfigurations(java.util.List<android.hardware.camera2.params.OutputConfiguration>, android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method @Deprecated public abstract void createConstrainedHighSpeedCaptureSession(@NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+ method public void createExtensionSession(@NonNull android.hardware.camera2.params.ExtensionSessionConfiguration) throws android.hardware.camera2.CameraAccessException;
method @NonNull public abstract android.hardware.camera2.CaptureRequest.Builder createReprocessCaptureRequest(@NonNull android.hardware.camera2.TotalCaptureResult) throws android.hardware.camera2.CameraAccessException;
method @Deprecated public abstract void createReprocessableCaptureSession(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method @Deprecated public abstract void createReprocessableCaptureSessionByConfigurations(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
@@ -17676,8 +17677,44 @@
field public static final int ERROR_MAX_CAMERAS_IN_USE = 2; // 0x2
}
+ public final class CameraExtensionCharacteristics {
+ method @NonNull public <T> java.util.List<android.util.Size> getExtensionSupportedSizes(int, @NonNull Class<T>);
+ method @NonNull public java.util.List<android.util.Size> getExtensionSupportedSizes(int, int);
+ method @NonNull public java.util.List<java.lang.Integer> getSupportedExtensions();
+ field public static final int EXTENSION_AUTOMATIC = 0; // 0x0
+ field public static final int EXTENSION_BEAUTY = 1; // 0x1
+ field public static final int EXTENSION_BOKEH = 2; // 0x2
+ field public static final int EXTENSION_HDR = 3; // 0x3
+ field public static final int EXTENSION_NIGHT = 4; // 0x4
+ }
+
+ public abstract class CameraExtensionSession implements java.lang.AutoCloseable {
+ method public int capture(@NonNull android.hardware.camera2.CaptureRequest, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraExtensionSession.ExtensionCaptureCallback) throws android.hardware.camera2.CameraAccessException;
+ method public void close() throws android.hardware.camera2.CameraAccessException;
+ method @NonNull public android.hardware.camera2.CameraDevice getDevice();
+ method public int setRepeatingRequest(@NonNull android.hardware.camera2.CaptureRequest, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraExtensionSession.ExtensionCaptureCallback) throws android.hardware.camera2.CameraAccessException;
+ method public void stopRepeating() throws android.hardware.camera2.CameraAccessException;
+ }
+
+ public abstract static class CameraExtensionSession.ExtensionCaptureCallback {
+ ctor public CameraExtensionSession.ExtensionCaptureCallback();
+ method public void onCaptureFailed(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest);
+ method public void onCaptureProcessStarted(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest);
+ method public void onCaptureSequenceAborted(@NonNull android.hardware.camera2.CameraExtensionSession, int);
+ method public void onCaptureSequenceCompleted(@NonNull android.hardware.camera2.CameraExtensionSession, int);
+ method public void onCaptureStarted(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest, long);
+ }
+
+ public abstract static class CameraExtensionSession.StateCallback {
+ ctor public CameraExtensionSession.StateCallback();
+ method public void onClosed(@NonNull android.hardware.camera2.CameraExtensionSession);
+ method public abstract void onConfigureFailed(@NonNull android.hardware.camera2.CameraExtensionSession);
+ method public abstract void onConfigured(@NonNull android.hardware.camera2.CameraExtensionSession);
+ }
+
public final class CameraManager {
method @NonNull public android.hardware.camera2.CameraCharacteristics getCameraCharacteristics(@NonNull String) throws android.hardware.camera2.CameraAccessException;
+ method @NonNull public android.hardware.camera2.CameraExtensionCharacteristics getCameraExtensionCharacteristics(@NonNull String) throws android.hardware.camera2.CameraAccessException;
method @NonNull public String[] getCameraIdList() throws android.hardware.camera2.CameraAccessException;
method @NonNull public java.util.Set<java.util.Set<java.lang.String>> getConcurrentCameraIds() throws android.hardware.camera2.CameraAccessException;
method @RequiresPermission(android.Manifest.permission.CAMERA) public boolean isConcurrentSessionConfigurationSupported(@NonNull java.util.Map<java.lang.String,android.hardware.camera2.params.SessionConfiguration>) throws android.hardware.camera2.CameraAccessException;
@@ -18187,6 +18224,14 @@
method public android.util.Rational getElement(int, int);
}
+ public final class ExtensionSessionConfiguration {
+ ctor public ExtensionSessionConfiguration(int, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraExtensionSession.StateCallback);
+ method @NonNull public java.util.concurrent.Executor getExecutor();
+ method public int getExtension();
+ method @NonNull public java.util.List<android.hardware.camera2.params.OutputConfiguration> getOutputConfigurations();
+ method @NonNull public android.hardware.camera2.CameraExtensionSession.StateCallback getStateCallback();
+ }
+
public final class Face {
method public android.graphics.Rect getBounds();
method public int getId();
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 37e1280..f9eecae 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.hardware.camera2.params.ExtensionSessionConfiguration;
import android.hardware.camera2.params.InputConfiguration;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
@@ -351,6 +352,66 @@
throws CameraAccessException;
/**
+ * Initialize a specific device-specific extension augmented camera capture
+ * session.
+ *
+ * <p>Extension sessions can be used to enable device-specific operation modes like
+ * {@link CameraExtensionCharacteristics#EXTENSION_NIGHT} or
+ * {@link CameraExtensionCharacteristics#EXTENSION_HDR}. These modes are less flexible than the
+ * full camera API, but enable access to more sophisticated processing algorithms that can
+ * capture multi-frame bursts to generate single output images. To query for available
+ * extensions on this device call
+ * {@link CameraExtensionCharacteristics#getSupportedExtensions()}.</p>
+ *
+ * <p>This method will also trigger the setup of the internal
+ * processing pipeline for extension augmented preview and multi-frame
+ * still capture.</p>
+ *
+ * <p>If a prior CameraCaptureSession already exists when this method is called, the previous
+ * session will no longer be able to accept new capture requests and will be closed. Any
+ * in-progress capture requests made on the prior session will be completed before it's closed.
+ * </p>
+ *
+ * <p>The CameraExtensionSession will be active until the client
+ * either calls CameraExtensionSession.close() or creates a new camera
+ * capture session. In both cases all internal resources will be
+ * released, continuous repeating requests stopped and any pending
+ * multi-frame capture requests flushed.</p>
+ *
+ * <p>Note that the CameraExtensionSession currently supports at most two
+ * multi frame capture surface formats: ImageFormat.YUV_420_888 and
+ * ImageFormat.JPEG. Clients must query the multi-frame capture format support using
+ * {@link CameraExtensionCharacteristics#getExtensionSupportedSizes(int, int)}.
+ * For repeating requests CameraExtensionSession supports only
+ * {@link android.graphics.SurfaceTexture} as output. Clients can query the supported resolution
+ * for the repeating request output using
+ * {@link CameraExtensionCharacteristics#getExtensionSupportedSizes(int, Class)
+ * getExtensionSupportedSizes(..., Class)}.</p>
+ *
+ * <p>At the very minimum the initialization expects either one valid output
+ * surface for repeating or one valid output for high-quality single requests registered in the
+ * outputs argument of the extension configuration argument. At the maximum the initialization
+ * will accept two valid output surfaces, one for repeating and the other for single requests.
+ * Additional unsupported surfaces passed to ExtensionSessionConfiguration will cause an
+ * {@link IllegalArgumentException} to be thrown.</p>
+ *
+ * @param extensionConfiguration extension configuration
+ * @throws IllegalArgumentException If both the preview and still
+ * capture surfaces are not set or invalid, or if any of the
+ * registered surfaces do not meet the device-specific
+ * extension requirements such as dimensions and/or
+ * (output format)/(surface type), or if the extension is not
+ * supported.
+ * @see CameraExtensionCharacteristics#getSupportedExtensions
+ * @see CameraExtensionCharacteristics#getExtensionSupportedSizes
+ */
+ public void createExtensionSession(
+ @NonNull ExtensionSessionConfiguration extensionConfiguration)
+ throws CameraAccessException {
+ throw new UnsupportedOperationException("No default implementation");
+ }
+
+ /**
* Standard camera operation mode.
*
* @see #createCustomCaptureSession
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
new file mode 100644
index 0000000..d3eb377
--- /dev/null
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -0,0 +1,546 @@
+/*
+ * Copyright (C) 2020 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.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.graphics.ImageFormat;
+import android.hardware.camera2.extension.ICameraExtensionsProxyService;
+import android.hardware.camera2.extension.IImageCaptureExtenderImpl;
+import android.hardware.camera2.extension.IPreviewExtenderImpl;
+import android.hardware.camera2.extension.SizeList;
+import android.hardware.camera2.params.ExtensionSessionConfiguration;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.os.ConditionVariable;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Size;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * <p>Allows clients to query availability and supported resolutions of camera extensions.</p>
+ *
+ * <p>Camera extensions give camera clients access to device-specific algorithms and sequences that
+ * can improve the overall image quality of snapshots in various cases such as low light, selfies,
+ * portraits, and scenes that can benefit from enhanced dynamic range. Often such sophisticated
+ * processing sequences will rely on multiple camera frames as input and will produce a single
+ * output.</p>
+ *
+ * <p>Camera extensions are not guaranteed to be present on all devices so camera clients must
+ * query for their availability via {@link CameraExtensionCharacteristics#getSupportedExtensions()}.
+ * </p>
+ *
+ * <p>In order to use any available camera extension, camera clients must create a corresponding
+ * {@link CameraExtensionSession} via
+ * {@link CameraDevice#createExtensionSession(ExtensionSessionConfiguration)}</p>
+ *
+ * <p>Camera clients must be aware that device-specific camera extensions may support only a
+ * subset of the available camera resolutions and must first query
+ * {@link CameraExtensionCharacteristics#getExtensionSupportedSizes(int, int)} for supported
+ * single high-quality request output sizes and
+ * {@link CameraExtensionCharacteristics#getExtensionSupportedSizes(int, Class)} for supported
+ * repeating request output sizes.</p>
+ *
+ * @see CameraManager#getCameraExtensionCharacteristics(String)
+ */
+public final class CameraExtensionCharacteristics {
+ private static final String TAG = "CameraExtensionCharacteristics";
+
+ /**
+ * Device-specific extension implementation for automatic selection of particular extension
+ * such as HDR or NIGHT depending on the current lighting and environment conditions.
+ */
+ public static final int EXTENSION_AUTOMATIC = 0;
+
+ /**
+ * Device-specific extension implementation which tends to smooth the skin and apply other
+ * cosmetic effects to people's faces.
+ */
+ public static final int EXTENSION_BEAUTY = 1;
+
+ /**
+ * Device-specific extension implementation which can blur certain regions of the final image
+ * thereby "enhancing" focus for all remaining non-blurred parts.
+ */
+ public static final int EXTENSION_BOKEH = 2;
+
+ /**
+ * Device-specific extension implementation for enhancing the dynamic range of the
+ * final image.
+ */
+ public static final int EXTENSION_HDR = 3;
+
+ /**
+ * Device-specific extension implementation that aims to suppress noise and improve the
+ * overall image quality under low light conditions.
+ */
+ public static final int EXTENSION_NIGHT = 4;
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, value = {EXTENSION_AUTOMATIC,
+ EXTENSION_BEAUTY,
+ EXTENSION_BOKEH,
+ EXTENSION_HDR,
+ EXTENSION_NIGHT})
+ public @interface Extension {
+ }
+
+ /**
+ * Default camera output in case additional processing from CameraX extensions is not needed
+ *
+ * @hide
+ */
+ public static final int NON_PROCESSING_INPUT_FORMAT = ImageFormat.PRIVATE;
+
+ /**
+ * CameraX extensions require YUV_420_888 as default input for processing at the moment
+ *
+ * @hide
+ */
+ public static final int PROCESSING_INPUT_FORMAT = ImageFormat.YUV_420_888;
+
+ private static final @Extension
+ int[] EXTENSION_LIST = new int[]{
+ EXTENSION_AUTOMATIC,
+ EXTENSION_BEAUTY,
+ EXTENSION_BOKEH,
+ EXTENSION_HDR,
+ EXTENSION_NIGHT};
+
+ private final Context mContext;
+ private final String mCameraId;
+ private final CameraCharacteristics mChars;
+
+ /**
+ * @hide
+ */
+ public CameraExtensionCharacteristics(Context context, String cameraId,
+ CameraCharacteristics chars) {
+ mContext = context;
+ mCameraId = cameraId;
+ mChars = chars;
+ }
+
+ private static List<Size> generateSupportedSizes(List<SizeList> sizesList,
+ Integer format,
+ StreamConfigurationMap streamMap) {
+ // Per API contract it is assumed that the extension is able to support all
+ // camera advertised sizes for a given format in case it doesn't return
+ // a valid non-empty size list.
+ ArrayList<Size> ret = new ArrayList<>();
+ if ((sizesList != null) && (!sizesList.isEmpty())) {
+ for (SizeList entry : sizesList) {
+ if ((entry.format == format) && !entry.sizes.isEmpty()) {
+ for (android.hardware.camera2.extension.Size sz : entry.sizes) {
+ ret.add(new Size(sz.width, sz.height));
+ }
+ return ret;
+ }
+ }
+ }
+ Size[] supportedSizes = streamMap.getOutputSizes(format);
+ if (supportedSizes != null) {
+ ret.addAll(Arrays.asList(supportedSizes));
+ }
+ return ret;
+ }
+
+ /**
+ * A per-process global camera extension manager instance, to track and
+ * initialize/release extensions depending on client activity.
+ */
+ private static final class CameraExtensionManagerGlobal {
+ private static final String TAG = "CameraExtensionManagerGlobal";
+ private static final String PROXY_PACKAGE_NAME = "com.android.camera";
+ private static final String PROXY_SERVICE_NAME =
+ "com.android.camera.CameraExtensionsProxyService";
+
+ // Singleton instance
+ private static final CameraExtensionManagerGlobal GLOBAL_CAMERA_MANAGER =
+ new CameraExtensionManagerGlobal();
+ private final Object mLock = new Object();
+ private final int PROXY_SERVICE_DELAY_MS = 1000;
+ private InitializerFuture mInitFuture = null;
+ private ServiceConnection mConnection = null;
+ private ICameraExtensionsProxyService mProxy = null;
+
+ // Singleton, don't allow construction
+ private CameraExtensionManagerGlobal() {}
+
+ public static CameraExtensionManagerGlobal get() {
+ return GLOBAL_CAMERA_MANAGER;
+ }
+
+ private void connectToProxyLocked(Context ctx) {
+ if (mConnection == null) {
+ Intent intent = new Intent();
+ intent.setClassName(PROXY_PACKAGE_NAME, PROXY_SERVICE_NAME);
+ mInitFuture = new InitializerFuture();
+ mConnection = new ServiceConnection() {
+ @Override
+ public void onServiceDisconnected(ComponentName component) {
+ mInitFuture.setStatus(false);
+ mConnection = null;
+ mProxy = null;
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName component, IBinder binder) {
+ mProxy = ICameraExtensionsProxyService.Stub.asInterface(binder);
+ mInitFuture.setStatus(true);
+ }
+ };
+ ctx.bindService(intent, mConnection, Context.BIND_AUTO_CREATE |
+ Context.BIND_IMPORTANT | Context.BIND_ABOVE_CLIENT |
+ Context.BIND_NOT_VISIBLE);
+
+ try {
+ mInitFuture.get(PROXY_SERVICE_DELAY_MS, TimeUnit.MILLISECONDS);
+ } catch (TimeoutException e) {
+ Log.e(TAG, "Timed out while initializing proxy service!");
+ }
+ }
+ }
+
+ private static class InitializerFuture implements Future<Boolean> {
+ private volatile Boolean mStatus;
+ ConditionVariable mCondVar = new ConditionVariable(/*opened*/false);
+
+ public void setStatus(boolean status) {
+ mStatus = status;
+ mCondVar.open();
+ }
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ return false; // don't allow canceling this task
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false; // can never cancel this task
+ }
+
+ @Override
+ public boolean isDone() {
+ return mStatus != null;
+ }
+
+ @Override
+ public Boolean get() {
+ mCondVar.block();
+ return mStatus;
+ }
+
+ @Override
+ public Boolean get(long timeout, TimeUnit unit) throws TimeoutException {
+ long timeoutMs = unit.convert(timeout, TimeUnit.MILLISECONDS);
+ if (!mCondVar.block(timeoutMs)) {
+ throw new TimeoutException(
+ "Failed to receive status after " + timeout + " " + unit);
+ }
+
+ if (mStatus == null) {
+ throw new AssertionError();
+ }
+ return mStatus;
+ }
+ }
+
+ public long registerClient(Context ctx) {
+ synchronized (mLock) {
+ connectToProxyLocked(ctx);
+ if (mProxy != null) {
+ try {
+ return mProxy.registerClient();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to initialize extension! Extension service does "
+ + " not respond!");
+ return -1;
+ }
+ } else {
+ return -1;
+ }
+ }
+ }
+
+ public void unregisterClient(long clientId) {
+ synchronized (mLock) {
+ if (mProxy != null) {
+ try {
+ mProxy.unregisterClient(clientId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to de-initialize extension! Extension service does"
+ + " not respond!");
+ }
+ }
+ }
+ }
+
+ public IPreviewExtenderImpl initializePreviewExtension(int extensionType)
+ throws RemoteException {
+ synchronized (mLock) {
+ if (mProxy != null) {
+ return mProxy.initializePreviewExtension(extensionType);
+ } else {
+ return null;
+ }
+ }
+ }
+
+ public IImageCaptureExtenderImpl initializeImageExtension(int extensionType)
+ throws RemoteException {
+ synchronized (mLock) {
+ if (mProxy != null) {
+ return mProxy.initializeImageExtension(extensionType);
+ } else {
+ return null;
+ }
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static long registerClient(Context ctx) {
+ return CameraExtensionManagerGlobal.get().registerClient(ctx);
+ }
+
+ /**
+ * @hide
+ */
+ public static void unregisterClient(long clientId) {
+ CameraExtensionManagerGlobal.get().unregisterClient(clientId);
+ }
+
+ /**
+ * @hide
+ */
+ public static boolean isExtensionSupported(String cameraId, int extensionType,
+ CameraCharacteristics chars) {
+ Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders;
+ try {
+ extenders = initializeExtension(extensionType);
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+
+ try {
+ return extenders.first.isExtensionAvailable(cameraId, chars.getNativeMetadata()) &&
+ extenders.second.isExtensionAvailable(cameraId, chars.getNativeMetadata());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to query extension availability! Extension service does not"
+ + " respond!");
+ return false;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> initializeExtension(
+ @Extension int extensionType) {
+ IPreviewExtenderImpl previewExtender;
+ IImageCaptureExtenderImpl imageExtender;
+ try {
+ previewExtender =
+ CameraExtensionManagerGlobal.get().initializePreviewExtension(extensionType);
+ imageExtender =
+ CameraExtensionManagerGlobal.get().initializeImageExtension(extensionType);
+ } catch (RemoteException e) {
+ throw new IllegalStateException("Failed to initialize extension: " + extensionType);
+ }
+ if ((imageExtender == null) || (previewExtender == null)) {
+ throw new IllegalArgumentException("Unknown extension: " + extensionType);
+ }
+
+ return new Pair<>(previewExtender, imageExtender);
+ }
+
+ private static <T> boolean isOutputSupportedFor(Class<T> klass) {
+ Objects.requireNonNull(klass, "klass must not be null");
+
+ if (klass == android.graphics.SurfaceTexture.class) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Return a list of supported device-specific extensions for a given camera device.
+ *
+ * @return non-modifiable list of available extensions
+ */
+ public @NonNull List<Integer> getSupportedExtensions() {
+ ArrayList<Integer> ret = new ArrayList<>();
+ long clientId = registerClient(mContext);
+ if (clientId < 0) {
+ return Collections.unmodifiableList(ret);
+ }
+
+ try {
+ for (int extensionType : EXTENSION_LIST) {
+ if (isExtensionSupported(mCameraId, extensionType, mChars)) {
+ ret.add(extensionType);
+ }
+ }
+ } finally {
+ unregisterClient(clientId);
+ }
+
+ return Collections.unmodifiableList(ret);
+ }
+
+ /**
+ * Get a list of sizes compatible with {@code klass} to use as an output for the
+ * repeating request
+ * {@link CameraExtensionSession#setRepeatingRequest}.
+ *
+ * <p>Note that device-specific extensions are allowed to support only a subset
+ * of the camera output surfaces and resolutions.
+ * The {@link android.graphics.SurfaceTexture} class is guaranteed at least one size for
+ * backward compatible cameras whereas other output classes are not guaranteed to be supported.
+ * </p>
+ *
+ * @param extension the extension type
+ * @param klass a non-{@code null} {@link Class} object reference
+ * @return non-modifiable list of available sizes or an empty list if the Surface output is not
+ * supported
+ * @throws NullPointerException if {@code klass} was {@code null}
+ * @throws IllegalArgumentException in case of unsupported extension.
+ */
+ @NonNull
+ public <T> List<Size> getExtensionSupportedSizes(@Extension int extension,
+ @NonNull Class<T> klass) {
+ if (!isOutputSupportedFor(klass)) {
+ return new ArrayList<>();
+ }
+ // TODO: Revisit this code once the Extension preview processor output format
+ // ambiguity is resolved in b/169799538.
+
+ long clientId = registerClient(mContext);
+ if (clientId < 0) {
+ throw new IllegalArgumentException("Unsupported extensions");
+ }
+
+ try {
+ if (!isExtensionSupported(mCameraId, extension, mChars)) {
+ throw new IllegalArgumentException("Unsupported extension");
+ }
+
+ Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
+ initializeExtension(extension);
+ StreamConfigurationMap streamMap = mChars.get(
+ CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+ extenders.first.init(mCameraId, mChars.getNativeMetadata());
+ return generateSupportedSizes(extenders.first.getSupportedResolutions(),
+ ImageFormat.PRIVATE, streamMap);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to query the extension supported sizes! Extension service does"
+ + " not respond!");
+ return new ArrayList<>();
+ } finally {
+ unregisterClient(clientId);
+ }
+ }
+
+ /**
+ * Check whether a given extension is available and return the
+ * supported output surface resolutions that can be used for high-quality capture
+ * requests via {@link CameraExtensionSession#capture}.
+ *
+ * <p>Note that device-specific extensions are allowed to support only a subset
+ * of the camera resolutions advertised by
+ * {@link StreamConfigurationMap#getOutputSizes}.</p>
+ *
+ * <p>Device-specific extensions currently support at most two
+ * multi-frame capture surface formats, ImageFormat.YUV_420_888 or
+ * ImageFormat.JPEG.</p>
+ *
+ * @param extension the extension type
+ * @param format device-specific extension output format
+ * @return non-modifiable list of available sizes or an empty list if the format is not
+ * supported.
+ * @throws IllegalArgumentException in case of format different from ImageFormat.JPEG /
+ * ImageFormat.YUV_420_888; or unsupported extension.
+ */
+ public @NonNull
+ List<Size> getExtensionSupportedSizes(@Extension int extension, int format) {
+ try {
+ long clientId = registerClient(mContext);
+ if (clientId < 0) {
+ throw new IllegalArgumentException("Unsupported extensions");
+ }
+
+ try {
+ if (!isExtensionSupported(mCameraId, extension, mChars)) {
+ throw new IllegalArgumentException("Unsupported extension");
+ }
+
+ Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
+ initializeExtension(extension);
+ StreamConfigurationMap streamMap = mChars.get(
+ CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+ if (format == ImageFormat.YUV_420_888) {
+ extenders.second.init(mCameraId, mChars.getNativeMetadata());
+ if (extenders.second.getCaptureProcessor() == null) {
+ // Extensions that don't implement any capture processor are limited to
+ // JPEG only!
+ return new ArrayList<>();
+ }
+ return generateSupportedSizes(extenders.second.getSupportedResolutions(),
+ format, streamMap);
+ } else if (format == ImageFormat.JPEG) {
+ extenders.second.init(mCameraId, mChars.getNativeMetadata());
+ if (extenders.second.getCaptureProcessor() == null) {
+ return generateSupportedSizes(null, format, streamMap);
+ }
+
+ return new ArrayList<>();
+ }
+
+ throw new IllegalArgumentException("Unsupported format: " + format);
+ } finally {
+ unregisterClient(clientId);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to query the extension supported sizes! Extension service does"
+ + " not respond!");
+ return new ArrayList<>();
+ }
+ }
+}
diff --git a/core/java/android/hardware/camera2/CameraExtensionSession.java b/core/java/android/hardware/camera2/CameraExtensionSession.java
new file mode 100644
index 0000000..877dfbc
--- /dev/null
+++ b/core/java/android/hardware/camera2/CameraExtensionSession.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2020 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.annotation.NonNull;
+
+import java.util.concurrent.Executor;
+
+/**
+ * A camera capture session that enables access to device-specific camera extensions, which
+ * often use multi-frame bursts and sophisticated post-process algorithms for image capture.
+ *
+ * <p>The capture session will be returned after a successful call to
+ * {@link CameraDevice#createExtensionSession} as part of the argument
+ * in the registered state callback {@link StateCallback#onConfigured}
+ * method. </p>
+ *
+ * <p>Note that CameraExtensionSession is currently limited to a maximum of two output
+ * surfaces for continuous repeating and multi-frame processing respectively. Some
+ * features such as capture settings will not be supported as the device-specific
+ * Extension is allowed to override all capture parameters.</p>
+ *
+ * <p>Information about support for specific device-specific extensions can be queried
+ * from {@link CameraExtensionCharacteristics}. </p>
+ */
+public abstract class CameraExtensionSession implements AutoCloseable {
+ /** @hide */
+ public CameraExtensionSession () {}
+
+ /**
+ * A callback object for tracking the progress of a
+ * {@link CaptureRequest} submitted to the camera device.
+ *
+ * <p>This callback is invoked when a request triggers a capture to start,
+ * and when the device-specific Extension post processing begins. In case of an
+ * error capturing an image, the error method is triggered instead of
+ * the completion method.</p>
+ *
+ * @see #capture
+ * @see #setRepeatingRequest
+ */
+ public static abstract class ExtensionCaptureCallback {
+
+ /**
+ * This method is called when the camera device has started
+ * capturing the initial input image of the device-specific extension
+ * post-process request.
+ *
+ * <p>This callback is invoked right as the capture of a frame begins,
+ * so it is the most appropriate time for playing a shutter sound,
+ * or triggering UI indicators of capture.</p>
+ *
+ * <p>The request that is being used for this capture is provided,
+ * along with the actual timestamp for the start of exposure.</p>
+ *
+ * <p>The default implementation of this method does nothing.</p>
+ *
+ * @param session the session received during
+ * {@link StateCallback#onConfigured(CameraExtensionSession)}
+ * @param request the request for the capture that just begun
+ * @param timestamp the timestamp at start of capture for repeating
+ * request or the timestamp at start of capture of the
+ * first frame in a multi-frame capture.
+ */
+ public void onCaptureStarted(@NonNull CameraExtensionSession session,
+ @NonNull CaptureRequest request, long timestamp) {
+ // default empty implementation
+ }
+
+ /**
+ * This method is called when an image (or images in case of multi-frame
+ * capture) is captured and device-specific extension
+ * processing is triggered.
+ *
+ * <p>Each request will generate at most {@code 1}
+ * {@link #onCaptureProcessStarted}.</p>
+ *
+ * <p>The default implementation of this method does nothing.</p>
+ *
+ * @param session the session received during
+ * {@link StateCallback#onConfigured(CameraExtensionSession)}
+ * @param request The request that was given to the CameraExtensionSession
+ *
+ * @see #capture
+ * @see #setRepeatingRequest
+ */
+ public void onCaptureProcessStarted(@NonNull CameraExtensionSession session,
+ @NonNull CaptureRequest request) {
+ // default empty implementation
+ }
+
+ /**
+ * This method is called instead of
+ * {@link #onCaptureProcessStarted} when the camera device failed
+ * to produce the required input for the device-specific extension. The
+ * cause could be a failed camera capture request, a failed
+ * capture result or dropped camera frame.
+ *
+ * <p>Other requests are unaffected, and some or all image buffers
+ * from the capture may have been pushed to their respective output
+ * streams.</p>
+ *
+ * <p>The default implementation of this method does nothing.</p>
+ *
+ * @param session the session received during
+ * {@link StateCallback#onConfigured(CameraExtensionSession)}
+ * @param request The request that was given to the CameraDevice
+ *
+ * @see #capture
+ * @see #setRepeatingRequest
+ */
+ public void onCaptureFailed(@NonNull CameraExtensionSession session,
+ @NonNull CaptureRequest request) {
+ // default empty implementation
+ }
+
+ /**
+ * This method is called independently of the others in
+ * ExtensionCaptureCallback, when a capture sequence finishes.
+ *
+ * <p>In total, there will be at least one
+ * {@link #onCaptureProcessStarted}/{@link #onCaptureFailed}
+ * invocation before this callback is triggered. If the capture
+ * sequence is aborted before any requests have begun processing,
+ * {@link #onCaptureSequenceAborted} is invoked instead.</p>
+ *
+ * <p>The default implementation does nothing.</p>
+ *
+ * @param session the session received during
+ * {@link StateCallback#onConfigured(CameraExtensionSession)}
+ * @param sequenceId A sequence ID returned by the {@link #capture}
+ * family of functions.
+ * @see #onCaptureSequenceAborted
+ */
+ public void onCaptureSequenceCompleted(@NonNull CameraExtensionSession session,
+ int sequenceId) {
+ // default empty implementation
+ }
+
+ /**
+ * This method is called when a capture sequence aborts.
+ *
+ * <p>Due to the asynchronous nature of the camera device, not all
+ * submitted captures are immediately processed. It is possible to
+ * clear out the pending requests by a variety of operations such
+ * as {@link CameraExtensionSession#stopRepeating}. When such an event
+ * happens, {@link #onCaptureProcessStarted} will not be called.</p>
+ *
+ * <p>The default implementation does nothing.</p>
+ *
+ * @param session the session received during
+ * {@link StateCallback#onConfigured(CameraExtensionSession)}
+ * @param sequenceId A sequence ID returned by the {@link #capture}
+ * family of functions.
+ * @see #onCaptureProcessStarted
+ */
+ public void onCaptureSequenceAborted(@NonNull CameraExtensionSession session,
+ int sequenceId) {
+ // default empty implementation
+ }
+ }
+
+ /**
+ * A callback object for receiving updates about the state of a camera extension session.
+ *
+ */
+ public static abstract class StateCallback {
+
+ /**
+ * This method is called when the camera device has finished configuring itself, and the
+ * session can start processing capture requests.
+ *
+ * <p>If the camera device configuration fails, then {@link #onConfigureFailed} will
+ * be invoked instead of this callback.</p>
+ *
+ * @param session A valid extension session
+ */
+ public abstract void onConfigured(@NonNull CameraExtensionSession session);
+
+ /**
+ * This method is called if the session cannot be configured as requested.
+ *
+ * <p>This can happen if the set of requested outputs contains unsupported sizes,
+ * too many outputs are requested at once or the camera device encounters an
+ * unrecoverable error during configuration.</p>
+ *
+ * <p>The session is considered to be closed, and all methods called on it after this
+ * callback is invoked will throw an IllegalStateException.</p>
+ *
+ * @param session the session instance that failed to configure
+ */
+ public abstract void onConfigureFailed(@NonNull CameraExtensionSession session);
+
+ /**
+ * This method is called when the session is closed.
+ *
+ * <p>A session is closed when a new session is created by the parent camera device,
+ * or when the parent camera device is closed (either by the user closing the device,
+ * or due to a camera device disconnection or fatal error).</p>
+ *
+ * <p>Once a session is closed, all methods on it will throw an IllegalStateException, and
+ * any repeating requests are stopped (as if {@link #stopRepeating()} was called).
+ * However, any in-progress capture requests submitted to the session will be completed
+ * as normal.</p>
+ *
+ * @param session the session received during
+ * {@link StateCallback#onConfigured(CameraExtensionSession)}
+ */
+ public void onClosed(@NonNull CameraExtensionSession session) {
+ // default empty implementation
+ }
+ }
+
+ /**
+ * Get the camera device that this session is created for.
+ */
+ @NonNull
+ public android.hardware.camera2.CameraDevice getDevice() {
+ throw new UnsupportedOperationException("Subclasses must override this method");
+ }
+
+ /**
+ * Submit a request for device-specific processing using input
+ * from the camera device, to produce a single high-quality output result.
+ *
+ * <p>Note that single capture requests currently do not support
+ * client parameters. Settings included in the request will
+ * be entirely overridden by the device-specific extension. </p>
+ *
+ * <p>The {@link CaptureRequest.Builder#addTarget} supports only one
+ * ImageFormat.YUV_420_888 or ImageFormat.JPEG target surface. {@link CaptureRequest}
+ * arguments that include further targets will cause
+ * IllegalArgumentException to be thrown. </p>
+ *
+ * <p>Each request will produce one new frame for one target Surface, set
+ * with the CaptureRequest builder's
+ * {@link CaptureRequest.Builder#addTarget} method.</p>
+ *
+ * <p>Multiple requests can be in progress at once. Requests are
+ * processed in first-in, first-out order.</p>
+ *
+ * <p>Requests submitted through this method have higher priority than
+ * those submitted through {@link #setRepeatingRequest}, and will be
+ * processed as soon as the current repeat processing completes.</p>
+ *
+ * @param request the settings for this capture
+ * @param executor the executor which will be used for invoking the
+ * listener.
+ * @param listener The callback object to notify once this request has
+ * been processed.
+ * @return int A unique capture sequence ID used by
+ * {@link ExtensionCaptureCallback#onCaptureSequenceCompleted}.
+ * @throws CameraAccessException if the camera device is no longer
+ * connected or has encountered a fatal error
+ * @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 request targets no Surfaces
+ * or Surfaces that are not configured as outputs for this
+ * session; or the request targets a set of Surfaces that
+ * cannot be submitted simultaneously.
+ */
+ public int capture(@NonNull CaptureRequest request,
+ @NonNull Executor executor,
+ @NonNull ExtensionCaptureCallback listener) throws CameraAccessException {
+ throw new UnsupportedOperationException("Subclasses must override this method");
+ }
+
+ /**
+ * Request endlessly repeating device-specific extension processing of
+ * camera images.
+ *
+ * <p>With this method, the camera device will continually capture images
+ * and process them using the device-specific extension at the maximum
+ * rate possible.</p>
+ *
+ * <p>Note that repeating capture requests currently do not support
+ * client parameters. Settings included in the request will
+ * be completely overridden by the device-specific extension.</p>
+ *
+ * <p>The {@link CaptureRequest.Builder#addTarget} supports only one
+ * target surface. {@link CaptureRequest} arguments that include further
+ * targets will cause IllegalArgumentException to be thrown.</p>
+ *
+ * <p>Repeating requests are a simple way for an application to maintain a
+ * preview or other continuous stream of frames.</p>
+ *
+ * <p>Repeat requests have lower priority than those submitted
+ * through {@link #capture}, so if {@link #capture} is called when a
+ * repeating request is active, the capture request will be processed
+ * before any further repeating requests are processed.</p>
+ *
+ * <p>To stop the repeating capture, call {@link #stopRepeating}.</p>
+ *
+ * <p>Calling this method will replace any earlier repeating request.</p>
+ *
+ * @param request the request to repeat indefinitely
+ * @param executor the executor which will be used for invoking the
+ * listener.
+ * @param listener The callback object to notify every time the
+ * request finishes processing.
+ * @return int A unique capture sequence ID used by
+ * {@link ExtensionCaptureCallback#onCaptureSequenceCompleted}.
+ * @throws CameraAccessException if the camera device is no longer
+ * connected or has encountered a fatal error
+ * @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 request references no
+ * Surfaces or references Surfaces that are not currently
+ * configured as outputs.
+ * @see #capture
+ */
+ public int setRepeatingRequest(@NonNull CaptureRequest request,
+ @NonNull Executor executor,
+ @NonNull ExtensionCaptureCallback listener) throws CameraAccessException {
+ throw new UnsupportedOperationException("Subclasses must override this method");
+ }
+
+ /**
+ * Cancel any ongoing repeating capture set by
+ * {@link #setRepeatingRequest setRepeatingRequest}. Has no effect on
+ * requests submitted through {@link #capture capture}.
+ *
+ * <p>Any currently in-flight captures will still complete.</p>
+ *
+ * @throws CameraAccessException if the camera device is no longer
+ * connected or has encountered a fatal error
+ * @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.
+ * @see #setRepeatingRequest
+ */
+ public void stopRepeating() throws CameraAccessException {
+ throw new UnsupportedOperationException("Subclasses must override this method");
+ }
+
+ /**
+ * Close this capture session asynchronously.
+ *
+ * <p>Closing a session frees up the target output Surfaces of the session
+ * for reuse with either a new session, or to other APIs that can draw
+ * to Surfaces.</p>
+ *
+ * <p>Note that creating a new capture session with
+ * {@link android.hardware.camera2.CameraDevice#createCaptureSession} or
+ * {@link android.hardware.camera2.CameraDevice#createExtensionSession}
+ * will close any existing capture session automatically, and call the
+ * older session listener's {@link StateCallback#onClosed} callback.
+ * Using
+ * {@link android.hardware.camera2.CameraDevice#createCaptureSession} or
+ * {@link android.hardware.camera2.CameraDevice#createExtensionSession}
+ * directly without closing is the recommended approach for quickly
+ * switching to a new session, since unchanged target outputs can be
+ * reused more efficiently.</p>
+ *
+ * <p>Once a session is closed, all methods on it will throw an
+ * IllegalStateException, and any repeating requests are
+ * stopped (as if {@link #stopRepeating()} was called).</p>
+ *
+ * <p>Closing a session is idempotent; closing more than once has no
+ * effect.</p>
+ */
+ public void close() throws CameraAccessException {
+ throw new UnsupportedOperationException("Subclasses must override this method");
+ }
+}
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index cdfdc1f..3e0e3f62 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -29,6 +29,7 @@
import android.hardware.ICameraServiceListener;
import android.hardware.camera2.impl.CameraDeviceImpl;
import android.hardware.camera2.impl.CameraMetadataNative;
+import android.hardware.camera2.params.ExtensionSessionConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.utils.CameraIdAndSessionConfiguration;
import android.hardware.camera2.utils.ConcurrentCameraIdCombination;
@@ -435,6 +436,28 @@
}
/**
+ * <p>Query the camera extension capabilities of a camera device.</p>
+ *
+ * @param cameraId The id of the camera device to query. This must be a standalone
+ * camera ID which can be directly opened by {@link #openCamera}.
+ * @return The properties of the given camera
+ *
+ * @throws IllegalArgumentException if the cameraId does not match any
+ * known camera device.
+ * @throws CameraAccessException if the camera device has been disconnected.
+ *
+ * @see CameraExtensionCharacteristics
+ * @see CameraDevice#createExtensionSession(ExtensionSessionConfiguration)
+ * @see CameraExtensionSession
+ */
+ @NonNull
+ public CameraExtensionCharacteristics getCameraExtensionCharacteristics(
+ @NonNull String cameraId) throws CameraAccessException {
+ CameraCharacteristics chars = getCameraCharacteristics(cameraId);
+ return new CameraExtensionCharacteristics(mContext, cameraId, chars);
+ }
+
+ /**
* Helper for opening a connection to a camera with the given ID.
*
* @param cameraId The unique identifier of the camera device to open
@@ -473,7 +496,8 @@
callback,
executor,
characteristics,
- mContext.getApplicationInfo().targetSdkVersion);
+ mContext.getApplicationInfo().targetSdkVersion,
+ mContext);
ICameraDeviceCallbacks callbacks = deviceImpl.getCallbacks();
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 41cc12f..924dcee 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -112,6 +112,15 @@
}
/**
+ * Retrieves the CameraMetadataNative instance.
+ *
+ * @hide
+ */
+ public CameraMetadataNative getNativeMetadata() {
+ return mNativeInstance;
+ }
+
+ /**
* @hide
*/
protected abstract Class<TKey> getKeyClass();
diff --git a/core/java/android/hardware/camera2/extension/CaptureBundle.aidl b/core/java/android/hardware/camera2/extension/CaptureBundle.aidl
new file mode 100644
index 0000000..24a0d2f
--- /dev/null
+++ b/core/java/android/hardware/camera2/extension/CaptureBundle.aidl
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2020, 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.extension;
+
+import android.hardware.camera2.impl.CameraMetadataNative;
+import android.hardware.camera2.extension.ParcelImage;
+
+/** @hide */
+parcelable CaptureBundle
+{
+ int stage;
+ int sequenceId;
+ CameraMetadataNative captureResult;
+ ParcelImage captureImage;
+}
diff --git a/core/java/android/hardware/camera2/extension/CaptureStageImpl.aidl b/core/java/android/hardware/camera2/extension/CaptureStageImpl.aidl
new file mode 100644
index 0000000..f2187a3
--- /dev/null
+++ b/core/java/android/hardware/camera2/extension/CaptureStageImpl.aidl
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2020, 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.extension;
+
+import android.hardware.camera2.impl.CameraMetadataNative;
+
+/** @hide */
+parcelable CaptureStageImpl
+{
+ int id;
+ CameraMetadataNative parameters;
+}
diff --git a/core/java/android/hardware/camera2/extension/ICameraExtensionsProxyService.aidl b/core/java/android/hardware/camera2/extension/ICameraExtensionsProxyService.aidl
new file mode 100644
index 0000000..2a6d22c
--- /dev/null
+++ b/core/java/android/hardware/camera2/extension/ICameraExtensionsProxyService.aidl
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2020, 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.extension;
+
+import android.hardware.camera2.extension.IPreviewExtenderImpl;
+import android.hardware.camera2.extension.IImageCaptureExtenderImpl;
+
+/** @hide */
+interface ICameraExtensionsProxyService
+{
+ long registerClient();
+ void unregisterClient(long clientId);
+ @nullable IPreviewExtenderImpl initializePreviewExtension(int extensionType);
+ @nullable IImageCaptureExtenderImpl initializeImageExtension(int extensionType);
+}
diff --git a/core/java/android/hardware/camera2/extension/ICaptureProcessorImpl.aidl b/core/java/android/hardware/camera2/extension/ICaptureProcessorImpl.aidl
new file mode 100644
index 0000000..022b084
--- /dev/null
+++ b/core/java/android/hardware/camera2/extension/ICaptureProcessorImpl.aidl
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2020, 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.extension;
+
+import android.view.Surface;
+import android.hardware.camera2.extension.CaptureBundle;
+import android.hardware.camera2.extension.Size;
+
+/** @hide */
+interface ICaptureProcessorImpl
+{
+ void onOutputSurface(in Surface surface, int imageFormat);
+ void onResolutionUpdate(in Size size);
+ void onImageFormatUpdate(int imageFormat);
+ void process(in List<CaptureBundle> capturelist);
+}
diff --git a/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl b/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
new file mode 100644
index 0000000..c04e75e
--- /dev/null
+++ b/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2020, 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.extension;
+
+import android.hardware.camera2.impl.CameraMetadataNative;
+
+import android.hardware.camera2.extension.CaptureStageImpl;
+import android.hardware.camera2.extension.ICaptureProcessorImpl;
+import android.hardware.camera2.extension.SizeList;
+
+/** @hide */
+interface IImageCaptureExtenderImpl
+{
+ void onInit(in String cameraId, in CameraMetadataNative cameraCharacteristics);
+ void onDeInit();
+ @nullable CaptureStageImpl onPresetSession();
+ @nullable CaptureStageImpl onEnableSession();
+ @nullable CaptureStageImpl onDisableSession();
+
+ boolean isExtensionAvailable(in String cameraId, in CameraMetadataNative chars);
+ void init(in String cameraId, in CameraMetadataNative chars);
+ @nullable ICaptureProcessorImpl getCaptureProcessor();
+ @nullable List<CaptureStageImpl> getCaptureStages();
+ int getMaxCaptureStage();
+ @nullable List<SizeList> getSupportedResolutions();
+}
diff --git a/core/java/android/hardware/camera2/extension/IPreviewExtenderImpl.aidl b/core/java/android/hardware/camera2/extension/IPreviewExtenderImpl.aidl
new file mode 100644
index 0000000..2d67344
--- /dev/null
+++ b/core/java/android/hardware/camera2/extension/IPreviewExtenderImpl.aidl
@@ -0,0 +1,46 @@
+/**
+ * Copyright (c) 2020, 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.extension;
+
+import android.hardware.camera2.impl.CameraMetadataNative;
+
+import android.hardware.camera2.extension.CaptureStageImpl;
+import android.hardware.camera2.extension.IPreviewImageProcessorImpl;
+import android.hardware.camera2.extension.IRequestUpdateProcessorImpl;
+import android.hardware.camera2.extension.SizeList;
+
+/** @hide */
+interface IPreviewExtenderImpl
+{
+ void onInit(in String cameraId, in CameraMetadataNative cameraCharacteristics);
+ void onDeInit();
+ @nullable CaptureStageImpl onPresetSession();
+ @nullable CaptureStageImpl onEnableSession();
+ @nullable CaptureStageImpl onDisableSession();
+
+ void init(in String cameraId, in CameraMetadataNative chars);
+ boolean isExtensionAvailable(in String cameraId, in CameraMetadataNative chars);
+ @nullable CaptureStageImpl getCaptureStage();
+
+ const int PROCESSOR_TYPE_REQUEST_UPDATE_ONLY = 0;
+ const int PROCESSOR_TYPE_IMAGE_PROCESSOR = 1;
+ const int PROCESSOR_TYPE_NONE = 2;
+ int getProcessorType();
+ @nullable IPreviewImageProcessorImpl getPreviewImageProcessor();
+ @nullable IRequestUpdateProcessorImpl getRequestUpdateProcessor();
+
+ @nullable List<SizeList> getSupportedResolutions();
+}
diff --git a/core/java/android/hardware/camera2/extension/IPreviewImageProcessorImpl.aidl b/core/java/android/hardware/camera2/extension/IPreviewImageProcessorImpl.aidl
new file mode 100644
index 0000000..f7e4023
--- /dev/null
+++ b/core/java/android/hardware/camera2/extension/IPreviewImageProcessorImpl.aidl
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2020, 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.extension;
+
+import android.hardware.camera2.impl.CameraMetadataNative;
+import android.view.Surface;
+import android.hardware.camera2.extension.ParcelImage;
+import android.hardware.camera2.extension.Size;
+
+/** @hide */
+interface IPreviewImageProcessorImpl
+{
+ void onOutputSurface(in Surface surface, int imageFormat);
+ void onResolutionUpdate(in Size size);
+ void onImageFormatUpdate(int imageFormat);
+ void process(in ParcelImage image, in CameraMetadataNative result, int sequenceId);
+}
diff --git a/core/java/android/hardware/camera2/extension/IRequestUpdateProcessorImpl.aidl b/core/java/android/hardware/camera2/extension/IRequestUpdateProcessorImpl.aidl
new file mode 100644
index 0000000..f077024
--- /dev/null
+++ b/core/java/android/hardware/camera2/extension/IRequestUpdateProcessorImpl.aidl
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2020, 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.extension;
+
+import android.hardware.camera2.impl.CameraMetadataNative;
+import android.view.Surface;
+import android.hardware.camera2.extension.CaptureStageImpl;
+import android.hardware.camera2.extension.Size;
+
+/** @hide */
+interface IRequestUpdateProcessorImpl
+{
+ void onOutputSurface(in Surface surface, int imageFormat);
+ void onResolutionUpdate(in Size size);
+ void onImageFormatUpdate(int imageFormat);
+ @nullable CaptureStageImpl process(in CameraMetadataNative result, int sequenceId);
+}
diff --git a/core/java/android/hardware/camera2/extension/ParcelImage.aidl b/core/java/android/hardware/camera2/extension/ParcelImage.aidl
new file mode 100644
index 0000000..dd5584f
--- /dev/null
+++ b/core/java/android/hardware/camera2/extension/ParcelImage.aidl
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2020, 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.extension;
+
+import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
+import android.os.ParcelFileDescriptor;
+
+/** @hide */
+parcelable ParcelImage
+{
+ int format;
+ int width;
+ int height;
+ int transform;
+ int scalingMode;
+ long timestamp;
+ int planeCount;
+ Rect crop;
+ HardwareBuffer buffer;
+ ParcelFileDescriptor fence;
+}
diff --git a/core/java/android/hardware/camera2/extension/Size.aidl b/core/java/android/hardware/camera2/extension/Size.aidl
new file mode 100644
index 0000000..ec222d3
--- /dev/null
+++ b/core/java/android/hardware/camera2/extension/Size.aidl
@@ -0,0 +1,23 @@
+/**
+ * Copyright (c) 2020, 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.extension;
+
+/** @hide */
+parcelable Size
+{
+ int width;
+ int height;
+}
diff --git a/core/java/android/hardware/camera2/extension/SizeList.aidl b/core/java/android/hardware/camera2/extension/SizeList.aidl
new file mode 100644
index 0000000..946cf98
--- /dev/null
+++ b/core/java/android/hardware/camera2/extension/SizeList.aidl
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2020, 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.extension;
+
+import android.hardware.camera2.extension.Size;
+
+/** @hide */
+parcelable SizeList
+{
+ int format;
+ List<Size> sizes;
+}
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index f564ad7..ce3c81a 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -19,10 +19,12 @@
import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable;
import android.annotation.NonNull;
+import android.content.Context;
import android.hardware.ICameraService;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraExtensionSession;
import android.hardware.camera2.CameraOfflineSession;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CaptureFailure;
@@ -32,6 +34,7 @@
import android.hardware.camera2.ICameraDeviceUser;
import android.hardware.camera2.ICameraOfflineSession;
import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.ExtensionSessionConfiguration;
import android.hardware.camera2.params.InputConfiguration;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
@@ -110,6 +113,7 @@
private final String mCameraId;
private final CameraCharacteristics mCharacteristics;
private final int mTotalPartialCount;
+ private final Context mContext;
private static final long NANO_PER_SECOND = 1000000000; //ns
@@ -127,6 +131,7 @@
private FrameNumberTracker mFrameNumberTracker = new FrameNumberTracker();
private CameraCaptureSessionCore mCurrentSession;
+ private CameraExtensionSessionImpl mCurrentExtensionSession;
private int mNextSessionId = 0;
private final int mAppTargetSdkVersion;
@@ -250,7 +255,8 @@
};
public CameraDeviceImpl(String cameraId, StateCallback callback, Executor executor,
- CameraCharacteristics characteristics, int appTargetSdkVersion) {
+ CameraCharacteristics characteristics, int appTargetSdkVersion,
+ Context ctx) {
if (cameraId == null || callback == null || executor == null || characteristics == null) {
throw new IllegalArgumentException("Null argument given");
}
@@ -259,6 +265,7 @@
mDeviceExecutor = executor;
mCharacteristics = characteristics;
mAppTargetSdkVersion = appTargetSdkVersion;
+ mContext = ctx;
final int MAX_TAG_LEN = 23;
String tag = String.format("CameraDevice-JV-%s", mCameraId);
@@ -1322,6 +1329,10 @@
mRemoteDevice.unlinkToDeath(this, /*flags*/0);
}
+ if (mCurrentExtensionSession != null) {
+ mCurrentExtensionSession.release();
+ mCurrentExtensionSession = null;
+ }
// Only want to fire the onClosed callback once;
// either a normal close where the remote device is valid
// or a close after a startup error (no remote device but in error state)
@@ -2297,4 +2308,16 @@
return mRemoteDevice.getGlobalAudioRestriction();
}
}
+
+ @Override
+ public void createExtensionSession(ExtensionSessionConfiguration extensionConfiguration)
+ throws CameraAccessException {
+ try {
+ mCurrentExtensionSession = CameraExtensionSessionImpl.createCameraExtensionSession(this,
+ mContext,
+ extensionConfiguration);
+ } catch (RemoteException e) {
+ throw new CameraAccessException(CameraAccessException.CAMERA_ERROR);
+ }
+ }
}
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionForwardProcessor.java b/core/java/android/hardware/camera2/impl/CameraExtensionForwardProcessor.java
new file mode 100644
index 0000000..8eb1dcc
--- /dev/null
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionForwardProcessor.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2020 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.impl;
+
+import android.annotation.SuppressLint;
+import android.hardware.camera2.CameraExtensionCharacteristics;
+import android.hardware.camera2.extension.IPreviewImageProcessorImpl;
+import android.hardware.camera2.extension.ParcelImage;
+import android.hardware.camera2.TotalCaptureResult;
+import android.media.Image;
+import android.media.ImageReader;
+import android.media.ImageWriter;
+import android.annotation.NonNull;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Size;
+import android.view.Surface;
+
+// Wrap a given 'PreviewImageProcessorImpl' so that the processed output can
+// be redirected to a given surface or looped back in the internal intermediate surface.
+public class CameraExtensionForwardProcessor {
+ public final static String TAG = "CameraExtensionForward";
+ private final static int FORWARD_QUEUE_SIZE = 3;
+
+ private final IPreviewImageProcessorImpl mProcessor;
+ private final long mOutputSurfaceUsage;
+ private final int mOutputSurfaceFormat;
+
+ private ImageReader mIntermediateReader = null;
+ private Surface mIntermediateSurface = null;
+ private Size mResolution = null;
+ private Surface mOutputSurface = null;
+ private ImageWriter mOutputWriter = null;
+ private boolean mOutputAbandoned = false;
+
+ public CameraExtensionForwardProcessor(@NonNull IPreviewImageProcessorImpl processor,
+ int format, long surfaceUsage) {
+ mProcessor = processor;
+ mOutputSurfaceUsage = surfaceUsage;
+ mOutputSurfaceFormat = format;
+ }
+
+ public void close() {
+ if (mOutputWriter != null) {
+ mOutputWriter.close();
+ mOutputWriter = null;
+ }
+
+ if (mIntermediateReader != null) {
+ mIntermediateReader.close();
+ mIntermediateReader = null;
+ }
+ }
+
+ public void onOutputSurface(Surface surface, int format) {
+ mOutputSurface = surface;
+ try {
+ initializePipeline();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to initialize forward processor, extension service does not"
+ + " respond!");
+ }
+ }
+
+ public void onResolutionUpdate(Size size) {
+ mResolution = size;
+ }
+
+ public void onImageFormatUpdate(int format) {
+ if (format != CameraExtensionCharacteristics.PROCESSING_INPUT_FORMAT) {
+ Log.e(TAG, "Unsupported input format: " + format);
+ }
+ }
+
+ @SuppressLint("WrongConstant")
+ private void initializePipeline() throws RemoteException {
+ if (mOutputWriter != null) {
+ mOutputWriter.close();
+ mOutputWriter = null;
+ }
+
+ if (mIntermediateReader == null) {
+ mIntermediateReader = ImageReader.newInstance(mResolution.getWidth(),
+ mResolution.getHeight(), CameraExtensionCharacteristics.PROCESSING_INPUT_FORMAT,
+ FORWARD_QUEUE_SIZE, mOutputSurfaceUsage);
+ mIntermediateSurface = mIntermediateReader.getSurface();
+ mIntermediateReader.setOnImageAvailableListener(new ForwardCallback(), null);
+
+ mProcessor.onOutputSurface(mIntermediateSurface, mOutputSurfaceFormat);
+ // PreviewImageProcessorImpl always expect the extension processing format as input
+ mProcessor.onImageFormatUpdate(CameraExtensionCharacteristics.PROCESSING_INPUT_FORMAT);
+ android.hardware.camera2.extension.Size sz =
+ new android.hardware.camera2.extension.Size();
+ sz.width = mResolution.getWidth();
+ sz.height = mResolution.getHeight();
+ mProcessor.onResolutionUpdate(sz);
+ }
+ }
+
+ public void process(ParcelImage image, TotalCaptureResult totalCaptureResult)
+ throws RemoteException {
+ if ((mIntermediateSurface != null) && (mIntermediateSurface.isValid()) &&
+ !mOutputAbandoned) {
+ mProcessor.process(image, totalCaptureResult.getNativeMetadata(),
+ totalCaptureResult.getSequenceId());
+ }
+ }
+
+ private class ForwardCallback implements ImageReader.OnImageAvailableListener {
+ @Override public void onImageAvailable(ImageReader reader) {
+ Image processedImage = null;
+ try {
+ processedImage = mIntermediateReader.acquireNextImage();
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Failed to acquire processed image!");
+ return;
+ }
+
+ if (mOutputSurface != null && mOutputSurface.isValid() && !mOutputAbandoned) {
+ if (mOutputWriter == null) {
+ mOutputWriter = ImageWriter.newInstance(mOutputSurface, FORWARD_QUEUE_SIZE,
+ processedImage.getFormat());
+ }
+ try {
+ mOutputWriter.queueInputImage(processedImage);
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Failed to queue processed buffer!");
+ processedImage.close();
+ mOutputAbandoned = true;
+ }
+ } else {
+ processedImage.close();
+ }
+ }
+ }
+}
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
new file mode 100644
index 0000000..8fe7158
--- /dev/null
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -0,0 +1,1649 @@
+/*
+ * Copyright (C) 2020 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.impl;
+
+import android.content.Context;
+import android.graphics.ImageFormat;
+import android.graphics.PixelFormat;
+import android.graphics.SurfaceTexture;
+import android.hardware.HardwareBuffer;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraExtensionCharacteristics;
+import android.hardware.camera2.CameraExtensionSession;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureFailure;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.extension.CaptureBundle;
+import android.hardware.camera2.extension.CaptureStageImpl;
+import android.hardware.camera2.extension.ICaptureProcessorImpl;
+import android.hardware.camera2.extension.IImageCaptureExtenderImpl;
+import android.hardware.camera2.extension.IPreviewExtenderImpl;
+import android.hardware.camera2.extension.IRequestUpdateProcessorImpl;
+import android.hardware.camera2.extension.ParcelImage;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.ExtensionSessionConfiguration;
+import android.hardware.camera2.params.OutputConfiguration;
+import android.hardware.camera2.params.SessionConfiguration;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.hardware.camera2.utils.SurfaceUtils;
+import android.media.Image;
+import android.media.ImageReader;
+import android.media.ImageWriter;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.Pair;
+import android.util.Size;
+import android.view.Surface;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+
+public final class CameraExtensionSessionImpl extends CameraExtensionSession {
+ private static final int PREVIEW_QUEUE_SIZE = 3;
+ private static final String TAG = "CameraExtensionSessionImpl";
+
+ private final Executor mExecutor;
+ private final CameraDevice mCameraDevice;
+ private final long mExtensionClientId;
+ private final IImageCaptureExtenderImpl mImageExtender;
+ private final IPreviewExtenderImpl mPreviewExtender;
+ private final Handler mHandler;
+ private final HandlerThread mHandlerThread;
+ private final StateCallback mCallbacks;
+ private final List<Size> mSupportedPreviewSizes;
+
+ private CameraCaptureSession mCaptureSession = null;
+ private Surface mCameraRepeatingSurface, mClientRepeatingRequestSurface;
+ private Surface mCameraBurstSurface, mClientCaptureSurface;
+ private ImageReader mRepeatingRequestImageReader = null;
+ private ImageReader mBurstCaptureImageReader = null;
+ private ImageReader mStubCaptureImageReader = null;
+ private ImageWriter mRepeatingRequestImageWriter = null;
+
+ private ICaptureProcessorImpl mImageProcessor = null;
+ private CameraExtensionForwardProcessor mPreviewImageProcessor = null;
+ private IRequestUpdateProcessorImpl mPreviewRequestUpdateProcessor = null;
+ private int mPreviewProcessorType = IPreviewExtenderImpl.PROCESSOR_TYPE_NONE;
+
+ private boolean mInitialized;
+ // Enable/Disable internal preview/(repeating request). Extensions expect
+ // that preview/(repeating request) is enabled and active at any point in time.
+ // In case the client doesn't explicitly enable repeating requests, the framework
+ // will do so internally.
+ private boolean mInternalRepeatingRequestEnabled = true;
+
+ // Lock to synchronize cross-thread access to device public interface
+ final Object mInterfaceLock = new Object(); // access from this class and Session only!
+
+ private static class SurfaceInfo {
+ public int mWidth = 0;
+ public int mHeight = 0;
+ public int mFormat = PixelFormat.RGBA_8888;
+ public long mUsage = 0;
+ }
+
+ private static final int SUPPORTED_CAPTURE_OUTPUT_FORMATS[] = {
+ CameraExtensionCharacteristics.PROCESSING_INPUT_FORMAT,
+ ImageFormat.JPEG
+ };
+
+ private static int nativeGetSurfaceWidth(Surface surface) {
+ return SurfaceUtils.getSurfaceSize(surface).getWidth();
+ }
+
+ private static int nativeGetSurfaceHeight(Surface surface) {
+ return SurfaceUtils.getSurfaceSize(surface).getHeight();
+ }
+
+ private static int nativeGetSurfaceFormat(Surface surface) {
+ return SurfaceUtils.getSurfaceFormat(surface);
+ }
+
+ private static Surface getBurstCaptureSurface(
+ @NonNull List<OutputConfiguration> outputConfigs,
+ @NonNull HashMap<Integer, List<Size>> supportedCaptureSizes) {
+ for (OutputConfiguration config : outputConfigs) {
+ SurfaceInfo surfaceInfo = querySurface(config.getSurface());
+ for (int supportedFormat : SUPPORTED_CAPTURE_OUTPUT_FORMATS) {
+ if (surfaceInfo.mFormat == supportedFormat) {
+ Size captureSize = new Size(surfaceInfo.mWidth, surfaceInfo.mHeight);
+ if (supportedCaptureSizes.containsKey(supportedFormat)) {
+ if (supportedCaptureSizes.get(surfaceInfo.mFormat).contains(captureSize)) {
+ return config.getSurface();
+ } else {
+ throw new IllegalArgumentException("Capture size not supported!");
+ }
+ }
+ return config.getSurface();
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private static @Nullable Surface getRepeatingRequestSurface(
+ @NonNull List<OutputConfiguration> outputConfigs,
+ @Nullable List<Size> supportedPreviewSizes) {
+ for (OutputConfiguration config : outputConfigs) {
+ SurfaceInfo surfaceInfo = querySurface(config.getSurface());
+ if ((surfaceInfo.mFormat ==
+ CameraExtensionCharacteristics.NON_PROCESSING_INPUT_FORMAT) ||
+ // The default RGBA_8888 is also implicitly supported because camera will
+ // internally override it to
+ // 'CameraExtensionCharacteristics.NON_PROCESSING_INPUT_FORMAT'
+ (surfaceInfo.mFormat == PixelFormat.RGBA_8888)) {
+ Size repeatingRequestSurfaceSize = new Size(surfaceInfo.mWidth,
+ surfaceInfo.mHeight);
+ if ((supportedPreviewSizes == null) ||
+ (!supportedPreviewSizes.contains(repeatingRequestSurfaceSize))) {
+ throw new IllegalArgumentException("Repeating request surface size " +
+ repeatingRequestSurfaceSize + " not supported!");
+ }
+
+ return config.getSurface();
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.CAMERA)
+ public static CameraExtensionSessionImpl createCameraExtensionSession(
+ @NonNull CameraDevice cameraDevice,
+ @NonNull Context ctx,
+ @NonNull ExtensionSessionConfiguration config)
+ throws CameraAccessException, RemoteException {
+ long clientId = CameraExtensionCharacteristics.registerClient(ctx);
+ if (clientId < 0) {
+ throw new UnsupportedOperationException("Unsupported extension!");
+ }
+
+ String cameraId = cameraDevice.getId();
+ CameraManager manager = ctx.getSystemService(CameraManager.class);
+ CameraCharacteristics chars = manager.getCameraCharacteristics(cameraId);
+ CameraExtensionCharacteristics extensionChars = new CameraExtensionCharacteristics(ctx,
+ cameraId, chars);
+
+ if (!CameraExtensionCharacteristics.isExtensionSupported(cameraDevice.getId(),
+ config.getExtension(), chars)) {
+ throw new UnsupportedOperationException("Unsupported extension type: " +
+ config.getExtension());
+ }
+
+ if (config.getOutputConfigurations().isEmpty() ||
+ config.getOutputConfigurations().size() > 2) {
+ throw new IllegalArgumentException("Unexpected amount of output surfaces, received: " +
+ config.getOutputConfigurations().size() + " expected <= 2");
+ }
+
+ Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
+ CameraExtensionCharacteristics.initializeExtension(config.getExtension());
+
+ int suitableSurfaceCount = 0;
+ List<Size> supportedPreviewSizes = extensionChars.getExtensionSupportedSizes(
+ config.getExtension(), SurfaceTexture.class);
+ Surface repeatingRequestSurface = getRepeatingRequestSurface(
+ config.getOutputConfigurations(), supportedPreviewSizes);
+ if (repeatingRequestSurface != null) {
+ suitableSurfaceCount++;
+ }
+
+ HashMap<Integer, List<Size>> supportedCaptureSizes = new HashMap<>();
+ for (int format : SUPPORTED_CAPTURE_OUTPUT_FORMATS) {
+ List<Size> supportedSizes = extensionChars.getExtensionSupportedSizes(
+ config.getExtension(), format);
+ if (supportedSizes != null) {
+ supportedCaptureSizes.put(format, supportedSizes);
+ }
+ }
+ Surface burstCaptureSurface = getBurstCaptureSurface(config.getOutputConfigurations(),
+ supportedCaptureSizes);
+ if (burstCaptureSurface != null) {
+ suitableSurfaceCount++;
+ }
+
+ if (suitableSurfaceCount != config.getOutputConfigurations().size()) {
+ throw new IllegalArgumentException("One or more unsupported output surfaces found!");
+ }
+
+ extenders.first.init(cameraId, chars.getNativeMetadata());
+ extenders.first.onInit(cameraId, chars.getNativeMetadata());
+ extenders.second.init(cameraId, chars.getNativeMetadata());
+ extenders.second.onInit(cameraId, chars.getNativeMetadata());
+
+ CameraExtensionSessionImpl session = new CameraExtensionSessionImpl(
+ extenders.second,
+ extenders.first,
+ supportedPreviewSizes,
+ clientId,
+ cameraDevice,
+ repeatingRequestSurface,
+ burstCaptureSurface,
+ config.getStateCallback(),
+ config.getExecutor());
+
+ session.initialize();
+
+ return session;
+ }
+
+ private CameraExtensionSessionImpl(@NonNull IImageCaptureExtenderImpl imageExtender,
+ @NonNull IPreviewExtenderImpl previewExtender,
+ @NonNull List<Size> previewSizes,
+ long extensionClientId,
+ @NonNull CameraDevice cameraDevice,
+ @Nullable Surface repeatingRequestSurface,
+ @Nullable Surface burstCaptureSurface,
+ @NonNull StateCallback callback,
+ @NonNull Executor executor) {
+ mExtensionClientId = extensionClientId;
+ mImageExtender = imageExtender;
+ mPreviewExtender = previewExtender;
+ mCameraDevice = cameraDevice;
+ mCallbacks = callback;
+ mExecutor = executor;
+ mClientRepeatingRequestSurface = repeatingRequestSurface;
+ mClientCaptureSurface = burstCaptureSurface;
+ mSupportedPreviewSizes = previewSizes;
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ mInitialized = false;
+ }
+
+ private static @NonNull SurfaceInfo querySurface(@NonNull Surface s) {
+ ImageWriter writer = null;
+ Image img = null;
+ SurfaceInfo surfaceInfo = new SurfaceInfo();
+ int nativeFormat = nativeGetSurfaceFormat(s);
+ int dataspace = SurfaceUtils.getSurfaceDataspace(s);
+ // Jpeg surfaces cannot be queried for their usage and other parameters
+ // in the usual way below. A buffer can only be de-queued after the
+ // producer overrides the surface dimensions to (width*height) x 1.
+ if ((nativeFormat == StreamConfigurationMap.HAL_PIXEL_FORMAT_BLOB) &&
+ (dataspace == StreamConfigurationMap.HAL_DATASPACE_V0_JFIF)) {
+ surfaceInfo.mFormat = ImageFormat.JPEG;
+ surfaceInfo.mWidth = nativeGetSurfaceWidth(s);
+ surfaceInfo.mHeight = nativeGetSurfaceHeight(s);
+ return surfaceInfo;
+ }
+
+ HardwareBuffer buffer = null;
+ try {
+ writer = ImageWriter.newInstance(s, 1);
+ img = writer.dequeueInputImage();
+ buffer = img.getHardwareBuffer();
+ surfaceInfo.mFormat = buffer.getFormat();
+ surfaceInfo.mWidth = buffer.getWidth();
+ surfaceInfo.mHeight = buffer.getHeight();
+ surfaceInfo.mUsage = buffer.getUsage();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to query surface, returning defaults!");
+ } finally {
+ if (buffer != null) {
+ buffer.close();
+ }
+ if (img != null) {
+ img.close();
+ }
+ if (writer != null) {
+ writer.close();
+ }
+ }
+
+ return surfaceInfo;
+ }
+
+ private void initializeRepeatingRequestPipeline() throws RemoteException {
+ SurfaceInfo repeatingSurfaceInfo = new SurfaceInfo();
+ mPreviewProcessorType = mPreviewExtender.getProcessorType();
+ if (mClientRepeatingRequestSurface != null) {
+ repeatingSurfaceInfo = querySurface(mClientRepeatingRequestSurface);
+ } else {
+ // Make the intermediate surface behave as any regular 'SurfaceTexture'
+ SurfaceInfo captureSurfaceInfo = querySurface(mClientCaptureSurface);
+ Size captureSize = new Size(captureSurfaceInfo.mWidth, captureSurfaceInfo.mHeight);
+ Size previewSize = findSmallestAspectMatchedSize(mSupportedPreviewSizes, captureSize);
+ repeatingSurfaceInfo.mWidth = previewSize.getWidth();
+ repeatingSurfaceInfo.mHeight = previewSize.getHeight();
+ repeatingSurfaceInfo.mUsage = HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE;
+ }
+
+ if (mPreviewProcessorType == IPreviewExtenderImpl.PROCESSOR_TYPE_IMAGE_PROCESSOR) {
+ try {
+ mPreviewImageProcessor = new CameraExtensionForwardProcessor(
+ mPreviewExtender.getPreviewImageProcessor(), repeatingSurfaceInfo.mFormat,
+ repeatingSurfaceInfo.mUsage);
+ } catch (ClassCastException e) {
+ throw new UnsupportedOperationException("Failed casting preview processor!");
+ }
+ mPreviewImageProcessor.onImageFormatUpdate(
+ CameraExtensionCharacteristics.PROCESSING_INPUT_FORMAT);
+ mPreviewImageProcessor.onResolutionUpdate(new Size(repeatingSurfaceInfo.mWidth,
+ repeatingSurfaceInfo.mHeight));
+ mRepeatingRequestImageReader = ImageReader.newInstance(repeatingSurfaceInfo.mWidth,
+ repeatingSurfaceInfo.mHeight,
+ CameraExtensionCharacteristics.PROCESSING_INPUT_FORMAT, PREVIEW_QUEUE_SIZE,
+ repeatingSurfaceInfo.mUsage);
+ mCameraRepeatingSurface = mRepeatingRequestImageReader.getSurface();
+ } else if (mPreviewProcessorType ==
+ IPreviewExtenderImpl.PROCESSOR_TYPE_REQUEST_UPDATE_ONLY) {
+ try {
+ mPreviewRequestUpdateProcessor = mPreviewExtender.getRequestUpdateProcessor();
+ } catch (ClassCastException e) {
+ throw new UnsupportedOperationException("Failed casting preview processor!");
+ }
+ if (mClientRepeatingRequestSurface != null) {
+ mPreviewRequestUpdateProcessor.onOutputSurface(mClientRepeatingRequestSurface,
+ nativeGetSurfaceFormat(mClientRepeatingRequestSurface));
+ mRepeatingRequestImageWriter = ImageWriter.newInstance(
+ mClientRepeatingRequestSurface, PREVIEW_QUEUE_SIZE,
+ CameraExtensionCharacteristics.NON_PROCESSING_INPUT_FORMAT);
+ }
+ mRepeatingRequestImageReader = ImageReader.newInstance(repeatingSurfaceInfo.mWidth,
+ repeatingSurfaceInfo.mHeight,
+ CameraExtensionCharacteristics.NON_PROCESSING_INPUT_FORMAT,
+ PREVIEW_QUEUE_SIZE, repeatingSurfaceInfo.mUsage);
+ mCameraRepeatingSurface = mRepeatingRequestImageReader.getSurface();
+ android.hardware.camera2.extension.Size sz =
+ new android.hardware.camera2.extension.Size();
+ sz.width = repeatingSurfaceInfo.mWidth;
+ sz.height = repeatingSurfaceInfo.mHeight;
+ mPreviewRequestUpdateProcessor.onResolutionUpdate(sz);
+ mPreviewRequestUpdateProcessor.onImageFormatUpdate(
+ CameraExtensionCharacteristics.NON_PROCESSING_INPUT_FORMAT);
+ } else {
+ if (mClientRepeatingRequestSurface != null) {
+ mRepeatingRequestImageWriter = ImageWriter.newInstance(
+ mClientRepeatingRequestSurface, PREVIEW_QUEUE_SIZE,
+ CameraExtensionCharacteristics.NON_PROCESSING_INPUT_FORMAT);
+ }
+ mRepeatingRequestImageReader = ImageReader.newInstance(repeatingSurfaceInfo.mWidth,
+ repeatingSurfaceInfo.mHeight,
+ CameraExtensionCharacteristics.NON_PROCESSING_INPUT_FORMAT,
+ PREVIEW_QUEUE_SIZE, repeatingSurfaceInfo.mUsage);
+ mCameraRepeatingSurface = mRepeatingRequestImageReader.getSurface();
+ }
+ mRepeatingRequestImageReader
+ .setOnImageAvailableListener(new ImageLoopbackCallback(), mHandler);
+ }
+
+ private void initializeBurstCapturePipeline() throws RemoteException {
+ mImageProcessor = mImageExtender.getCaptureProcessor();
+ if ((mImageProcessor == null) && (mImageExtender.getMaxCaptureStage() != 1)) {
+ throw new UnsupportedOperationException("Multiple stages expected without" +
+ " a valid capture processor!");
+ }
+
+ if (mImageProcessor != null) {
+ if (mClientCaptureSurface != null) {
+ SurfaceInfo surfaceInfo = querySurface(mClientCaptureSurface);
+ mBurstCaptureImageReader = ImageReader.newInstance(surfaceInfo.mWidth,
+ surfaceInfo.mHeight, CameraExtensionCharacteristics.PROCESSING_INPUT_FORMAT,
+ mImageExtender.getMaxCaptureStage());
+ mImageProcessor.onOutputSurface(mClientCaptureSurface, surfaceInfo.mFormat);
+ } else {
+ // The client doesn't intend to trigger multi-frame capture, however the
+ // image extender still needs to get initialized and the camera still capture
+ // stream configured for the repeating request processing to work.
+ mBurstCaptureImageReader = ImageReader.newInstance(
+ mRepeatingRequestImageReader.getWidth(),
+ mRepeatingRequestImageReader.getHeight(),
+ CameraExtensionCharacteristics.PROCESSING_INPUT_FORMAT, 1);
+ // The still capture output is not going to be used but we still need a valid
+ // surface to register.
+ mStubCaptureImageReader = ImageReader.newInstance(
+ mRepeatingRequestImageReader.getWidth(),
+ mRepeatingRequestImageReader.getHeight(),
+ CameraExtensionCharacteristics.PROCESSING_INPUT_FORMAT, 1);
+ mImageProcessor.onOutputSurface(mStubCaptureImageReader.getSurface(),
+ CameraExtensionCharacteristics.PROCESSING_INPUT_FORMAT);
+ }
+
+ mCameraBurstSurface = mBurstCaptureImageReader.getSurface();
+ android.hardware.camera2.extension.Size sz =
+ new android.hardware.camera2.extension.Size();
+ sz.width = mBurstCaptureImageReader.getWidth();
+ sz.height = mBurstCaptureImageReader.getHeight();
+ mImageProcessor.onResolutionUpdate(sz);
+ mImageProcessor.onImageFormatUpdate(mBurstCaptureImageReader.getImageFormat());
+ } else {
+ if (mClientCaptureSurface != null) {
+ // Redirect camera output directly in to client output surface
+ mCameraBurstSurface = mClientCaptureSurface;
+ } else {
+ // The client doesn't intend to trigger multi-frame capture, however the
+ // image extender still needs to get initialized and the camera still capture
+ // stream configured for the repeating request processing to work.
+ mBurstCaptureImageReader = ImageReader.newInstance(
+ mRepeatingRequestImageReader.getWidth(),
+ mRepeatingRequestImageReader.getHeight(),
+ // Camera devices accept only Jpeg output if the image processor is null
+ ImageFormat.JPEG, 1);
+ mCameraBurstSurface = mBurstCaptureImageReader.getSurface();
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public synchronized void initialize() throws CameraAccessException, RemoteException {
+ if (mInitialized) {
+ Log.d(TAG,
+ "Session already initialized");
+ return;
+ }
+
+ ArrayList<CaptureStageImpl> sessionParamsList = new ArrayList<>();
+ ArrayList<OutputConfiguration> outputList = new ArrayList<>();
+ initializeRepeatingRequestPipeline();
+ outputList.add(new OutputConfiguration(mCameraRepeatingSurface));
+ CaptureStageImpl previewSessionParams = mPreviewExtender.onPresetSession();
+ if (previewSessionParams != null) {
+ sessionParamsList.add(previewSessionParams);
+ }
+ initializeBurstCapturePipeline();
+ outputList.add(new OutputConfiguration(mCameraBurstSurface));
+ CaptureStageImpl stillCaptureSessionParams = mImageExtender.onPresetSession();
+ if (stillCaptureSessionParams != null) {
+ sessionParamsList.add(stillCaptureSessionParams);
+ }
+
+ SessionConfiguration sessionConfig = new SessionConfiguration(
+ SessionConfiguration.SESSION_REGULAR,
+ outputList,
+ new HandlerExecutor(mHandler),
+ new SessionStateHandler());
+
+ if (!sessionParamsList.isEmpty()) {
+ CaptureRequest sessionParamRequest = createRequest(mCameraDevice, sessionParamsList,
+ null, CameraDevice.TEMPLATE_PREVIEW);
+ sessionConfig.setSessionParameters(sessionParamRequest);
+ }
+
+ mCameraDevice.createCaptureSession(sessionConfig);
+ }
+
+ @Override
+ public @NonNull CameraDevice getDevice() {
+ synchronized (mInterfaceLock) {
+ return mCameraDevice;
+ }
+ }
+
+ @Override
+ public int setRepeatingRequest(@NonNull CaptureRequest request,
+ @NonNull Executor executor,
+ @NonNull ExtensionCaptureCallback listener)
+ throws CameraAccessException {
+ synchronized (mInterfaceLock) {
+ if (!mInitialized) {
+ throw new IllegalStateException("Uninitialized component");
+ }
+
+ if (mClientRepeatingRequestSurface == null) {
+ throw new IllegalArgumentException("No registered preview surface");
+ }
+
+ if (!request.containsTarget(mClientRepeatingRequestSurface) ||
+ (request.getTargets().size() != 1)) {
+ throw new IllegalArgumentException("Invalid repeating request output target!");
+ }
+
+ mInternalRepeatingRequestEnabled = false;
+ try {
+ return setRepeatingRequest(mPreviewExtender.getCaptureStage(),
+ new RepeatingRequestHandler(request, executor, listener));
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to set repeating request! Extension service does not "
+ + "respond");
+ throw new CameraAccessException(CameraAccessException.CAMERA_ERROR);
+ }
+ }
+ }
+
+ private ArrayList<CaptureStageImpl> compileInitialRequestList() {
+ ArrayList<CaptureStageImpl> captureStageList = new ArrayList<>();
+ try {
+ CaptureStageImpl initialPreviewParams = mPreviewExtender.onEnableSession();
+ if (initialPreviewParams != null) {
+ captureStageList.add(initialPreviewParams);
+ }
+
+ CaptureStageImpl initialStillCaptureParams = mImageExtender.onEnableSession();
+ if (initialStillCaptureParams != null) {
+ captureStageList.add(initialStillCaptureParams);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to initialize session parameters! Extension service does not"
+ + " respond!");
+ }
+
+ return captureStageList;
+ }
+
+ private static List<CaptureRequest> createBurstRequest(CameraDevice cameraDevice,
+ List<CaptureStageImpl> captureStageList, CaptureRequest clientRequest,
+ Surface target, int captureTemplate, Map<CaptureRequest, Integer> captureMap) {
+ CaptureRequest.Builder requestBuilder;
+ ArrayList<CaptureRequest> ret = new ArrayList<>();
+ for (CaptureStageImpl captureStage : captureStageList) {
+ try {
+ requestBuilder = cameraDevice.createCaptureRequest(captureTemplate);
+ } catch (CameraAccessException e) {
+ return null;
+ }
+
+ // Set user supported jpeg quality and rotation parameters
+ Integer jpegRotation = clientRequest.get(CaptureRequest.JPEG_ORIENTATION);
+ if (jpegRotation != null) {
+ requestBuilder.set(CaptureRequest.JPEG_ORIENTATION, jpegRotation);
+ }
+ Byte jpegQuality = clientRequest.get(CaptureRequest.JPEG_QUALITY);
+ if (jpegQuality != null) {
+ requestBuilder.set(CaptureRequest.JPEG_QUALITY, jpegQuality);
+ }
+
+ requestBuilder.addTarget(target);
+ CaptureRequest request = requestBuilder.build();
+ CameraMetadataNative.update(request.getNativeMetadata(), captureStage.parameters);
+ ret.add(request);
+ captureMap.put(request, captureStage.id);
+ }
+
+ return ret;
+ }
+
+ private static CaptureRequest createRequest(CameraDevice cameraDevice,
+ List<CaptureStageImpl> captureStageList,
+ Surface target,
+ int captureTemplate) throws CameraAccessException {
+ CaptureRequest.Builder requestBuilder;
+ requestBuilder = cameraDevice.createCaptureRequest(captureTemplate);
+ if (target != null) {
+ requestBuilder.addTarget(target);
+ }
+
+ CaptureRequest ret = requestBuilder.build();
+ for (CaptureStageImpl captureStage : captureStageList) {
+ if (captureStage != null) {
+ CameraMetadataNative.update(ret.getNativeMetadata(), captureStage.parameters);
+ }
+ }
+ return ret;
+ }
+
+ @Override
+ public int capture(@NonNull CaptureRequest request,
+ @NonNull Executor executor,
+ @NonNull ExtensionCaptureCallback listener) throws CameraAccessException {
+ if (!mInitialized) {
+ throw new IllegalStateException("Uninitialized component");
+ }
+
+ if (mClientCaptureSurface == null) {
+ throw new IllegalArgumentException("No output surface registered for single requests!");
+ }
+
+ if (!request.containsTarget(mClientCaptureSurface) || (request.getTargets().size() != 1)) {
+ throw new IllegalArgumentException("Invalid single capture output target!");
+ }
+
+ HashMap<CaptureRequest, Integer> requestMap = new HashMap<>();
+ List<CaptureRequest> burstRequest;
+ try {
+ burstRequest = createBurstRequest(mCameraDevice,
+ mImageExtender.getCaptureStages(), request, mCameraBurstSurface,
+ CameraDevice.TEMPLATE_STILL_CAPTURE, requestMap);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to initialize internal burst request! Extension service does"
+ + " not respond!");
+ throw new CameraAccessException(CameraAccessException.CAMERA_ERROR);
+ }
+ if (burstRequest == null) {
+ throw new UnsupportedOperationException("Failed to create still capture burst request");
+ }
+
+ return mCaptureSession.captureBurstRequests(burstRequest, new HandlerExecutor(mHandler),
+ new BurstRequestHandler(request, executor, listener, requestMap));
+ }
+
+ @Override
+ public void stopRepeating() throws CameraAccessException {
+ synchronized (mInterfaceLock) {
+ if (!mInitialized) {
+ throw new IllegalStateException("Uninitialized component");
+ }
+
+ mInternalRepeatingRequestEnabled = true;
+ mCaptureSession.stopRepeating();
+ }
+ }
+
+ @Override
+ public void close() throws CameraAccessException {
+ synchronized (mInterfaceLock) {
+ if (mInitialized) {
+ mInternalRepeatingRequestEnabled = false;
+ mCaptureSession.stopRepeating();
+
+ ArrayList<CaptureStageImpl> captureStageList = new ArrayList<>();
+ try {
+ CaptureStageImpl disableParams = mPreviewExtender.onDisableSession();
+ if (disableParams != null) {
+ captureStageList.add(disableParams);
+ }
+
+ CaptureStageImpl disableStillCaptureParams =
+ mImageExtender.onDisableSession();
+ if (disableStillCaptureParams != null) {
+ captureStageList.add(disableStillCaptureParams);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to disable extension! Extension service does not "
+ + "respond!");
+ }
+ if (!captureStageList.isEmpty()) {
+ CaptureRequest disableRequest = createRequest(mCameraDevice, captureStageList,
+ mCameraRepeatingSurface, CameraDevice.TEMPLATE_PREVIEW);
+ mCaptureSession.capture(disableRequest, new CloseRequestHandler(), mHandler);
+ }
+
+ mCaptureSession.close();
+ }
+ }
+ }
+
+ private void setInitialCaptureRequest(List<CaptureStageImpl> captureStageList,
+ InitialRequestHandler requestHandler)
+ throws CameraAccessException {
+ CaptureRequest initialRequest = createRequest(mCameraDevice,
+ captureStageList, mCameraRepeatingSurface, CameraDevice.TEMPLATE_PREVIEW);
+ mCaptureSession.capture(initialRequest, requestHandler, mHandler);
+ }
+
+ private int setRepeatingRequest(CaptureStageImpl captureStage,
+ CameraCaptureSession.CaptureCallback requestHandler)
+ throws CameraAccessException {
+ ArrayList<CaptureStageImpl> captureStageList = new ArrayList<>();
+ captureStageList.add(captureStage);
+ CaptureRequest repeatingRequest = createRequest(mCameraDevice,
+ captureStageList, mCameraRepeatingSurface, CameraDevice.TEMPLATE_PREVIEW);
+ return mCaptureSession.setSingleRepeatingRequest(repeatingRequest,
+ new HandlerExecutor(mHandler), requestHandler);
+ }
+
+ /** @hide */
+ public void release() {
+ synchronized (mInterfaceLock) {
+ mInternalRepeatingRequestEnabled = false;
+ mInitialized = false;
+ mHandlerThread.quitSafely();
+
+ try {
+ mPreviewExtender.onDeInit();
+ mImageExtender.onDeInit();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to release extensions! Extension service does not"
+ + " respond!");
+ }
+
+ if (mExtensionClientId >= 0) {
+ CameraExtensionCharacteristics.unregisterClient(mExtensionClientId);
+ }
+
+ if (mRepeatingRequestImageReader != null) {
+ mRepeatingRequestImageReader.close();
+ mRepeatingRequestImageReader = null;
+ }
+
+ if (mBurstCaptureImageReader != null) {
+ mBurstCaptureImageReader.close();
+ mBurstCaptureImageReader = null;
+ }
+
+ if (mStubCaptureImageReader != null) {
+ mStubCaptureImageReader.close();
+ mStubCaptureImageReader = null;
+ }
+
+ if (mRepeatingRequestImageWriter != null) {
+ mRepeatingRequestImageWriter.close();
+ mRepeatingRequestImageWriter = null;
+ }
+
+ if (mPreviewImageProcessor != null) {
+ mPreviewImageProcessor.close();
+ mPreviewImageProcessor = null;
+ }
+
+ mCaptureSession = null;
+ mImageProcessor = null;
+ mCameraRepeatingSurface = mClientRepeatingRequestSurface = null;
+ mCameraBurstSurface = mClientCaptureSurface = null;
+ }
+ }
+
+ private void notifyConfigurationFailure() {
+ synchronized (mInterfaceLock) {
+ if (mInitialized) {
+ return;
+ }
+ }
+
+ release();
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(
+ () -> mCallbacks.onConfigureFailed(CameraExtensionSessionImpl.this));
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private void notifyConfigurationSuccess() {
+ synchronized (mInterfaceLock) {
+ if (mInitialized) {
+ return;
+ } else {
+ mInitialized = true;
+ }
+ }
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallbacks.onConfigured(CameraExtensionSessionImpl.this));
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private class SessionStateHandler extends
+ android.hardware.camera2.CameraCaptureSession.StateCallback {
+ @Override
+ public void onClosed(@NonNull CameraCaptureSession session) {
+ release();
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallbacks.onClosed(CameraExtensionSessionImpl.this));
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
+ public void onConfigureFailed(@NonNull CameraCaptureSession session) {
+ notifyConfigurationFailure();
+ }
+
+ @Override
+ public void onConfigured(@NonNull CameraCaptureSession session) {
+ boolean status = true;
+ synchronized (mInterfaceLock) {
+ mCaptureSession = session;
+
+ ArrayList<CaptureStageImpl> initialRequestList = compileInitialRequestList();
+ if (!initialRequestList.isEmpty()) {
+ try {
+ setInitialCaptureRequest(initialRequestList, new InitialRequestHandler());
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Failed to initialize the initial capture request!");
+ status = false;
+ }
+ } else {
+ try {
+ setRepeatingRequest(mPreviewExtender.getCaptureStage(),
+ new RepeatingRequestHandler(null, null, null));
+ } catch (CameraAccessException | RemoteException e) {
+ Log.e(TAG, "Failed to initialize internal repeating request!");
+ status = false;
+ }
+
+ }
+ }
+
+ if (!status) {
+ notifyConfigurationFailure();
+ }
+ }
+ }
+
+ private class BurstRequestHandler extends CameraCaptureSession.CaptureCallback {
+ private final Executor mExecutor;
+ private final ExtensionCaptureCallback mCallbacks;
+ private final CaptureRequest mClientRequest;
+ private final HashMap<CaptureRequest, Integer> mCaptureRequestMap;
+
+ private HashMap<Integer, Pair<Image, TotalCaptureResult>> mCaptureStageMap =
+ new HashMap<>();
+ private LongSparseArray<Pair<Image, Integer>> mCapturePendingMap =
+ new LongSparseArray<>();
+
+ private ImageCallback mImageCallback = null;
+ private boolean mCaptureFailed = false;
+
+ public BurstRequestHandler(@NonNull CaptureRequest request, @NonNull Executor executor,
+ @NonNull ExtensionCaptureCallback callbacks,
+ @NonNull HashMap<CaptureRequest, Integer> requestMap) {
+ mClientRequest = request;
+ mExecutor = executor;
+ mCallbacks = callbacks;
+ mCaptureRequestMap = requestMap;
+ }
+
+ private void notifyCaptureFailed() {
+ if (!mCaptureFailed) {
+ mCaptureFailed = true;
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(
+ () -> mCallbacks.onCaptureFailed(CameraExtensionSessionImpl.this,
+ mClientRequest));
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
+ @Override
+ public void onCaptureStarted(@NonNull CameraCaptureSession session,
+ @NonNull CaptureRequest request,
+ long timestamp,
+ long frameNumber) {
+ // Trigger the client callback only once in case of burst request
+ boolean initialCallback = false;
+ synchronized (mInterfaceLock) {
+ if ((mImageProcessor != null) && (mImageCallback == null)) {
+ mImageCallback = new ImageCallback(mBurstCaptureImageReader);
+ mBurstCaptureImageReader.setOnImageAvailableListener(mImageCallback, mHandler);
+ initialCallback = true;
+ } else if (mImageProcessor == null) {
+ // No burst expected in this case
+ initialCallback = true;
+ }
+ }
+
+ if (initialCallback) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(
+ () -> mCallbacks.onCaptureStarted(CameraExtensionSessionImpl.this,
+ mClientRequest, timestamp));
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
+ @Override
+ public void onCaptureBufferLost(@NonNull CameraCaptureSession session,
+ @NonNull CaptureRequest request,
+ @NonNull Surface target, long frameNumber) {
+ notifyCaptureFailed();
+ }
+
+ @Override
+ public void onCaptureFailed(@NonNull CameraCaptureSession session,
+ @NonNull CaptureRequest request,
+ @NonNull CaptureFailure failure) {
+ notifyCaptureFailed();
+ }
+
+ @Override
+ public void onCaptureSequenceAborted(@NonNull CameraCaptureSession session,
+ int sequenceId) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(
+ () -> mCallbacks.onCaptureSequenceAborted(CameraExtensionSessionImpl.this,
+ sequenceId));
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
+ public void onCaptureSequenceCompleted(@NonNull CameraCaptureSession session,
+ int sequenceId,
+ long frameNumber) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(
+ () -> mCallbacks
+ .onCaptureSequenceCompleted(CameraExtensionSessionImpl.this,
+ sequenceId));
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
+ public void onCaptureCompleted(@NonNull CameraCaptureSession session,
+ @NonNull CaptureRequest request,
+ @NonNull TotalCaptureResult result) {
+ if (!mCaptureRequestMap.containsKey(request)) {
+ Log.e(TAG,
+ "Unexpected still capture request received!");
+ return;
+ }
+ Integer stageId = mCaptureRequestMap.get(request);
+
+ Long timestamp = result.get(CaptureResult.SENSOR_TIMESTAMP);
+ if (timestamp != null) {
+ if (mImageProcessor != null) {
+ if (mCapturePendingMap.indexOfKey(timestamp) >= 0) {
+ Image img = mCapturePendingMap.get(timestamp).first;
+ mCaptureStageMap.put(stageId,
+ new Pair<>(img,
+ result));
+ checkAndFireBurstProcessing();
+ } else {
+ mCapturePendingMap.put(timestamp,
+ new Pair<>(null,
+ stageId));
+ mCaptureStageMap.put(stageId,
+ new Pair<>(null,
+ result));
+ }
+ } else {
+ mCaptureRequestMap.clear();
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(
+ () -> mCallbacks
+ .onCaptureProcessStarted(CameraExtensionSessionImpl.this,
+ mClientRequest));
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ } else {
+ Log.e(TAG,
+ "Capture result without valid sensor timestamp!");
+ }
+ }
+
+ private void checkAndFireBurstProcessing() {
+ if (mCaptureRequestMap.size() == mCaptureStageMap.size()) {
+ for (Pair<Image, TotalCaptureResult> captureStage : mCaptureStageMap
+ .values()) {
+ if ((captureStage.first == null) || (captureStage.second == null)) {
+ return;
+ }
+ }
+
+ mCaptureRequestMap.clear();
+ mCapturePendingMap.clear();
+ boolean processStatus = true;
+ List<CaptureBundle> captureList = initializeParcelable(mCaptureStageMap);
+ try {
+ mImageProcessor.process(captureList);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to process multi-frame request! Extension service "
+ + "does not respond!");
+ processStatus = false;
+ }
+
+ for (CaptureBundle bundle : captureList) {
+ bundle.captureImage.buffer.close();
+ }
+ captureList.clear();
+ for (Pair<Image, TotalCaptureResult> captureStage : mCaptureStageMap.values()) {
+ captureStage.first.close();
+ }
+ mCaptureStageMap.clear();
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (processStatus) {
+ mExecutor.execute(() -> mCallbacks.onCaptureProcessStarted(
+ CameraExtensionSessionImpl.this, mClientRequest));
+ } else {
+ mExecutor.execute(() -> mCallbacks.onCaptureFailed(
+ CameraExtensionSessionImpl.this, mClientRequest));
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
+ private class ImageCallback implements ImageReader.OnImageAvailableListener {
+ public ImageCallback(@NonNull ImageReader reader) {
+ //Check for any pending buffers
+ onImageAvailable(reader);
+ }
+
+ @Override
+ public void onImageAvailable(ImageReader reader) {
+ Image img;
+ try {
+ while ((!mCaptureRequestMap.isEmpty()) &&
+ (img = reader.acquireNextImage()) != null) {
+ long timestamp = img.getTimestamp();
+ reader.detachImage(img);
+ if (mCapturePendingMap.indexOfKey(timestamp) >= 0) {
+ Integer stageId = mCapturePendingMap.get(timestamp).second;
+ Pair<Image, TotalCaptureResult> captureStage =
+ mCaptureStageMap.get(stageId);
+ if (captureStage != null) {
+ mCaptureStageMap.put(stageId,
+ new Pair<>(img,
+ captureStage.second));
+ checkAndFireBurstProcessing();
+ } else {
+ Log.e(TAG,
+ "Capture stage: " +
+ mCapturePendingMap.get(timestamp).second +
+ " is absent!");
+ }
+ } else {
+ mCapturePendingMap.put(timestamp,
+ new Pair<>(img,
+ -1));
+ }
+ }
+ } catch (IllegalStateException e) {
+ // This is possible in case the maximum number of images is acquired.
+ }
+ }
+ }
+ }
+
+ private class ImageLoopbackCallback implements ImageReader.OnImageAvailableListener {
+ @Override public void onImageAvailable(ImageReader reader) {
+ Image img;
+ try {
+ img = reader.acquireNextImage();
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Failed to acquire and loopback image!");
+ return;
+ }
+ if (img == null) {
+ Log.e(TAG,
+ "Invalid image!");
+ return;
+ }
+ img.close();
+ }
+ }
+
+ private class InitialRequestHandler extends CameraCaptureSession.CaptureCallback {
+ @Override
+ public void onCaptureSequenceAborted(@NonNull CameraCaptureSession session,
+ int sequenceId) {
+ Log.e(TAG, "Initial capture request aborted!");
+ notifyConfigurationFailure();
+ }
+
+ @Override
+ public void onCaptureFailed(@NonNull CameraCaptureSession session,
+ @NonNull CaptureRequest request,
+ @NonNull CaptureFailure failure) {
+ Log.e(TAG, "Initial capture request failed!");
+ notifyConfigurationFailure();
+ }
+
+ @Override
+ public void onCaptureSequenceCompleted(@NonNull CameraCaptureSession session,
+ int sequenceId,
+ long frameNumber) {
+ boolean status = true;
+ synchronized (mInterfaceLock) {
+ /**
+ * Initialize and set the initial repeating request which will execute in the
+ * absence of client repeating requests.
+ */
+ try {
+ setRepeatingRequest(mPreviewExtender.getCaptureStage(),
+ new RepeatingRequestHandler(null, null, null));
+ } catch (CameraAccessException | RemoteException e) {
+ Log.e(TAG, "Failed to start the internal repeating request!");
+ status = false;
+ }
+
+ }
+
+ if (!status) {
+ notifyConfigurationFailure();
+ }
+ }
+ }
+
+ private class CloseRequestHandler extends CameraCaptureSession.CaptureCallback {
+ @Override
+ public void onCaptureStarted(@NonNull CameraCaptureSession session,
+ @NonNull CaptureRequest request,
+ long timestamp,
+ long frameNumber) {
+ synchronized (mInterfaceLock) {
+ mRepeatingRequestImageReader
+ .setOnImageAvailableListener(new ImageLoopbackCallback(), mHandler);
+ }
+ }
+ }
+
+ // This handler can operate in two modes:
+ // 1) Using valid client callbacks, which means camera buffers will be propagated the
+ // registered output surfaces and clients will be notified accordingly.
+ // 2) Without any client callbacks where an internal repeating request is kept active
+ // to satisfy the extensions continuous preview/(repeating request) requirement.
+ private class RepeatingRequestHandler extends CameraCaptureSession.CaptureCallback {
+ private final Executor mExecutor;
+ private final ExtensionCaptureCallback mCallbacks;
+ private final CaptureRequest mClientRequest;
+ private final boolean mClientNotificationsEnabled;
+ private ImageReader.OnImageAvailableListener mImageCallback = null;
+ private LongSparseArray<Pair<Image, TotalCaptureResult>> mPendingResultMap =
+ new LongSparseArray<>();
+
+ private boolean mRequestUpdatedNeeded = false;
+
+ public RepeatingRequestHandler(@Nullable CaptureRequest clientRequest,
+ @Nullable Executor executor,
+ @Nullable ExtensionCaptureCallback listener) {
+ mClientRequest = clientRequest;
+ mExecutor = executor;
+ mCallbacks = listener;
+ mClientNotificationsEnabled =
+ (mClientRequest != null) && (mExecutor != null) && (mCallbacks != null);
+ }
+
+ @Override
+ public void onCaptureStarted(@NonNull CameraCaptureSession session,
+ @NonNull CaptureRequest request,
+ long timestamp,
+ long frameNumber) {
+ synchronized (mInterfaceLock) {
+ // Setup the image callback handler for this repeating request just once
+ // after streaming resumes.
+ if (mImageCallback == null) {
+ if (mPreviewProcessorType ==
+ IPreviewExtenderImpl.PROCESSOR_TYPE_IMAGE_PROCESSOR) {
+ if (mClientNotificationsEnabled) {
+ mPreviewImageProcessor.onOutputSurface(mClientRepeatingRequestSurface,
+ nativeGetSurfaceFormat(mClientRepeatingRequestSurface));
+ } else {
+ mPreviewImageProcessor.onOutputSurface(null, -1);
+ }
+ mImageCallback = new ImageProcessCallback();
+ } else {
+ mImageCallback = mClientNotificationsEnabled ?
+ new ImageForwardCallback(mRepeatingRequestImageWriter) :
+ new ImageLoopbackCallback();
+ }
+ mRepeatingRequestImageReader
+ .setOnImageAvailableListener(mImageCallback, mHandler);
+ }
+ }
+
+ if (mClientNotificationsEnabled) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(
+ () -> mCallbacks.onCaptureStarted(CameraExtensionSessionImpl.this,
+ mClientRequest, timestamp));
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
+ @Override
+ public void onCaptureSequenceAborted(@NonNull CameraCaptureSession session,
+ int sequenceId) {
+ synchronized (mInterfaceLock) {
+ if (mInternalRepeatingRequestEnabled) {
+ resumeInternalRepeatingRequest(true);
+ }
+ }
+
+ if (mClientNotificationsEnabled) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(
+ () -> mCallbacks
+ .onCaptureSequenceAborted(CameraExtensionSessionImpl.this,
+ sequenceId));
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ } else {
+ notifyConfigurationFailure();
+ }
+
+ }
+
+ @Override
+ public void onCaptureSequenceCompleted(@NonNull CameraCaptureSession session,
+ int sequenceId,
+ long frameNumber) {
+
+ synchronized (mInterfaceLock) {
+ if (mRequestUpdatedNeeded) {
+ mRequestUpdatedNeeded = false;
+ resumeInternalRepeatingRequest(false);
+ } else if (mInternalRepeatingRequestEnabled) {
+ resumeInternalRepeatingRequest(true);
+ }
+ }
+
+ if (mClientNotificationsEnabled) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(
+ () -> mCallbacks
+ .onCaptureSequenceCompleted(CameraExtensionSessionImpl.this,
+ sequenceId));
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
+ @Override
+ public void onCaptureFailed(@NonNull CameraCaptureSession session,
+ @NonNull CaptureRequest request,
+ @NonNull CaptureFailure failure) {
+
+ if (mClientNotificationsEnabled) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(
+ () -> mCallbacks.onCaptureFailed(CameraExtensionSessionImpl.this,
+ mClientRequest));
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
+ @Override
+ public void onCaptureCompleted(@NonNull CameraCaptureSession session,
+ @NonNull CaptureRequest request,
+ @NonNull TotalCaptureResult result) {
+ boolean notifyClient = mClientNotificationsEnabled;
+ boolean processStatus = true;
+
+ synchronized (mInterfaceLock) {
+ final Long timestamp = result.get(CaptureResult.SENSOR_TIMESTAMP);
+ if (timestamp != null) {
+ if (mPreviewProcessorType ==
+ IPreviewExtenderImpl.PROCESSOR_TYPE_REQUEST_UPDATE_ONLY) {
+ CaptureStageImpl captureStage = null;
+ try {
+ captureStage = mPreviewRequestUpdateProcessor.process(
+ result.getNativeMetadata(), result.getSequenceId());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Extension service does not respond during " +
+ "processing!");
+ }
+ if (captureStage != null) {
+ try {
+ setRepeatingRequest(captureStage, this);
+ mRequestUpdatedNeeded = true;
+ } catch (IllegalStateException e) {
+ // This is possible in case the camera device closes and the
+ // and the callback here is executed before the onClosed
+ // notification.
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Failed to update repeating request settings!");
+ }
+ } else {
+ mRequestUpdatedNeeded = false;
+ }
+ } else if (mPreviewProcessorType ==
+ IPreviewExtenderImpl.PROCESSOR_TYPE_IMAGE_PROCESSOR) {
+ int idx = mPendingResultMap.indexOfKey(timestamp);
+ if (idx >= 0) {
+ ParcelImage parcelImage = initializeParcelImage(
+ mPendingResultMap.get(timestamp).first);
+ try {
+ mPreviewImageProcessor.process(parcelImage, result);
+ } catch (RemoteException e) {
+ processStatus = false;
+ Log.e(TAG, "Extension service does not respond during " +
+ "processing, dropping frame!");
+ } catch (RuntimeException e) {
+ // Runtime exceptions can happen in a few obscure cases where the
+ // client tries to initialize a new capture session while this
+ // session is still ongoing. In such scenario, the camera will
+ // disconnect from the intermediate output surface, which will
+ // invalidate the images that we acquired previously. This can
+ // happen before we get notified via "onClosed" so there aren't
+ // many options to avoid the exception.
+ processStatus = false;
+ Log.e(TAG, "Runtime exception encountered during buffer " +
+ "processing, dropping frame!");
+ } finally {
+ parcelImage.buffer.close();
+ mPendingResultMap.get(timestamp).first.close();
+ }
+ discardPendingRepeatingResults(idx, mPendingResultMap, false);
+ } else {
+ notifyClient = false;
+ mPendingResultMap.put(timestamp,
+ new Pair<>(null,
+ result));
+ }
+ } else {
+ // No special handling for PROCESSOR_TYPE_NONE
+ }
+ if (notifyClient) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (processStatus) {
+ mExecutor.execute(() -> mCallbacks
+ .onCaptureProcessStarted(CameraExtensionSessionImpl.this,
+ mClientRequest));
+ } else {
+ mExecutor.execute(
+ () -> mCallbacks
+ .onCaptureFailed(CameraExtensionSessionImpl.this,
+ mClientRequest));
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ } else {
+ Log.e(TAG,
+ "Result without valid sensor timestamp!");
+ }
+ }
+
+ if (!notifyClient) {
+ notifyConfigurationSuccess();
+ }
+ }
+
+ private void resumeInternalRepeatingRequest(boolean internal) {
+ try {
+ if (internal) {
+ setRepeatingRequest(mPreviewExtender.getCaptureStage(),
+ new RepeatingRequestHandler(null, null, null));
+ } else {
+ setRepeatingRequest(mPreviewExtender.getCaptureStage(), this);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to resume internal repeating request, extension service"
+ + " fails to respond!");
+ } catch (IllegalStateException e) {
+ // This is possible in case we try to resume before the state "onClosed"
+ // notification is able to reach us.
+ Log.w(TAG, "Failed to resume internal repeating request!");
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Failed to resume internal repeating request!");
+ }
+ }
+
+ // Find the timestamp of the oldest pending buffer
+ private Long calculatePruneThreshold(
+ LongSparseArray<Pair<Image, TotalCaptureResult>> previewMap) {
+ long oldestTimestamp = Long.MAX_VALUE;
+ for (int idx = 0; idx < previewMap.size(); idx++) {
+ Pair<Image, TotalCaptureResult> entry = previewMap.valueAt(idx);
+ long timestamp = previewMap.keyAt(idx);
+ if ((entry.first != null) && (timestamp < oldestTimestamp)) {
+ oldestTimestamp = timestamp;
+ }
+ }
+ return (oldestTimestamp == Long.MAX_VALUE) ? 0 : oldestTimestamp;
+ }
+
+ private void discardPendingRepeatingResults(int idx, LongSparseArray<Pair<Image,
+ TotalCaptureResult>> previewMap, boolean notifyCurrentIndex) {
+ if (idx < 0) {
+ return;
+ }
+ for (int i = idx; i >= 0; i--) {
+ if (previewMap.valueAt(i).first != null) {
+ Log.w(TAG, "Discard pending buffer with timestamp: " + previewMap.keyAt(i));
+ previewMap.valueAt(i).first.close();
+ } else {
+ Log.w(TAG, "Discard pending result with timestamp: " + previewMap.keyAt(i));
+ if (mClientNotificationsEnabled && ((i != idx) || notifyCurrentIndex)) {
+ Log.w(TAG, "Preview frame drop with timestamp: " + previewMap.keyAt(i));
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(
+ () -> mCallbacks
+ .onCaptureFailed(CameraExtensionSessionImpl.this,
+ mClientRequest));
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+ previewMap.removeAt(i);
+ }
+ }
+
+ private class ImageForwardCallback implements ImageReader.OnImageAvailableListener {
+ private final ImageWriter mOutputWriter;
+
+ public ImageForwardCallback(@NonNull ImageWriter imageWriter) {
+ mOutputWriter = imageWriter;
+ }
+
+ @Override public void onImageAvailable(ImageReader reader) {
+ Image img;
+ try {
+ img = reader.acquireNextImage();
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Failed to acquire and propagate repeating request image!");
+ return;
+ }
+ if (img == null) {
+ Log.e(TAG,
+ "Invalid image!");
+ return;
+ }
+ try {
+ mOutputWriter.queueInputImage(img);
+ } catch (IllegalStateException e) {
+ // This is possible in case the client disconnects from the output surface
+ // abruptly.
+ Log.w(TAG, "Output surface likely abandoned, dropping buffer!");
+ img.close();
+ }
+ }
+ }
+
+ private class ImageProcessCallback implements ImageReader.OnImageAvailableListener {
+ @Override
+ public void onImageAvailable(ImageReader reader) {
+ Image img;
+ try {
+ img = reader.acquireNextImage();
+ } catch (IllegalStateException e) {
+ // We reached the maximum acquired images limit. This is possible in case we
+ // have capture failures that result in absent or missing capture results. In
+ // such scenario we can prune the oldest pending buffer.
+ discardPendingRepeatingResults(
+ mPendingResultMap
+ .indexOfKey(calculatePruneThreshold(mPendingResultMap)),
+ mPendingResultMap, true);
+
+ img = reader.acquireNextImage();
+ }
+ if (img == null) {
+ Log.e(TAG,
+ "Invalid preview buffer!");
+ return;
+ }
+
+ try {
+ reader.detachImage(img);
+ } catch (Exception e) {
+ Log.e(TAG,
+ "Failed to detach image!");
+ img.close();
+ return;
+ }
+
+ long timestamp = img.getTimestamp();
+ int idx = mPendingResultMap.indexOfKey(timestamp);
+ if (idx >= 0) {
+ boolean processStatus = true;
+ ParcelImage parcelImage = initializeParcelImage(img);
+ try {
+ mPreviewImageProcessor.process(parcelImage,
+ mPendingResultMap.get(timestamp).second);
+ } catch (RemoteException e) {
+ processStatus = false;
+ Log.e(TAG, "Extension service does not respond during " +
+ "processing, dropping frame!");
+ } finally {
+ parcelImage.buffer.close();
+ img.close();
+ }
+ discardPendingRepeatingResults(idx, mPendingResultMap, false);
+ if (mClientNotificationsEnabled) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (processStatus) {
+ mExecutor.execute(() -> mCallbacks.onCaptureProcessStarted(
+ CameraExtensionSessionImpl.this,
+ mClientRequest));
+ } else {
+ mExecutor.execute(() -> mCallbacks.onCaptureFailed(
+ CameraExtensionSessionImpl.this,
+ mClientRequest));
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ } else {
+ mPendingResultMap.put(timestamp, new Pair<>(img, null));
+ }
+ }
+ }
+ }
+
+ private static Size findSmallestAspectMatchedSize(@NonNull List<Size> sizes,
+ @NonNull Size arSize) {
+ final float TOLL = .01f;
+
+ if (arSize.getHeight() == 0) {
+ throw new IllegalArgumentException("Invalid input aspect ratio");
+ }
+
+ float targetAR = ((float) arSize.getWidth()) / arSize.getHeight();
+ Size ret = null;
+ Size fallbackSize = null;
+ for (Size sz : sizes) {
+ if (fallbackSize == null) {
+ fallbackSize = sz;
+ }
+ if ((sz.getHeight() > 0) &&
+ ((ret == null) ||
+ (ret.getWidth() * ret.getHeight()) <
+ (sz.getWidth() * sz.getHeight()))) {
+ float currentAR = ((float) sz.getWidth()) / sz.getHeight();
+ if (Math.abs(currentAR - targetAR) <= TOLL) {
+ ret = sz;
+ }
+ }
+ }
+ if (ret == null) {
+ Log.e(TAG, "AR matched size not found returning first size in list");
+ ret = fallbackSize;
+ }
+
+ return ret;
+ }
+
+ private final class HandlerExecutor implements Executor {
+ private final Handler mHandler;
+
+ public HandlerExecutor(Handler handler) {
+ mHandler = handler;
+ }
+
+ @Override
+ public void execute(Runnable runCmd) {
+ try {
+ mHandler.post(runCmd);
+ } catch (RejectedExecutionException e) {
+ Log.w(TAG, "Handler thread unavailable, skipping message!");
+ }
+ }
+ }
+
+ private static ParcelImage initializeParcelImage(Image img) {
+ ParcelImage parcelImage = new ParcelImage();
+ parcelImage.buffer = img.getHardwareBuffer();
+ if (img.getFenceFd() >= 0) {
+ try {
+ parcelImage.fence = ParcelFileDescriptor.fromFd(img.getFenceFd());
+ } catch (IOException e) {
+ Log.e(TAG,"Failed to parcel buffer fence!");
+ }
+ }
+ parcelImage.format = img.getFormat();
+ parcelImage.timestamp = img.getTimestamp();
+ parcelImage.transform = img.getTransform();
+ parcelImage.scalingMode = img.getScalingMode();
+ parcelImage.planeCount = img.getPlaneCount();
+ parcelImage.crop = img.getCropRect();
+
+ return parcelImage;
+ }
+
+ private static List<CaptureBundle> initializeParcelable(
+ HashMap<Integer, Pair<Image, TotalCaptureResult>> captureMap) {
+ ArrayList<CaptureBundle> ret = new ArrayList<>();
+ for (Integer stagetId : captureMap.keySet()) {
+ Pair<Image, TotalCaptureResult> entry = captureMap.get(stagetId);
+ CaptureBundle bundle = new CaptureBundle();
+ bundle.stage = stagetId;
+ bundle.captureImage = initializeParcelImage(entry.first);
+ bundle.sequenceId = entry.second.getSequenceId();
+ bundle.captureResult = entry.second.getNativeMetadata();
+ ret.add(bundle);
+ }
+
+ return ret;
+ }
+}
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 5cc7bf8..e9bae0b 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -382,6 +382,19 @@
return newObject;
}
+ /**
+ * Set all metadata values in the destination argument by using the corresponding
+ * values from the source. Metadata tags present in the destination and absent
+ * from the source will remain unmodified.
+ *
+ * @param dst Destination metadata
+ * @param src Source metadata
+ * @hide
+ */
+ public static void update(CameraMetadataNative dst, CameraMetadataNative src) {
+ nativeUpdate(dst.mMetadataPtr, src.mMetadataPtr);
+ }
+
public static final @android.annotation.NonNull Parcelable.Creator<CameraMetadataNative> CREATOR =
new Parcelable.Creator<CameraMetadataNative>() {
@Override
@@ -1719,6 +1732,9 @@
private static native long nativeAllocateCopy(long ptr)
throws NullPointerException;
+
+ @FastNative
+ private static native void nativeUpdate(long dst, long src);
private static synchronized native void nativeWriteToParcel(Parcel dest, long ptr);
private static synchronized native void nativeReadFromParcel(Parcel source, long ptr);
private static synchronized native void nativeSwap(long ptr, long otherPtr)
diff --git a/core/java/android/hardware/camera2/params/ExtensionSessionConfiguration.java b/core/java/android/hardware/camera2/params/ExtensionSessionConfiguration.java
new file mode 100644
index 0000000..c81d339
--- /dev/null
+++ b/core/java/android/hardware/camera2/params/ExtensionSessionConfiguration.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2020 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.annotation.NonNull;
+
+import android.hardware.camera2.CameraExtensionCharacteristics.Extension;
+import android.hardware.camera2.CameraExtensionSession;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * A class that aggregates all supported arguments for
+ * {@link CameraExtensionSession} initialization.
+ */
+public final class ExtensionSessionConfiguration {
+ private static final String TAG = "ExtensionSessionConfiguration";
+
+ private int mExtensionType;
+ private List<OutputConfiguration> mOutputs;
+ private Executor mExecutor = null;
+ private CameraExtensionSession.StateCallback mCallback = null;
+
+ /**
+ * Create a new ExtensionSessionConfiguration
+ *
+ * @param extension to be used for processing
+ * @param outputs a list of output configurations for the capture session
+ * @param executor the executor which will be used for invoking the callbacks
+ * @param listener callbacks to be invoked when the state of the
+ * CameraExtensionSession changes
+ */
+ public ExtensionSessionConfiguration(@Extension int extension,
+ @NonNull List<OutputConfiguration> outputs, @NonNull Executor executor,
+ @NonNull CameraExtensionSession.StateCallback listener) {
+ mExtensionType = extension;
+ mOutputs = outputs;
+ mExecutor = executor;
+ mCallback = listener;
+ }
+
+ /**
+ * Retrieve the extension type.
+ *
+ * @return the extension type.
+ */
+ public @Extension
+ int getExtension() {
+ return mExtensionType;
+ }
+
+ /**
+ * Retrieve the {@link OutputConfiguration} list for the capture
+ * session.
+ *
+ * @return A list of output configurations for the capture session.
+ */
+ public @NonNull
+ List<OutputConfiguration> getOutputConfigurations() {
+ return mOutputs;
+ }
+
+ /**
+ * Retrieve the CameraCaptureSession.StateCallback
+ * listener.
+ *
+ * @return A state callback interface implementation.
+ */
+ public @NonNull
+ CameraExtensionSession.StateCallback getStateCallback() {
+ return mCallback;
+ }
+
+ /**
+ * Retrieve the Executor for the CameraExtensionSession instance.
+ *
+ * @return The Executor on which the callback will be invoked.
+ */
+ public @NonNull
+ Executor getExecutor() {
+ return mExecutor;
+ }
+}
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index 9d32a809..8bb0b184 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -1896,7 +1896,8 @@
// from system/core/include/system/graphics.h
private static final int HAL_PIXEL_FORMAT_RAW16 = 0x20;
- private static final int HAL_PIXEL_FORMAT_BLOB = 0x21;
+ /** @hide */
+ public static final int HAL_PIXEL_FORMAT_BLOB = 0x21;
private static final int HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED = 0x22;
private static final int HAL_PIXEL_FORMAT_YCbCr_420_888 = 0x23;
private static final int HAL_PIXEL_FORMAT_RAW_OPAQUE = 0x24;
@@ -1910,7 +1911,8 @@
private static final int HAL_DATASPACE_RANGE_SHIFT = 27;
private static final int HAL_DATASPACE_UNKNOWN = 0x0;
- private static final int HAL_DATASPACE_V0_JFIF =
+ /** @hide */
+ public static final int HAL_DATASPACE_V0_JFIF =
(2 << HAL_DATASPACE_STANDARD_SHIFT) |
(3 << HAL_DATASPACE_TRANSFER_SHIFT) |
(1 << HAL_DATASPACE_RANGE_SHIFT);
diff --git a/core/jni/android_hardware_HardwareBuffer.cpp b/core/jni/android_hardware_HardwareBuffer.cpp
index 2944f72..b25b4f7 100644
--- a/core/jni/android_hardware_HardwareBuffer.cpp
+++ b/core/jni/android_hardware_HardwareBuffer.cpp
@@ -31,7 +31,6 @@
#include <binder/Parcel.h>
#include <private/gui/ComposerService.h>
-#include <ui/GraphicBuffer.h>
#include <ui/PixelFormat.h>
#include <hardware/gralloc1.h>
@@ -224,6 +223,16 @@
}
}
+GraphicBuffer* android_hardware_HardwareBuffer_getNativeGraphicBuffer(
+ JNIEnv* env, jobject hardwareBufferObj) {
+ if (env->IsInstanceOf(hardwareBufferObj, gHardwareBufferClassInfo.clazz)) {
+ return GraphicBufferWrapper_to_GraphicBuffer(
+ env->GetLongField(hardwareBufferObj, gHardwareBufferClassInfo.mNativeObject));
+ } else {
+ return nullptr;
+ }
+}
+
jobject android_hardware_HardwareBuffer_createFromAHardwareBuffer(
JNIEnv* env, AHardwareBuffer* hardwareBuffer) {
GraphicBuffer* buffer = AHardwareBuffer_to_GraphicBuffer(hardwareBuffer);
diff --git a/core/jni/android_hardware_camera2_CameraMetadata.cpp b/core/jni/android_hardware_camera2_CameraMetadata.cpp
index 919e351..315ca2e 100644
--- a/core/jni/android_hardware_camera2_CameraMetadata.cpp
+++ b/core/jni/android_hardware_camera2_CameraMetadata.cpp
@@ -249,6 +249,32 @@
return metadata->entryCount();
}
+static void CameraMetadata_update(JNIEnv *env, jclass thiz, jlong dst, jlong src) {
+ ALOGV("%s", __FUNCTION__);
+
+ CameraMetadata* metadataDst = CameraMetadata_getPointerThrow(env, dst);
+ CameraMetadata* metadataSrc = CameraMetadata_getPointerThrow(env, src);
+
+ if (((metadataDst == NULL) || (metadataDst->isEmpty())) ||
+ ((metadataSrc == NULL) || metadataSrc->isEmpty())) {
+ return;
+ }
+
+ auto metaBuffer = metadataSrc->getAndLock();
+ camera_metadata_ro_entry_t entry;
+ auto entryCount = get_camera_metadata_entry_count(metaBuffer);
+ for (size_t i = 0; i < entryCount; i++) {
+ auto stat = get_camera_metadata_ro_entry(metaBuffer, i, &entry);
+ if (stat != NO_ERROR) {
+ ALOGE("%s: Failed to retrieve source metadata!", __func__);
+ metadataSrc->unlock(metaBuffer);
+ return;
+ }
+ metadataDst->update(entry.tag, entry.data.u8, entry.count);
+ }
+ metadataSrc->unlock(metaBuffer);
+}
+
static jlong CameraMetadata_getBufferSize(JNIEnv *env, jclass thiz, jlong ptr) {
ALOGV("%s", __FUNCTION__);
@@ -565,6 +591,9 @@
{ "nativeAllocateCopy",
"(J)J",
(void *)CameraMetadata_allocateCopy },
+ { "nativeUpdate",
+ "(JJ)V",
+ (void*)CameraMetadata_update },
{ "nativeIsEmpty",
"(J)Z",
(void*)CameraMetadata_isEmpty },
diff --git a/core/jni/include/android_runtime/android_hardware_HardwareBuffer.h b/core/jni/include/android_runtime/android_hardware_HardwareBuffer.h
index c452b74..dfd8035 100644
--- a/core/jni/include/android_runtime/android_hardware_HardwareBuffer.h
+++ b/core/jni/include/android_runtime/android_hardware_HardwareBuffer.h
@@ -18,6 +18,7 @@
#define _ANDROID_HARDWARE_HARDWAREBUFFER_H
#include <android/hardware_buffer.h>
+#include <ui/GraphicBuffer.h>
#include "jni.h"
@@ -27,6 +28,10 @@
extern AHardwareBuffer* android_hardware_HardwareBuffer_getNativeHardwareBuffer(
JNIEnv* env, jobject hardwareBufferObj);
+/* Gets the underlying GraphicBuffer for a HardwareBuffer. */
+extern GraphicBuffer* android_hardware_HardwareBuffer_getNativeGraphicBuffer(
+ JNIEnv* env, jobject hardwareBufferObj);
+
/* Returns a HardwareBuffer wrapper for the underlying AHardwareBuffer. */
extern jobject android_hardware_HardwareBuffer_createFromAHardwareBuffer(
JNIEnv* env, AHardwareBuffer* hardwareBuffer);
diff --git a/media/java/android/media/Image.java b/media/java/android/media/Image.java
index 0ef4b94..1616c03 100644
--- a/media/java/android/media/Image.java
+++ b/media/java/android/media/Image.java
@@ -212,6 +212,23 @@
public abstract int getScalingMode();
/**
+ * Get the fence file descriptor associated with this frame.
+ * @return The fence file descriptor for this frame.
+ * @hide
+ */
+ public int getFenceFd() {
+ return -1;
+ }
+
+ /**
+ * Get the number of planes.
+ * @return The number of expected planes.
+ * @hide
+ */
+ public int getPlaneCount() {
+ return -1;
+ }
+ /**
* Get the {@link android.hardware.HardwareBuffer HardwareBuffer} handle of the input image
* intended for GPU and/or hardware access.
* <p>
@@ -330,8 +347,9 @@
* @return true if the image is attachable to a new owner, false if the image is still attached
* to its current owner, or the image is a stand-alone image and is not attachable to
* a new owner.
+ * @hide
*/
- boolean isAttachable() {
+ public boolean isAttachable() {
throwISEIfImageIsInvalid();
return false;
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index 5dc6f75..f5b204a 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -18,8 +18,10 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
+import android.graphics.GraphicBuffer;
import android.graphics.ImageFormat;
import android.graphics.ImageFormat.Format;
+import android.graphics.Rect;
import android.hardware.HardwareBuffer;
import android.hardware.HardwareBuffer.Usage;
import android.os.Handler;
@@ -715,8 +717,9 @@
* @throws IllegalStateException If the ImageReader or image have been
* closed, or the has been detached, or has not yet been
* acquired.
+ * @hide
*/
- void detachImage(Image image) {
+ public void detachImage(Image image) {
if (image == null) {
throw new IllegalArgumentException("input image must not be null");
}
@@ -898,6 +901,18 @@
}
@Override
+ public int getPlaneCount() {
+ throwISEIfImageIsInvalid();
+ return ImageReader.this.mNumPlanes;
+ }
+
+ @Override
+ public int getFenceFd() {
+ throwISEIfImageIsInvalid();
+ return nativeGetFenceFd();
+ }
+
+ @Override
public HardwareBuffer getHardwareBuffer() {
throwISEIfImageIsInvalid();
return nativeGetHardwareBuffer();
@@ -931,7 +946,7 @@
}
@Override
- boolean isAttachable() {
+ public boolean isAttachable() {
throwISEIfImageIsInvalid();
return mIsDetached.get();
}
@@ -1048,6 +1063,7 @@
private synchronized native int nativeGetWidth();
private synchronized native int nativeGetHeight();
private synchronized native int nativeGetFormat(int readerFormat);
+ private synchronized native int nativeGetFenceFd();
private synchronized native HardwareBuffer nativeGetHardwareBuffer();
}
@@ -1069,6 +1085,67 @@
private synchronized native int nativeImageSetup(Image i);
/**
+ * @hide
+ */
+ public static class ImagePlane extends android.media.Image.Plane {
+ private ImagePlane(int rowStride, int pixelStride, ByteBuffer buffer) {
+ mRowStride = rowStride;
+ mPixelStride = pixelStride;
+ mBuffer = buffer;
+ /**
+ * Set the byteBuffer order according to host endianness (native
+ * order), otherwise, the byteBuffer order defaults to
+ * ByteOrder.BIG_ENDIAN.
+ */
+ mBuffer.order(ByteOrder.nativeOrder());
+ }
+
+ @Override
+ public ByteBuffer getBuffer() {
+ return mBuffer;
+ }
+
+ @Override
+ public int getPixelStride() {
+ return mPixelStride;
+ }
+
+ @Override
+ public int getRowStride() {
+ return mRowStride;
+ }
+
+ final private int mPixelStride;
+ final private int mRowStride;
+
+ private ByteBuffer mBuffer;
+ }
+
+ /**
+ * @hide
+ */
+ public static ImagePlane[] initializeImagePlanes(int numPlanes,
+ GraphicBuffer buffer, int fenceFd, int format, long timestamp, int transform,
+ int scalingMode, Rect crop) {
+
+ return nativeCreateImagePlanes(numPlanes, buffer, fenceFd, format, crop.left, crop.top,
+ crop.right, crop.bottom);
+ }
+
+ private synchronized static native ImagePlane[] nativeCreateImagePlanes(int numPlanes,
+ GraphicBuffer buffer, int fenceFd, int format, int cropLeft, int cropTop,
+ int cropRight, int cropBottom);
+
+ /**
+ * @hide
+ */
+ public static void unlockGraphicBuffer(GraphicBuffer buffer) {
+ nativeUnlockGraphicBuffer(buffer);
+ }
+
+ private synchronized static native void nativeUnlockGraphicBuffer(GraphicBuffer buffer);
+
+ /**
* We use a class initializer to allow the native code to cache some
* field offsets.
*/
diff --git a/media/java/android/media/ImageWriter.java b/media/java/android/media/ImageWriter.java
index 7bc2b31..92db946 100644
--- a/media/java/android/media/ImageWriter.java
+++ b/media/java/android/media/ImageWriter.java
@@ -18,6 +18,7 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
+import android.graphics.GraphicBuffer;
import android.graphics.ImageFormat;
import android.graphics.ImageFormat.Format;
import android.graphics.PixelFormat;
@@ -360,16 +361,20 @@
throw new IllegalStateException("Image from ImageWriter is invalid");
}
- // For images from other components, need to detach first, then attach.
+ // For images from other components that have non-null owner, need to detach first,
+ // then attach. Images without owners must already be attachable.
if (!ownedByMe) {
- if (!(image.getOwner() instanceof ImageReader)) {
+ if (image.getOwner() == null) {
+
+ } else if ((image.getOwner() instanceof ImageReader)) {
+ ImageReader prevOwner = (ImageReader) image.getOwner();
+
+ prevOwner.detachImage(image);
+ } else {
throw new IllegalArgumentException("Only images from ImageReader can be queued to"
+ " ImageWriter, other image source is not supported yet!");
}
- ImageReader prevOwner = (ImageReader) image.getOwner();
-
- prevOwner.detachImage(image);
attachAndQueueInputImage(image);
// This clears the native reference held by the original owner.
// When this Image is detached later by this ImageWriter, the
@@ -565,9 +570,18 @@
// need do some cleanup to make sure no orphaned
// buffer caused leak.
Rect crop = image.getCropRect();
- nativeAttachAndQueueImage(mNativeContext, image.getNativeContext(), image.getFormat(),
- image.getTimestamp(), crop.left, crop.top, crop.right, crop.bottom,
- image.getTransform(), image.getScalingMode());
+ if (image.getNativeContext() != 0) {
+ nativeAttachAndQueueImage(mNativeContext, image.getNativeContext(), image.getFormat(),
+ image.getTimestamp(), crop.left, crop.top, crop.right, crop.bottom,
+ image.getTransform(), image.getScalingMode());
+ } else {
+ GraphicBuffer gb = GraphicBuffer.createFromHardwareBuffer(image.getHardwareBuffer());
+ nativeAttachAndQueueGraphicBuffer(mNativeContext, gb, image.getFormat(),
+ image.getTimestamp(), crop.left, crop.top, crop.right, crop.bottom,
+ image.getTransform(), image.getScalingMode());
+ gb.destroy();
+ image.close();
+ }
}
/**
@@ -771,7 +785,7 @@
}
@Override
- boolean isAttachable() {
+ public boolean isAttachable() {
throwISEIfImageIsInvalid();
// Don't allow Image to be detached from ImageWriter for now, as no
// detach API is exposed.
@@ -898,6 +912,9 @@
private synchronized native int nativeAttachAndQueueImage(long nativeCtx,
long imageNativeBuffer, int imageFormat, long timestampNs, int left,
int top, int right, int bottom, int transform, int scalingMode);
+ private synchronized native int nativeAttachAndQueueGraphicBuffer(long nativeCtx,
+ GraphicBuffer graphicBuffer, int imageFormat, long timestampNs, int left,
+ int top, int right, int bottom, int transform, int scalingMode);
private synchronized native void cancelImage(long nativeCtx, Image image);
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index bd2a0eaa7..5174c0c 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -30,6 +30,7 @@
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/android_view_Surface.h>
+#include <android_runtime/android_graphics_GraphicBuffer.h>
#include <android_runtime/android_hardware_HardwareBuffer.h>
#include <grallocusage/GrallocUsageConversion.h>
@@ -42,6 +43,8 @@
#include <inttypes.h>
#include <android/hardware_buffer_jni.h>
+#include <ui/Rect.h>
+
#define ANDROID_MEDIA_IMAGEREADER_CTX_JNI_ID "mNativeContext"
#define ANDROID_MEDIA_SURFACEIMAGE_BUFFER_JNI_ID "mNativeBuffer"
#define ANDROID_MEDIA_SURFACEIMAGE_TS_JNI_ID "mTimestamp"
@@ -78,6 +81,11 @@
jmethodID ctor;
} gSurfacePlaneClassInfo;
+static struct {
+ jclass clazz;
+ jmethodID ctor;
+} gImagePlaneClassInfo;
+
// Get an ID that's unique within this process.
static int32_t createProcessUniqueId() {
static volatile int32_t globalCounter = 0;
@@ -347,6 +355,15 @@
"(Landroid/media/ImageReader$SurfaceImage;IILjava/nio/ByteBuffer;)V");
LOG_ALWAYS_FATAL_IF(gSurfacePlaneClassInfo.ctor == NULL,
"Can not find SurfacePlane constructor");
+
+ planeClazz = env->FindClass("android/media/ImageReader$ImagePlane");
+ LOG_ALWAYS_FATAL_IF(planeClazz == NULL, "Can not find ImagePlane class");
+ // FindClass only gives a local reference of jclass object.
+ gImagePlaneClassInfo.clazz = (jclass) env->NewGlobalRef(planeClazz);
+ gImagePlaneClassInfo.ctor = env->GetMethodID(gImagePlaneClassInfo.clazz, "<init>",
+ "(IILjava/nio/ByteBuffer;)V");
+ LOG_ALWAYS_FATAL_IF(gImagePlaneClassInfo.ctor == NULL,
+ "Can not find ImagePlane constructor");
}
static void ImageReader_init(JNIEnv* env, jobject thiz, jobject weakThiz, jint width, jint height,
@@ -729,6 +746,89 @@
return true;
}
+static void ImageReader_unlockGraphicBuffer(JNIEnv* env, jobject /*thiz*/,
+ jobject buffer) {
+ sp<GraphicBuffer> graphicBuffer =
+ android_graphics_GraphicBuffer_getNativeGraphicsBuffer(env, buffer);
+ if (graphicBuffer.get() == NULL) {
+ jniThrowRuntimeException(env, "Invalid graphic buffer!");
+ }
+
+ status_t res = graphicBuffer->unlock();
+ if (res != OK) {
+ jniThrowRuntimeException(env, "unlock buffer failed");
+ }
+}
+
+static jobjectArray ImageReader_createImagePlanes(JNIEnv* env, jobject /*thiz*/,
+ int numPlanes, jobject buffer, int fenceFd, int format, int cropLeft, int cropTop,
+ int cropRight, int cropBottom)
+{
+ ALOGV("%s: create ImagePlane array with size %d", __FUNCTION__, numPlanes);
+ int rowStride = 0;
+ int pixelStride = 0;
+ uint8_t *pData = NULL;
+ uint32_t dataSize = 0;
+ jobject byteBuffer = NULL;
+
+ PublicFormat publicReaderFormat = static_cast<PublicFormat>(format);
+ int halReaderFormat = mapPublicFormatToHalFormat(publicReaderFormat);
+
+ if (isFormatOpaque(halReaderFormat) && numPlanes > 0) {
+ String8 msg;
+ msg.appendFormat("Format 0x%x is opaque, thus not writable, the number of planes (%d)"
+ " must be 0", halReaderFormat, numPlanes);
+ jniThrowException(env, "java/lang/IllegalArgumentException", msg.string());
+ return NULL;
+ }
+
+ jobjectArray imagePlanes = env->NewObjectArray(numPlanes, gImagePlaneClassInfo.clazz,
+ /*initial_element*/NULL);
+ if (imagePlanes == NULL) {
+ jniThrowRuntimeException(env, "Failed to create ImagePlane arrays,"
+ " probably out of memory");
+ return NULL;
+ }
+ if (isFormatOpaque(halReaderFormat)) {
+ // Return 0 element surface array.
+ return imagePlanes;
+ }
+
+ LockedImage lockedImg = LockedImage();
+ uint32_t lockUsage = GRALLOC_USAGE_SW_READ_OFTEN;
+
+ Rect cropRect(cropLeft, cropTop, cropRight, cropBottom);
+ status_t res = lockImageFromBuffer(
+ android_graphics_GraphicBuffer_getNativeGraphicsBuffer(env, buffer), lockUsage,
+ cropRect, fenceFd, &lockedImg);
+ if (res != OK) {
+ jniThrowExceptionFmt(env, "java/lang/RuntimeException",
+ "lock buffer failed for format 0x%x", format);
+ return nullptr;
+ }
+
+ // Create all ImagePlanes
+ for (int i = 0; i < numPlanes; i++) {
+ if (!Image_getLockedImageInfo(env, &lockedImg, i, halReaderFormat,
+ &pData, &dataSize, &pixelStride, &rowStride)) {
+ return NULL;
+ }
+ byteBuffer = env->NewDirectByteBuffer(pData, dataSize);
+ if ((byteBuffer == NULL) && (env->ExceptionCheck() == false)) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Failed to allocate ByteBuffer");
+ return NULL;
+ }
+
+ // Finally, create this ImagePlane.
+ jobject imagePlane = env->NewObject(gImagePlaneClassInfo.clazz,
+ gImagePlaneClassInfo.ctor, rowStride, pixelStride, byteBuffer);
+ env->SetObjectArrayElement(imagePlanes, i, imagePlane);
+ }
+
+ return imagePlanes;
+}
+
static jobjectArray Image_createSurfacePlanes(JNIEnv* env, jobject thiz,
int numPlanes, int readerFormat, uint64_t ndkReaderUsage)
{
@@ -801,6 +901,16 @@
return getBufferHeight(buffer);
}
+static jint Image_getFenceFd(JNIEnv* env, jobject thiz)
+{
+ BufferItem* buffer = Image_getBufferItem(env, thiz);
+ if ((buffer != NULL) && (buffer->mFence.get() != NULL)){
+ return buffer->mFence->get();
+ }
+
+ return -1;
+}
+
static jint Image_getFormat(JNIEnv* env, jobject thiz, jint readerFormat)
{
if (isFormatOpaque(readerFormat)) {
@@ -844,6 +954,11 @@
{"nativeImageSetup", "(Landroid/media/Image;)I", (void*)ImageReader_imageSetup },
{"nativeGetSurface", "()Landroid/view/Surface;", (void*)ImageReader_getSurface },
{"nativeDetachImage", "(Landroid/media/Image;)I", (void*)ImageReader_detachImage },
+ {"nativeCreateImagePlanes",
+ "(ILandroid/graphics/GraphicBuffer;IIIIII)[Landroid/media/ImageReader$ImagePlane;",
+ (void*)ImageReader_createImagePlanes },
+ {"nativeUnlockGraphicBuffer",
+ "(Landroid/graphics/GraphicBuffer;)V", (void*)ImageReader_unlockGraphicBuffer },
{"nativeDiscardFreeBuffers", "()V", (void*)ImageReader_discardFreeBuffers }
};
@@ -853,6 +968,7 @@
{"nativeGetWidth", "()I", (void*)Image_getWidth },
{"nativeGetHeight", "()I", (void*)Image_getHeight },
{"nativeGetFormat", "(I)I", (void*)Image_getFormat },
+ {"nativeGetFenceFd", "()I", (void*)Image_getFenceFd },
{"nativeGetHardwareBuffer", "()Landroid/hardware/HardwareBuffer;",
(void*)Image_getHardwareBuffer },
};
diff --git a/media/jni/android_media_ImageWriter.cpp b/media/jni/android_media_ImageWriter.cpp
index 936edb3..5d959a3 100644
--- a/media/jni/android_media_ImageWriter.cpp
+++ b/media/jni/android_media_ImageWriter.cpp
@@ -29,6 +29,7 @@
#include <ui/PublicFormat.h>
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/android_view_Surface.h>
+#include <android_runtime/android_graphics_GraphicBuffer.h>
#include <android_runtime/android_hardware_HardwareBuffer.h>
#include <private/android/AHardwareBufferHelpers.h>
#include <jni.h>
@@ -673,36 +674,12 @@
Image_setNativeContext(env, image, NULL, -1);
}
-static jint ImageWriter_attachAndQueueImage(JNIEnv* env, jobject thiz, jlong nativeCtx,
- jlong nativeBuffer, jint imageFormat, jlong timestampNs, jint left, jint top,
+static status_t attachAndQeueuGraphicBuffer(JNIEnv* env, JNIImageWriterContext *ctx,
+ sp<Surface> surface, sp<GraphicBuffer> gb, jlong timestampNs, jint left, jint top,
jint right, jint bottom, jint transform, jint scalingMode) {
- ALOGV("%s", __FUNCTION__);
- JNIImageWriterContext* const ctx = reinterpret_cast<JNIImageWriterContext *>(nativeCtx);
- if (ctx == NULL || thiz == NULL) {
- jniThrowException(env, "java/lang/IllegalStateException",
- "ImageWriterContext is not initialized");
- return -1;
- }
-
- sp<Surface> surface = ctx->getProducer();
status_t res = OK;
- if (isFormatOpaque(imageFormat) != isFormatOpaque(ctx->getBufferFormat())) {
- jniThrowException(env, "java/lang/IllegalStateException",
- "Trying to attach an opaque image into a non-opaque ImageWriter, or vice versa");
- return -1;
- }
-
- // Image is guaranteed to be from ImageReader at this point, so it is safe to
- // cast to BufferItem pointer.
- BufferItem* buffer = reinterpret_cast<BufferItem*>(nativeBuffer);
- if (buffer == NULL) {
- jniThrowException(env, "java/lang/IllegalStateException",
- "Image is not initialized or already closed");
- return -1;
- }
-
// Step 1. Attach Image
- res = surface->attachBuffer(buffer->mGraphicBuffer.get());
+ res = surface->attachBuffer(gb.get());
if (res != OK) {
ALOGE("Attach image failed: %s (%d)", strerror(-res), res);
switch (res) {
@@ -756,7 +733,7 @@
// queue the "attached" flag before calling queueBuffer. In case
// queueBuffer() fails, remove it from the queue.
ctx->queueAttachedFlag(true);
- res = anw->queueBuffer(anw.get(), buffer->mGraphicBuffer.get(), /*fenceFd*/
+ res = anw->queueBuffer(anw.get(), gb.get(), /*fenceFd*/
-1);
if (res != OK) {
ALOGE("%s: Queue buffer failed: %s (%d)", __FUNCTION__, strerror(-res), res);
@@ -779,6 +756,67 @@
return res;
}
+static jint ImageWriter_attachAndQueueImage(JNIEnv* env, jobject thiz, jlong nativeCtx,
+ jlong nativeBuffer, jint imageFormat, jlong timestampNs, jint left, jint top,
+ jint right, jint bottom, jint transform, jint scalingMode) {
+ ALOGV("%s", __FUNCTION__);
+ JNIImageWriterContext* const ctx = reinterpret_cast<JNIImageWriterContext *>(nativeCtx);
+ if (ctx == NULL || thiz == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "ImageWriterContext is not initialized");
+ return -1;
+ }
+
+ sp<Surface> surface = ctx->getProducer();
+ if (isFormatOpaque(imageFormat) != isFormatOpaque(ctx->getBufferFormat())) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Trying to attach an opaque image into a non-opaque ImageWriter, or vice versa");
+ return -1;
+ }
+
+ // Image is guaranteed to be from ImageReader at this point, so it is safe to
+ // cast to BufferItem pointer.
+ BufferItem* buffer = reinterpret_cast<BufferItem*>(nativeBuffer);
+ if (buffer == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Image is not initialized or already closed");
+ return -1;
+ }
+
+ return attachAndQeueuGraphicBuffer(env, ctx, surface, buffer->mGraphicBuffer, timestampNs, left,
+ top, right, bottom, transform, scalingMode);
+}
+
+static jint ImageWriter_attachAndQueueGraphicBuffer(JNIEnv* env, jobject thiz, jlong nativeCtx,
+ jobject buffer, jint format, jlong timestampNs, jint left, jint top,
+ jint right, jint bottom, jint transform, jint scalingMode) {
+ ALOGV("%s", __FUNCTION__);
+ JNIImageWriterContext* const ctx = reinterpret_cast<JNIImageWriterContext *>(nativeCtx);
+ if (ctx == NULL || thiz == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "ImageWriterContext is not initialized");
+ return -1;
+ }
+
+ sp<Surface> surface = ctx->getProducer();
+ if (isFormatOpaque(format) != isFormatOpaque(ctx->getBufferFormat())) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Trying to attach an opaque image into a non-opaque ImageWriter, or vice versa");
+ return -1;
+ }
+
+ sp<GraphicBuffer> graphicBuffer = android_graphics_GraphicBuffer_getNativeGraphicsBuffer(env,
+ buffer);
+ if (graphicBuffer.get() == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "Trying to attach an invalid graphic buffer");
+ return -1;
+ }
+
+ return attachAndQeueuGraphicBuffer(env, ctx, surface, graphicBuffer, timestampNs, left,
+ top, right, bottom, transform, scalingMode);
+}
+
// --------------------------Image methods---------------------------------------
static void Image_getNativeContext(JNIEnv* env, jobject thiz,
@@ -1011,6 +1049,9 @@
(void*)ImageWriter_init },
{"nativeClose", "(J)V", (void*)ImageWriter_close },
{"nativeAttachAndQueueImage", "(JJIJIIIIII)I", (void*)ImageWriter_attachAndQueueImage },
+ {"nativeAttachAndQueueGraphicBuffer",
+ "(JLandroid/graphics/GraphicBuffer;IJIIIIII)I",
+ (void*)ImageWriter_attachAndQueueGraphicBuffer },
{"nativeDequeueInputImage", "(JLandroid/media/Image;)V", (void*)ImageWriter_dequeueImage },
{"nativeQueueInputImage", "(JLandroid/media/Image;JIIIIII)V", (void*)ImageWriter_queueImage },
{"cancelImage", "(JLandroid/media/Image;)V", (void*)ImageWriter_cancelImage },
diff --git a/packages/services/CameraExtensionsProxy/Android.bp b/packages/services/CameraExtensionsProxy/Android.bp
new file mode 100644
index 0000000..54b7453
--- /dev/null
+++ b/packages/services/CameraExtensionsProxy/Android.bp
@@ -0,0 +1,29 @@
+//
+// Copyright (C) 2020 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.
+//
+
+android_app {
+ name: "CameraExtensionsProxy",
+ srcs: ["src/**/*.java"],
+ libs: ["androidx.camera.extensions.stub"],
+ platform_apis: true,
+ certificate: "platform",
+}
+
+filegroup {
+ name: "CameraExtensionsProxy-aidl-sources",
+ srcs: ["src/**/*.aidl"],
+ path: "src",
+}
diff --git a/packages/services/CameraExtensionsProxy/AndroidManifest.xml b/packages/services/CameraExtensionsProxy/AndroidManifest.xml
new file mode 100644
index 0000000..7416cba
--- /dev/null
+++ b/packages/services/CameraExtensionsProxy/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.camera">
+
+ <uses-permission android:name="android.permission.CAMERA" />
+
+ <application
+ android:label="@string/app_name"
+ android:defaultToDeviceProtectedStorage="true"
+ android:directBootAware="true">
+
+ <service android:name=".CameraExtensionsProxyService"
+ android:exported="true">
+ </service>
+ <uses-library android:name="androidx.camera.extensions.impl" android:required="false" />
+ </application>
+
+</manifest>
diff --git a/packages/services/CameraExtensionsProxy/res/values/strings.xml b/packages/services/CameraExtensionsProxy/res/values/strings.xml
new file mode 100644
index 0000000..5062b3d
--- /dev/null
+++ b/packages/services/CameraExtensionsProxy/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="app_name">CameraExtensionsProxy</string>
+
+</resources>
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/camera/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/camera/CameraExtensionsProxyService.java
new file mode 100644
index 0000000..0f05019
--- /dev/null
+++ b/packages/services/CameraExtensionsProxy/src/com/android/camera/CameraExtensionsProxyService.java
@@ -0,0 +1,857 @@
+/**
+ * Copyright (c) 2020, 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 com.android.camera;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.GraphicBuffer;
+import android.graphics.Rect;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraExtensionCharacteristics;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.extension.CaptureBundle;
+import android.hardware.camera2.extension.CaptureStageImpl;
+import android.hardware.camera2.extension.ICameraExtensionsProxyService;
+import android.hardware.camera2.extension.ICaptureProcessorImpl;
+import android.hardware.camera2.extension.IPreviewExtenderImpl;
+import android.hardware.camera2.extension.IPreviewImageProcessorImpl;
+import android.hardware.camera2.extension.IRequestUpdateProcessorImpl;
+import android.hardware.camera2.extension.ParcelImage;
+import android.hardware.camera2.extension.IImageCaptureExtenderImpl;
+import android.hardware.camera2.extension.SizeList;
+import android.hardware.camera2.impl.CameraMetadataNative;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.HardwareBuffer;
+import android.media.Image;
+import android.media.ImageReader;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Size;
+import android.view.Surface;
+
+import androidx.camera.extensions.impl.AutoImageCaptureExtenderImpl;
+import androidx.camera.extensions.impl.AutoPreviewExtenderImpl;
+import androidx.camera.extensions.impl.BeautyImageCaptureExtenderImpl;
+import androidx.camera.extensions.impl.BeautyPreviewExtenderImpl;
+import androidx.camera.extensions.impl.BokehImageCaptureExtenderImpl;
+import androidx.camera.extensions.impl.BokehPreviewExtenderImpl;
+import androidx.camera.extensions.impl.CaptureProcessorImpl;
+import androidx.camera.extensions.impl.ExtensionVersionImpl;
+import androidx.camera.extensions.impl.HdrImageCaptureExtenderImpl;
+import androidx.camera.extensions.impl.HdrPreviewExtenderImpl;
+import androidx.camera.extensions.impl.ImageCaptureExtenderImpl;
+import androidx.camera.extensions.impl.InitializerImpl;
+import androidx.camera.extensions.impl.NightImageCaptureExtenderImpl;
+import androidx.camera.extensions.impl.NightPreviewExtenderImpl;
+import androidx.camera.extensions.impl.PreviewExtenderImpl;
+import androidx.camera.extensions.impl.PreviewExtenderImpl.ProcessorType;
+import androidx.camera.extensions.impl.PreviewImageProcessorImpl;
+import androidx.camera.extensions.impl.RequestUpdateProcessorImpl;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.List;
+
+public class CameraExtensionsProxyService extends Service {
+ private static final String TAG = "CameraExtensionsProxyService";
+
+ private static final String CAMERA_EXTENSION_VERSION_NAME =
+ "androidx.camera.extensions.impl.ExtensionVersionImpl";
+ private static final String LATEST_VERSION = "1.1.0";
+ private static final String[] SUPPORTED_VERSION_PREFIXES = {"1.1.", "1.0."};
+ private static final boolean EXTENSIONS_PRESENT = checkForExtensions();
+ private static final String EXTENSIONS_VERSION = EXTENSIONS_PRESENT ?
+ (new ExtensionVersionImpl()).checkApiVersion(LATEST_VERSION) : null;
+ private static final boolean LATEST_VERSION_SUPPORTED =
+ EXTENSIONS_PRESENT && EXTENSIONS_VERSION.startsWith(SUPPORTED_VERSION_PREFIXES[0]);
+
+ private static boolean checkForExtensions() {
+ try {
+ Class.forName(CAMERA_EXTENSION_VERSION_NAME);
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+
+ String extensionVersion = (new ExtensionVersionImpl()).checkApiVersion(LATEST_VERSION);
+ for (String supportedVersion : SUPPORTED_VERSION_PREFIXES) {
+ if (extensionVersion.startsWith(supportedVersion)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * A per-process global camera extension manager instance, to track and
+ * initialize/release extensions depending on client activity.
+ */
+ private static final class CameraExtensionManagerGlobal {
+ private static final String TAG = "CameraExtensionManagerGlobal";
+ private final int EXTENSION_DELAY_MS = 1000;
+
+ private final Handler mHandler;
+ private final HandlerThread mHandlerThread;
+ private final Object mLock = new Object();
+
+ private long mCurrentClientCount = 0;
+ private ArraySet<Long> mActiveClients = new ArraySet<>();
+
+ // Singleton instance
+ private static final CameraExtensionManagerGlobal GLOBAL_CAMERA_MANAGER =
+ new CameraExtensionManagerGlobal();
+
+ // Singleton, don't allow construction
+ private CameraExtensionManagerGlobal() {
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ }
+
+ private final static class InitializeHandler
+ implements InitializerImpl.OnExtensionsInitializedCallback {
+ private final InitializerFuture mStatusFuture;
+
+ public InitializeHandler(InitializerFuture statusFuture) {
+ mStatusFuture = statusFuture;
+ }
+
+ @Override
+ public void onSuccess() {
+ mStatusFuture.setStatus(true);
+ }
+
+ @Override
+ public void onFailure(int error) {
+ mStatusFuture.setStatus(false);
+ }
+ }
+
+ private final static class ReleaseHandler
+ implements InitializerImpl.OnExtensionsDeinitializedCallback {
+ private final InitializerFuture mStatusFuture;
+
+ public ReleaseHandler(InitializerFuture statusFuture) {
+ mStatusFuture = statusFuture;
+ }
+
+ @Override public void onSuccess() {
+ mStatusFuture.setStatus(true);
+ }
+
+ @Override
+ public void onFailure(int i) {
+ mStatusFuture.setStatus(false);
+ }
+ }
+
+ private static class InitializerFuture implements Future<Boolean> {
+ private volatile Boolean mStatus;
+ ConditionVariable mCondVar = new ConditionVariable(/*opened*/false);
+
+ public void setStatus(boolean status) {
+ mStatus = status;
+ mCondVar.open();
+ }
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ return false; // don't allow canceling this task
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false; // can never cancel this task
+ }
+
+ @Override
+ public boolean isDone() {
+ return mStatus != null;
+ }
+
+ @Override
+ public Boolean get() {
+ mCondVar.block();
+ return mStatus;
+ }
+
+ @Override
+ public Boolean get(long timeout, TimeUnit unit) throws TimeoutException {
+ long timeoutMs = unit.convert(timeout, TimeUnit.MILLISECONDS);
+ if (!mCondVar.block(timeoutMs)) {
+ throw new TimeoutException(
+ "Failed to receive status after " + timeout + " " + unit);
+ }
+
+ if (mStatus == null) {
+ throw new AssertionError();
+ }
+ return mStatus;
+ }
+
+ }
+
+ public static CameraExtensionManagerGlobal get() {
+ return GLOBAL_CAMERA_MANAGER;
+ }
+
+ public long registerClient(Context ctx) {
+ synchronized (mLock) {
+ if (LATEST_VERSION_SUPPORTED) {
+ if (mActiveClients.isEmpty()) {
+ InitializerFuture status = new InitializerFuture();
+ InitializerImpl.init(EXTENSIONS_VERSION, ctx, new InitializeHandler(status),
+ new HandlerExecutor(mHandler));
+ boolean initSuccess;
+ try {
+ initSuccess = status.get(EXTENSION_DELAY_MS,
+ TimeUnit.MILLISECONDS);
+ } catch (TimeoutException e) {
+ Log.e(TAG, "Timed out while initializing camera extensions!");
+ return -1;
+ }
+ if (!initSuccess) {
+ Log.e(TAG, "Failed while initializing camera extensions!");
+ return -1;
+ }
+ }
+ }
+
+ long ret = mCurrentClientCount;
+ mCurrentClientCount++;
+ if (mCurrentClientCount < 0) {
+ mCurrentClientCount = 0;
+ }
+ mActiveClients.add(ret);
+
+ return ret;
+ }
+ }
+
+ public void unregisterClient(long clientId) {
+ synchronized (mLock) {
+ if (mActiveClients.remove(clientId) && mActiveClients.isEmpty() &&
+ LATEST_VERSION_SUPPORTED) {
+ InitializerFuture status = new InitializerFuture();
+ InitializerImpl.deinit(new ReleaseHandler(status),
+ new HandlerExecutor(mHandler));
+ boolean releaseSuccess;
+ try {
+ releaseSuccess = status.get(EXTENSION_DELAY_MS, TimeUnit.MILLISECONDS);
+ } catch (TimeoutException e) {
+ Log.e(TAG, "Timed out while releasing camera extensions!");
+ return;
+ }
+ if (!releaseSuccess) {
+ Log.e(TAG, "Failed while releasing camera extensions!");
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ private static long registerClient(Context ctx) {
+ if (!EXTENSIONS_PRESENT) {
+ return -1;
+ }
+ return CameraExtensionManagerGlobal.get().registerClient(ctx);
+ }
+
+ /**
+ * @hide
+ */
+ public static void unregisterClient(long clientId) {
+ if (!EXTENSIONS_PRESENT) {
+ return;
+ }
+ CameraExtensionManagerGlobal.get().unregisterClient(clientId);
+ }
+
+ /**
+ * @hide
+ */
+ public static Pair<PreviewExtenderImpl, ImageCaptureExtenderImpl> initializeExtension(
+ int extensionType) {
+ switch (extensionType) {
+ case CameraExtensionCharacteristics.EXTENSION_AUTOMATIC:
+ return new Pair<>(new AutoPreviewExtenderImpl(),
+ new AutoImageCaptureExtenderImpl());
+ case CameraExtensionCharacteristics.EXTENSION_BEAUTY:
+ return new Pair<>(new BeautyPreviewExtenderImpl(),
+ new BeautyImageCaptureExtenderImpl());
+ case CameraExtensionCharacteristics.EXTENSION_BOKEH:
+ return new Pair<>(new BokehPreviewExtenderImpl(),
+ new BokehImageCaptureExtenderImpl());
+ case CameraExtensionCharacteristics.EXTENSION_HDR:
+ return new Pair<>(new HdrPreviewExtenderImpl(), new HdrImageCaptureExtenderImpl());
+ case CameraExtensionCharacteristics.EXTENSION_NIGHT:
+ return new Pair<>(new NightPreviewExtenderImpl(),
+ new NightImageCaptureExtenderImpl());
+ default:
+ throw new IllegalArgumentException("Unknown extension: " + extensionType);
+ }
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ // This will setup the camera vendor tag descriptor in the service process
+ try {
+ CameraManager manager = getSystemService(CameraManager.class);
+
+ String [] cameraIds = manager.getCameraIdListNoLazy();
+ if (cameraIds != null) {
+ for (String cameraId : cameraIds) {
+ CameraCharacteristics ch = manager.getCameraCharacteristics(cameraId);
+ }
+ }
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Failed to query camera characteristics!");
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ }
+
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return new CameraExtensionsProxyServiceStub();
+ }
+
+ private static List<SizeList> initializeParcelable(
+ List<Pair<Integer, android.util.Size[]>> sizes) {
+ if (sizes == null) {
+ return null;
+ }
+ ArrayList<SizeList> ret = new ArrayList<>(sizes.size());
+ for (Pair<Integer, Size[]> entry : sizes) {
+ SizeList sizeList = new SizeList();
+ sizeList.format = entry.first;
+ sizeList.sizes = new ArrayList<>();
+ for (android.util.Size size : entry.second) {
+ android.hardware.camera2.extension.Size sz =
+ new android.hardware.camera2.extension.Size();
+ sz.width = size.getWidth();
+ sz.height = size.getHeight();
+ sizeList.sizes.add(sz);
+ }
+ }
+
+ return ret;
+ }
+
+ private static CameraMetadataNative initializeParcelableMetadata(
+ List<Pair<CaptureRequest.Key, Object>> paramList) {
+ if (paramList == null) {
+ return null;
+ }
+
+ CameraMetadataNative ret = new CameraMetadataNative();
+ for (Pair<CaptureRequest.Key, Object> param : paramList) {
+ ret.set(param.first, param.second);
+ }
+
+ return ret;
+ }
+
+ private static android.hardware.camera2.extension.CaptureStageImpl initializeParcelable(
+ androidx.camera.extensions.impl.CaptureStageImpl captureStage) {
+ if (captureStage == null) {
+ return null;
+ }
+
+ android.hardware.camera2.extension.CaptureStageImpl ret =
+ new android.hardware.camera2.extension.CaptureStageImpl();
+ ret.id = captureStage.getId();
+ ret.parameters = initializeParcelableMetadata(captureStage.getParameters());
+
+ return ret;
+ }
+
+ private class CameraExtensionsProxyServiceStub extends ICameraExtensionsProxyService.Stub {
+ @Override
+ public long registerClient() {
+ return CameraExtensionsProxyService.registerClient(CameraExtensionsProxyService.this);
+ }
+
+ @Override
+ public void unregisterClient(long clientId) {
+ CameraExtensionsProxyService.unregisterClient(clientId);
+ }
+
+ @Override
+ public IPreviewExtenderImpl initializePreviewExtension(int extensionType) {
+ Pair<PreviewExtenderImpl, ImageCaptureExtenderImpl> extension;
+ try {
+ extension = initializeExtension(extensionType);
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+
+ return new PreviewExtenderImplStub(extension.first);
+ }
+
+ @Override
+ public IImageCaptureExtenderImpl initializeImageExtension(int extensionType) {
+ Pair<PreviewExtenderImpl, ImageCaptureExtenderImpl> extension;
+ try {
+ extension = initializeExtension(extensionType);
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+
+ return new ImageCaptureExtenderImplStub(extension.second);
+ }
+ }
+
+ private class PreviewExtenderImplStub extends IPreviewExtenderImpl.Stub {
+ private final PreviewExtenderImpl mPreviewExtender;
+
+ public PreviewExtenderImplStub(PreviewExtenderImpl previewExtender) {
+ mPreviewExtender = previewExtender;
+ }
+
+ @Override
+ public void onInit(String cameraId, CameraMetadataNative cameraCharacteristics) {
+ mPreviewExtender.onInit(cameraId, new CameraCharacteristics(cameraCharacteristics),
+ CameraExtensionsProxyService.this);
+ }
+
+ @Override
+ public void onDeInit() {
+ mPreviewExtender.onDeInit();
+ }
+
+ @Override
+ public CaptureStageImpl onPresetSession() {
+ return initializeParcelable(mPreviewExtender.onPresetSession());
+ }
+
+ @Override
+ public CaptureStageImpl onEnableSession() {
+ return initializeParcelable(mPreviewExtender.onEnableSession());
+ }
+
+ @Override
+ public CaptureStageImpl onDisableSession() {
+ return initializeParcelable(mPreviewExtender.onDisableSession());
+ }
+
+ @Override
+ public void init(String cameraId, CameraMetadataNative chars) {
+ if (LATEST_VERSION_SUPPORTED) {
+ mPreviewExtender.init(cameraId, new CameraCharacteristics(chars));
+ }
+ }
+
+ @Override
+ public boolean isExtensionAvailable(String cameraId, CameraMetadataNative chars) {
+ return mPreviewExtender.isExtensionAvailable(cameraId,
+ new CameraCharacteristics(chars));
+ }
+
+ @Override
+ public CaptureStageImpl getCaptureStage() {
+ return initializeParcelable(mPreviewExtender.getCaptureStage());
+ }
+
+ @Override
+ public int getProcessorType() {
+ ProcessorType processorType = mPreviewExtender.getProcessorType();
+ if (processorType == ProcessorType.PROCESSOR_TYPE_REQUEST_UPDATE_ONLY) {
+ return IPreviewExtenderImpl.PROCESSOR_TYPE_REQUEST_UPDATE_ONLY;
+ } else if (processorType == ProcessorType.PROCESSOR_TYPE_IMAGE_PROCESSOR) {
+ return IPreviewExtenderImpl.PROCESSOR_TYPE_IMAGE_PROCESSOR;
+ } else {
+ return IPreviewExtenderImpl.PROCESSOR_TYPE_NONE;
+ }
+ }
+
+ @Override
+ public IPreviewImageProcessorImpl getPreviewImageProcessor() {
+ PreviewImageProcessorImpl processor;
+ try {
+ processor = (PreviewImageProcessorImpl) mPreviewExtender.getProcessor();
+ } catch (ClassCastException e) {
+ Log.e(TAG, "Failed casting preview processor!");
+ return null;
+ }
+
+ if (processor != null) {
+ return new PreviewImageProcessorImplStub(processor);
+ }
+
+ return null;
+ }
+
+ @Override
+ public IRequestUpdateProcessorImpl getRequestUpdateProcessor() {
+ RequestUpdateProcessorImpl processor;
+ try {
+ processor = (RequestUpdateProcessorImpl) mPreviewExtender.getProcessor();
+ } catch (ClassCastException e) {
+ Log.e(TAG, "Failed casting preview processor!");
+ return null;
+ }
+
+ if (processor != null) {
+ return new RequestUpdateProcessorImplStub(processor);
+ }
+
+ return null;
+ }
+
+ @Override
+ public List<SizeList> getSupportedResolutions() {
+ if (LATEST_VERSION_SUPPORTED) {
+ List<Pair<Integer, android.util.Size[]>> sizes =
+ mPreviewExtender.getSupportedResolutions();
+ if ((sizes != null) && !sizes.isEmpty()) {
+ return initializeParcelable(sizes);
+ }
+ }
+ return null;
+ }
+ }
+
+ private class ImageCaptureExtenderImplStub extends IImageCaptureExtenderImpl.Stub {
+ private final ImageCaptureExtenderImpl mImageExtender;
+
+ public ImageCaptureExtenderImplStub(ImageCaptureExtenderImpl imageExtender) {
+ mImageExtender = imageExtender;
+ }
+
+ @Override
+ public void onInit(String cameraId, CameraMetadataNative cameraCharacteristics) {
+ mImageExtender.onInit(cameraId, new CameraCharacteristics(cameraCharacteristics),
+ CameraExtensionsProxyService.this);
+ }
+
+ @Override
+ public void onDeInit() {
+ mImageExtender.onDeInit();
+ }
+
+ @Override
+ public CaptureStageImpl onPresetSession() {
+ return initializeParcelable(mImageExtender.onPresetSession());
+ }
+
+ @Override
+ public CaptureStageImpl onEnableSession() {
+ return initializeParcelable(mImageExtender.onEnableSession());
+ }
+
+ @Override
+ public CaptureStageImpl onDisableSession() {
+ return initializeParcelable(mImageExtender.onDisableSession());
+ }
+
+ @Override
+ public void init(String cameraId, CameraMetadataNative chars) {
+ if (LATEST_VERSION_SUPPORTED) {
+ mImageExtender.init(cameraId, new CameraCharacteristics(chars));
+ }
+ }
+
+ @Override
+ public boolean isExtensionAvailable(String cameraId, CameraMetadataNative chars) {
+ return mImageExtender.isExtensionAvailable(cameraId,
+ new CameraCharacteristics(chars));
+ }
+
+ @Override
+ public ICaptureProcessorImpl getCaptureProcessor() {
+ CaptureProcessorImpl captureProcessor = mImageExtender.getCaptureProcessor();
+ if (captureProcessor != null) {
+ return new CaptureProcessorImplStub(captureProcessor);
+ }
+
+ return null;
+ }
+
+
+ @Override
+ public List<CaptureStageImpl> getCaptureStages() {
+ List<androidx.camera.extensions.impl.CaptureStageImpl> captureStages =
+ mImageExtender.getCaptureStages();
+ if (captureStages != null) {
+ ArrayList<android.hardware.camera2.extension.CaptureStageImpl> ret =
+ new ArrayList<>();
+ for (androidx.camera.extensions.impl.CaptureStageImpl stage : captureStages) {
+ ret.add(initializeParcelable(stage));
+ }
+
+ return ret;
+ }
+
+ return null;
+ }
+
+ @Override
+ public int getMaxCaptureStage() {
+ return mImageExtender.getMaxCaptureStage();
+ }
+
+ @Override
+ public List<SizeList> getSupportedResolutions() {
+ if (LATEST_VERSION_SUPPORTED) {
+ List<Pair<Integer, android.util.Size[]>> sizes =
+ mImageExtender.getSupportedResolutions();
+ if ((sizes != null) && !sizes.isEmpty()) {
+ return initializeParcelable(sizes);
+ }
+ }
+
+ return null;
+ }
+ }
+
+ private class CaptureProcessorImplStub extends ICaptureProcessorImpl.Stub {
+ private final CaptureProcessorImpl mCaptureProcessor;
+
+ public CaptureProcessorImplStub(CaptureProcessorImpl captureProcessor) {
+ mCaptureProcessor = captureProcessor;
+ }
+
+ @Override
+ public void onOutputSurface(Surface surface, int imageFormat) {
+ mCaptureProcessor.onOutputSurface(surface, imageFormat);
+ }
+
+ @Override
+ public void onResolutionUpdate(android.hardware.camera2.extension.Size size) {
+ mCaptureProcessor.onResolutionUpdate(new android.util.Size(size.width, size.height));
+ }
+
+ @Override
+ public void onImageFormatUpdate(int imageFormat) {
+ mCaptureProcessor.onImageFormatUpdate(imageFormat);
+ }
+
+ @Override
+ public void process(List<CaptureBundle> captureList) {
+ HashMap<Integer, Pair<Image, TotalCaptureResult>> captureMap = new HashMap<>();
+ for (CaptureBundle captureBundle : captureList) {
+ captureMap.put(captureBundle.stage, new Pair<> (
+ new ExtensionImage(captureBundle.captureImage),
+ new TotalCaptureResult(captureBundle.captureResult,
+ captureBundle.sequenceId)));
+ }
+ if (!captureMap.isEmpty()) {
+ mCaptureProcessor.process(captureMap);
+ } else {
+ Log.e(TAG, "Process request with absent capture stages!");
+ }
+ }
+ }
+
+ private class PreviewImageProcessorImplStub extends IPreviewImageProcessorImpl.Stub {
+ private final PreviewImageProcessorImpl mProcessor;
+
+ public PreviewImageProcessorImplStub(PreviewImageProcessorImpl processor) {
+ mProcessor = processor;
+ }
+
+ @Override
+ public void onOutputSurface(Surface surface, int imageFormat) {
+ mProcessor.onOutputSurface(surface, imageFormat);
+ }
+
+ @Override
+ public void onResolutionUpdate(android.hardware.camera2.extension.Size size) {
+ mProcessor.onResolutionUpdate(new android.util.Size(size.width, size.height));
+ }
+
+ @Override
+ public void onImageFormatUpdate(int imageFormat) {
+ mProcessor.onImageFormatUpdate(imageFormat);
+ }
+
+ @Override
+ public void process(android.hardware.camera2.extension.ParcelImage image,
+ CameraMetadataNative result, int sequenceId) {
+ mProcessor.process(new ExtensionImage(image),
+ new TotalCaptureResult(result, sequenceId));
+ }
+ }
+
+ private class RequestUpdateProcessorImplStub extends IRequestUpdateProcessorImpl.Stub {
+ private final RequestUpdateProcessorImpl mProcessor;
+
+ public RequestUpdateProcessorImplStub(RequestUpdateProcessorImpl processor) {
+ mProcessor = processor;
+ }
+
+ @Override
+ public void onOutputSurface(Surface surface, int imageFormat) {
+ mProcessor.onOutputSurface(surface, imageFormat);
+ }
+
+ @Override
+ public void onResolutionUpdate(android.hardware.camera2.extension.Size size) {
+ mProcessor.onResolutionUpdate(new android.util.Size(size.width, size.height));
+ }
+
+ @Override
+ public void onImageFormatUpdate(int imageFormat) {
+ mProcessor.onImageFormatUpdate(imageFormat);
+ }
+
+ @Override
+ public CaptureStageImpl process(CameraMetadataNative result, int sequenceId) {
+ return initializeParcelable(
+ mProcessor.process(new TotalCaptureResult(result, sequenceId)));
+ }
+ }
+
+ private class ExtensionImage extends android.media.Image {
+ private final android.hardware.camera2.extension.ParcelImage mParcelImage;
+ private GraphicBuffer mGraphicBuffer;
+ private ImageReader.ImagePlane[] mPlanes;
+
+ private ExtensionImage(android.hardware.camera2.extension.ParcelImage parcelImage) {
+ mParcelImage = parcelImage;
+ mIsImageValid = true;
+ }
+
+ @Override
+ public int getFormat() {
+ throwISEIfImageIsInvalid();
+ return mParcelImage.format;
+ }
+
+ @Override
+ public int getWidth() {
+ throwISEIfImageIsInvalid();
+ return mParcelImage.width;
+ }
+
+ @Override
+ public HardwareBuffer getHardwareBuffer() {
+ throwISEIfImageIsInvalid();
+ return mParcelImage.buffer;
+ }
+
+ @Override
+ public int getHeight() {
+ throwISEIfImageIsInvalid();
+ return mParcelImage.height;
+ }
+
+ @Override
+ public long getTimestamp() {
+ throwISEIfImageIsInvalid();
+ return mParcelImage.timestamp;
+ }
+
+ @Override
+ public int getTransform() {
+ throwISEIfImageIsInvalid();
+ return mParcelImage.transform;
+ }
+
+ @Override
+ public int getScalingMode() {
+ throwISEIfImageIsInvalid();
+ return mParcelImage.scalingMode;
+ }
+
+ @Override
+ public Plane[] getPlanes() {
+ throwISEIfImageIsInvalid();
+ if (mPlanes == null) {
+ int fenceFd = mParcelImage.fence != null ? mParcelImage.fence.getFd() : -1;
+ mGraphicBuffer = GraphicBuffer.createFromHardwareBuffer(mParcelImage.buffer);
+ mPlanes = ImageReader.initializeImagePlanes(mParcelImage.planeCount, mGraphicBuffer,
+ fenceFd, mParcelImage.format, mParcelImage.timestamp,
+ mParcelImage.transform, mParcelImage.scalingMode, mParcelImage.crop);
+ }
+ // Shallow copy is fine.
+ return mPlanes.clone();
+ }
+
+ @Override
+ protected final void finalize() throws Throwable {
+ try {
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ @Override
+ public boolean isAttachable() {
+ throwISEIfImageIsInvalid();
+ // Clients must always detach parcelable images
+ return true;
+ }
+
+ @Override
+ public Rect getCropRect() {
+ throwISEIfImageIsInvalid();
+ return mParcelImage.crop;
+ }
+
+ @Override
+ public void close() {
+ mIsImageValid = false;
+
+ if (mGraphicBuffer != null) {
+ ImageReader.unlockGraphicBuffer(mGraphicBuffer);
+ mGraphicBuffer.destroy();
+ mGraphicBuffer = null;
+ }
+
+ if (mPlanes != null) {
+ mPlanes = null;
+ }
+
+ if (mParcelImage.buffer != null) {
+ mParcelImage.buffer.close();
+ mParcelImage.buffer = null;
+ }
+
+ if (mParcelImage.fence != null) {
+ try {
+ mParcelImage.fence.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ mParcelImage.fence = null;
+ }
+ }
+ }
+}