diff options
15 files changed, 2006 insertions, 5 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 119a3de6835e..228a8f867844 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -18976,6 +18976,7 @@ package android.hardware.camera2 { field public static final int EXTENSION_AUTOMATIC = 0; // 0x0 field @Deprecated public static final int EXTENSION_BEAUTY = 1; // 0x1 field public static final int EXTENSION_BOKEH = 2; // 0x2 + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final int EXTENSION_EYES_FREE_VIDEOGRAPHY = 5; // 0x5 field public static final int EXTENSION_FACE_RETOUCH = 1; // 0x1 field public static final int EXTENSION_HDR = 3; // 0x3 field public static final int EXTENSION_NIGHT = 4; // 0x4 diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 30d57bb190be..4fe1c8b0a9e4 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -4480,6 +4480,94 @@ package android.hardware.camera2 { } +package android.hardware.camera2.extension { + + @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract class AdvancedExtender { + ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") protected AdvancedExtender(@NonNull android.hardware.camera2.CameraManager); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract java.util.List<android.hardware.camera2.CaptureRequest.Key> getAvailableCaptureRequestKeys(@NonNull String); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract java.util.List<android.hardware.camera2.CaptureResult.Key> getAvailableCaptureResultKeys(@NonNull String); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public long getMetadataVendorId(@NonNull String); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract android.hardware.camera2.extension.SessionProcessor getSessionProcessor(); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract java.util.Map<java.lang.Integer,java.util.List<android.util.Size>> getSupportedCaptureOutputResolutions(@NonNull String); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract java.util.Map<java.lang.Integer,java.util.List<android.util.Size>> getSupportedPreviewOutputResolutions(@NonNull String); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void init(@NonNull String, @NonNull android.hardware.camera2.extension.CharacteristicsMap); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract boolean isExtensionAvailable(@NonNull String, @NonNull android.hardware.camera2.extension.CharacteristicsMap); + } + + @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract class CameraExtensionService extends android.app.Service { + ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") protected CameraExtensionService(); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public android.os.IBinder onBind(@Nullable android.content.Intent); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract android.hardware.camera2.extension.AdvancedExtender onInitializeAdvancedExtension(int); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract boolean onRegisterClient(@NonNull android.os.IBinder); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void onUnregisterClient(@NonNull android.os.IBinder); + } + + @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class CameraOutputSurface { + ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public CameraOutputSurface(@NonNull android.view.Surface, @Nullable android.util.Size); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int getImageFormat(); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @Nullable public android.util.Size getSize(); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @Nullable public android.view.Surface getSurface(); + } + + @FlaggedApi("com.android.internal.camera.flags.concert_mode") public class CharacteristicsMap { + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @Nullable public android.hardware.camera2.CameraCharacteristics get(@NonNull String); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public java.util.Set<java.lang.String> getCameraIds(); + } + + @FlaggedApi("com.android.internal.camera.flags.concert_mode") public class ExtensionConfiguration { + ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public ExtensionConfiguration(int, int, @NonNull java.util.List<android.hardware.camera2.extension.ExtensionOutputConfiguration>, @Nullable android.hardware.camera2.CaptureRequest); + } + + @FlaggedApi("com.android.internal.camera.flags.concert_mode") public class ExtensionOutputConfiguration { + ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public ExtensionOutputConfiguration(@NonNull java.util.List<android.hardware.camera2.extension.CameraOutputSurface>, int, @Nullable String, int); + } + + @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class RequestProcessor { + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void abortCaptures(); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int setRepeating(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @Nullable java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.RequestProcessor.RequestCallback); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void stopRepeating(); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int submit(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @Nullable java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.RequestProcessor.RequestCallback); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int submitBurst(@NonNull java.util.List<android.hardware.camera2.extension.RequestProcessor.Request>, @Nullable java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.RequestProcessor.RequestCallback); + } + + @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final class RequestProcessor.Request { + ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public RequestProcessor.Request(@NonNull java.util.List<java.lang.Integer>, @NonNull java.util.List<android.util.Pair<android.hardware.camera2.CaptureRequest.Key,java.lang.Object>>, int); + } + + @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static interface RequestProcessor.RequestCallback { + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureBufferLost(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, long, int); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureCompleted(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @Nullable android.hardware.camera2.TotalCaptureResult); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureFailed(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @NonNull android.hardware.camera2.CaptureFailure); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureProgressed(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @NonNull android.hardware.camera2.CaptureResult); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureSequenceAborted(int); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureSequenceCompleted(int, long); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureStarted(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, long, long); + } + + @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract class SessionProcessor { + ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") protected SessionProcessor(); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void deInitSession(@NonNull android.os.IBinder); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract android.hardware.camera2.extension.ExtensionConfiguration initSession(@NonNull android.os.IBinder, @NonNull String, @NonNull android.hardware.camera2.extension.CharacteristicsMap, @NonNull android.hardware.camera2.extension.CameraOutputSurface, @NonNull android.hardware.camera2.extension.CameraOutputSurface); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void onCaptureSessionEnd(); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void onCaptureSessionStart(@NonNull android.hardware.camera2.extension.RequestProcessor, @NonNull String); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void setParameters(@NonNull android.hardware.camera2.CaptureRequest); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract int startCapture(@Nullable java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.SessionProcessor.CaptureCallback); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract int startRepeating(@Nullable java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.SessionProcessor.CaptureCallback); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract int startTrigger(@NonNull android.hardware.camera2.CaptureRequest, @Nullable java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.SessionProcessor.CaptureCallback); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void stopRepeating(); + } + + @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static interface SessionProcessor.CaptureCallback { + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureCompleted(long, int, @NonNull android.hardware.camera2.CaptureResult); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureFailed(int); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureProcessStarted(int); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureSequenceAborted(int); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureSequenceCompleted(int); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureStarted(int, long); + } + +} + package android.hardware.camera2.params { public final class OutputConfiguration implements android.os.Parcelable { diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java index 0a61c32a9cf5..d4d1ab373157 100644 --- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java @@ -15,6 +15,7 @@ */ package android.hardware.camera2; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -39,11 +40,15 @@ import android.os.ConditionVariable; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemProperties; +import android.util.FeatureFlagUtils; +import android.util.IntArray; import android.util.Log; import android.util.Pair; import android.util.Range; import android.util.Size; +import com.android.internal.camera.flags.Flags; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -129,6 +134,12 @@ public final class CameraExtensionCharacteristics { public static final int EXTENSION_NIGHT = 4; /** + * An extension that aims to lock and stabilize a given region or object of interest. + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final int EXTENSION_EYES_FREE_VIDEOGRAPHY = 5; + + /** * @hide */ @Retention(RetentionPolicy.SOURCE) @@ -136,7 +147,8 @@ public final class CameraExtensionCharacteristics { EXTENSION_FACE_RETOUCH, EXTENSION_BOKEH, EXTENSION_HDR, - EXTENSION_NIGHT}) + EXTENSION_NIGHT, + EXTENSION_EYES_FREE_VIDEOGRAPHY}) public @interface Extension { } @@ -594,8 +606,13 @@ public final class CameraExtensionCharacteristics { return Collections.unmodifiableList(ret); } + IntArray extensionList = new IntArray(EXTENSION_LIST.length); + extensionList.addAll(EXTENSION_LIST); + if (Flags.concertMode()) { + extensionList.add(EXTENSION_EYES_FREE_VIDEOGRAPHY); + } try { - for (int extensionType : EXTENSION_LIST) { + for (int extensionType : extensionList.toArray()) { if (isExtensionSupported(mCameraId, extensionType, mCharacteristicsMapNative)) { ret.add(extensionType); } diff --git a/core/java/android/hardware/camera2/extension/AdvancedExtender.java b/core/java/android/hardware/camera2/extension/AdvancedExtender.java new file mode 100644 index 000000000000..fb2df546f545 --- /dev/null +++ b/core/java/android/hardware/camera2/extension/AdvancedExtender.java @@ -0,0 +1,353 @@ +/* + * Copyright (C) 2023 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.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraManager; +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.CaptureResult; +import android.hardware.camera2.impl.CameraMetadataNative; +import android.hardware.camera2.impl.CaptureCallback; +import android.util.Log; +import android.util.Size; + +import com.android.internal.camera.flags.Flags; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Advanced contract for implementing Extensions. ImageCapture/Preview + * Extensions are both implemented on this interface. + * + * <p>This advanced contract empowers implementations to gain access to + * more Camera2 capability. This includes: (1) Add custom surfaces with + * specific formats like YUV, RAW, RAW_DEPTH. (2) Access to + * the capture request callbacks as well as all the images retrieved of + * various image formats. (3) + * Able to triggers single or repeating request with the capabilities to + * specify target surfaces, template id and parameters. + * + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_CONCERT_MODE) +public abstract class AdvancedExtender { + private HashMap<String, Long> mMetadataVendorIdMap = new HashMap<>(); + private final CameraManager mCameraManager; + + private static final String TAG = "AdvancedExtender"; + + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + protected AdvancedExtender(@NonNull CameraManager cameraManager) { + mCameraManager = cameraManager; + try { + String [] cameraIds = mCameraManager.getCameraIdListNoLazy(); + if (cameraIds != null) { + for (String cameraId : cameraIds) { + CameraCharacteristics chars = mCameraManager.getCameraCharacteristics(cameraId); + Object thisClass = CameraCharacteristics.Key.class; + Class<CameraCharacteristics.Key<?>> keyClass = + (Class<CameraCharacteristics.Key<?>>)thisClass; + ArrayList<CameraCharacteristics.Key<?>> vendorKeys = + chars.getNativeMetadata().getAllVendorKeys(keyClass); + if ((vendorKeys != null) && !vendorKeys.isEmpty()) { + mMetadataVendorIdMap.put(cameraId, vendorKeys.get(0).getVendorId()); + } + } + } + } catch (CameraAccessException e) { + Log.e(TAG, "Failed to query camera characteristics!"); + } + } + + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public long getMetadataVendorId(@NonNull String cameraId) { + long vendorId = mMetadataVendorIdMap.containsKey(cameraId) ? + mMetadataVendorIdMap.get(cameraId) : Long.MAX_VALUE; + return vendorId; + } + + /** + * Indicates whether the extension is supported on the device. + * + * @param cameraId The camera2 id string of the camera. + * @param charsMap A map consisting of the camera ids and + * the {@link android.hardware.camera2.CameraCharacteristics}s. For + * every camera, the map contains at least + * the CameraCharacteristics for the camera + * id. + * If the camera is logical camera, it will + * also contain associated + * physical camera ids and their + * CameraCharacteristics. + * @return true if the extension is supported, otherwise false + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public abstract boolean isExtensionAvailable(@NonNull String cameraId, + @NonNull CharacteristicsMap charsMap); + + /** + * Initializes the extender to be used with the specified camera. + * + * <p>This should be called before any other method on the extender. + * The exception is {@link #isExtensionAvailable}. + * + * @param cameraId The camera2 id string of the camera. + * @param map A map consisting of the camera ids and + * the {@link android.hardware.camera2.CameraCharacteristics}s. For + * every camera, the map contains at least + * the CameraCharacteristics for the camera + * id. + * If the camera is logical camera, it will + * also contain associated + * physical camera ids and their + * CameraCharacteristics. + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public abstract void init(@NonNull String cameraId, @NonNull CharacteristicsMap map); + + /** + * Returns supported output format/size map for preview. The format + * could be PRIVATE or YUV_420_888. Implementations must support + * PRIVATE format at least. + * + * <p>The preview surface format in the CameraCaptureSession may not + * be identical to the supported preview output format returned here. + * @param cameraId The camera2 id string of the camera. + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @NonNull + public abstract Map<Integer, List<Size>> getSupportedPreviewOutputResolutions( + @NonNull String cameraId); + + /** + * Returns supported output format/size map for image capture. OEM is + * required to support both JPEG and YUV_420_888 format output. + * + * <p>The surface created with this supported + * format/size could be either added in CameraCaptureSession with HAL + * processing OR it configures intermediate surfaces(YUV/RAW..) and + * writes the output to the output surface. + * @param cameraId The camera2 id string of the camera. + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @NonNull + public abstract Map<Integer, List<Size>> getSupportedCaptureOutputResolutions( + @NonNull String cameraId); + + /** + * Returns a processor for activating extension sessions. It + * implements all the interactions required for starting an extension + * and cleanup. + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @NonNull + public abstract SessionProcessor getSessionProcessor(); + + /** + * Returns a list of orthogonal capture request keys. + * + * <p>Any keys included in the list will be configurable by clients of + * the extension and will affect the extension functionality.</p> + * + * <p>Please note that the keys {@link CaptureRequest#JPEG_QUALITY} + * and {@link CaptureRequest#JPEG_ORIENTATION} are always supported + * regardless of being added to the list or not. To support common + * camera operations like zoom, tap-to-focus, flash and + * exposure compensation, we recommend supporting the following keys + * if possible. + * <pre> + * zoom: {@link CaptureRequest#CONTROL_ZOOM_RATIO} + * {@link CaptureRequest#SCALER_CROP_REGION} + * tap-to-focus: + * {@link CaptureRequest#CONTROL_AF_MODE} + * {@link CaptureRequest#CONTROL_AF_TRIGGER} + * {@link CaptureRequest#CONTROL_AF_REGIONS} + * {@link CaptureRequest#CONTROL_AE_REGIONS} + * {@link CaptureRequest#CONTROL_AWB_REGIONS} + * flash: + * {@link CaptureRequest#CONTROL_AE_MODE} + * {@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER} + * {@link CaptureRequest#FLASH_MODE} + * exposure compensation: + * {@link CaptureRequest#CONTROL_AE_EXPOSURE_COMPENSATION} + * </pre> + * + * @param cameraId The camera2 id string of the camera. + * + * @return The list of supported orthogonal capture keys, or empty + * list if no capture settings are not supported. + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @NonNull + public abstract List<CaptureRequest.Key> getAvailableCaptureRequestKeys( + @NonNull String cameraId); + + /** + * Returns a list of supported capture result keys. + * + * <p>Any keys included in this list must be available as part of the + * registered {@link CaptureCallback#onCaptureCompleted} callback.</p> + * + * <p>At the very minimum, it is expected that the result key list is + * a superset of the capture request keys.</p> + * + * @param cameraId The camera2 id string of the camera. + * @return The list of supported capture result keys, or + * empty list if capture results are not supported. + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @NonNull + public abstract List<CaptureResult.Key> getAvailableCaptureResultKeys( + @NonNull String cameraId); + + + private final class AdvancedExtenderImpl extends IAdvancedExtenderImpl.Stub { + @Override + public boolean isExtensionAvailable(String cameraId, + Map<String, CameraMetadataNative> charsMapNative) { + return AdvancedExtender.this.isExtensionAvailable(cameraId, + new CharacteristicsMap(charsMapNative)); + } + + @Override + public void init(String cameraId, Map<String, CameraMetadataNative> charsMapNative) { + AdvancedExtender.this.init(cameraId, new CharacteristicsMap(charsMapNative)); + } + + @Override + public List<SizeList> getSupportedPostviewResolutions( + android.hardware.camera2.extension.Size captureSize) { + // Feature is currently unsupported + return null; + } + + @Override + public List<SizeList> getSupportedPreviewOutputResolutions(String cameraId) { + return initializeParcelable( + AdvancedExtender.this.getSupportedPreviewOutputResolutions(cameraId)); + } + + @Override + public List<SizeList> getSupportedCaptureOutputResolutions(String cameraId) { + return initializeParcelable( + AdvancedExtender.this.getSupportedCaptureOutputResolutions(cameraId)); + } + + @Override + public LatencyRange getEstimatedCaptureLatencyRange(String cameraId, + android.hardware.camera2.extension.Size outputSize, int format) { + // Feature is currently unsupported + return null; + } + + @Override + public ISessionProcessorImpl getSessionProcessor() { + return AdvancedExtender.this.getSessionProcessor().getSessionProcessorBinder(); + } + + @Override + public CameraMetadataNative getAvailableCaptureRequestKeys(String cameraId) { + List<CaptureRequest.Key> supportedCaptureKeys = + AdvancedExtender.this.getAvailableCaptureRequestKeys(cameraId); + + if (!supportedCaptureKeys.isEmpty()) { + CameraMetadataNative ret = new CameraMetadataNative(); + long vendorId = getMetadataVendorId(cameraId); + ret.setVendorId(vendorId); + int requestKeyTags[] = new int[supportedCaptureKeys.size()]; + int i = 0; + for (CaptureRequest.Key key : supportedCaptureKeys) { + requestKeyTags[i++] = CameraMetadataNative.getTag(key.getName(), vendorId); + } + ret.set(CameraCharacteristics.REQUEST_AVAILABLE_REQUEST_KEYS, requestKeyTags); + + return ret; + } + + return null; + } + + @Override + public CameraMetadataNative getAvailableCaptureResultKeys(String cameraId) { + List<CaptureResult.Key> supportedResultKeys = + AdvancedExtender.this.getAvailableCaptureResultKeys(cameraId); + + if (!supportedResultKeys.isEmpty()) { + CameraMetadataNative ret = new CameraMetadataNative(); + long vendorId = getMetadataVendorId(cameraId); + ret.setVendorId(vendorId); + int resultKeyTags [] = new int[supportedResultKeys.size()]; + int i = 0; + for (CaptureResult.Key key : supportedResultKeys) { + resultKeyTags[i++] = CameraMetadataNative.getTag(key.getName(), vendorId); + } + ret.set(CameraCharacteristics.REQUEST_AVAILABLE_RESULT_KEYS, resultKeyTags); + + return ret; + } + + return null; + } + + @Override + public boolean isCaptureProcessProgressAvailable() { + // Feature is currently unsupported + return false; + } + + @Override + public boolean isPostviewAvailable() { + // Feature is currently unsupported + return false; + } + } + + @NonNull IAdvancedExtenderImpl getAdvancedExtenderBinder() { + return new AdvancedExtenderImpl(); + } + + private static List<SizeList> initializeParcelable( + Map<Integer, List<android.util.Size>> sizes) { + if (sizes == null) { + return null; + } + ArrayList<SizeList> ret = new ArrayList<>(sizes.size()); + for (Map.Entry<Integer, List<android.util.Size>> entry : sizes.entrySet()) { + SizeList sizeList = new SizeList(); + sizeList.format = entry.getKey(); + sizeList.sizes = new ArrayList<>(); + for (android.util.Size size : entry.getValue()) { + android.hardware.camera2.extension.Size sz = + new android.hardware.camera2.extension.Size(); + sz.width = size.getWidth(); + sz.height = size.getHeight(); + sizeList.sizes.add(sz); + } + ret.add(sizeList); + } + + return ret; + } +} diff --git a/core/java/android/hardware/camera2/extension/CameraExtensionService.java b/core/java/android/hardware/camera2/extension/CameraExtensionService.java new file mode 100644 index 000000000000..1426d7bceb7f --- /dev/null +++ b/core/java/android/hardware/camera2/extension/CameraExtensionService.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2023 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.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.camera.flags.Flags; + +/** + * Base service class that extension service implementations must extend. + * + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_CONCERT_MODE) +public abstract class CameraExtensionService extends Service { + private static final String TAG = "CameraExtensionService"; + private static Object mLock = new Object(); + + @GuardedBy("mLock") + private static IInitializeSessionCallback mInitializeCb = null; + + private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { + @Override + public void binderDied() { + synchronized (mLock) { + mInitializeCb = null; + } + } + }; + + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + protected CameraExtensionService() {} + + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @Override + @NonNull + public IBinder onBind(@Nullable Intent intent) { + return new CameraExtensionServiceImpl(); + } + + private class CameraExtensionServiceImpl extends ICameraExtensionsProxyService.Stub { + @Override + public boolean registerClient(IBinder token) throws RemoteException { + return CameraExtensionService.this.onRegisterClient(token); + } + + @Override + public void unregisterClient(IBinder token) throws RemoteException { + CameraExtensionService.this.onUnregisterClient(token); + } + + @Override + public boolean advancedExtensionsSupported() throws RemoteException { + return true; + } + + @Override + public void initializeSession(IInitializeSessionCallback cb) { + boolean ret = false; + synchronized (mLock) { + if (mInitializeCb == null) { + mInitializeCb = cb; + try { + mInitializeCb.asBinder().linkToDeath(mDeathRecipient, 0); + } catch (RemoteException e) { + Log.e(TAG, "Failure to register binder death notifier!"); + } + ret = true; + } + } + + try { + if (ret) { + cb.onSuccess(); + } else { + cb.onFailure(); + } + } catch (RemoteException e) { + + Log.e(TAG, "Client doesn't respond!"); + } + } + + @Override + public void releaseSession() { + synchronized (mLock) { + if (mInitializeCb != null) { + mInitializeCb.asBinder().unlinkToDeath(mDeathRecipient, 0); + mInitializeCb = null; + } + } + } + + @Override + public IPreviewExtenderImpl initializePreviewExtension(int extensionType) + throws RemoteException { + // Basic Extension API is not supported + return null; + } + + @Override + public IImageCaptureExtenderImpl initializeImageExtension(int extensionType) + throws RemoteException { + // Basic Extension API is not supported + return null; + } + + @Override + public IAdvancedExtenderImpl initializeAdvancedExtension(int extensionType) + throws RemoteException { + return CameraExtensionService.this.onInitializeAdvancedExtension( + extensionType).getAdvancedExtenderBinder(); + } + } + + /** + * Register an extension client. The client must call this method + * after successfully binding to the service. + * + * @param token Binder token that can be used for adding + * death notifier in case the client exits + * unexpectedly. + * @return true if the registration is successful, false otherwise + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public abstract boolean onRegisterClient(@NonNull IBinder token); + + /** + * Unregister an extension client. + * + * @param token Binder token + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public abstract void onUnregisterClient(@NonNull IBinder token); + + /** + * Initialize and return an advanced extension. + * + * @param extensionType {@link android.hardware.camera2.CameraExtensionCharacteristics} + * extension type + * @return Valid advanced extender of the requested type + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @NonNull + public abstract AdvancedExtender onInitializeAdvancedExtension(int extensionType); +} diff --git a/core/java/android/hardware/camera2/extension/CameraOutputSurface.java b/core/java/android/hardware/camera2/extension/CameraOutputSurface.java new file mode 100644 index 000000000000..f98ebee5d7c7 --- /dev/null +++ b/core/java/android/hardware/camera2/extension/CameraOutputSurface.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2023 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.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.hardware.camera2.utils.SurfaceUtils; +import android.util.Size; +import android.view.Surface; + +import com.android.internal.camera.flags.Flags; + + +/** + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_CONCERT_MODE) +public final class CameraOutputSurface { + private final OutputSurface mOutputSurface; + + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + CameraOutputSurface(@NonNull OutputSurface surface) { + mOutputSurface = surface; + } + + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public CameraOutputSurface(@NonNull Surface surface, + @Nullable Size size ) { + mOutputSurface = new OutputSurface(); + mOutputSurface.surface = surface; + mOutputSurface.imageFormat = SurfaceUtils.getSurfaceFormat(surface); + if (size != null) { + mOutputSurface.size = new android.hardware.camera2.extension.Size(); + mOutputSurface.size.width = size.getWidth(); + mOutputSurface.size.height = size.getHeight(); + } + } + + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @Nullable + public Surface getSurface() { + return mOutputSurface.surface; + } + + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @Nullable + public android.util.Size getSize() { + if (mOutputSurface.size != null) { + return new Size(mOutputSurface.size.width, mOutputSurface.size.height); + } + return null; + } + + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public int getImageFormat() { + return mOutputSurface.imageFormat; + } +} diff --git a/core/java/android/hardware/camera2/extension/CharacteristicsMap.java b/core/java/android/hardware/camera2/extension/CharacteristicsMap.java new file mode 100644 index 000000000000..af83595babef --- /dev/null +++ b/core/java/android/hardware/camera2/extension/CharacteristicsMap.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2023 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.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.impl.CameraMetadataNative; + +import com.android.internal.camera.flags.Flags; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_CONCERT_MODE) +public class CharacteristicsMap { + private final HashMap<String, CameraCharacteristics> mCharMap; + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + CharacteristicsMap(@NonNull Map<String, CameraMetadataNative> charsMap) { + mCharMap = new HashMap<>(); + for (Map.Entry<String, CameraMetadataNative> entry : charsMap.entrySet()) { + mCharMap.put(entry.getKey(), new CameraCharacteristics(entry.getValue())); + } + } + + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @NonNull + public Set<String> getCameraIds() { + return mCharMap.keySet(); + } + + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @Nullable + public CameraCharacteristics get(@NonNull String cameraId) { + return mCharMap.get(cameraId); + } +} diff --git a/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java b/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java new file mode 100644 index 000000000000..2d9ab765d1e0 --- /dev/null +++ b/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2023 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.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.hardware.camera2.CaptureRequest; + +import com.android.internal.camera.flags.Flags; + +import java.util.ArrayList; +import java.util.List; + +/** + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_CONCERT_MODE) +public class ExtensionConfiguration { + private final int mSessionType; + private final int mSessionTemplateId; + private final List<ExtensionOutputConfiguration> mOutputs; + private final CaptureRequest mSessionParameters; + + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public ExtensionConfiguration(int sessionType, int sessionTemplateId, @NonNull + List<ExtensionOutputConfiguration> outputs, @Nullable CaptureRequest sessionParams) { + mSessionType = sessionType; + mSessionTemplateId = sessionTemplateId; + mOutputs = outputs; + mSessionParameters = sessionParams; + } + + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + CameraSessionConfig getCameraSessionConfig() { + if (mOutputs.isEmpty()) { + return null; + } + + CameraSessionConfig ret = new CameraSessionConfig(); + ret.sessionTemplateId = mSessionTemplateId; + ret.sessionType = mSessionType; + ret.outputConfigs = new ArrayList<>(mOutputs.size()); + for (ExtensionOutputConfiguration outputConfig : mOutputs) { + ret.outputConfigs.add(outputConfig.getOutputConfig()); + } + if (mSessionParameters != null) { + ret.sessionParameter = mSessionParameters.getNativeCopy(); + } + + return ret; + } +} diff --git a/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java b/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java new file mode 100644 index 000000000000..85d180d379df --- /dev/null +++ b/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2023 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.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; + +import com.android.internal.camera.flags.Flags; + +import java.util.ArrayList; +import java.util.List; + +/** + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_CONCERT_MODE) +public class ExtensionOutputConfiguration { + private final List<CameraOutputSurface> mSurfaces; + private final String mPhysicalCameraId; + private final int mOutputConfigId; + private final int mSurfaceGroupId; + + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public ExtensionOutputConfiguration(@NonNull List<CameraOutputSurface> outputs, + int outputConfigId, @Nullable String physicalCameraId, int surfaceGroupId) { + mSurfaces = outputs; + mPhysicalCameraId = physicalCameraId; + mOutputConfigId = outputConfigId; + mSurfaceGroupId = surfaceGroupId; + } + + private void initializeOutputConfig(@NonNull CameraOutputConfig config, + @NonNull CameraOutputSurface surface) { + config.surface = surface.getSurface(); + if (surface.getSize() != null) { + config.size = new Size(); + config.size.width = surface.getSize().getWidth(); + config.size.height = surface.getSize().getHeight(); + } + config.imageFormat = surface.getImageFormat(); + config.type = CameraOutputConfig.TYPE_SURFACE; + config.physicalCameraId = mPhysicalCameraId; + config.outputId = new OutputConfigId(); + config.outputId.id = mOutputConfigId; + config.surfaceGroupId = mSurfaceGroupId; + } + + @Nullable CameraOutputConfig getOutputConfig() { + if (mSurfaces.isEmpty()) { + return null; + } + + CameraOutputConfig ret = new CameraOutputConfig(); + initializeOutputConfig(ret, mSurfaces.get(0)); + if (mSurfaces.size() > 1) { + ret.sharedSurfaceConfigs = new ArrayList<>(mSurfaces.size() - 1); + for (int i = 1; i < mSurfaces.size(); i++) { + CameraOutputConfig sharedConfig = new CameraOutputConfig(); + initializeOutputConfig(sharedConfig, mSurfaces.get(i)); + ret.sharedSurfaceConfigs.add(sharedConfig); + } + } + + return ret; + } +} diff --git a/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl b/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl index 0581ec08a131..a4a17701604c 100644 --- a/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl +++ b/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl @@ -34,7 +34,7 @@ interface ISessionProcessorImpl in Map<String, CameraMetadataNative> charsMap, in OutputSurface previewSurface, in OutputSurface imageCaptureSurface, in OutputSurface postviewSurface); void deInitSession(in IBinder token); - void onCaptureSessionStart(IRequestProcessorImpl requestProcessor); + void onCaptureSessionStart(IRequestProcessorImpl requestProcessor, in String statsKey); void onCaptureSessionEnd(); int startRepeating(in ICaptureCallback callback); void stopRepeating(); diff --git a/core/java/android/hardware/camera2/extension/RequestProcessor.java b/core/java/android/hardware/camera2/extension/RequestProcessor.java new file mode 100644 index 000000000000..7c099d67e6e9 --- /dev/null +++ b/core/java/android/hardware/camera2/extension/RequestProcessor.java @@ -0,0 +1,582 @@ +/* + * Copyright (C) 2023 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.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CaptureFailure; +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.CaptureResult; +import android.hardware.camera2.TotalCaptureResult; +import android.hardware.camera2.impl.CameraMetadataNative; +import android.hardware.camera2.impl.PhysicalCaptureResultInfo; +import android.os.Binder; +import android.os.RemoteException; +import android.util.Log; +import android.util.Pair; + +import com.android.internal.camera.flags.Flags; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; + +/** + * An Interface to execute Camera2 capture requests. + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_CONCERT_MODE) +public final class RequestProcessor { + private final static String TAG = "RequestProcessor"; + private final IRequestProcessorImpl mRequestProcessor; + private final long mVendorId; + + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + RequestProcessor (@NonNull IRequestProcessorImpl requestProcessor, long vendorId) { + mRequestProcessor = requestProcessor; + mVendorId = vendorId; + } + + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public interface RequestCallback { + /** + * This method is called when the camera device has started + * capturing the output image for the request, at the beginning of + * image exposure, or when the camera device has started + * processing an input image for a reprocess request. + * + * @param request The request that was given to the + * RequestProcessor + * @param timestamp the timestamp at start of capture for a + * regular request, or the timestamp at the input + * image's start of capture for a + * reprocess request, in nanoseconds. + * @param frameNumber the frame number for this capture + * + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + void onCaptureStarted(@NonNull Request request, long frameNumber, long timestamp); + + /** + * This method is called when an image capture makes partial forward + * progress; some (but not all) results from an image capture are + * available. + * + * <p>The result provided here will contain some subset of the fields + * of a full result. Multiple {@link #onCaptureProgressed} calls may + * happen per capture; a given result field will only be present in + * one partial capture at most. The final {@link #onCaptureCompleted} + * call will always contain all the fields (in particular, the union + * of all the fields of all the partial results composing the total + * result).</p> + * + * <p>For each request, some result data might be available earlier + * than others. The typical delay between each partial result (per + * request) is a single frame interval. + * For performance-oriented use-cases, applications should query the + * metadata they need to make forward progress from the partial + * results and avoid waiting for the completed result.</p> + * + * <p>For a particular request, {@link #onCaptureProgressed} may happen + * before or after {@link #onCaptureStarted}.</p> + * + * <p>Each request will generate at least {@code 1} partial results, + * and at most {@link + * CameraCharacteristics#REQUEST_PARTIAL_RESULT_COUNT} partial + * results.</p> + * + * <p>Depending on the request settings, the number of partial + * results per request will vary, although typically the partial + * count could be the same as long as the + * camera device subsystems enabled stay the same.</p> + * + * @param request The request that was given to the RequestProcessor + * @param partialResult The partial output metadata from the capture, + * which includes a subset of the {@link + * TotalCaptureResult} fields. + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + void onCaptureProgressed(@NonNull Request request, @NonNull CaptureResult partialResult); + + /** + * This method is called when an image capture has fully completed and + * all the result metadata is available. + * + * <p>This callback will always fire after the last {@link + * #onCaptureProgressed}; in other words, no more partial results will + * be delivered once the completed result is available.</p> + * + * <p>For performance-intensive use-cases where latency is a factor, + * consider using {@link #onCaptureProgressed} instead.</p> + * + * + * @param request The request that was given to the RequestProcessor + * @param totalCaptureResult The total output metadata from the + * capture, including the final capture + * parameters and the state of the camera + * system during capture. + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + void onCaptureCompleted(@NonNull Request request, + @Nullable TotalCaptureResult totalCaptureResult); + + /** + * This method is called instead of {@link #onCaptureCompleted} when the + * camera device failed to produce a {@link CaptureResult} for the + * request. + * + * <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>If a logical multi-camera fails to generate capture result for + * one of its physical cameras, this method will be called with a + * {@link CaptureFailure} for that physical camera. In such cases, as + * long as the logical camera capture result is valid, {@link + * #onCaptureCompleted} will still be called.</p> + * + * <p>The default implementation of this method does nothing.</p> + * + * @param request The request that was given to the RequestProcessor + * @param failure The output failure from the capture, including the + * failure reason and the frame number. + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + void onCaptureFailed(@NonNull Request request, @NonNull CaptureFailure failure); + + /** + * <p>This method is called if a single buffer for a capture could not + * be sent to its destination surface.</p> + * + * <p>If the whole capture failed, then {@link #onCaptureFailed} will be + * called instead. If some but not all buffers were captured but the + * result metadata will not be available, then captureFailed will be + * invoked with {@link CaptureFailure#wasImageCaptured} + * returning true, along with one or more calls to {@link + * #onCaptureBufferLost} for the failed outputs.</p> + * + * @param request The request that was given to the RequestProcessor + * @param frameNumber The frame number for the request + * @param outputStreamId The output stream id that the buffer will not + * be produced for + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + void onCaptureBufferLost(@NonNull Request request, long frameNumber, int outputStreamId); + + /** + * This method is called independently of the others in + * CaptureCallback, when a capture sequence finishes and all {@link + * CaptureResult} or {@link CaptureFailure} for it have been returned + * via this listener. + * + * <p>In total, there will be at least one result/failure returned by + * this listener before this callback is invoked. If the capture + * sequence is aborted before any requests have been processed, + * {@link #onCaptureSequenceAborted} is invoked instead.</p> + * + * @param sequenceId A sequence ID returned by the RequestProcessor + * capture family of methods + * @param frameNumber The last frame number (returned by {@link + * CaptureResult#getFrameNumber} + * or {@link CaptureFailure#getFrameNumber}) in + * the capture sequence. + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + void onCaptureSequenceCompleted(int sequenceId, long frameNumber); + + /** + * This method is called independently of the others in + * CaptureCallback, when a capture sequence aborts before any {@link + * CaptureResult} or {@link CaptureFailure} for it have been returned + * via this listener. + * + * <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 RequestProcessor#stopRepeating} or + * {@link RequestProcessor#abortCaptures}. When such an event + * happens, {@link #onCaptureSequenceCompleted} will not be called.</p> + * @param sequenceId A sequence ID returned by the RequestProcessor + * capture family of methods + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + void onCaptureSequenceAborted(int sequenceId); + } + + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public final static class Request { + private final List<Integer> mOutputIds; + private final List<Pair<CaptureRequest.Key, Object>> mParameters; + private final int mTemplateId; + + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public Request(@NonNull List<Integer> outputConfigIds, + @NonNull List<Pair<CaptureRequest.Key, Object>> parameters, int templateId) { + mOutputIds = outputConfigIds; + mParameters = parameters; + mTemplateId = templateId; + } + + /** + * Gets the target ids of {@link ExtensionOutputConfiguration} which identifies + * corresponding Surface to be the targeted for the request. + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @NonNull + List<Integer> getOutputConfigIds() { + return mOutputIds; + } + + /** + * Gets all the parameters. + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @NonNull + List<Pair<CaptureRequest.Key, Object>> getParameters() { + return mParameters; + } + + /** + * Gets the template id. + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + Integer getTemplateId() { + return mTemplateId; + } + + @NonNull List<OutputConfigId> getTargetIds() { + ArrayList<OutputConfigId> ret = new ArrayList<>(mOutputIds.size()); + int idx = 0; + for (Integer outputId : mOutputIds) { + OutputConfigId configId = new OutputConfigId(); + configId.id = outputId; + ret.add(idx++, configId); + } + + return ret; + } + + @NonNull + static CameraMetadataNative getParametersMetadata(long vendorId, + @NonNull List<Pair<CaptureRequest.Key, Object>> parameters) { + CameraMetadataNative ret = new CameraMetadataNative(); + ret.setVendorId(vendorId); + for (Pair<CaptureRequest.Key, Object> pair : parameters) { + ret.set(pair.first, pair.second); + } + + return ret; + } + + @NonNull + static List<android.hardware.camera2.extension.Request> initializeParcelable( + long vendorId, @NonNull List<Request> requests) { + ArrayList<android.hardware.camera2.extension.Request> ret = + new ArrayList<>(requests.size()); + int requestId = 0; + for (Request req : requests) { + android.hardware.camera2.extension.Request request = + new android.hardware.camera2.extension.Request(); + request.requestId = requestId++; + request.templateId = req.getTemplateId(); + request.targetOutputConfigIds = req.getTargetIds(); + request.parameters = getParametersMetadata(vendorId, req.getParameters()); + ret.add(request.requestId, request); + } + + return ret; + } + } + + /** + * Submit a capture request. + * @param request Capture request to queued in the Camera2 session + * @param executor the executor which will be used for + * invoking the callbacks or null to use the + * current thread's looper + * @param callback Request callback implementation + * @return the id of the capture sequence or -1 in case the processor + * encounters a fatal error or receives an invalid argument. + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public int submit(@NonNull Request request, @Nullable Executor executor, + @NonNull RequestCallback callback) { + ArrayList<Request> requests = new ArrayList<>(1); + requests.add(0, request); + List<android.hardware.camera2.extension.Request> parcelableRequests = + Request.initializeParcelable(mVendorId, requests); + + try { + return mRequestProcessor.submit(parcelableRequests.get(0), + new RequestCallbackImpl(requests, callback, executor)); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Submits a list of requests. + * @param requests List of capture requests to be queued in the + * Camera2 session + * @param executor the executor which will be used for + * invoking the callbacks or null to use the + * current thread's looper + * @param callback Request callback implementation + * @return the id of the capture sequence or -1 in case the processor + * encounters a fatal error or receives an invalid argument. + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public int submitBurst(@NonNull List<Request> requests, @Nullable Executor executor, + @NonNull RequestCallback callback) { + List<android.hardware.camera2.extension.Request> parcelableRequests = + Request.initializeParcelable(mVendorId, requests); + + try { + return mRequestProcessor.submitBurst(parcelableRequests, + new RequestCallbackImpl(requests, callback, executor)); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Set a repeating request. + * @param request Repeating capture request to be se in the + * Camera2 session + * @param executor the executor which will be used for + * invoking the callbacks or null to use the + * current thread's looper + * @param callback Request callback implementation + * @return the id of the capture sequence or -1 in case the processor + * encounters a fatal error or receives an invalid argument. + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public int setRepeating(@NonNull Request request, @Nullable Executor executor, + @NonNull RequestCallback callback) { + ArrayList<Request> requests = new ArrayList<>(1); + requests.add(0, request); + List<android.hardware.camera2.extension.Request> parcelableRequests = + Request.initializeParcelable(mVendorId, requests); + + try { + return mRequestProcessor.setRepeating(parcelableRequests.get(0), + new RequestCallbackImpl(requests, callback, executor)); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Abort all ongoing capture requests. + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public void abortCaptures() { + try { + mRequestProcessor.abortCaptures(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Stop the current repeating request. + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public void stopRepeating() { + try { + mRequestProcessor.stopRepeating(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + private static class RequestCallbackImpl extends IRequestCallback.Stub { + private final List<Request> mRequests; + private final RequestCallback mCallback; + private final Executor mExecutor; + + public RequestCallbackImpl(@NonNull List<Request> requests, + @NonNull RequestCallback callback, @Nullable Executor executor) { + mCallback = callback; + mRequests = requests; + mExecutor = executor; + } + + @Override + public void onCaptureStarted(int requestId, long frameNumber, long timestamp) { + if (mRequests.get(requestId) != null) { + final long ident = Binder.clearCallingIdentity(); + try { + if (mExecutor != null) { + mExecutor.execute(() -> mCallback.onCaptureStarted( + mRequests.get(requestId), frameNumber, timestamp)); + } else { + mCallback.onCaptureStarted(mRequests.get(requestId), frameNumber, + timestamp); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } else { + Log.e(TAG,"Request id: " + requestId + " not found!"); + } + } + + @Override + public void onCaptureProgressed(int requestId, ParcelCaptureResult partialResult) { + if (mRequests.get(requestId) != null) { + CaptureResult result = new CaptureResult(partialResult.cameraId, + partialResult.results, partialResult.parent, partialResult.sequenceId, + partialResult.frameNumber); + final long ident = Binder.clearCallingIdentity(); + try { + if (mExecutor != null) { + mExecutor.execute( + () -> mCallback.onCaptureProgressed(mRequests.get(requestId), + result)); + } else { + mCallback.onCaptureProgressed(mRequests.get(requestId), result); + } + + } finally { + Binder.restoreCallingIdentity(ident); + } + } else { + Log.e(TAG,"Request id: " + requestId + " not found!"); + } + } + + @Override + public void onCaptureCompleted(int requestId, ParcelTotalCaptureResult totalCaptureResult) { + if (mRequests.get(requestId) != null) { + PhysicalCaptureResultInfo[] physicalResults = new PhysicalCaptureResultInfo[0]; + if ((totalCaptureResult.physicalResult != null) && + (!totalCaptureResult.physicalResult.isEmpty())) { + int count = totalCaptureResult.physicalResult.size(); + physicalResults = new PhysicalCaptureResultInfo[count]; + physicalResults = totalCaptureResult.physicalResult.toArray( + physicalResults); + } + ArrayList<CaptureResult> partials = new ArrayList<>( + totalCaptureResult.partials.size()); + for (ParcelCaptureResult parcelResult : totalCaptureResult.partials) { + partials.add(new CaptureResult(parcelResult.cameraId, parcelResult.results, + parcelResult.parent, parcelResult.sequenceId, + parcelResult.frameNumber)); + } + TotalCaptureResult result = new TotalCaptureResult( + totalCaptureResult.logicalCameraId, totalCaptureResult.results, + totalCaptureResult.parent, totalCaptureResult.sequenceId, + totalCaptureResult.frameNumber, partials, totalCaptureResult.sessionId, + physicalResults); + final long ident = Binder.clearCallingIdentity(); + try { + if (mExecutor != null) { + mExecutor.execute( + () -> mCallback.onCaptureCompleted(mRequests.get(requestId), + result)); + } else { + mCallback.onCaptureCompleted(mRequests.get(requestId), result); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } else { + Log.e(TAG,"Request id: " + requestId + " not found!"); + } + } + + @Override + public void onCaptureFailed(int requestId, + android.hardware.camera2.extension.CaptureFailure captureFailure) { + if (mRequests.get(requestId) != null) { + android.hardware.camera2.CaptureFailure failure = + new android.hardware.camera2.CaptureFailure(captureFailure.request, + captureFailure.reason, captureFailure.dropped, + captureFailure.sequenceId, captureFailure.frameNumber, + captureFailure.errorPhysicalCameraId); + final long ident = Binder.clearCallingIdentity(); + try { + if (mExecutor != null) { + mExecutor.execute(() -> mCallback.onCaptureFailed(mRequests.get(requestId), + failure)); + } else { + mCallback.onCaptureFailed(mRequests.get(requestId), failure); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } else { + Log.e(TAG,"Request id: " + requestId + " not found!"); + } + } + + @Override + public void onCaptureBufferLost(int requestId, long frameNumber, int outputStreamId) { + if (mRequests.get(requestId) != null) { + final long ident = Binder.clearCallingIdentity(); + try { + if (mExecutor != null) { + mExecutor.execute( + () -> mCallback.onCaptureBufferLost(mRequests.get(requestId), + frameNumber, outputStreamId)); + } else { + mCallback.onCaptureBufferLost(mRequests.get(requestId), frameNumber, + outputStreamId); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } else { + Log.e(TAG,"Request id: " + requestId + " not found!"); + } + } + + @Override + public void onCaptureSequenceCompleted(int sequenceId, long frameNumber) { + final long ident = Binder.clearCallingIdentity(); + try { + if (mExecutor != null) { + mExecutor.execute(() -> mCallback.onCaptureSequenceCompleted(sequenceId, + frameNumber)); + } else { + mCallback.onCaptureSequenceCompleted(sequenceId, frameNumber); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override + public void onCaptureSequenceAborted(int sequenceId) { + final long ident = Binder.clearCallingIdentity(); + try { + if (mExecutor != null) { + mExecutor.execute(() -> mCallback.onCaptureSequenceAborted(sequenceId)); + } else { + mCallback.onCaptureSequenceAborted(sequenceId); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } +} diff --git a/core/java/android/hardware/camera2/extension/SessionProcessor.java b/core/java/android/hardware/camera2/extension/SessionProcessor.java new file mode 100644 index 000000000000..6ed0c1404212 --- /dev/null +++ b/core/java/android/hardware/camera2/extension/SessionProcessor.java @@ -0,0 +1,495 @@ +/* + * Copyright (C) 2023 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.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.CaptureResult; +import android.hardware.camera2.impl.CameraMetadataNative; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +import com.android.internal.camera.flags.Flags; + +import java.util.ArrayList; +import java.util.Map; +import java.util.concurrent.Executor; + +/** + * Interface for creating Camera2 CameraCaptureSessions with extension + * enabled based on the advanced extension interface. + * + * <p><pre> + * The flow of a extension session is shown below: + * (1) {@link #initSession}: Camera framework prepares streams + * configuration for creating CameraCaptureSession. Output surfaces for + * Preview and ImageCapture are passed in and implementation is + * responsible for outputting the results to these surfaces. + * + * (2) {@link #onCaptureSessionStart}: It is called after + * CameraCaptureSession is configured. A {@link RequestProcessor} is + * passed for the implementation to send repeating requests and single + * requests. + * + * (3) {@link #startRepeating}: Camera framework will call this method to + * start the repeating request after CameraCaptureSession is called. + * Implementations should start the repeating request by {@link + * RequestProcessor}. Implementations can also update the repeating + * request if needed later. + * + * (4) {@link #setParameters}: The passed parameters will be attached + * to the repeating request and single requests but the implementation can + * choose to apply some of them only. + * + * (5) {@link #startCapture}: It is called when apps want + * to start a multi-frame image capture. {@link CaptureCallback} will be + * called to report the status and the output image will be written to the + * capture output surface specified in {@link #initSession}. + * + * (5) {@link #onCaptureSessionEnd}: It is called right BEFORE + * CameraCaptureSession.close() is called. + * + * (6) {@link #deInitSession}: called when CameraCaptureSession is closed. + * </pre> + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_CONCERT_MODE) +public abstract class SessionProcessor { + private static final String TAG = "SessionProcessor"; + + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + protected SessionProcessor() {} + + /** + * Callback for notifying the status of {@link + * #startCapture} and {@link #startRepeating}. + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public interface CaptureCallback { + /** + * This method is called when the camera device has started + * capturing the initial input + * image. + * + * For a multi-frame capture, the method is called when the + * CameraCaptureSession.CaptureCallback onCaptureStarted of first + * frame is called and its timestamp is directly forwarded to + * timestamp parameter of this method. + * + * @param captureSequenceId id of the current capture sequence + * @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, + * in nanoseconds. + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + void onCaptureStarted(int captureSequenceId, long timestamp); + + /** + * This method is called when an image (or images in case of + * multi-frame capture) is captured and device-specific extension + * processing is triggered. + * + * @param captureSequenceId id of the current capture sequence + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + void onCaptureProcessStarted(int captureSequenceId); + + /** + * 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. + * + * @param captureSequenceId id of the current capture sequence + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + void onCaptureFailed(int captureSequenceId); + + /** + * This method is called independently of the others in the + * CaptureCallback, 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> + * + * @param captureSequenceId id of the current capture sequence + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + void onCaptureSequenceCompleted(int captureSequenceId); + + /** + * This method is called when a capture sequence aborts. + * + * @param captureSequenceId id of the current capture sequence + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + void onCaptureSequenceAborted(int captureSequenceId); + + /** + * Capture result callback that needs to be called when the + * process capture results are ready as part of frame + * post-processing. + * + * This callback will fire after {@link #onCaptureStarted}, {@link + * #onCaptureProcessStarted} and before {@link + * #onCaptureSequenceCompleted}. The callback is not expected to + * fire in case of capture failure {@link #onCaptureFailed} or + * capture abort {@link #onCaptureSequenceAborted}. + * + * @param shutterTimestamp The timestamp at the start + * of capture. The same timestamp value + * passed to {@link #onCaptureStarted}. + * @param requestId the capture request id that generated the + * capture results. This is the return value of + * either {@link #startRepeating} or {@link + * #startCapture}. + * @param results The supported capture results. Do note + * that if results 'android.jpeg.quality' and + * android.jpeg.orientation' are present in the + * process capture input results, then the values + * must also be passed as part of this callback. + * The camera framework guarantees that those two + * settings and results are always supported and + * applied by the corresponding framework. + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + void onCaptureCompleted(long shutterTimestamp, int requestId, + @NonNull CaptureResult results); + } + + /** + * Initializes the session for the extension. This is where the + * extension implementations allocate resources for + * preparing a CameraCaptureSession. After initSession() is called, + * the camera ID, cameraCharacteristics and context will not change + * until deInitSession() has been called. + * + * <p>The framework specifies the output surface configurations for + * preview using the 'previewSurface' argument and for still capture + * using the 'imageCaptureSurface' argument and implementations must + * return a {@link ExtensionConfiguration} which consists of a list of + * {@link CameraOutputSurface} and session parameters. The {@link + * ExtensionConfiguration} will be used to configure the + * CameraCaptureSession. + * + * <p>Implementations are responsible for outputting correct camera + * images output to these output surfaces.</p> + * + * @param token Binder token that can be used to register a death + * notifier callback + * @param cameraId The camera2 id string of the camera. + * @param map Maps camera ids to camera characteristics + * @param previewSurface contains output surface for preview + * @param imageCaptureSurface contains the output surface for image + * capture + * @return a {@link ExtensionConfiguration} consisting of a list of + * {@link CameraOutputConfig} and session parameters which will decide + * the {@link android.hardware.camera2.params.SessionConfiguration} + * for configuring the CameraCaptureSession. Please note that the + * OutputConfiguration list may not be part of any + * supported or mandatory stream combination BUT implementations must + * ensure this list will always produce a valid camera capture + * session. + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @NonNull + public abstract ExtensionConfiguration initSession(@NonNull IBinder token, + @NonNull String cameraId, @NonNull CharacteristicsMap map, + @NonNull CameraOutputSurface previewSurface, + @NonNull CameraOutputSurface imageCaptureSurface); + + /** + * Notify to de-initialize the extension. This callback will be + * invoked after CameraCaptureSession is closed. After onDeInit() was + * called, it is expected that the camera ID, cameraCharacteristics + * will no longer hold and tear down any resources allocated + * for this extension. Aborts all pending captures. + * @param token Binder token that can be used to unlink any previously + * linked death notifier callbacks + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public abstract void deInitSession(@NonNull IBinder token); + + /** + * This will be invoked once after the {@link + * android.hardware.camera2.CameraCaptureSession} + * has been created. {@link RequestProcessor} is passed for + * implementations to submit single requests or set repeating + * requests. This extension RequestProcessor will be valid to use + * until onCaptureSessionEnd is called. + * @param requestProcessor The request processor to be used for + * managing capture requests + * @param statsKey Unique key for telemetry + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public abstract void onCaptureSessionStart(@NonNull RequestProcessor requestProcessor, + @NonNull String statsKey); + + /** + * This will be invoked before the {@link + * android.hardware.camera2.CameraCaptureSession} is + * closed. {@link RequestProcessor} passed in onCaptureSessionStart + * will no longer accept any requests after onCaptureSessionEnd() + * returns. + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public abstract void onCaptureSessionEnd(); + + /** + * Starts the repeating request after CameraCaptureSession is called. + * Implementations should start the repeating request by {@link + * RequestProcessor}. Implementations can also update the + * repeating request when needed later. + * + * @param executor the executor which will be used for + * invoking the callbacks or null to use the + * current thread's looper + * @param callback a callback to report the status. + * @return the id of the capture sequence. + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public abstract int startRepeating(@Nullable Executor executor, + @NonNull CaptureCallback callback); + + /** + * Stop the repeating request. To prevent implementations from not + * calling stopRepeating, the framework will first stop the repeating + * request of current CameraCaptureSession and call this API to signal + * implementations that the repeating request was stopped and going + * forward calling {@link RequestProcessor#setRepeating} will simply + * do nothing. + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public abstract void stopRepeating(); + + /** + * Start a multi-frame capture. + * + * When the capture is completed, {@link + * CaptureCallback#onCaptureSequenceCompleted} + * is called and {@code OnImageAvailableListener#onImageAvailable} + * will also be called on the ImageReader that creates the image + * capture output surface. + * + * <p>Only one capture can perform at a time. Starting a capture when + * another capture is running will cause onCaptureFailed to be called + * immediately. + * + * @param executor the executor which will be used for + * invoking the callbacks or null to use the + * current thread's looper + * @param callback a callback to report the status. + * @return the id of the capture sequence. + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public abstract int startCapture(@Nullable Executor executor, + @NonNull CaptureCallback callback); + + /** + * The camera framework will call these APIs to pass parameters from + * the app to the extension implementation. It is expected that the + * implementation would (eventually) update the repeating request if + * the keys are supported. Setting a value to null explicitly un-sets + * the value. + *@param captureRequest Request that includes all client parameter + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public abstract void setParameters(@NonNull CaptureRequest captureRequest); + + /** + * The camera framework will call this interface in response to client + * requests involving the output preview surface. Typical examples + * include requests that include AF/AE triggers. + * Extensions can disregard any capture request keys that were not + * advertised in + * {@link AdvancedExtender#getAvailableCaptureRequestKeys}. + * + * @param captureRequest Capture request that includes the respective + * triggers. + * @param executor the executor which will be used for + * invoking the callbacks or null to use the + * current thread's looper + * @param callback a callback to report the status. + * @return the id of the capture sequence. + * + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public abstract int startTrigger(@NonNull CaptureRequest captureRequest, + @Nullable Executor executor, @NonNull CaptureCallback callback); + + private final class SessionProcessorImpl extends ISessionProcessorImpl.Stub { + private long mVendorId = -1; + @Override + public CameraSessionConfig initSession(IBinder token, String cameraId, + Map<String, CameraMetadataNative> charsMap, OutputSurface previewSurface, + OutputSurface imageCaptureSurface, OutputSurface postviewSurface) + throws RemoteException { + ExtensionConfiguration config = SessionProcessor.this.initSession(token, cameraId, + new CharacteristicsMap(charsMap), + new CameraOutputSurface(previewSurface), + new CameraOutputSurface(imageCaptureSurface)); + if (config == null) { + throw new IllegalArgumentException("Invalid extension configuration"); + } + + Object thisClass = CameraCharacteristics.Key.class; + Class<CameraCharacteristics.Key<?>> keyClass = + (Class<CameraCharacteristics.Key<?>>)thisClass; + ArrayList<CameraCharacteristics.Key<?>> vendorKeys = + charsMap.get(cameraId).getAllVendorKeys(keyClass); + if ((vendorKeys != null) && !vendorKeys.isEmpty()) { + mVendorId = vendorKeys.get(0).getVendorId(); + } + return config.getCameraSessionConfig(); + } + + @Override + public void deInitSession(IBinder token) throws RemoteException { + SessionProcessor.this.deInitSession(token); + } + + @Override + public void onCaptureSessionStart(IRequestProcessorImpl requestProcessor, String statsKey) + throws RemoteException { + SessionProcessor.this.onCaptureSessionStart( + new RequestProcessor(requestProcessor, mVendorId), statsKey); + } + + @Override + public void onCaptureSessionEnd() throws RemoteException { + SessionProcessor.this.onCaptureSessionEnd(); + } + + @Override + public int startRepeating(ICaptureCallback callback) throws RemoteException { + return SessionProcessor.this.startRepeating(/*executor*/ null, + new CaptureCallbackImpl(callback)); + } + + @Override + public void stopRepeating() throws RemoteException { + SessionProcessor.this.stopRepeating(); + } + + @Override + public int startCapture(ICaptureCallback callback, boolean isPostviewRequested) + throws RemoteException { + return SessionProcessor.this.startCapture(/*executor*/ null, + new CaptureCallbackImpl(callback)); + } + + @Override + public void setParameters(CaptureRequest captureRequest) throws RemoteException { + SessionProcessor.this.setParameters(captureRequest); + } + + @Override + public int startTrigger(CaptureRequest captureRequest, ICaptureCallback callback) + throws RemoteException { + return SessionProcessor.this.startTrigger(captureRequest, /*executor*/ null, + new CaptureCallbackImpl(callback)); + } + + @Override + public LatencyPair getRealtimeCaptureLatency() throws RemoteException { + // Feature is not supported + return null; + } + } + + private static final class CaptureCallbackImpl implements CaptureCallback { + private final ICaptureCallback mCaptureCallback; + + CaptureCallbackImpl(@NonNull ICaptureCallback cb) { + mCaptureCallback = cb; + } + + @Override + public void onCaptureStarted(int captureSequenceId, long timestamp) { + try { + mCaptureCallback.onCaptureStarted(captureSequenceId, timestamp); + } catch (RemoteException e) { + Log.e(TAG, "Failed to notify capture start due to remote exception!"); + } + } + + @Override + public void onCaptureProcessStarted(int captureSequenceId) { + try { + mCaptureCallback.onCaptureProcessStarted(captureSequenceId); + } catch (RemoteException e) { + Log.e(TAG, "Failed to notify process start due to remote exception!"); + } + } + + @Override + public void onCaptureFailed(int captureSequenceId) { + try { + mCaptureCallback.onCaptureFailed(captureSequenceId); + } catch (RemoteException e) { + Log.e(TAG, "Failed to notify capture failure start due to remote exception!"); + } + } + + @Override + public void onCaptureSequenceCompleted(int captureSequenceId) { + try { + mCaptureCallback.onCaptureSequenceCompleted(captureSequenceId); + } catch (RemoteException e) { + Log.e(TAG, "Failed to notify capture sequence done due to remote exception!"); + } + } + + @Override + public void onCaptureSequenceAborted(int captureSequenceId) { + try { + mCaptureCallback.onCaptureSequenceAborted(captureSequenceId); + } catch (RemoteException e) { + Log.e(TAG, "Failed to notify capture sequence abort due to remote exception!"); + } + } + + @Override + public void onCaptureCompleted(long shutterTimestamp, int requestId, + @androidx.annotation.NonNull CaptureResult results) { + try { + mCaptureCallback.onCaptureCompleted(shutterTimestamp, requestId, + results.getNativeCopy()); + } catch (RemoteException e) { + Log.e(TAG, "Failed to notify capture complete due to remote exception!"); + } + } + } + + @NonNull ISessionProcessorImpl getSessionProcessorBinder() { + return new SessionProcessorImpl(); + } +} diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java index 4ef45726e7a9..98bc31161591 100644 --- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java @@ -674,7 +674,8 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes try { if (mSessionProcessor != null) { mInitialized = true; - mSessionProcessor.onCaptureSessionStart(mRequestProcessor); + mSessionProcessor.onCaptureSessionStart(mRequestProcessor, + mStatsAggregator.getStatsKey()); } else { Log.v(TAG, "Failed to start capture session, session " + " released before extension start!"); diff --git a/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java b/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java index 8cd5e83241ad..3050a51d7955 100644 --- a/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java +++ b/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java @@ -118,4 +118,13 @@ public class ExtensionSessionStatsAggregator { + " type: '" + stats.type + "'\n" + " isAdvanced: '" + stats.isAdvanced + "'\n"; } + + /** + * Return the current statistics key + * + * @return the current statistics key + */ + public String getStatsKey() { + return mStats.key; + } } diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java index 502ee4db80c8..b315f4a0f0c5 100644 --- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java +++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java @@ -1329,7 +1329,7 @@ public class CameraExtensionsProxyService extends Service { } @Override - public void onCaptureSessionStart(IRequestProcessorImpl requestProcessor) { + public void onCaptureSessionStart(IRequestProcessorImpl requestProcessor, String statsKey) { mSessionProcessor.onCaptureSessionStart( new RequestProcessorStub(requestProcessor, mCameraId)); } |