diff options
| author | 2020-11-16 13:05:47 -0800 | |
|---|---|---|
| committer | 2021-03-02 14:27:16 -0800 | |
| commit | 80fc5cad540e14d27e2eed1ac8e96f3eb0fa96e1 (patch) | |
| tree | f3c71a7d506eb29cee09ec93f632bd400e9a0452 | |
| parent | 04501d566e0f4e976f3993f6dbbd997d0e014a51 (diff) | |
Camera: Add multi-resolution image reader and reprocessing support
- Add MultiResolutionImageReader class for variable size image output.
- Add camera characteristics for support multi-resolution output and input
streams.
- Update reprocessing interface doc for variable size support.
Test: atest MultiResolutionImageReaderTest, MultiResolutionReprocessCaptureTest
Bug: 156254356
Change-Id: I913abe38020c224c40903f42b8d45d40b65a5612
17 files changed, 1435 insertions, 92 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 8a543de40b18..a96f2ba2fa6e 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -17745,6 +17745,7 @@ package android.hardware.camera2 { field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Size> SCALER_DEFAULT_SECURE_IMAGE_SIZE; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_CONCURRENT_STREAM_COMBINATIONS; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_STREAM_COMBINATIONS; + field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MultiResolutionStreamConfigurationMap> SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.StreamConfigurationMap> SCALER_STREAM_CONFIGURATION_MAP; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> SENSOR_AVAILABLE_TEST_PATTERN_MODES; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.BlackLevelPattern> SENSOR_BLACK_LEVEL_PATTERN; @@ -18349,9 +18350,20 @@ package android.hardware.camera2 { field public static final int MAX_THUMBNAIL_DIMENSION = 256; // 0x100 } + public class MultiResolutionImageReader implements java.lang.AutoCloseable { + method public void close(); + method protected void finalize(); + method public void flush(); + method @NonNull public android.hardware.camera2.params.MultiResolutionStreamInfo getStreamInfoForImageReader(@NonNull android.media.ImageReader); + method @NonNull public android.view.Surface getSurface(); + method @NonNull public static android.hardware.camera2.MultiResolutionImageReader newInstance(@NonNull java.util.Collection<android.hardware.camera2.params.MultiResolutionStreamInfo>, int, @IntRange(from=1) int); + method public void setOnImageAvailableListener(@Nullable android.media.ImageReader.OnImageAvailableListener, @Nullable java.util.concurrent.Executor); + } + public final class TotalCaptureResult extends android.hardware.camera2.CaptureResult { method @NonNull public java.util.List<android.hardware.camera2.CaptureResult> getPartialResults(); - method public java.util.Map<java.lang.String,android.hardware.camera2.CaptureResult> getPhysicalCameraResults(); + method @Deprecated public java.util.Map<java.lang.String,android.hardware.camera2.CaptureResult> getPhysicalCameraResults(); + method @NonNull public java.util.Map<java.lang.String,android.hardware.camera2.TotalCaptureResult> getPhysicalCameraTotalResults(); } } @@ -18400,9 +18412,11 @@ package android.hardware.camera2.params { public final class InputConfiguration { ctor public InputConfiguration(int, int, int); + ctor public InputConfiguration(@NonNull java.util.Collection<android.hardware.camera2.params.MultiResolutionStreamInfo>, int); method public int getFormat(); method public int getHeight(); method public int getWidth(); + method public boolean isMultiResolution(); } public final class LensShadingMap { @@ -18445,6 +18459,20 @@ package android.hardware.camera2.params { field public static final int METERING_WEIGHT_MIN = 0; // 0x0 } + public final class MultiResolutionStreamConfigurationMap { + method @NonNull public int[] getInputFormats(); + method @NonNull public java.util.Collection<android.hardware.camera2.params.MultiResolutionStreamInfo> getInputInfo(int); + method @NonNull public int[] getOutputFormats(); + method @NonNull public java.util.Collection<android.hardware.camera2.params.MultiResolutionStreamInfo> getOutputInfo(int); + } + + public class MultiResolutionStreamInfo { + ctor public MultiResolutionStreamInfo(int, int, @NonNull String); + method public int getHeight(); + method @NonNull public String getPhysicalCameraId(); + method public int getWidth(); + } + public final class OisSample { ctor public OisSample(long, float, float); method public long getTimestamp(); @@ -18457,6 +18485,7 @@ package android.hardware.camera2.params { ctor public OutputConfiguration(int, @NonNull android.view.Surface); ctor public OutputConfiguration(@NonNull android.util.Size, @NonNull Class<T>); method public void addSurface(@NonNull android.view.Surface); + method @NonNull public static java.util.Collection<android.hardware.camera2.params.OutputConfiguration> createInstancesForMultiResolutionOutput(@NonNull android.hardware.camera2.MultiResolutionImageReader); method public int describeContents(); method public void enableSurfaceSharing(); method public int getMaxSharedSurfaceCount(); diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 16ab900dee06..07ebbaff67ea 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -24,13 +24,11 @@ import android.hardware.camera2.impl.PublicKey; import android.hardware.camera2.impl.SyntheticKey; import android.hardware.camera2.params.RecommendedStreamConfigurationMap; import android.hardware.camera2.params.SessionConfiguration; -import android.hardware.camera2.utils.ArrayUtils; import android.hardware.camera2.utils.TypeReference; import android.os.Build; import android.util.Rational; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -641,27 +639,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri */ @NonNull public Set<String> getPhysicalCameraIds() { - int[] availableCapabilities = get(REQUEST_AVAILABLE_CAPABILITIES); - if (availableCapabilities == null) { - throw new AssertionError("android.request.availableCapabilities must be non-null " - + "in the characteristics"); - } - - if (!ArrayUtils.contains(availableCapabilities, - REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA)) { - return Collections.emptySet(); - } - byte[] physicalCamIds = get(LOGICAL_MULTI_CAMERA_PHYSICAL_IDS); - - String physicalCamIdString = null; - try { - physicalCamIdString = new String(physicalCamIds, "UTF-8"); - } catch (java.io.UnsupportedEncodingException e) { - throw new AssertionError("android.logicalCam.physicalIds must be UTF-8 string"); - } - String[] physicalCameraIdArray = physicalCamIdString.split("\0"); - - return Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(physicalCameraIdArray))); + return mProperties.getPhysicalCameraIds(); } /*@O~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ @@ -2931,6 +2909,74 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<android.util.Size>("android.scaler.defaultSecureImageSize", android.util.Size.class); /** + * <p>The available multi-resolution stream configurations that this + * physical camera device supports + * (i.e. format, width, height, output/input stream).</p> + * <p>This list contains a subset of the parent logical camera's multi-resolution stream + * configurations which belong to this physical camera, and it will advertise and will only + * advertise the maximum supported resolutions for a particular format.</p> + * <p>If this camera device isn't a physical camera device constituting a logical camera, + * but a standalone ULTRA_HIGH_RESOLUTION_SENSOR camera, this field represents the + * multi-resolution input/output stream configurations of default mode and max resolution + * modes. The sizes will be the maximum resolution of a particular format for default mode + * and max resolution mode.</p> + * <p>This field will only be advertised if the device is a physical camera of a + * logical multi-camera device or an ultra high resolution sensor camera. For a logical + * multi-camera, the camera API will derive the logical camera’s multi-resolution stream + * configurations from all physical cameras. For an ultra high resolution sensor camera, this + * is used directly as the camera’s multi-resolution stream configurations.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * <p><b>Limited capability</b> - + * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the + * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p> + * + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + * @hide + */ + public static final Key<android.hardware.camera2.params.StreamConfiguration[]> SCALER_PHYSICAL_CAMERA_MULTI_RESOLUTION_STREAM_CONFIGURATIONS = + new Key<android.hardware.camera2.params.StreamConfiguration[]>("android.scaler.physicalCameraMultiResolutionStreamConfigurations", android.hardware.camera2.params.StreamConfiguration[].class); + + /** + * <p>The multi-resolution stream configurations supported by this logical camera + * or ultra high resolution sensor camera device.</p> + * <p>Multi-resolution streams can be used by a LOGICAL_MULTI_CAMERA or an + * ULTRA_HIGH_RESOLUTION_SENSOR camera where the images sent or received can vary in + * resolution per frame. This is useful in cases where the camera device's effective full + * resolution changes depending on factors such as the current zoom level, lighting + * condition, focus distance, or pixel mode.</p> + * <ul> + * <li>For a logical multi-camera implementing optical zoom, at different zoom level, a + * different physical camera may be active, resulting in different full-resolution image + * sizes.</li> + * <li>For an ultra high resolution camera, depending on whether the camera operates in default + * mode, or maximum resolution mode, the output full-size images may be of either binned + * resolution or maximum resolution.</li> + * </ul> + * <p>To use multi-resolution output streams, the supported formats can be queried by {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getOutputFormats }. + * A {@link android.hardware.camera2.MultiResolutionImageReader } can then be created for a + * supported format with the MultiResolutionStreamInfo group queried by {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getOutputInfo }.</p> + * <p>If a camera device supports multi-resolution output streams for a particular format, for + * each of its mandatory stream combinations, the camera device will support using a + * MultiResolutionImageReader for the MAXIMUM stream of supported formats. Refer to + * {@link android.hardware.camera2.CameraDevice#createCaptureSession } for additional details.</p> + * <p>To use multi-resolution input streams, the supported formats can be queried by {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getInputFormats }. + * A reprocessable CameraCaptureSession can then be created using an {@link android.hardware.camera2.params.InputConfiguration InputConfiguration} constructed with + * the input MultiResolutionStreamInfo group, queried by {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getInputInfo }.</p> + * <p>If a camera device supports multi-resolution {@code YUV} input and multi-resolution + * {@code YUV} output, or multi-resolution {@code PRIVATE} input and multi-resolution + * {@code PRIVATE} output, {@code JPEG} and {@code YUV} are guaranteed to be supported + * multi-resolution output stream formats. Refer to + * {@link android.hardware.camera2.CameraDevice#createCaptureSession } for + * details about the additional mandatory stream combinations in this case.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + */ + @PublicKey + @NonNull + @SyntheticKey + public static final Key<android.hardware.camera2.params.MultiResolutionStreamConfigurationMap> SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP = + new Key<android.hardware.camera2.params.MultiResolutionStreamConfigurationMap>("android.scaler.multiResolutionStreamConfigurationMap", android.hardware.camera2.params.MultiResolutionStreamConfigurationMap.class); + + /** * <p>The area of the image sensor which corresponds to active pixels after any geometric * distortion correction has been applied.</p> * <p>This is the rectangle representing the size of the active region of the sensor (i.e. diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index ac6ba0a4ac58..af48b71f9962 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -770,6 +770,8 @@ public abstract class CameraDevice implements AutoCloseable { * streams with {@code Y8} in all guaranteed stream combinations for the device's hardware level * and capabilities.</p> * + * <p>Clients can access the above mandatory stream combination tables via + * {@link android.hardware.camera2.params.MandatoryStreamCombination}.</p> * * <p>Devices capable of outputting HEIC formats ({@link StreamConfigurationMap#getOutputFormats} * contains {@link android.graphics.ImageFormat#HEIC}) will support substituting {@code JPEG} @@ -777,8 +779,33 @@ public abstract class CameraDevice implements AutoCloseable { * level and capabilities. Calling createCaptureSession with both JPEG and HEIC outputs is not * supported.</p> * - * <p>Clients can access the above mandatory stream combination tables via - * {@link android.hardware.camera2.params.MandatoryStreamCombination}.</p> + * <p>Devices capable of multi-resolution output for a particular format ( + * {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getOutputInfo} + * returns a non-empty list) support using {@link MultiResolutionImageReader} for MAXIMUM + * resolution streams of that format for all mandatory stream combinations. For example, + * if a LIMITED camera device supports multi-resolution output streams for both {@code JPEG} and + * {@code PRIVATE}, in addition to the stream configurations + * in the LIMITED and Legacy table above, the camera device supports the following guaranteed + * stream combinations ({@code MULTI_RES} in the Max size column refers to a {@link + * MultiResolutionImageReader} created based on the variable max resolutions supported): + * + * <table> + * <tr><th colspan="7">LEGACY-level additional guaranteed combinations with MultiResolutionoutputs</th></tr> + * <tr> <th colspan="2" id="rb">Target 1</th> <th colspan="2" id="rb">Target 2</th> <th colspan="2" id="rb">Target 3</th> <th rowspan="2">Sample use case(s)</th> </tr> + * <tr> <th>Type</th><th id="rb">Max size</th> <th>Type</th><th id="rb">Max size</th> <th>Type</th><th id="rb">Max size</th></tr> + * <tr> <td>{@code PRIV}</td><td id="rb">{@code MULTI_RES}</td> <td colspan="2" id="rb"></td> <td colspan="2" id="rb"></td> <td>Simple preview, GPU video processing, or no-preview video recording.</td> </tr> + * <tr> <td>{@code JPEG}</td><td id="rb">{@code MULTI_RES}</td> <td colspan="2" id="rb"></td> <td colspan="2" id="rb"></td> <td>No-viewfinder still image capture.</td> </tr> + * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code JPEG}</td><td id="rb">{@code MULTI_RES}</td> <td colspan="2" id="rb"></td> <td>Standard still imaging.</td> </tr> + * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code PREVIEW}</td> <td>{@code JPEG}</td><td id="rb">{@code MULTI_RES}</td> <td>Still capture plus in-app processing.</td> </tr> + * </table><br> + * <table> + * <tr><th colspan="7">LIMITED-level additional guaranteed configurations with MultiResolutionoutputs</th></tr> + * <tr><th colspan="2" id="rb">Target 1</th><th colspan="2" id="rb">Target 2</th><th colspan="2" id="rb">Target 3</th> <th rowspan="2">Sample use case(s)</th> </tr> + * <tr><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th></tr> + * <tr> <td>{@code YUV }</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code PREVIEW}</td> <td>{@code JPEG}</td><td id="rb">{@code MULTI_RES}</td> <td>Two-input in-app processing with still capture.</td> </tr> + * </table><br> + * The same logic applies to other hardware levels and capabilities. + * </p> * * <p>Since the capabilities of camera devices vary greatly, a given camera device may support * target combinations with sizes outside of these guarantees, but this can only be tested for @@ -939,6 +966,32 @@ public abstract class CameraDevice implements AutoCloseable { * </table><br> * </p> * + * <p>If a camera device supports multi-resolution {@code YUV} input and multi-resolution + * {@code YUV} output or supports multi-resolution {@code PRIVATE} input and multi-resolution + * {@code PRIVATE} output, the additional mandatory stream combinations for LIMITED and FULL devices are listed + * below ({@code MULTI_RES} in the Max size column refers to a + * {@link MultiResolutionImageReader} for output, and a multi-resolution + * {@link InputConfiguration} for input): + * <table> + * <tr><th colspan="11">LIMITED-level additional guaranteed configurations for creating a reprocessable capture session with multi-resolution input and multi-resolution outputs<br>({@code PRIV} input is guaranteed only if PRIVATE reprocessing is supported. {@code YUV} input is guaranteed only if YUV reprocessing is supported)</th></tr> + * <tr><th colspan="2" id="rb">Input</th><th colspan="2" id="rb">Target 1</th><th colspan="2" id="rb">Target 2</th><th colspan="2" id="rb">Target 3</th><th colspan="2" id="rb">Target 4</th><th rowspan="2">Sample use case(s)</th> </tr> + * <tr><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th></tr> + * <tr> <td>{@code PRIV}/{@code YUV}</td><td id="rb">{@code MULTI_RES}</td> <td>Same as input</td><td id="rb">{@code MULTI_RES}</td> <td>{@code JPEG}</td><td id="rb">{@code MULTI_RES}</td> <td></td><td id="rb"></td> <td></td><td id="rb"></td> <td>No-viewfinder still image reprocessing.</td> </tr> + * <tr> <td>{@code PRIV}/{@code YUV}</td><td id="rb">{@code MULTI_RES}</td> <td>Same as input</td><td id="rb">{@code MULTI_RES}</td> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code JPEG}</td><td id="rb">{@code MULTI_RES}</td> <td></td><td id="rb"></td> <td>ZSL(Zero-Shutter-Lag) still imaging.</td> </tr> + * <tr> <td>{@code PRIV}/{@code YUV}</td><td id="rb">{@code MULTI_RES}</td> <td>Same as input</td><td id="rb">{@code MULTI_RES}</td> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code JPEG}</td><td id="rb">{@code MULTI_RES}</td> <td></td><td id="rb"></td> <td>ZSL still and in-app processing imaging.</td> </tr> + * <tr> <td>{@code PRIV}/{@code YUV}</td><td id="rb">{@code MULTI_RES}</td> <td>Same as input</td><td id="rb">{@code MULTI_RES}</td> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code JPEG}</td><td id="rb">{@code MULTI_RES}</td> <td>ZSL in-app processing with still capture.</td> </tr> + * </table><br> + * <table> + * <tr><th colspan="11">FULL-level additional guaranteed configurations for creating a reprocessable capture session with multi-resolution input and multi-resolution outputs<br>({@code PRIV} input is guaranteed only if PRIVATE reprocessing is supported. {@code YUV} input is guaranteed only if YUV reprocessing is supported)</th></tr> + * <tr><th colspan="2" id="rb">Input</th><th colspan="2" id="rb">Target 1</th><th colspan="2" id="rb">Target 2</th><th colspan="2" id="rb">Target 3</th><th colspan="2" id="rb">Target 4</th><th rowspan="2">Sample use case(s)</th> </tr> + * <tr><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th></tr> + * <tr> <td>{@code PRIV}</td><td id="rb">{@code MULTI_RES}</td> <td>{@code PRIV}</td><td id="rb">{@code MULTI_RES}</td> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV}</td><td id="rb">{@code MULTI_RES}</td> <td></td><td id="rb"></td> <td>Maximum-resolution ZSL in-app processing with regular preview.</td> </tr> + * <tr> <td>{@code PRIV}</td><td id="rb">{@code MULTI_RES}</td> <td>{@code PRIV}</td><td id="rb">{@code MULTI_RES}</td> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV}</td><td id="rb">{@code MULTI_RES}</td> <td></td><td id="rb"></td> <td>Maximum-resolution two-input ZSL in-app processing.</td> </tr> + * <tr> <td>{@code PRIV}/{@code YUV}</td><td id="rb">{@code MULTI_RES}</td> <td>Same as input</td><td id="rb">{@code MULTI_RES}</td> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code JPEG}</td><td id="rb">{@code MULTI_RES}</td> <td>ZSL still capture and in-app processing.</td> </tr> + * </table><br> + * No additional mandatory stream combinations for RAW capability and LEVEL-3 hardware level. + * </p> + * * <h3>Constrained high-speed recording</h3> * * <p>The application can use a diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 3e0e3f62574f..a3c6f2f1eafd 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -31,6 +31,7 @@ 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.params.StreamConfiguration; import android.hardware.camera2.utils.CameraIdAndSessionConfiguration; import android.hardware.camera2.utils.ConcurrentCameraIdCombination; import android.hardware.display.DisplayManager; @@ -51,6 +52,7 @@ import android.view.Display; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; +import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.Executor; @@ -372,6 +374,47 @@ public final class CameraManager { } /** + * Get all physical cameras' multi-resolution stream configuration map + * + * <p>For a logical multi-camera, query the map between physical camera id and + * the physical camera's multi-resolution stream configuration. This map is in turn + * combined to form the logical camera's multi-resolution stream configuration map.</p> + */ + private Map<String, StreamConfiguration[]> getPhysicalCameraMultiResolutionConfigs( + CameraMetadataNative info, ICameraService cameraService) + throws CameraAccessException { + HashMap<String, StreamConfiguration[]> multiResolutionStreamConfigurations = + new HashMap<String, StreamConfiguration[]>(); + + // Query the characteristics of all physical sub-cameras, and combine the multi-resolution + // stream configurations. Note that framework derived formats such as HEIC and DEPTH_JPEG + // aren't supported as multi-resolution input or output formats. + Set<String> physicalCameraIds = info.getPhysicalCameraIds(); + try { + for (String physicalCameraId : physicalCameraIds) { + CameraMetadataNative physicalCameraInfo = + cameraService.getCameraCharacteristics(physicalCameraId); + StreamConfiguration[] configs = physicalCameraInfo.get( + CameraCharacteristics. + SCALER_PHYSICAL_CAMERA_MULTI_RESOLUTION_STREAM_CONFIGURATIONS); + if (configs != null) { + multiResolutionStreamConfigurations.put(physicalCameraId, configs); + } + } + + // TODO: If this is an ultra high resolution sensor camera, combine the multi-resolution + // stream combination from "info" as well. + } catch (RemoteException e) { + ServiceSpecificException sse = new ServiceSpecificException( + ICameraService.ERROR_DISCONNECTED, + "Camera service is currently unavailable"); + throwAsPublicException(sse); + } + + return multiResolutionStreamConfigurations; + } + + /** * <p>Query the capabilities of a camera device. These capabilities are * immutable for a given camera.</p> * @@ -418,12 +461,19 @@ public final class CameraManager { } catch (NumberFormatException e) { Log.v(TAG, "Failed to parse camera Id " + cameraId + " to integer"); } + boolean hasConcurrentStreams = CameraManagerGlobal.get().cameraIdHasConcurrentStreamsLocked(cameraId); info.setHasMandatoryConcurrentStreams(hasConcurrentStreams); info.setDisplaySize(displaySize); - characteristics = new CameraCharacteristics(info); + Map<String, StreamConfiguration[]> multiResolutionSizeMap = + getPhysicalCameraMultiResolutionConfigs(info, cameraService); + if (multiResolutionSizeMap.size() > 0) { + info.setMultiResolutionStreamConfigurationMap(multiResolutionSizeMap); + } + + characteristics = new CameraCharacteristics(info); } catch (ServiceSpecificException e) { throwAsPublicException(e); } catch (RemoteException e) { diff --git a/core/java/android/hardware/camera2/MultiResolutionImageReader.java b/core/java/android/hardware/camera2/MultiResolutionImageReader.java new file mode 100644 index 000000000000..c592f19bc45c --- /dev/null +++ b/core/java/android/hardware/camera2/MultiResolutionImageReader.java @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2021 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.CallbackExecutor; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.graphics.ImageFormat; +import android.graphics.ImageFormat.Format; +import android.hardware.HardwareBuffer; +import android.hardware.HardwareBuffer.Usage; +import android.media.Image; +import android.media.ImageReader; +import android.hardware.camera2.params.MultiResolutionStreamInfo; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import android.view.Surface; + + +import java.nio.NioUtils; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Executor; + +/** + * <p>The MultiResolutionImageReader class wraps a group of {@link ImageReader ImageReaders} with + * the same format and different sizes, source camera Id, or camera sensor modes.</p> + * + * <p>The main use case of this class is for a + * {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA logical + * multi-camera} or an ultra high resolution sensor camera to output variable-size images. For a + * logical multi-camera which implements optical zoom, different physical cameras may have different + * maximum resolutions. As a result, when the camera device switches between physical cameras + * depending on zoom ratio, the maximum resolution for a particular format may change. For an + * ultra high resolution sensor camera, the camera device may deem it better or worse to run in + * maximum resolution mode / default mode depending on lighting conditions. So the application may + * choose to let the camera device decide on its behalf.</p> + * + * <p>MultiResolutionImageReader should be used for a camera device only if the camera device + * supports multi-resolution output stream by advertising the specified output format in {@link + * CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP}.</p> + * + * <p>To acquire images from the MultiResolutionImageReader, the application must use the + * {@link ImageReader} object passed by + * {@link ImageReader.OnImageAvailableListener#onImageAvailable} callback to call + * {@link ImageReader#acquireNextImage} or {@link ImageReader#acquireLatestImage}. The application + * must not use the {@link ImageReader} passed by an {@link + * ImageReader.OnImageAvailableListener#onImageAvailable} callback to acquire future images + * because future images may originate from a different {@link ImageReader} contained within the + * {@code MultiResolutionImageReader}.</p> + * + * + * @see ImageReader + * @see android.hardware.camera2.CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP + */ +public class MultiResolutionImageReader implements AutoCloseable { + + private static final String TAG = "MultiResolutionImageReader"; + + /** + * <p> + * Create a new multi-resolution reader based on a group of camera stream properties returned + * by a camera device. + * </p> + * <p> + * The valid size and formats depend on the camera characteristics. + * {@code MultiResolutionImageReader} for an image format is supported by the camera device if + * the format is in the supported multi-resolution output stream formats returned by + * {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getOutputFormats}. + * If the image format is supported, the {@code MultiResolutionImageReader} object can be + * created with the {@code streams} objects returned by + * {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getOutputInfo}. + * </p> + * <p> + * The {@code maxImages} parameter determines the maximum number of + * {@link Image} objects that can be be acquired from each of the {@code ImageReader} + * within the {@code MultiResolutionImageReader}. However, requesting more buffers will + * use up more memory, so it is important to use only the minimum number necessary. The + * application is strongly recommended to acquire no more than {@code maxImages} images + * from all of the internal ImageReader objects combined. By keeping track of the number of + * acquired images for the MultiResolutionImageReader, the application doesn't need to do the + * bookkeeping for each internal ImageReader returned from {@link + * ImageReader.OnImageAvailableListener#onImageAvailable onImageAvailable} callback. + * </p> + * <p> + * Unlike the normal ImageReader, the MultiResolutionImageReader has a more complex + * configuration sequence. Instead of passing the same surface to OutputConfiguration and + * CaptureRequest, the + * {@link android.hardware.camera2.params.OutputConfiguration#createInstancesForMultiResolutionOutput} + * call needs to be used to create the OutputConfigurations for session creation, and then + * {@link #getSurface} is used to get {@link CaptureRequest.Builder#addTarget the target for + * CaptureRequest}. + * </p> + * @param streams The group of multi-resolution stream info, which is used to create + * a multi-resolution reader containing a number of ImageReader objects. Each + * ImageReader object represents a multi-resolution stream in the group. + * @param format The format of the Image that this multi-resolution reader will produce. + * This must be one of the {@link android.graphics.ImageFormat} or + * {@link android.graphics.PixelFormat} constants. Note that not all formats are + * supported, like ImageFormat.NV21. The supported multi-resolution + * reader format can be queried by {@link + * android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getOutputFormats}. + * @param maxImages The maximum number of images the user will want to + * access simultaneously. This should be as small as possible to + * limit memory use. Once maxImages images are obtained by the + * user from any given internal ImageReader, one of them has to be released before + * a new Image will become available for access through the ImageReader's + * {@link ImageReader#acquireLatestImage()} or + * {@link ImageReader#acquireNextImage()}. Must be greater than 0. + * @see Image + * @see + * android.hardware.camera2.CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP + * @see + * android.hardware.camera2.params.MultiResolutionStreamConfigurationMap + */ + public static @NonNull MultiResolutionImageReader newInstance( + @NonNull Collection<MultiResolutionStreamInfo> streams, + @Format int format, + @IntRange(from = 1) int maxImages) { + return new MultiResolutionImageReader(streams, format, maxImages); + } + + /** + * @hide + */ + protected MultiResolutionImageReader(Collection<MultiResolutionStreamInfo> streams, + int format, int maxImages) { + mFormat = format; + mMaxImages = maxImages; + + if (streams == null || streams.size() <= 1) { + throw new IllegalArgumentException( + "The streams info collection must contain at least 2 entries"); + } + if (mMaxImages < 1) { + throw new IllegalArgumentException( + "Maximum outstanding image count must be at least 1"); + } + + if (format == ImageFormat.NV21) { + throw new IllegalArgumentException( + "NV21 format is not supported"); + } + + int numImageReaders = streams.size(); + mReaders = new ImageReader[numImageReaders]; + mStreamInfo = new MultiResolutionStreamInfo[numImageReaders]; + int index = 0; + for (MultiResolutionStreamInfo streamInfo : streams) { + mReaders[index] = ImageReader.newInstance(streamInfo.getWidth(), + streamInfo.getHeight(), format, maxImages); + mStreamInfo[index] = streamInfo; + index++; + } + } + + /** + * Set onImageAvailableListener callback. + * + * <p>This function sets the onImageAvailableListener for all the internal + * {@link ImageReader} objects.</p> + * + * <p>For a multi-resolution ImageReader, the timestamps of images acquired in + * onImageAvailable callback from different internal ImageReaders may become + * out-of-order due to the asynchronous callbacks between the different resolution + * image queues.</p> + * + * @param listener + * The listener that will be run. + * @param executor + * The executor which will be used when invoking the callback. + */ + @SuppressLint({"ExecutorRegistration", "SamShouldBeLast"}) + public void setOnImageAvailableListener( + @Nullable ImageReader.OnImageAvailableListener listener, + @Nullable @CallbackExecutor Executor executor) { + for (int i = 0; i < mReaders.length; i++) { + mReaders[i].setOnImageAvailableListenerWithExecutor(listener, executor); + } + } + + @Override + public void close() { + flush(); + + for (int i = 0; i < mReaders.length; i++) { + mReaders[i].close(); + } + } + + @Override + protected void finalize() { + close(); + } + + /** + * Flush pending images from all internal ImageReaders + * + * <p>Acquire and close pending images from all internal ImageReaders. This has the same + * effect as calling acquireLatestImage() on all internal ImageReaders, and closing all + * latest images.</p> + */ + public void flush() { + flushOther(null); + } + + /** + * Flush pending images from other internal ImageReaders + * + * <p>Acquire and close pending images from all internal ImageReaders except for the + * one specified.</p> + * + * @param reader The ImageReader object that won't be flushed. + * + * @hide + */ + public void flushOther(ImageReader reader) { + for (int i = 0; i < mReaders.length; i++) { + if (reader != null && reader == mReaders[i]) { + continue; + } + + while (true) { + Image image = mReaders[i].acquireNextImageNoThrowISE(); + if (image == null) { + break; + } else { + image.close(); + } + } + } + } + + /** + * Get the internal ImageReader objects + * + * @hide + */ + public @NonNull ImageReader[] getReaders() { + return mReaders; + } + + /** + * Get the surface that is used as a target for {@link CaptureRequest} + * + * <p>The application must use the surface returned by this function as a target for + * {@link CaptureRequest}. The camera device makes the decision on which internal + * {@code ImageReader} will receive the output image.</p> + * + * <p>Please note that holding on to the Surface objects returned by this method is not enough + * to keep their parent MultiResolutionImageReaders from being reclaimed. In that sense, a + * Surface acts like a {@link java.lang.ref.WeakReference weak reference} to the + * MultiResolutionImageReader that provides it.</p> + * + * @return a {@link Surface} to use as the target for a capture request. + */ + public @NonNull Surface getSurface() { + //TODO: Pick the surface from the reader for default mode stream. + return mReaders[0].getSurface(); + } + + /** + * Get the MultiResolutionStreamInfo describing the ImageReader an image originates from + * + *<p>An image from a {@code MultiResolutionImageReader} is produced from one of the underlying + *{@code ImageReader}s. This function returns the {@link MultiResolutionStreamInfo} to describe + *the property for that {@code ImageReader}, such as width, height, and physical camera Id.</p> + * + * @param reader An internal ImageReader within {@code MultiResolutionImageReader}. + * + * @return The stream info describing the internal {@code ImageReader}. + */ + public @NonNull MultiResolutionStreamInfo getStreamInfoForImageReader( + @NonNull ImageReader reader) { + for (int i = 0; i < mReaders.length; i++) { + if (reader == mReaders[i]) { + return mStreamInfo[i]; + } + } + + throw new IllegalArgumentException("ImageReader doesn't belong to this multi-resolution " + + "imagereader"); + } + + // mReaders and mStreamInfo has the same length, and their entries are 1:1 mapped. + private final ImageReader[] mReaders; + private final MultiResolutionStreamInfo[] mStreamInfo; + + private final int mFormat; + private final int mMaxImages; +} diff --git a/core/java/android/hardware/camera2/TotalCaptureResult.java b/core/java/android/hardware/camera2/TotalCaptureResult.java index da65f71ce02c..df8eeccbe800 100644 --- a/core/java/android/hardware/camera2/TotalCaptureResult.java +++ b/core/java/android/hardware/camera2/TotalCaptureResult.java @@ -61,8 +61,8 @@ public final class TotalCaptureResult extends CaptureResult { private final List<CaptureResult> mPartialResults; private final int mSessionId; - // The map between physical camera id and capture result - private final HashMap<String, CaptureResult> mPhysicalCaptureResults; + // The map between physical camera ids and their total capture result + private final HashMap<String, TotalCaptureResult> mPhysicalCaptureResults; /** * Takes ownership of the passed-in camera metadata and the partial results @@ -83,10 +83,11 @@ public final class TotalCaptureResult extends CaptureResult { mSessionId = sessionId; - mPhysicalCaptureResults = new HashMap<String, CaptureResult>(); + mPhysicalCaptureResults = new HashMap<String, TotalCaptureResult>(); for (PhysicalCaptureResultInfo onePhysicalResult : physicalResults) { - CaptureResult physicalResult = new CaptureResult(onePhysicalResult.getCameraId(), - onePhysicalResult.getCameraMetadata(), parent, extras); + TotalCaptureResult physicalResult = new TotalCaptureResult( + onePhysicalResult.getCameraId(), onePhysicalResult.getCameraMetadata(), + parent, extras, /*partials*/null, sessionId, new PhysicalCaptureResultInfo[0]); mPhysicalCaptureResults.put(onePhysicalResult.getCameraId(), physicalResult); } @@ -103,7 +104,7 @@ public final class TotalCaptureResult extends CaptureResult { mPartialResults = new ArrayList<>(); mSessionId = CameraCaptureSession.SESSION_ID_NONE; - mPhysicalCaptureResults = new HashMap<String, CaptureResult>(); + mPhysicalCaptureResults = new HashMap<String, TotalCaptureResult>(); } /** @@ -146,8 +147,37 @@ public final class TotalCaptureResult extends CaptureResult { * cameras. Otherwise, an empty map is returned.</p> * @return unmodifiable map between physical camera ids and their capture result metadata + * + * @deprecated + * <p>Please use {@link #getPhysicalCameraTotalResults() instead to get the + * physical cameras' {@code TotalCaptureResult}.</p> */ public Map<String, CaptureResult> getPhysicalCameraResults() { return Collections.unmodifiableMap(mPhysicalCaptureResults); } + + /** + * Get the map between physical camera ids and their total capture result metadata + * + * <p>This function can be called for logical multi-camera devices, which are devices that have + * REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA capability.</p> + * + * <p>If one or more streams from the underlying physical cameras were requested by the + * corresponding capture request, this function returns the total result metadata for those + * physical cameras. Otherwise, an empty map is returned.</p> + * + * <p>This function replaces the deprecated {@link #getPhysicalCameraResults}, and its return + * value is a map of TotalCaptureResult rather than CaptureResult. </p> + * + * <p>To reprocess an image from a physical camera stream, typically returned from a + * {@link MultiResolutionImageReader}, the application must look up this map to get the {@link + * TotalCaptureResult} from the physical camera and pass it to {@link + * CameraDevice#createReprocessCaptureRequest}.</p> + * + * @return unmodifiable map between physical camera ids and their total capture result metadata + */ + @NonNull + public Map<String, TotalCaptureResult> getPhysicalCameraTotalResults() { + return Collections.unmodifiableMap(mPhysicalCaptureResults); + } } diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index ce3c81a2bfd6..4defd23231b8 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -36,6 +36,8 @@ 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.MultiResolutionStreamConfigurationMap; +import android.hardware.camera2.params.MultiResolutionStreamInfo; import android.hardware.camera2.params.OutputConfiguration; import android.hardware.camera2.params.SessionConfiguration; import android.hardware.camera2.params.StreamConfigurationMap; @@ -468,7 +470,8 @@ public class CameraDeviceImpl extends CameraDevice } if (inputConfig != null) { int streamId = mRemoteDevice.createInputStream(inputConfig.getWidth(), - inputConfig.getHeight(), inputConfig.getFormat()); + inputConfig.getHeight(), inputConfig.getFormat(), + inputConfig.isMultiResolution()); mConfiguredInput = new SimpleEntry<Integer, InputConfiguration>( streamId, inputConfig); } @@ -1355,7 +1358,42 @@ public class CameraDeviceImpl extends CameraDevice } private void checkInputConfiguration(InputConfiguration inputConfig) { - if (inputConfig != null) { + if (inputConfig == null) { + return; + } + + if (inputConfig.isMultiResolution()) { + MultiResolutionStreamConfigurationMap configMap = mCharacteristics.get( + CameraCharacteristics.SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP); + + int[] inputFormats = configMap.getInputFormats(); + boolean validFormat = false; + for (int format : inputFormats) { + if (format == inputConfig.getFormat()) { + validFormat = true; + } + } + + if (validFormat == false) { + throw new IllegalArgumentException("multi-resolution input format " + + inputConfig.getFormat() + " is not valid"); + } + + boolean validSize = false; + Collection<MultiResolutionStreamInfo> inputStreamInfo = + configMap.getInputInfo(inputConfig.getFormat()); + for (MultiResolutionStreamInfo info : inputStreamInfo) { + if (inputConfig.getWidth() == info.getWidth() && + inputConfig.getHeight() == info.getHeight()) { + validSize = true; + } + } + + if (validSize == false) { + throw new IllegalArgumentException("Multi-resolution input size " + + inputConfig.getWidth() + "x" + inputConfig.getHeight() + " is not valid"); + } + } else { StreamConfigurationMap configMap = mCharacteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java index e9bae0b621ce..0cdf744ecd68 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -16,6 +16,7 @@ package android.hardware.camera2.impl; +import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.ImageFormat; import android.graphics.Point; @@ -53,6 +54,7 @@ import android.hardware.camera2.params.Face; import android.hardware.camera2.params.HighSpeedVideoConfiguration; import android.hardware.camera2.params.LensShadingMap; import android.hardware.camera2.params.MandatoryStreamCombination; +import android.hardware.camera2.params.MultiResolutionStreamConfigurationMap; import android.hardware.camera2.params.OisSample; import android.hardware.camera2.params.RecommendedStreamConfiguration; import android.hardware.camera2.params.RecommendedStreamConfigurationMap; @@ -61,6 +63,7 @@ import android.hardware.camera2.params.StreamConfiguration; import android.hardware.camera2.params.StreamConfigurationDuration; import android.hardware.camera2.params.StreamConfigurationMap; import android.hardware.camera2.params.TonemapCurve; +import android.hardware.camera2.utils.ArrayUtils; import android.hardware.camera2.utils.TypeReference; import android.location.Location; import android.location.LocationManager; @@ -79,9 +82,14 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; import java.util.List; import java.util.Objects; +import java.util.Set; /** * Implementation of camera metadata marshal/unmarshal across Binder to @@ -747,6 +755,15 @@ public class CameraMetadataNative implements Parcelable { return (T) metadata.getExtendedSceneModeCapabilities(); } }); + sGetCommandMap.put( + CameraCharacteristics.SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP.getNativeKey(), + new GetCommand() { + @Override + @SuppressWarnings("unchecked") + public <T> T getValue(CameraMetadataNative metadata, Key<T> key) { + return (T) metadata.getMultiResolutionStreamConfigurationMap(); + } + }); } private int[] getAvailableFormats() { @@ -1688,6 +1705,7 @@ public class CameraMetadataNative implements Parcelable { private boolean mHasMandatoryConcurrentStreams = false; private Size mDisplaySize = new Size(0, 0); private long mBufferSize = 0; + private MultiResolutionStreamConfigurationMap mMultiResolutionStreamConfigurationMap = null; /** * Set the current camera Id. @@ -1723,6 +1741,30 @@ public class CameraMetadataNative implements Parcelable { mDisplaySize = displaySize; } + /** + * Set the multi-resolution stream configuration map. + * + * @param multiResolutionMap The multi-resolution stream configuration map. + * + * @hide + */ + public void setMultiResolutionStreamConfigurationMap( + @NonNull Map<String, StreamConfiguration[]> multiResolutionMap) { + mMultiResolutionStreamConfigurationMap = + new MultiResolutionStreamConfigurationMap(multiResolutionMap); + } + + /** + * Get the multi-resolution stream configuration map. + * + * @return The multi-resolution stream configuration map. + * + * @hide + */ + public MultiResolutionStreamConfigurationMap getMultiResolutionStreamConfigurationMap() { + return mMultiResolutionStreamConfigurationMap; + } + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private long mMetadataPtr; // native std::shared_ptr<CameraMetadata>* @@ -1777,6 +1819,7 @@ public class CameraMetadataNative implements Parcelable { mCameraId = other.mCameraId; mHasMandatoryConcurrentStreams = other.mHasMandatoryConcurrentStreams; mDisplaySize = other.mDisplaySize; + mMultiResolutionStreamConfigurationMap = other.mMultiResolutionStreamConfigurationMap; updateNativeAllocation(); other.updateNativeAllocation(); } @@ -1980,6 +2023,39 @@ public class CameraMetadataNative implements Parcelable { return true; } + /** + * Return the set of physical camera ids that this logical {@link CameraDevice} is made + * up of. + * + * If the camera device isn't a logical camera, return an empty set. + * + * @hide + */ + public Set<String> getPhysicalCameraIds() { + int[] availableCapabilities = get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES); + if (availableCapabilities == null) { + throw new AssertionError("android.request.availableCapabilities must be non-null " + + "in the characteristics"); + } + + if (!ArrayUtils.contains(availableCapabilities, + CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA)) { + return Collections.emptySet(); + } + byte[] physicalCamIds = get(CameraCharacteristics.LOGICAL_MULTI_CAMERA_PHYSICAL_IDS); + + String physicalCamIdString = null; + try { + physicalCamIdString = new String(physicalCamIds, "UTF-8"); + } catch (java.io.UnsupportedEncodingException e) { + throw new AssertionError("android.logicalCam.physicalIds must be UTF-8 string"); + } + String[] physicalCameraIdArray = physicalCamIdString.split("\0"); + + return Collections.unmodifiableSet( + new HashSet<String>(Arrays.asList(physicalCameraIdArray))); + } + static { registerAllMarshalers(); } diff --git a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java index ba4395f70214..b6b1968bfcdd 100644 --- a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java +++ b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java @@ -140,9 +140,10 @@ public class ICameraDeviceUserWrapper { } } - public int createInputStream(int width, int height, int format) throws CameraAccessException { + public int createInputStream(int width, int height, int format, boolean isMultiResolution) + throws CameraAccessException { try { - return mRemoteDevice.createInputStream(width, height, format); + return mRemoteDevice.createInputStream(width, height, format, isMultiResolution); } catch (Throwable t) { CameraManager.throwAsPublicException(t); throw new UnsupportedOperationException("Unexpected exception", t); diff --git a/core/java/android/hardware/camera2/params/InputConfiguration.java b/core/java/android/hardware/camera2/params/InputConfiguration.java index 0a50f974aca8..d63683feed9b 100644 --- a/core/java/android/hardware/camera2/params/InputConfiguration.java +++ b/core/java/android/hardware/camera2/params/InputConfiguration.java @@ -16,9 +16,17 @@ package android.hardware.camera2.params; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.graphics.ImageFormat.Format; +import android.hardware.camera2.params.MultiResolutionStreamInfo; import android.hardware.camera2.utils.HashCodeHelpers; +import java.util.Collection; +import java.util.List; + +import static com.android.internal.util.Preconditions.*; + /** * Immutable class to store an input configuration that is used to create a reprocessable capture * session. @@ -31,11 +39,12 @@ public final class InputConfiguration { private final int mWidth; private final int mHeight; private final int mFormat; + private final boolean mIsMultiResolution; /** * Create an input configration with the width, height, and user-defined format. * - * <p>Images of an user-defined format are accessible by applications. Use + * <p>Images of a user-defined format are accessible by applications. Use * {@link android.hardware.camera2.CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP} * to query supported input formats</p> * @@ -51,6 +60,52 @@ public final class InputConfiguration { mWidth = width; mHeight = height; mFormat = format; + mIsMultiResolution = false; + } + + /** + * Create an input configration with the format and a list of multi-resolution input stream + * info. + * + * <p>Use {@link + * android.hardware.camera2.CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP} + * to query supported multi-resolution input formats.</p> + * + * <p>To do reprocessing with variable resolution input, the application calls + * {@link android.media.ImageWriter#queueInputImage ImageWriter.queueInputImage} + * using an image from an {@link android.media.ImageReader ImageReader} or {@link + * android.hardware.camera2.MultiResolutionImageReader MultiResolutionImageReader}. See + * {@link android.hardware.camera2.CameraDevice#createReprocessCaptureRequest} for more + * details on camera reprocessing. + * </p> + * + * @param multiResolutionInputs A group of multi-resolution input info for the specified format. + * @param format Format of the input buffers. One of ImageFormat or PixelFormat constants. + * + * @see android.graphics.ImageFormat + * @see android.graphics.PixelFormat + * @see + * android.hardware.camera2.CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP + */ + public InputConfiguration(@NonNull Collection<MultiResolutionStreamInfo> multiResolutionInputs, + @Format int format) { + checkCollectionNotEmpty(multiResolutionInputs, "Input multi-resolution stream info"); + //TODO: Pick the default mode stream info for ultra-high resolution sensor camera + MultiResolutionStreamInfo info = multiResolutionInputs.iterator().next(); + mWidth = info.getWidth(); + mHeight = info.getHeight(); + mFormat = format; + mIsMultiResolution = true; + } + + /** + * @hide + */ + public InputConfiguration(int width, int height, int format, boolean isMultiResolution) { + mWidth = width; + mHeight = height; + mFormat = format; + mIsMultiResolution = isMultiResolution; } /** @@ -81,6 +136,18 @@ public final class InputConfiguration { } /** + * Whether this input configuration is of multi-resolution. + * + * <p>An multi-resolution InputConfiguration means that the reprocessing session created from it + * allows input images of different sizes.</p> + * + * @return this input configuration is multi-resolution or not. + */ + public boolean isMultiResolution() { + return mIsMultiResolution; + } + + /** * Check if this InputConfiguration is equal to another InputConfiguration. * * <p>Two input configurations are equal if and only if they have the same widths, heights, and @@ -100,7 +167,8 @@ public final class InputConfiguration { if (otherInputConfig.getWidth() == mWidth && otherInputConfig.getHeight() == mHeight && - otherInputConfig.getFormat() == mFormat) { + otherInputConfig.getFormat() == mFormat && + otherInputConfig.isMultiResolution() == mIsMultiResolution) { return true; } return false; @@ -111,19 +179,21 @@ public final class InputConfiguration { */ @Override public int hashCode() { - return HashCodeHelpers.hashCode(mWidth, mHeight, mFormat); + return HashCodeHelpers.hashCode(mWidth, mHeight, mFormat, mIsMultiResolution ? 1 : 0); } /** * Return this {@link InputConfiguration} as a string representation. * - * <p> {@code "InputConfiguration(w:%d, h:%d, format:%d)"}, where {@code %d} represents - * the width, height, and format, respectively.</p> + * <p> {@code "InputConfiguration(w:%d, h:%d, format:%d, isMultiResolution:%d)"}, + * where {@code %d} represents the width, height, format, and multi-resolution flag + * respectively.</p> * * @return string representation of {@link InputConfiguration} */ @Override public String toString() { - return String.format("InputConfiguration(w:%d, h:%d, format:%d)", mWidth, mHeight, mFormat); + return String.format("InputConfiguration(w:%d, h:%d, format:%d, isMultiResolution %b)", + mWidth, mHeight, mFormat, mIsMultiResolution); } } diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java index 776d155e5b3e..8a0172ee8018 100644 --- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java +++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java @@ -1297,19 +1297,6 @@ public final class MandatoryStreamCombination { } /** - * Size comparison method used by size comparators. - */ - private static int compareSizes(int widthA, int heightA, int widthB, int heightB) { - long left = widthA * (long) heightA; - long right = widthB * (long) heightB; - if (left == right) { - left = widthA; - right = widthB; - } - return (left < right) ? -1 : (left > right ? 1 : 0); - } - - /** * Size comparator that compares the number of pixels it covers. * * <p>If two the areas of two sizes are same, compare the widths.</p> @@ -1317,8 +1304,8 @@ public final class MandatoryStreamCombination { public static class SizeComparator implements Comparator<Size> { @Override public int compare(@NonNull Size lhs, @NonNull Size rhs) { - return compareSizes(lhs.getWidth(), lhs.getHeight(), rhs.getWidth(), - rhs.getHeight()); + return StreamConfigurationMap.compareSizes(lhs.getWidth(), lhs.getHeight(), + rhs.getWidth(), rhs.getHeight()); } } diff --git a/core/java/android/hardware/camera2/params/MultiResolutionStreamConfigurationMap.java b/core/java/android/hardware/camera2/params/MultiResolutionStreamConfigurationMap.java new file mode 100644 index 000000000000..1b368fb8c010 --- /dev/null +++ b/core/java/android/hardware/camera2/params/MultiResolutionStreamConfigurationMap.java @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2021 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.annotation.Nullable; + +import android.graphics.ImageFormat; +import android.graphics.ImageFormat.Format; +import android.graphics.PixelFormat; +import android.hardware.camera2.params.MultiResolutionStreamInfo; +import android.hardware.camera2.params.StreamConfigurationMap; +import android.hardware.camera2.utils.HashCodeHelpers; + +import android.util.Size; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.List; +import java.util.Set; + +import static com.android.internal.util.Preconditions.*; + +/** + * Immutable class to store the information of the multi-resolution streams supported by + * the camera device. + * + * <p>For a {@link + * android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA + * logical multi-camera} or an ultra high resolution sensor camera, the maximum resolution of images + * produced by the camera device may be variable. For example, for a logical multi-camera, depending + * on factors such as current zoom ratio, the camera device may be backed by different physical + * cameras. If the physical cameras are of different resolutions, the application may intend to + * consume the variable full resolution images from the physical cameras. For an ultra high + * resolution sensor camera, the same use case exists where depending on lighting conditions, the + * camera device may deem it better to run in default mode and maximum resolution mode. + * </p> + * + * <p>For the use cases described above, multi-resolution output streams can be used by + * {@link android.hardware.camera2.MultiResolutionImageReader} to allow the + * camera device to output variable size maximum-resolution images.</p> + * + * <p>Similarly, multi-resolution input streams can be used for reprocessing of variable size + * images. In order to reprocess input images of different sizes, the {@link InputConfiguration} + * used for creating reprocessable session can be initialized using the group of input stream + * configurations returned by {@link #getInputInfo}.</p> + */ +public final class MultiResolutionStreamConfigurationMap { + /** + * Create a new {@link MultiResolutionStreamConfigurationMap}. + * + * @param configurations a non-{@code null} array of multi-resolution stream + * configurations supported by this camera device + * @hide + */ + public MultiResolutionStreamConfigurationMap( + @NonNull Map<String, StreamConfiguration[]> configurations) { + checkNotNull(configurations, "multi-resolution configurations must not be null"); + if (configurations.size() == 0) { + throw new IllegalArgumentException("multi-resolution configurations must not be empty"); + } + + mConfigurations = configurations; + + // For each multi-resolution stream configuration, track how many formats and sizes there + // are available to configure + for (Map.Entry<String, StreamConfiguration[]> entry : + mConfigurations.entrySet()) { + String cameraId = entry.getKey(); + StreamConfiguration[] configs = entry.getValue(); + + for (int i = 0; i < configs.length; i++) { + StreamConfiguration config = configs[i]; + int format = config.getFormat(); + + MultiResolutionStreamInfo multiResolutionStreamInfo = new MultiResolutionStreamInfo( + config.getWidth(), config.getHeight(), cameraId); + Map<Integer, List<MultiResolutionStreamInfo>> destMap; + if (config.isInput()) { + destMap = mMultiResolutionInputConfigs; + } else { + destMap = mMultiResolutionOutputConfigs; + } + + if (!destMap.containsKey(format)) { + List<MultiResolutionStreamInfo> multiResolutionStreamInfoList = + new ArrayList<MultiResolutionStreamInfo>(); + destMap.put(format, multiResolutionStreamInfoList); + } + destMap.get(format).add(multiResolutionStreamInfo); + } + } + } + + /** + * Size comparator that compares the number of pixels two MultiResolutionStreamInfo size covers. + * + * <p>If two the areas of two sizes are same, compare the widths.</p> + * + * @hide + */ + public static class SizeComparator implements Comparator<MultiResolutionStreamInfo> { + @Override + public int compare(@NonNull MultiResolutionStreamInfo lhs, + @NonNull MultiResolutionStreamInfo rhs) { + return StreamConfigurationMap.compareSizes( + lhs.getWidth(), lhs.getHeight(), rhs.getWidth(), rhs.getHeight()); + } + } + + /** + * Get the output formats in this multi-resolution stream configuration. + * + * <p>A logical multi-camera or an ultra high resolution sensor camera may support + * {@link android.hardware.camera2.MultiResolutionImageReader} to dynamically output maximum + * resolutions of different sizes (when switching between physical cameras, or between different + * modes of an ultra high resolution sensor camera). This function returns the formats + * supported for such case.</p> + * + * <p>All image formats returned by this function will be defined in either {@link ImageFormat} + * or in {@link PixelFormat} (and there is no possibility of collision).</p> + * + * @return an array of integer format, or empty array if multi-resolution output is not + * supported + * + * @see ImageFormat + * @see PixelFormat + * @see android.hardware.camera2.MultiResolutionImageReader + */ + public @NonNull @Format int[] getOutputFormats() { + return getPublicImageFormats(/*output*/true); + } + + /** + * Get the input formats in this multi-resolution stream configuration. + * + * <p>A logical multi-camera or ultra high resolution sensor camera may support reprocessing + * images of different resolutions when switching between physical cameras, or between + * different modes of the ultra high resolution sensor camera. This function returns the + * formats supported for such case.</p> + * + * <p>The supported output format for an input format can be queried by calling the camera + * device's {@link StreamConfigurationMap#getValidOutputFormatsForInput}.</p> + * + * <p>All image formats returned by this function will be defined in either {@link ImageFormat} + * or in {@link PixelFormat} (and there is no possibility of collision).</p> + * + * @return an array of integer format, or empty array if no multi-resolution reprocessing is + * supported + * + * @see ImageFormat + * @see PixelFormat + */ + public @NonNull @Format int[] getInputFormats() { + return getPublicImageFormats(/*output*/false); + } + + // Get the list of publicly visible multi-resolution input/output stream formats + private int[] getPublicImageFormats(boolean output) { + Map<Integer, List<MultiResolutionStreamInfo>> multiResolutionConfigs = + output ? mMultiResolutionOutputConfigs : mMultiResolutionInputConfigs; + int formatCount = multiResolutionConfigs.size(); + + int[] formats = new int[formatCount]; + int i = 0; + for (Integer format : multiResolutionConfigs.keySet()) { + formats[i++] = StreamConfigurationMap.imageFormatToPublic(format); + } + + return formats; + } + + /** + * Get a group of {@code MultiResolutionStreamInfo} with the requested output image + * {@code format} + * + * <p>The {@code format} should be a supported format (one of the formats returned by + * {@link #getOutputFormats}).</p> + * + * @param format an image format from {@link ImageFormat} or {@link PixelFormat} + * @return + * a group of supported {@link MultiResolutionStreamInfo}. If the {@code format} is not + * a supported multi-resolution output, an empty group is returned. + * + * @see ImageFormat + * @see PixelFormat + * @see #getOutputFormats + */ + public @NonNull Collection<MultiResolutionStreamInfo> getOutputInfo(@Format int format) { + return getInfo(format, /*false*/ true); + } + + /** + * Get a group of {@code MultiResolutionStreamInfo} with the requested input image {@code format} + * + * <p>The {@code format} should be a supported format (one of the formats returned by + * {@link #getInputFormats}).</p> + * + * @param format an image format from {@link ImageFormat} or {@link PixelFormat} + * @return + * a group of supported {@link MultiResolutionStreamInfo}. If the {@code format} is not + * a supported multi-resolution input, an empty group is returned. + * + * @see ImageFormat + * @see PixelFormat + * @see #getInputFormats + */ + public @NonNull Collection<MultiResolutionStreamInfo> getInputInfo(@Format int format) { + return getInfo(format, /*false*/ false); + } + + // Get multi-resolution stream info for a particular format + private @NonNull Collection<MultiResolutionStreamInfo> getInfo(int format, boolean output) { + int internalFormat = StreamConfigurationMap.imageFormatToInternal(format); + Map<Integer, List<MultiResolutionStreamInfo>> multiResolutionConfigs = + output ? mMultiResolutionOutputConfigs : mMultiResolutionInputConfigs; + if (multiResolutionConfigs.containsKey(internalFormat)) { + return Collections.unmodifiableCollection(multiResolutionConfigs.get(internalFormat)); + } else { + return Collections.emptyList(); + } + } + + private void appendConfigurationsString(StringBuilder sb, boolean output) { + sb.append(output ? "Outputs(" : "Inputs("); + int[] formats = getPublicImageFormats(output); + if (formats != null) { + for (int format : formats) { + Collection<MultiResolutionStreamInfo> streamInfoList = + getInfo(format, output); + sb.append("[" + StreamConfigurationMap.formatToString(format) + ":"); + for (MultiResolutionStreamInfo streamInfo : streamInfoList) { + sb.append(String.format("[w:%d, h:%d, id:%s], ", + streamInfo.getWidth(), streamInfo.getHeight(), + streamInfo.getPhysicalCameraId())); + } + // Remove the pending ", " + if (sb.charAt(sb.length() - 1) == ' ') { + sb.delete(sb.length() - 2, sb.length()); + } + sb.append("]"); + } + } + sb.append(")"); + } + + /** + * Check if this {@link MultiResolutionStreamConfigurationMap} is equal to another + * {@link MultiResolutionStreamConfigurationMap}. + * + * @return {@code true} if the objects were equal, {@code false} otherwise + */ + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (obj instanceof MultiResolutionStreamConfigurationMap) { + final MultiResolutionStreamConfigurationMap other = + (MultiResolutionStreamConfigurationMap) obj; + if (!mConfigurations.keySet().equals(other.mConfigurations.keySet())) { + return false; + } + + for (String id : mConfigurations.keySet()) { + if (!Arrays.equals(mConfigurations.get(id), other.mConfigurations.get(id))) { + return false; + } + } + + return true; + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return HashCodeHelpers.hashCodeGeneric( + mConfigurations, mMultiResolutionOutputConfigs, mMultiResolutionInputConfigs); + } + + /** + * Return this {@link MultiResolutionStreamConfigurationMap} as a string representation. + * + * <p>{@code "MultiResolutionStreamConfigurationMap(Outputs([format1: [w:%d, h:%d, id:%s], ... + * ... [w:%d, h:%d, id:%s]), [format2: [w:%d, h:%d, id:%s], ... [w:%d, h:%d, id:%s]], ...), + * Inputs([format1: [w:%d, h:%d, id:%s], ... [w:%d, h:%d, id:%s], ...).</p> + * + * @return string representation of {@link MultiResolutionStreamConfigurationMap} + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder("MultiResolutionStreamConfigurationMap("); + appendConfigurationsString(sb, /*output*/ true); + sb.append(","); + appendConfigurationsString(sb, /*output*/ false); + sb.append(")"); + + return sb.toString(); + } + + + private final Map<String, StreamConfiguration[]> mConfigurations; + + /** Format -> list of MultiResolutionStreamInfo used to create MultiResolutionImageReader */ + private final Map<Integer, List<MultiResolutionStreamInfo>> mMultiResolutionOutputConfigs + = new HashMap<Integer, List<MultiResolutionStreamInfo>>(); + /** Format -> list of MultiResolutionStreamInfo used for multi-resolution reprocessing */ + private final Map<Integer, List<MultiResolutionStreamInfo>> mMultiResolutionInputConfigs + = new HashMap<Integer, List<MultiResolutionStreamInfo>>(); +} diff --git a/core/java/android/hardware/camera2/params/MultiResolutionStreamInfo.java b/core/java/android/hardware/camera2/params/MultiResolutionStreamInfo.java new file mode 100644 index 000000000000..aa1d1d4aaa18 --- /dev/null +++ b/core/java/android/hardware/camera2/params/MultiResolutionStreamInfo.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2021 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 java.util.Objects; + +/** + * A utility class describing the properties of one stream of fixed-size image buffers + * backing a multi-resolution image stream. + * + * <p>A group of {@link MultiResolutionStreamInfo} are used to describe the properties of a + * multi-resolution image stream for a particular format. The + * {@link android.hardware.camera2.MultiResolutionImageReader} class represents a + * multi-resolution output stream, and is constructed using a group of + * {@link MultiResolutionStreamInfo}. A group of {@link MultiResolutionStreamInfo} can also be used + * to create a multi-resolution reprocessable camera capture session. See + * {@link android.hardware.camera2.params.InputConfiguration} for details.</p> + * + * @see InputConfiguration + * @see android.hardware.camera2.MultiResolutionImageReader + */ +public class MultiResolutionStreamInfo { + private int mStreamWidth; + private int mStreamHeight; + private String mPhysicalCameraId; + + /** + * Create a new {@link MultiResolutionStreamInfo}. + * + * <p>This class creates a {@link MultiResolutionStreamInfo} using image width, image height, + * and the physical camera Id images originate from.</p> + * + * <p>Normally applications do not need to create these directly. Use {@link + * MultiResolutionStreamConfigurationMap#getOutputInfo} or {@link + * MultiResolutionStreamConfigurationMap#getInputInfo} to obtain them for a particular format + * instead.</p> + */ + public MultiResolutionStreamInfo(int streamWidth, int streamHeight, + @NonNull String physicalCameraId) { + mStreamWidth = streamWidth; + mStreamHeight = streamHeight; + mPhysicalCameraId = physicalCameraId; + } + + /** + * The width of this particular image buffer stream in pixels. + */ + public int getWidth() { + return mStreamWidth; + } + + /** + * The height of this particular image buffer stream in pixels. + */ + public int getHeight() { + return mStreamHeight; + } + + /** + * The physical camera Id of this particular image buffer stream. + */ + public @NonNull String getPhysicalCameraId() { + return mPhysicalCameraId; + } + + /** + * Check if this {@link MultiResolutionStreamInfo} is equal to another + * {@link MultiResolutionStreamInfo}. + * + * @return {@code true} if the objects were equal, {@code false} otherwise + */ + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (obj instanceof MultiResolutionStreamInfo) { + final MultiResolutionStreamInfo other = (MultiResolutionStreamInfo) obj; + return mStreamWidth == other.mStreamWidth && + mStreamHeight == other.mStreamHeight && + mPhysicalCameraId.equals(other.mPhysicalCameraId); + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hash( + mStreamWidth, mStreamHeight, mPhysicalCameraId); + } +} diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java index a20a1bf194ea..e31bd601fc03 100644 --- a/core/java/android/hardware/camera2/params/OutputConfiguration.java +++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java @@ -24,9 +24,13 @@ import android.annotation.Nullable; import android.annotation.SystemApi; import android.graphics.ImageFormat; import android.hardware.camera2.CameraCaptureSession; +import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; +import android.hardware.camera2.MultiResolutionImageReader; +import android.hardware.camera2.params.MultiResolutionStreamInfo; import android.hardware.camera2.utils.HashCodeHelpers; import android.hardware.camera2.utils.SurfaceUtils; +import android.media.ImageReader; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; @@ -34,6 +38,7 @@ import android.util.Size; import android.view.Surface; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -81,6 +86,13 @@ import java.util.Objects; * {@link CameraCaptureSession#updateOutputConfiguration} can be called after the configuration * finalize method returns without exceptions.</li> * + * <li>If the camera device supports multi-resolution output streams, {@link + * CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP} will contain the + * formats and their corresponding stream info. The application can use an OutputConfiguration + * created with the multi-resolution stream info queried from {@link + * MultiResolutionStreamConfigurationMap#getOutputInfo} and + * {@link android.hardware.camera2.MultiResolutionImageReader} to capture variable size images. + * * </ul> * * <p> As of {@link android.os.Build.VERSION_CODES#P Android P}, all formats except @@ -88,6 +100,7 @@ import java.util.Objects; * device support. On prior API levels, only {@link ImageFormat#PRIVATE} format may be used.</p> * * @see CameraDevice#createCaptureSessionByOutputConfigurations + * @see CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP * */ public final class OutputConfiguration implements Parcelable { @@ -206,6 +219,33 @@ public final class OutputConfiguration implements Parcelable { } /** + * Set the multi-resolution output flag. + * + * <p>Specify that this OutputConfiguration is part of a multi-resolution output stream group + * used by {@link android.hardware.camera2.MultiResolutionImageReader}.</p> + * + * <p>This function must only be called for an OutputConfiguration with a non-negative + * group ID. And all OutputConfigurations of a MultiResolutionImageReader will have the same + * group ID and have this flag set.</p> + * + * @throws IllegalStateException If surface sharing is enabled via {@link #enableSurfaceSharing} + * call, or no non-negative group ID has been set. + * @hide + */ + void setMultiResolutionOutput() { + if (mIsShared) { + throw new IllegalStateException("Multi-resolution output flag must not be set for " + + "configuration with surface sharing"); + } + if (mSurfaceGroupId == SURFACE_GROUP_ID_NONE) { + throw new IllegalStateException("Multi-resolution output flag should only be set for " + + "surface with non-negative group ID"); + } + + mIsMultiResolution = true; + } + + /** * Create a new {@link OutputConfiguration} instance. * * <p>This constructor takes an argument for desired camera rotation</p> @@ -265,6 +305,45 @@ public final class OutputConfiguration implements Parcelable { mIsDeferredConfig = false; mIsShared = false; mPhysicalCameraId = null; + mIsMultiResolution = false; + } + + /** + * Create a list of {@link OutputConfiguration} instances for the outputs used by a + * {@link android.hardware.camera2.MultiResolutionImageReader}. + * + * <p>This constructor takes an argument for a + * {@link android.hardware.camera2.MultiResolutionImageReader}.</p> + * + * @param multiResolutionImageReader + * The multi-resolution image reader object. + */ + public static @NonNull Collection<OutputConfiguration> createInstancesForMultiResolutionOutput( + @NonNull MultiResolutionImageReader multiResolutionImageReader) { + checkNotNull(multiResolutionImageReader, "Multi-resolution image reader must not be null"); + + int groupId = MULTI_RESOLUTION_GROUP_ID_COUNTER; + MULTI_RESOLUTION_GROUP_ID_COUNTER++; + // Skip in case the group id counter overflows to -1, the invalid value. + if (MULTI_RESOLUTION_GROUP_ID_COUNTER == -1) { + MULTI_RESOLUTION_GROUP_ID_COUNTER++; + } + + ImageReader[] imageReaders = multiResolutionImageReader.getReaders(); + ArrayList<OutputConfiguration> configs = new ArrayList<OutputConfiguration>(); + for (int i = 0; i < imageReaders.length; i++) { + MultiResolutionStreamInfo streamInfo = + multiResolutionImageReader.getStreamInfoForImageReader(imageReaders[i]); + + OutputConfiguration config = new OutputConfiguration( + groupId, imageReaders[i].getSurface()); + config.setPhysicalCameraId(streamInfo.getPhysicalCameraId()); + config.setMultiResolutionOutput(); + configs.add(config); + // TODO: Set sensor pixel mode for ultra high resolution sensor camera. + } + + return configs; } /** @@ -319,6 +398,7 @@ public final class OutputConfiguration implements Parcelable { mIsDeferredConfig = true; mIsShared = false; mPhysicalCameraId = null; + mIsMultiResolution = false; } /** @@ -355,8 +435,18 @@ public final class OutputConfiguration implements Parcelable { * <p>Up to {@link #getMaxSharedSurfaceCount} surfaces can be shared for an OutputConfiguration. * The supported surfaces for sharing must be of type SurfaceTexture, SurfaceView, * MediaRecorder, MediaCodec, or implementation defined ImageReader.</p> + * + * <p>This function must not be called from OuptutConfigurations created by {@link + * #createInstancesForMultiResolutionOutput}.</p> + * + * @throws IllegalStateException If this OutputConfiguration is created via {@link + * #createInstancesForMultiResolutionOutput} to back a MultiResolutionImageReader. */ public void enableSurfaceSharing() { + if (mIsMultiResolution) { + throw new IllegalStateException("Cannot enable surface sharing on " + + "multi-resolution output configurations"); + } mIsShared = true; } @@ -368,8 +458,7 @@ public final class OutputConfiguration implements Parcelable { * This call achieves it by mapping the OutputConfiguration to the physical camera id.</p> * * <p>The valid physical camera ids can be queried by {@link - * android.hardware.camera2.CameraCharacteristics#getPhysicalCameraIds}. - * </p> + * CameraCharacteristics#getPhysicalCameraIds}.</p> * * <p>Passing in a null physicalCameraId means that the OutputConfiguration is for a logical * stream.</p> @@ -380,8 +469,16 @@ public final class OutputConfiguration implements Parcelable { * after {@link CameraDevice#createCaptureSessionByOutputConfigurations} or {@link * CameraDevice#createReprocessableCaptureSessionByConfigurations} has no effect.</p> * - * <p>The surface belonging to a physical camera OutputConfiguration must not be used as input - * or output of a reprocessing request. </p> + * <p>As of {@link android.os.Build.VERSION_CODES#S Android 12}, an image buffer from a + * physical camera stream can be used for reprocessing to logical camera streams and streams + * from the same physical camera if the camera device supports multi-resolution input and output + * streams. See {@link CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP} + * for details. The behaviors of reprocessing from a non-physical camera stream to a physical + * camera stream, and from a physical camera stream to a physical camera stream of different + * physical camera, are device-specific and not guaranteed to be supported.</p> + * + * <p>On prior API levels, the surface belonging to a physical camera OutputConfiguration must + * not be used as input or output of a reprocessing request. </p> */ public void setPhysicalCameraId(@Nullable String physicalCameraId) { mPhysicalCameraId = physicalCameraId; @@ -527,6 +624,7 @@ public final class OutputConfiguration implements Parcelable { this.mIsDeferredConfig = other.mIsDeferredConfig; this.mIsShared = other.mIsShared; this.mPhysicalCameraId = other.mPhysicalCameraId; + this.mIsMultiResolution = other.mIsMultiResolution; } /** @@ -543,6 +641,7 @@ public final class OutputConfiguration implements Parcelable { ArrayList<Surface> surfaces = new ArrayList<Surface>(); source.readTypedList(surfaces, Surface.CREATOR); String physicalCameraId = source.readString(); + boolean isMultiResolutionOutput = source.readInt() == 1; checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant"); @@ -566,6 +665,7 @@ public final class OutputConfiguration implements Parcelable { mConfiguredGenerationId = 0; } mPhysicalCameraId = physicalCameraId; + mIsMultiResolution = isMultiResolutionOutput; } /** @@ -665,6 +765,7 @@ public final class OutputConfiguration implements Parcelable { dest.writeInt(mIsShared ? 1 : 0); dest.writeTypedList(mSurfaces); dest.writeString(mPhysicalCameraId); + dest.writeInt(mIsMultiResolution ? 1 : 0); } /** @@ -694,7 +795,8 @@ public final class OutputConfiguration implements Parcelable { mConfiguredFormat != other.mConfiguredFormat || mConfiguredDataspace != other.mConfiguredDataspace || mConfiguredGenerationId != other.mConfiguredGenerationId || - !Objects.equals(mPhysicalCameraId, other.mPhysicalCameraId)) + !Objects.equals(mPhysicalCameraId, other.mPhysicalCameraId) || + mIsMultiResolution != other.mIsMultiResolution) return false; int minLen = Math.min(mSurfaces.size(), other.mSurfaces.size()); @@ -720,17 +822,24 @@ public final class OutputConfiguration implements Parcelable { return HashCodeHelpers.hashCode( mRotation, mConfiguredSize.hashCode(), mConfiguredFormat, mConfiguredDataspace, mSurfaceGroupId, mSurfaceType, mIsShared ? 1 : 0, - mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode()); + mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode(), + mIsMultiResolution ? 1 : 0); } return HashCodeHelpers.hashCode( mRotation, mSurfaces.hashCode(), mConfiguredGenerationId, mConfiguredSize.hashCode(), mConfiguredFormat, mConfiguredDataspace, mSurfaceGroupId, mIsShared ? 1 : 0, - mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode()); + mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode(), + mIsMultiResolution ? 1 : 0); } private static final String TAG = "OutputConfiguration"; + + // A surfaceGroupId counter used for MultiResolutionImageReader. Its value is + // incremented everytime {@link createInstancesForMultiResolutionOutput} is called. + private static int MULTI_RESOLUTION_GROUP_ID_COUNTER = 0; + private ArrayList<Surface> mSurfaces; private final int mRotation; private final int mSurfaceGroupId; @@ -749,4 +858,7 @@ public final class OutputConfiguration implements Parcelable { private boolean mIsShared; // The physical camera id that this output configuration is for. private String mPhysicalCameraId; + // Flag indicating if this config is for a multi-resolution output with a + // MultiResolutionImageReader + private boolean mIsMultiResolution; } diff --git a/core/java/android/hardware/camera2/params/SessionConfiguration.java b/core/java/android/hardware/camera2/params/SessionConfiguration.java index 8fc919f142a2..ea6b92d4f33e 100644 --- a/core/java/android/hardware/camera2/params/SessionConfiguration.java +++ b/core/java/android/hardware/camera2/params/SessionConfiguration.java @@ -130,11 +130,13 @@ public final class SessionConfiguration implements Parcelable { int inputWidth = source.readInt(); int inputHeight = source.readInt(); int inputFormat = source.readInt(); + boolean isInputMultiResolution = source.readBoolean(); ArrayList<OutputConfiguration> outConfigs = new ArrayList<OutputConfiguration>(); source.readTypedList(outConfigs, OutputConfiguration.CREATOR); if ((inputWidth > 0) && (inputHeight > 0) && (inputFormat != -1)) { - mInputConfig = new InputConfiguration(inputWidth, inputHeight, inputFormat); + mInputConfig = new InputConfiguration(inputWidth, inputHeight, + inputFormat, isInputMultiResolution); } mSessionType = sessionType; mOutputConfigurations = outConfigs; @@ -169,10 +171,12 @@ public final class SessionConfiguration implements Parcelable { dest.writeInt(mInputConfig.getWidth()); dest.writeInt(mInputConfig.getHeight()); dest.writeInt(mInputConfig.getFormat()); + dest.writeBoolean(mInputConfig.isMultiResolution()); } else { dest.writeInt(/*inputWidth*/ 0); dest.writeInt(/*inputHeight*/ 0); dest.writeInt(/*inputFormat*/ -1); + dest.writeBoolean(/*isMultiResolution*/ false); } dest.writeTypedList(mOutputConfigurations); } diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java index 10a814acd70b..a25ae6041d77 100644 --- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java +++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java @@ -1575,7 +1575,7 @@ public final class StreamConfigurationMap { return sizes; } - /** Get the list of publically visible output formats; does not include IMPL_DEFINED */ + /** Get the list of publicly visible output formats */ private int[] getPublicFormats(boolean output) { int[] formats = new int[getPublicFormatCount(output)]; @@ -1746,6 +1746,21 @@ public final class StreamConfigurationMap { return sb.toString(); } + /** + * Size comparison method used by size comparators. + * + * @hide + */ + public static int compareSizes(int widthA, int heightA, int widthB, int heightB) { + long left = widthA * (long) heightA; + long right = widthB * (long) heightB; + if (left == right) { + left = widthA; + right = widthB; + } + return (left < right) ? -1 : (left > right ? 1 : 0); + } + private void appendOutputsString(StringBuilder sb) { sb.append("Outputs("); int[] formats = getOutputFormats(); @@ -1843,7 +1858,10 @@ public final class StreamConfigurationMap { sb.append(")"); } - private String formatToString(int format) { + /** + * @hide + */ + public static String formatToString(int format) { switch (format) { case ImageFormat.YV12: return "YV12"; diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java index f5b204a4a908..5656dffe1d4a 100644 --- a/media/java/android/media/ImageReader.java +++ b/media/java/android/media/ImageReader.java @@ -16,6 +16,7 @@ package android.media; +import android.annotation.CallbackExecutor; import android.annotation.IntRange; import android.annotation.NonNull; import android.graphics.GraphicBuffer; @@ -24,6 +25,7 @@ import android.graphics.ImageFormat.Format; import android.graphics.Rect; import android.hardware.HardwareBuffer; import android.hardware.HardwareBuffer.Usage; +import android.hardware.camera2.MultiResolutionImageReader; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -36,7 +38,9 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.NioUtils; import java.util.List; +import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -134,7 +138,8 @@ public class ImageReader implements AutoCloseable { // If the format is private don't default to USAGE_CPU_READ_OFTEN since it may not // work, and is inscrutable anyway return new ImageReader(width, height, format, maxImages, - format == ImageFormat.PRIVATE ? 0 : HardwareBuffer.USAGE_CPU_READ_OFTEN); + format == ImageFormat.PRIVATE ? 0 : HardwareBuffer.USAGE_CPU_READ_OFTEN, + /*parent*/ null); } /** @@ -250,18 +255,37 @@ public class ImageReader implements AutoCloseable { // throw new IllegalArgumentException("The given format=" + Integer.toHexString(format) // + " & usage=" + Long.toHexString(usage) + " is not supported"); // } - return new ImageReader(width, height, format, maxImages, usage); + return new ImageReader(width, height, format, maxImages, usage, /*parent*/ null); } + /** + * @hide + */ + public static @NonNull ImageReader newInstance( + @IntRange(from = 1) int width, + @IntRange(from = 1) int height, + @Format int format, + @IntRange(from = 1) int maxImages, + @NonNull MultiResolutionImageReader parent) { + // If the format is private don't default to USAGE_CPU_READ_OFTEN since it may not + // work, and is inscrutable anyway + return new ImageReader(width, height, format, maxImages, + format == ImageFormat.PRIVATE ? 0 : HardwareBuffer.USAGE_CPU_READ_OFTEN, + parent); + } + + /** * @hide */ - protected ImageReader(int width, int height, int format, int maxImages, long usage) { + protected ImageReader(int width, int height, int format, int maxImages, long usage, + MultiResolutionImageReader parent) { mWidth = width; mHeight = height; mFormat = format; mUsage = usage; mMaxImages = maxImages; + mParent = parent; if (width < 1 || height < 1) { throw new IllegalArgumentException( @@ -433,6 +457,9 @@ public class ImageReader implements AutoCloseable { if (image != null) { image.close(); } + if (mParent != null) { + mParent.flushOther(this); + } } } @@ -584,12 +611,38 @@ public class ImageReader implements AutoCloseable { } if (mListenerHandler == null || mListenerHandler.getLooper() != looper) { mListenerHandler = new ListenerHandler(looper); + mListenerExecutor = new HandlerExecutor(mListenerHandler); } - mListener = listener; } else { - mListener = null; mListenerHandler = null; + mListenerExecutor = null; } + mListener = listener; + } + } + + /** + * Register a listener to be invoked when a new image becomes available + * from the ImageReader. + * + * @param listener + * The listener that will be run. + * @param executor + * The executor which will be used to invoke the listener. + * @throws IllegalArgumentException + * If no handler specified and the calling thread has no looper. + * + * @hide + */ + public void setOnImageAvailableListenerWithExecutor(@NonNull OnImageAvailableListener listener, + @NonNull Executor executor) { + if (executor == null) { + throw new IllegalArgumentException("executor must not be null"); + } + + synchronized (mListenerLock) { + mListenerExecutor = executor; + mListener = listener; } } @@ -763,12 +816,27 @@ public class ImageReader implements AutoCloseable { return; } - final Handler handler; + final Executor executor; + final OnImageAvailableListener listener; synchronized (ir.mListenerLock) { - handler = ir.mListenerHandler; + executor = ir.mListenerExecutor; + listener = ir.mListener; } - if (handler != null) { - handler.sendEmptyMessage(0); + final boolean isReaderValid; + synchronized (ir.mCloseLock) { + isReaderValid = ir.mIsReaderValid; + } + + // It's dangerous to fire onImageAvailable() callback when the ImageReader + // is being closed, as application could acquire next image in the + // onImageAvailable() callback. + if (executor != null && listener != null && isReaderValid) { + executor.execute(new Runnable() { + @Override + public void run() { + listener.onImageAvailable(ir); + } + }); } } @@ -785,11 +853,16 @@ public class ImageReader implements AutoCloseable { private final Object mCloseLock = new Object(); private boolean mIsReaderValid = false; private OnImageAvailableListener mListener; + private Executor mListenerExecutor; private ListenerHandler mListenerHandler; // Keep track of the successfully acquired Images. This need to be thread safe as the images // could be closed by different threads (e.g., application thread and GC thread). private List<Image> mAcquiredImages = new CopyOnWriteArrayList<>(); + // Applicable if this isn't a standalone ImageReader, but belongs to a + // MultiResolutionImageReader. + private final MultiResolutionImageReader mParent; + /** * This field is used by native code, do not access or modify. */ @@ -802,23 +875,22 @@ public class ImageReader implements AutoCloseable { public ListenerHandler(Looper looper) { super(looper, null, true /*async*/); } + } - @Override - public void handleMessage(Message msg) { - OnImageAvailableListener listener; - synchronized (mListenerLock) { - listener = mListener; - } + /** + * An adapter {@link Executor} that posts all executed tasks onto the + * given {@link Handler}. + **/ + private final class HandlerExecutor implements Executor { + private final Handler mHandler; + + public HandlerExecutor(@NonNull Handler handler) { + mHandler = Objects.requireNonNull(handler); + } - // It's dangerous to fire onImageAvailable() callback when the ImageReader is being - // closed, as application could acquire next image in the onImageAvailable() callback. - boolean isReaderValid = false; - synchronized (mCloseLock) { - isReaderValid = mIsReaderValid; - } - if (listener != null && isReaderValid) { - listener.onImageAvailable(ImageReader.this); - } + @Override + public void execute(Runnable command) { + mHandler.post(command); } } |