1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package android.hardware.camera2.params;
17 
18 import android.annotation.NonNull;
19 import android.annotation.Nullable;
20 
21 import android.graphics.ImageFormat;
22 import android.graphics.ImageFormat.Format;
23 import android.graphics.PixelFormat;
24 import android.hardware.camera2.params.MultiResolutionStreamInfo;
25 import android.hardware.camera2.params.StreamConfigurationMap;
26 import android.hardware.camera2.utils.HashCodeHelpers;
27 
28 import android.util.Size;
29 
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.Collection;
33 import java.util.Collections;
34 import java.util.Comparator;
35 import java.util.HashMap;
36 import java.util.Map;
37 import java.util.List;
38 import java.util.Set;
39 
40 import static com.android.internal.util.Preconditions.*;
41 
42 /**
43  * Immutable class to store the information of the multi-resolution streams supported by
44  * the camera device.
45  *
46  * <p>For a {@link
47  * android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA
48  * logical multi-camera} or an ultra high resolution sensor camera, the maximum resolution of images
49  * produced by the camera device may be variable. For example, for a logical multi-camera, depending
50  * on factors such as current zoom ratio, the camera device may be backed by different physical
51  * cameras. If the physical cameras are of different resolutions, the application may intend to
52  * consume the variable full resolution images from the physical cameras. For an ultra high
53  * resolution sensor camera, the same use case exists where depending on lighting conditions, the
54  * camera device may deem it better to run in default mode and maximum resolution mode.
55  * </p>
56  *
57  * <p>For the use cases described above, multi-resolution output streams can be used by
58  * {@link android.hardware.camera2.MultiResolutionImageReader} to allow the
59  * camera device to output variable size maximum-resolution images.</p>
60  *
61  * <p>Similarly, multi-resolution input streams can be used for reprocessing of variable size
62  * images. In order to reprocess input images of different sizes, the {@link InputConfiguration}
63  * used for creating reprocessable session can be initialized using the group of input stream
64  * configurations returned by {@link #getInputInfo}.</p>
65  */
66 public final class MultiResolutionStreamConfigurationMap {
67     /**
68      * Create a new {@link MultiResolutionStreamConfigurationMap}.
69      *
70      * @param configurations a non-{@code null} array of multi-resolution stream
71      *        configurations supported by this camera device
72      * @hide
73      */
MultiResolutionStreamConfigurationMap( @onNull Map<String, StreamConfiguration[]> configurations)74     public MultiResolutionStreamConfigurationMap(
75             @NonNull Map<String, StreamConfiguration[]> configurations) {
76         checkNotNull(configurations, "multi-resolution configurations must not be null");
77         if (configurations.size() == 0) {
78             throw new IllegalArgumentException("multi-resolution configurations must not be empty");
79         }
80 
81         mConfigurations = configurations;
82 
83         // For each multi-resolution stream configuration, track how many formats and sizes there
84         // are available to configure
85         for (Map.Entry<String, StreamConfiguration[]> entry :
86                 mConfigurations.entrySet()) {
87             String cameraId = entry.getKey();
88             StreamConfiguration[] configs = entry.getValue();
89 
90             for (int i = 0; i < configs.length; i++) {
91                 StreamConfiguration config = configs[i];
92                 int format = config.getFormat();
93 
94                 MultiResolutionStreamInfo multiResolutionStreamInfo = new MultiResolutionStreamInfo(
95                         config.getWidth(), config.getHeight(), cameraId);
96                 Map<Integer, List<MultiResolutionStreamInfo>> destMap;
97                 if (config.isInput()) {
98                     destMap = mMultiResolutionInputConfigs;
99                 } else {
100                     destMap = mMultiResolutionOutputConfigs;
101                 }
102 
103                 if (!destMap.containsKey(format)) {
104                     List<MultiResolutionStreamInfo> multiResolutionStreamInfoList =
105                             new ArrayList<MultiResolutionStreamInfo>();
106                     destMap.put(format, multiResolutionStreamInfoList);
107                 }
108                 destMap.get(format).add(multiResolutionStreamInfo);
109             }
110         }
111     }
112 
113     /**
114      * Size comparator that compares the number of pixels two MultiResolutionStreamInfo size covers.
115      *
116      * <p>If two the areas of two sizes are same, compare the widths.</p>
117      *
118      * @hide
119      */
120     public static class SizeComparator implements Comparator<MultiResolutionStreamInfo> {
121         @Override
compare(@onNull MultiResolutionStreamInfo lhs, @NonNull MultiResolutionStreamInfo rhs)122         public int compare(@NonNull MultiResolutionStreamInfo lhs,
123                 @NonNull MultiResolutionStreamInfo rhs) {
124             return StreamConfigurationMap.compareSizes(
125                     lhs.getWidth(), lhs.getHeight(), rhs.getWidth(), rhs.getHeight());
126         }
127     }
128 
129     /**
130      * Get the output formats in this multi-resolution stream configuration.
131      *
132      * <p>A logical multi-camera or an ultra high resolution sensor camera may support
133      * {@link android.hardware.camera2.MultiResolutionImageReader} to dynamically output maximum
134      * resolutions of different sizes (when switching between physical cameras, or between different
135      * modes of an ultra high resolution sensor camera). This function returns the formats
136      * supported for such case.</p>
137      *
138      * <p>All image formats returned by this function will be defined in either {@link ImageFormat}
139      * or in {@link PixelFormat} (and there is no possibility of collision).</p>
140      *
141      * @return an array of integer format, or empty array if multi-resolution output is not
142      *         supported
143      *
144      * @see ImageFormat
145      * @see PixelFormat
146      * @see android.hardware.camera2.MultiResolutionImageReader
147      */
getOutputFormats()148     public @NonNull @Format int[] getOutputFormats() {
149         return getPublicImageFormats(/*output*/true);
150     }
151 
152     /**
153      * Get the input formats in this multi-resolution stream configuration.
154      *
155      * <p>A logical multi-camera or ultra high resolution sensor camera may support reprocessing
156      * images of different resolutions when switching between physical cameras, or between
157      * different modes of the ultra high resolution sensor camera. This function returns the
158      * formats supported for such case.</p>
159      *
160      * <p>The supported output format for an input format can be queried by calling the camera
161      * device's {@link StreamConfigurationMap#getValidOutputFormatsForInput}.</p>
162      *
163      * <p>All image formats returned by this function will be defined in either {@link ImageFormat}
164      * or in {@link PixelFormat} (and there is no possibility of collision).</p>
165      *
166      * @return an array of integer format, or empty array if no multi-resolution reprocessing is
167      *         supported
168      *
169      * @see ImageFormat
170      * @see PixelFormat
171      */
getInputFormats()172     public @NonNull @Format int[] getInputFormats() {
173         return getPublicImageFormats(/*output*/false);
174     }
175 
176     // Get the list of publicly visible multi-resolution input/output stream formats
getPublicImageFormats(boolean output)177     private int[] getPublicImageFormats(boolean output) {
178         Map<Integer, List<MultiResolutionStreamInfo>> multiResolutionConfigs =
179                 output ? mMultiResolutionOutputConfigs : mMultiResolutionInputConfigs;
180         int formatCount = multiResolutionConfigs.size();
181 
182         int[] formats = new int[formatCount];
183         int i = 0;
184         for (Integer format : multiResolutionConfigs.keySet()) {
185             formats[i++] = StreamConfigurationMap.imageFormatToPublic(format);
186         }
187 
188         return formats;
189     }
190 
191     /**
192      * Get a group of {@code MultiResolutionStreamInfo} with the requested output image
193      * {@code format}
194      *
195      * <p>The {@code format} should be a supported format (one of the formats returned by
196      * {@link #getOutputFormats}).</p>
197      *
198      * @param format an image format from {@link ImageFormat} or {@link PixelFormat}
199      * @return
200      *          a group of supported {@link MultiResolutionStreamInfo}. If the {@code format} is not
201      *          a supported multi-resolution output, an empty group is returned.
202      *
203      * @see ImageFormat
204      * @see PixelFormat
205      * @see #getOutputFormats
206      */
getOutputInfo(@ormat int format)207     public @NonNull Collection<MultiResolutionStreamInfo> getOutputInfo(@Format int format) {
208         return getInfo(format, /*false*/ true);
209     }
210 
211     /**
212      * Get a group of {@code MultiResolutionStreamInfo} with the requested input image {@code format}
213      *
214      * <p>The {@code format} should be a supported format (one of the formats returned by
215      * {@link #getInputFormats}).</p>
216      *
217      * @param format an image format from {@link ImageFormat} or {@link PixelFormat}
218      * @return
219      *          a group of supported {@link MultiResolutionStreamInfo}. If the {@code format} is not
220      *          a supported multi-resolution input, an empty group is returned.
221      *
222      * @see ImageFormat
223      * @see PixelFormat
224      * @see #getInputFormats
225      */
getInputInfo(@ormat int format)226     public @NonNull Collection<MultiResolutionStreamInfo> getInputInfo(@Format int format) {
227         return getInfo(format, /*false*/ false);
228     }
229 
230     // Get multi-resolution stream info for a particular format
getInfo(int format, boolean output)231     private @NonNull Collection<MultiResolutionStreamInfo> getInfo(int format, boolean output) {
232         int internalFormat = StreamConfigurationMap.imageFormatToInternal(format);
233         Map<Integer, List<MultiResolutionStreamInfo>> multiResolutionConfigs =
234                 output ? mMultiResolutionOutputConfigs : mMultiResolutionInputConfigs;
235         if (multiResolutionConfigs.containsKey(internalFormat)) {
236             return Collections.unmodifiableCollection(multiResolutionConfigs.get(internalFormat));
237         } else {
238             return Collections.emptyList();
239         }
240     }
241 
appendConfigurationsString(StringBuilder sb, boolean output)242     private void appendConfigurationsString(StringBuilder sb, boolean output) {
243         sb.append(output ? "Outputs(" : "Inputs(");
244         int[] formats = getPublicImageFormats(output);
245         if (formats != null) {
246             for (int format : formats) {
247                 Collection<MultiResolutionStreamInfo> streamInfoList =
248                         getInfo(format, output);
249                 sb.append("[" + StreamConfigurationMap.formatToString(format) + ":");
250                 for (MultiResolutionStreamInfo streamInfo : streamInfoList) {
251                     sb.append(String.format("[w:%d, h:%d, id:%s], ",
252                             streamInfo.getWidth(), streamInfo.getHeight(),
253                             streamInfo.getPhysicalCameraId()));
254                 }
255                 // Remove the pending ", "
256                 if (sb.charAt(sb.length() - 1) == ' ') {
257                     sb.delete(sb.length() - 2, sb.length());
258                 }
259                 sb.append("]");
260             }
261         }
262         sb.append(")");
263     }
264 
265     /**
266      * Check if this {@link MultiResolutionStreamConfigurationMap} is equal to another
267      * {@link MultiResolutionStreamConfigurationMap}.
268      *
269      * @return {@code true} if the objects were equal, {@code false} otherwise
270      */
271     @Override
equals(final Object obj)272     public boolean equals(final Object obj) {
273         if (obj == null) {
274             return false;
275         }
276         if (this == obj) {
277             return true;
278         }
279         if (obj instanceof MultiResolutionStreamConfigurationMap) {
280             final MultiResolutionStreamConfigurationMap other =
281                     (MultiResolutionStreamConfigurationMap) obj;
282             if (!mConfigurations.keySet().equals(other.mConfigurations.keySet())) {
283                 return false;
284             }
285 
286             for (String id : mConfigurations.keySet()) {
287                 if (!Arrays.equals(mConfigurations.get(id), other.mConfigurations.get(id))) {
288                     return false;
289                 }
290             }
291 
292             return true;
293         }
294         return false;
295     }
296 
297     /**
298      * {@inheritDoc}
299      */
300     @Override
hashCode()301     public int hashCode() {
302         return HashCodeHelpers.hashCodeGeneric(
303                 mConfigurations, mMultiResolutionOutputConfigs, mMultiResolutionInputConfigs);
304     }
305 
306     /**
307      * Return this {@link MultiResolutionStreamConfigurationMap} as a string representation.
308      *
309      * <p>{@code "MultiResolutionStreamConfigurationMap(Outputs([format1: [w:%d, h:%d, id:%s], ...
310      * ... [w:%d, h:%d, id:%s]), [format2: [w:%d, h:%d, id:%s], ... [w:%d, h:%d, id:%s]], ...),
311      * Inputs([format1: [w:%d, h:%d, id:%s], ... [w:%d, h:%d, id:%s], ...).</p>
312      *
313      * @return string representation of {@link MultiResolutionStreamConfigurationMap}
314      */
315     @Override
toString()316     public String toString() {
317         StringBuilder sb = new StringBuilder("MultiResolutionStreamConfigurationMap(");
318         appendConfigurationsString(sb, /*output*/ true);
319         sb.append(",");
320         appendConfigurationsString(sb, /*output*/ false);
321         sb.append(")");
322 
323         return sb.toString();
324     }
325 
326 
327     private final Map<String, StreamConfiguration[]> mConfigurations;
328 
329     /** Format -> list of MultiResolutionStreamInfo used to create MultiResolutionImageReader */
330     private final Map<Integer, List<MultiResolutionStreamInfo>> mMultiResolutionOutputConfigs
331             = new HashMap<Integer, List<MultiResolutionStreamInfo>>();
332     /** Format -> list of MultiResolutionStreamInfo used for multi-resolution reprocessing */
333     private final Map<Integer, List<MultiResolutionStreamInfo>> mMultiResolutionInputConfigs
334             = new HashMap<Integer, List<MultiResolutionStreamInfo>>();
335 }
336