1 /*
2  * Copyright (C) 2018 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 
17 package android.hardware.camera2.params;
18 
19 import static android.hardware.camera2.params.StreamConfigurationMap.checkArgumentFormat;
20 
21 import static com.android.internal.util.Preconditions.*;
22 
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.graphics.ImageFormat;
26 import android.graphics.ImageFormat.Format;
27 import android.hardware.camera2.CameraCharacteristics;
28 import android.hardware.camera2.CameraDevice;
29 import android.hardware.camera2.CameraManager;
30 import android.hardware.camera2.CameraMetadata;
31 import android.hardware.camera2.params.StreamConfigurationMap;
32 import android.hardware.camera2.utils.HashCodeHelpers;
33 import android.media.CamcorderProfile;
34 import android.util.Log;
35 import android.util.Pair;
36 import android.util.Size;
37 
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 import java.util.Collections;
41 import java.util.Comparator;
42 import java.util.HashMap;
43 import java.util.List;
44 
45 /**
46  * Immutable class to store the available mandatory stream combination.
47  *
48  * <p>A mandatory stream combination refers to a specific entry in the documented sets of
49  * required stream {@link CameraDevice#createCaptureSession combinations}.
50  * These combinations of streams are required to be supported by the camera device.
51  *
52  * <p>The list of stream combinations is available by invoking
53  * {@link CameraCharacteristics#get} and passing key
54  * {@link android.hardware.camera2.CameraCharacteristics#SCALER_MANDATORY_STREAM_COMBINATIONS}.</p>
55  */
56 public final class MandatoryStreamCombination {
57     private static final String TAG = "MandatoryStreamCombination";
58     /**
59      * Immutable class to store available mandatory stream information.
60      */
61     public static final class MandatoryStreamInformation {
62         private final int mFormat;
63         private final ArrayList<Size> mAvailableSizes = new ArrayList<Size> ();
64         private final boolean mIsInput;
65 
66         /**
67          * Create a new {@link MandatoryStreamInformation}.
68          *
69            @param availableSizes List of possible stream sizes.
70          * @param format Image format.
71          *
72          * @throws IllegalArgumentException
73          *              if sizes is empty or if the format was not user-defined in
74          *              ImageFormat/PixelFormat.
75          * @hide
76          */
MandatoryStreamInformation(@onNull List<Size> availableSizes, int format)77         public MandatoryStreamInformation(@NonNull List<Size> availableSizes, int format) {
78             this(availableSizes, format, /*isInput*/false);
79         }
80 
81         /**
82          * Create a new {@link MandatoryStreamInformation}.
83          *
84            @param availableSizes List of possible stream sizes.
85          * @param format Image format.
86          * @param isInput Flag indicating whether this stream is input.
87          *
88          * @throws IllegalArgumentException
89          *              if sizes is empty or if the format was not user-defined in
90          *              ImageFormat/PixelFormat.
91          * @hide
92          */
MandatoryStreamInformation(@onNull List<Size> availableSizes, @Format int format, boolean isInput)93         public MandatoryStreamInformation(@NonNull List<Size> availableSizes, @Format int format,
94                 boolean isInput) {
95             if (availableSizes.isEmpty()) {
96                 throw new IllegalArgumentException("No available sizes");
97             }
98             mAvailableSizes.addAll(availableSizes);
99             mFormat = checkArgumentFormat(format);
100             mIsInput = isInput;
101         }
102 
103         /**
104          * Confirms whether or not this is an input stream.
105          * @return true in case the stream is input, false otherwise.
106          */
isInput()107         public boolean isInput() {
108             return mIsInput;
109         }
110 
111         /**
112          * Return the list of available sizes for this mandatory stream.
113          *
114          * <p>Per documented {@link CameraDevice#createCaptureSession guideline} the largest
115          * resolution in the result will be tested and guaranteed to work. If clients want to use
116          * smaller sizes, then the resulting
117          * {@link android.hardware.camera2.params.SessionConfiguration session configuration} can
118          * be tested either by calling {@link CameraDevice#createCaptureSession} or
119          * {@link CameraDevice#isSessionConfigurationSupported}.
120          *
121          * @return non-modifiable ascending list of available sizes.
122          */
getAvailableSizes()123         public @NonNull List<Size> getAvailableSizes() {
124             return Collections.unmodifiableList(mAvailableSizes);
125         }
126 
127         /**
128          * Retrieve the mandatory stream {@code format}.
129          *
130          * @return integer format.
131          */
getFormat()132         public @Format int getFormat() {
133             return mFormat;
134         }
135 
136         /**
137          * Check if this {@link MandatoryStreamInformation} is equal to another
138          * {@link MandatoryStreamInformation}.
139          *
140          * <p>Two vectors are only equal if and only if each of the respective elements is
141          * equal.</p>
142          *
143          * @return {@code true} if the objects were equal, {@code false} otherwise
144          */
145         @Override
equals(final Object obj)146         public boolean equals(final Object obj) {
147             if (obj == null) {
148                 return false;
149             }
150             if (this == obj) {
151                 return true;
152             }
153             if (obj instanceof MandatoryStreamInformation) {
154                 final MandatoryStreamInformation other = (MandatoryStreamInformation) obj;
155                 if ((mFormat != other.mFormat) || (mIsInput != other.mIsInput) ||
156                         (mAvailableSizes.size() != other.mAvailableSizes.size())) {
157                     return false;
158                 }
159 
160                 return mAvailableSizes.equals(other.mAvailableSizes);
161             }
162 
163             return false;
164         }
165 
166         /**
167          * {@inheritDoc}
168          */
169         @Override
hashCode()170         public int hashCode() {
171             return HashCodeHelpers.hashCode(mFormat, Boolean.hashCode(mIsInput),
172                     mAvailableSizes.hashCode());
173         }
174     }
175 
176     private final String mDescription;
177     private final boolean mIsReprocessable;
178     private final ArrayList<MandatoryStreamInformation> mStreamsInformation =
179             new ArrayList<MandatoryStreamInformation>();
180     /**
181      * Create a new {@link MandatoryStreamCombination}.
182      *
183      * @param streamsInformation list of available streams in the stream combination.
184      * @param description Summary of the stream combination use case.
185      * @param isReprocessable Flag whether the mandatory stream combination is reprocessable.
186      *
187      * @throws IllegalArgumentException
188      *              if stream information is empty
189      * @hide
190      */
MandatoryStreamCombination(@onNull List<MandatoryStreamInformation> streamsInformation, @NonNull String description, boolean isReprocessable)191     public MandatoryStreamCombination(@NonNull List<MandatoryStreamInformation> streamsInformation,
192             @NonNull String description, boolean isReprocessable) {
193         if (streamsInformation.isEmpty()) {
194             throw new IllegalArgumentException("Empty stream information");
195         }
196         mStreamsInformation.addAll(streamsInformation);
197         mDescription = description;
198         mIsReprocessable = isReprocessable;
199     }
200     /**
201      * Get the mandatory stream combination description.
202      *
203      * @return CharSequence with the mandatory combination description.
204      */
getDescription()205     public @NonNull CharSequence getDescription() {
206         return mDescription;
207     }
208 
209     /**
210      * Indicates whether the mandatory stream combination is reprocessable. Reprocessable is defined
211      * as a stream combination that contains one input stream
212      * ({@link MandatoryStreamInformation#isInput} return true).
213      *
214      * @return {@code true} in case the mandatory stream combination contains an input,
215      *         {@code false} otherwise.
216      */
isReprocessable()217     public boolean isReprocessable() {
218         return mIsReprocessable;
219     }
220 
221     /**
222      * Get information about each stream in the mandatory combination.
223      *
224      * @return Non-modifiable list of stream information.
225      *
226      */
getStreamsInformation()227     public @NonNull List<MandatoryStreamInformation> getStreamsInformation() {
228         return Collections.unmodifiableList(mStreamsInformation);
229     }
230 
231     /**
232      * Check if this {@link MandatoryStreamCombination} is equal to another
233      * {@link MandatoryStreamCombination}.
234      *
235      * <p>Two vectors are only equal if and only if each of the respective elements is equal.</p>
236      *
237      * @return {@code true} if the objects were equal, {@code false} otherwise
238      */
239     @Override
equals(final Object obj)240     public boolean equals(final Object obj) {
241         if (obj == null) {
242             return false;
243         }
244         if (this == obj) {
245             return true;
246         }
247         if (obj instanceof MandatoryStreamCombination) {
248             final MandatoryStreamCombination other = (MandatoryStreamCombination) obj;
249             if ((mDescription != other.mDescription) ||
250                     (mIsReprocessable != other.mIsReprocessable) ||
251                     (mStreamsInformation.size() != other.mStreamsInformation.size())) {
252                 return false;
253             }
254 
255             return mStreamsInformation.equals(other.mStreamsInformation);
256         }
257 
258         return false;
259     }
260 
261     /**
262      * {@inheritDoc}
263      */
264     @Override
hashCode()265     public int hashCode() {
266         return HashCodeHelpers.hashCode(Boolean.hashCode(mIsReprocessable), mDescription.hashCode(),
267                 mStreamsInformation.hashCode());
268     }
269 
270     private static enum SizeThreshold { VGA, PREVIEW, RECORD, MAXIMUM, s720p, s1440p }
271     private static enum ReprocessType { NONE, PRIVATE, YUV }
272     private static final class StreamTemplate {
273         public int mFormat;
274         public SizeThreshold mSizeThreshold;
275         public boolean mIsInput;
StreamTemplate(int format, SizeThreshold sizeThreshold)276         public StreamTemplate(int format, SizeThreshold sizeThreshold) {
277             this(format, sizeThreshold, /*isInput*/false);
278         }
279 
StreamTemplate(@ormat int format, @NonNull SizeThreshold sizeThreshold, boolean isInput)280         public StreamTemplate(@Format int format, @NonNull SizeThreshold sizeThreshold,
281                 boolean isInput) {
282             mFormat = format;
283             mSizeThreshold = sizeThreshold;
284             mIsInput = isInput;
285         }
286     }
287 
288     private static final class StreamCombinationTemplate {
289         public StreamTemplate[] mStreamTemplates;
290         public String mDescription;
291         public ReprocessType mReprocessType;
292 
StreamCombinationTemplate(@onNull StreamTemplate[] streamTemplates, @NonNull String description)293         public StreamCombinationTemplate(@NonNull StreamTemplate[] streamTemplates,
294                 @NonNull String description) {
295             this(streamTemplates, description, /*reprocessType*/ReprocessType.NONE);
296         }
297 
StreamCombinationTemplate(@onNull StreamTemplate[] streamTemplates, @NonNull String description, ReprocessType reprocessType)298         public StreamCombinationTemplate(@NonNull StreamTemplate[] streamTemplates,
299                 @NonNull String description,
300                 ReprocessType reprocessType) {
301             mStreamTemplates = streamTemplates;
302             mReprocessType = reprocessType;
303             mDescription = description;
304         }
305     }
306 
307     private static StreamCombinationTemplate sLegacyCombinations[] = {
308         new StreamCombinationTemplate(new StreamTemplate [] {
309                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.MAXIMUM) },
310                 "Simple preview, GPU video processing, or no-preview video recording"),
311         new StreamCombinationTemplate(new StreamTemplate [] {
312                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
313                 "No-viewfinder still image capture"),
314         new StreamCombinationTemplate(new StreamTemplate [] {
315                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
316                 "In-application video/image processing"),
317         new StreamCombinationTemplate(new StreamTemplate [] {
318                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
319                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
320                 "Standard still imaging"),
321         new StreamCombinationTemplate(new StreamTemplate [] {
322                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
323                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
324                 "In-app processing plus still capture"),
325         new StreamCombinationTemplate(new StreamTemplate [] {
326                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
327                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW) },
328                 "Standard recording"),
329         new StreamCombinationTemplate(new StreamTemplate [] {
330                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
331                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW) },
332                 "Preview plus in-app processing"),
333         new StreamCombinationTemplate(new StreamTemplate [] {
334                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
335                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
336                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
337                 "Still capture plus in-app processing")
338     };
339 
340     private static StreamCombinationTemplate sLimitedCombinations[] = {
341         new StreamCombinationTemplate(new StreamTemplate [] {
342                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
343                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.RECORD)},
344                 "High-resolution video recording with preview"),
345         new StreamCombinationTemplate(new StreamTemplate [] {
346                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
347                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD)},
348                 "High-resolution in-app video processing with preview"),
349         new StreamCombinationTemplate(new StreamTemplate [] {
350                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
351                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD) },
352                 "Two-input in-app video processing"),
353         new StreamCombinationTemplate(new StreamTemplate [] {
354                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
355                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.RECORD),
356                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.RECORD) },
357                 "High-resolution recording with video snapshot"),
358         new StreamCombinationTemplate(new StreamTemplate [] {
359                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
360                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD),
361                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.RECORD) },
362                 "High-resolution in-app processing with video snapshot"),
363         new StreamCombinationTemplate(new StreamTemplate [] {
364                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
365                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
366                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
367                 "Two-input in-app processing with still capture")
368     };
369 
370     private static StreamCombinationTemplate sBurstCombinations[] = {
371         new StreamCombinationTemplate(new StreamTemplate [] {
372                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
373                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.MAXIMUM) },
374                 "Maximum-resolution GPU processing with preview"),
375         new StreamCombinationTemplate(new StreamTemplate [] {
376                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
377                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
378                 "Maximum-resolution in-app processing with preview"),
379         new StreamCombinationTemplate(new StreamTemplate [] {
380                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
381                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
382                 "Maximum-resolution two-input in-app processsing")
383     };
384 
385     private static StreamCombinationTemplate sFullCombinations[] = {
386         new StreamCombinationTemplate(new StreamTemplate [] {
387                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.MAXIMUM),
388                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.MAXIMUM) },
389                 "Maximum-resolution GPU processing with preview"),
390         new StreamCombinationTemplate(new StreamTemplate [] {
391                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.MAXIMUM),
392                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
393                 "Maximum-resolution in-app processing with preview"),
394         new StreamCombinationTemplate(new StreamTemplate [] {
395                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM),
396                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
397                 "Maximum-resolution two-input in-app processsing"),
398         new StreamCombinationTemplate(new StreamTemplate [] {
399                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
400                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
401                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
402                 "Video recording with maximum-size video snapshot"),
403         new StreamCombinationTemplate(new StreamTemplate [] {
404                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.VGA),
405                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
406                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
407                 "Standard video recording plus maximum-resolution in-app processing"),
408         new StreamCombinationTemplate(new StreamTemplate [] {
409                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.VGA),
410                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
411                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
412                 "Preview plus two-input maximum-resolution in-app processing")
413     };
414 
415     private static StreamCombinationTemplate sRawCombinations[] = {
416         new StreamCombinationTemplate(new StreamTemplate [] {
417                 new StreamTemplate(ImageFormat.RAW_SENSOR,  SizeThreshold.MAXIMUM) },
418                 "No-preview DNG capture"),
419         new StreamCombinationTemplate(new StreamTemplate [] {
420                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
421                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
422                 "Standard DNG capture"),
423         new StreamCombinationTemplate(new StreamTemplate [] {
424                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
425                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
426                 "In-app processing plus DNG capture"),
427         new StreamCombinationTemplate(new StreamTemplate [] {
428                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
429                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
430                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
431                 "Video recording with DNG capture"),
432         new StreamCombinationTemplate(new StreamTemplate [] {
433                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
434                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
435                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
436                 "Preview with in-app processing and DNG capture"),
437         new StreamCombinationTemplate(new StreamTemplate [] {
438                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
439                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
440                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
441                 "Two-input in-app processing plus DNG capture"),
442         new StreamCombinationTemplate(new StreamTemplate [] {
443                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
444                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM),
445                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
446                 "Still capture with simultaneous JPEG and DNG"),
447         new StreamCombinationTemplate(new StreamTemplate [] {
448                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
449                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM),
450                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
451                 "In-app processing with simultaneous JPEG and DNG")
452     };
453 
454     private static StreamCombinationTemplate sLevel3Combinations[] = {
455         new StreamCombinationTemplate(new StreamTemplate [] {
456                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
457                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.VGA),
458                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM),
459                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
460                 "In-app viewfinder analysis with dynamic selection of output format"),
461         new StreamCombinationTemplate(new StreamTemplate [] {
462                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
463                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.VGA),
464                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM),
465                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
466                 "In-app viewfinder analysis with dynamic selection of output format")
467     };
468 
469     private static StreamCombinationTemplate sLimitedPrivateReprocCombinations[] = {
470         new StreamCombinationTemplate(new StreamTemplate [] {
471                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
472                 "No-viewfinder still image reprocessing",
473                 /*reprocessType*/ ReprocessType.PRIVATE),
474         new StreamCombinationTemplate(new StreamTemplate [] {
475                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
476                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
477                 "ZSL(Zero-Shutter-Lag) still imaging",
478                 /*reprocessType*/ ReprocessType.PRIVATE),
479         new StreamCombinationTemplate(new StreamTemplate [] {
480                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
481                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
482                 "ZSL still and in-app processing imaging",
483                 /*reprocessType*/ ReprocessType.PRIVATE),
484         new StreamCombinationTemplate(new StreamTemplate [] {
485                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
486                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
487                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
488                 "ZSL in-app processing with still capture",
489                 /*reprocessType*/ ReprocessType.PRIVATE),
490     };
491 
492     private static StreamCombinationTemplate sLimitedYUVReprocCombinations[] = {
493         new StreamCombinationTemplate(new StreamTemplate [] {
494                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
495                 "No-viewfinder still image reprocessing",
496                 /*reprocessType*/ ReprocessType.YUV),
497         new StreamCombinationTemplate(new StreamTemplate [] {
498                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
499                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
500                 "ZSL(Zero-Shutter-Lag) still imaging",
501                 /*reprocessType*/ ReprocessType.YUV),
502         new StreamCombinationTemplate(new StreamTemplate [] {
503                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
504                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
505                 "ZSL still and in-app processing imaging",
506                 /*reprocessType*/ ReprocessType.YUV),
507         new StreamCombinationTemplate(new StreamTemplate [] {
508                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
509                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
510                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
511                 "ZSL in-app processing with still capture",
512                 /*reprocessType*/ ReprocessType.YUV),
513     };
514 
515     private static StreamCombinationTemplate sFullPrivateReprocCombinations[] = {
516         new StreamCombinationTemplate(new StreamTemplate [] {
517                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
518                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD) },
519                 "High-resolution ZSL in-app video processing with regular preview",
520                 /*reprocessType*/ ReprocessType.PRIVATE),
521         new StreamCombinationTemplate(new StreamTemplate [] {
522                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
523                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
524                 "Maximum-resolution ZSL in-app processing with regular preview",
525                 /*reprocessType*/ ReprocessType.PRIVATE),
526         new StreamCombinationTemplate(new StreamTemplate [] {
527                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
528                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
529                 "Maximum-resolution two-input ZSL in-app processing",
530                 /*reprocessType*/ ReprocessType.PRIVATE),
531         new StreamCombinationTemplate(new StreamTemplate [] {
532                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
533                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
534                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
535                 "ZSL still capture and in-app processing",
536                 /*reprocessType*/ ReprocessType.PRIVATE),
537     };
538 
539     private static StreamCombinationTemplate sFullYUVReprocCombinations[] = {
540         new StreamCombinationTemplate(new StreamTemplate [] {
541                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW) },
542                 "Maximum-resolution multi-frame image fusion in-app processing with regular "
543                 + "preview",
544                 /*reprocessType*/ ReprocessType.YUV),
545         new StreamCombinationTemplate(new StreamTemplate [] {
546                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW) },
547                 "Maximum-resolution multi-frame image fusion two-input in-app processing",
548                 /*reprocessType*/ ReprocessType.YUV),
549         new StreamCombinationTemplate(new StreamTemplate [] {
550                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
551                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD) },
552                 "High-resolution ZSL in-app video processing with regular preview",
553                 /*reprocessType*/ ReprocessType.YUV),
554         new StreamCombinationTemplate(new StreamTemplate [] {
555                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
556                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
557                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
558                 "ZSL still capture and in-app processing",
559                 /*reprocessType*/ ReprocessType.YUV),
560     };
561 
562     private static StreamCombinationTemplate sRAWPrivateReprocCombinations[] = {
563         new StreamCombinationTemplate(new StreamTemplate [] {
564                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
565                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
566                 "Mutually exclusive ZSL in-app processing and DNG capture",
567                 /*reprocessType*/ ReprocessType.PRIVATE),
568         new StreamCombinationTemplate(new StreamTemplate [] {
569                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
570                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
571                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
572                 "Mutually exclusive ZSL in-app processing and preview with DNG capture",
573                 /*reprocessType*/ ReprocessType.PRIVATE),
574         new StreamCombinationTemplate(new StreamTemplate [] {
575                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
576                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
577                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
578                 "Mutually exclusive ZSL two-input in-app processing and DNG capture",
579                 /*reprocessType*/ ReprocessType.PRIVATE),
580         new StreamCombinationTemplate(new StreamTemplate [] {
581                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
582                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM),
583                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
584                 "Mutually exclusive ZSL still capture and preview with DNG capture",
585                 /*reprocessType*/ ReprocessType.PRIVATE),
586         new StreamCombinationTemplate(new StreamTemplate [] {
587                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
588                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM),
589                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
590                 "Mutually exclusive ZSL in-app processing with still capture and DNG capture",
591                 /*reprocessType*/ ReprocessType.PRIVATE),
592     };
593 
594     private static StreamCombinationTemplate sRAWYUVReprocCombinations[] = {
595         new StreamCombinationTemplate(new StreamTemplate [] {
596                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
597                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
598                 "Mutually exclusive ZSL in-app processing and DNG capture",
599                 /*reprocessType*/ ReprocessType.YUV),
600         new StreamCombinationTemplate(new StreamTemplate [] {
601                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
602                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
603                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
604                 "Mutually exclusive ZSL in-app processing and preview with DNG capture",
605                 /*reprocessType*/ ReprocessType.YUV),
606         new StreamCombinationTemplate(new StreamTemplate [] {
607                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
608                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
609                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
610                 "Mutually exclusive ZSL two-input in-app processing and DNG capture",
611                 /*reprocessType*/ ReprocessType.YUV),
612         new StreamCombinationTemplate(new StreamTemplate [] {
613                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
614                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM),
615                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
616                 "Mutually exclusive ZSL still capture and preview with DNG capture",
617                 /*reprocessType*/ ReprocessType.YUV),
618         new StreamCombinationTemplate(new StreamTemplate [] {
619                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
620                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM),
621                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
622                 "Mutually exclusive ZSL in-app processing with still capture and DNG capture",
623                 /*reprocessType*/ ReprocessType.YUV),
624     };
625 
626     private static StreamCombinationTemplate sLevel3PrivateReprocCombinations[] = {
627         new StreamCombinationTemplate(new StreamTemplate [] {
628                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
629                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.VGA),
630                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM),
631                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
632                 "In-app viewfinder analysis with ZSL, RAW, and JPEG reprocessing output",
633                 /*reprocessType*/ ReprocessType.PRIVATE),
634     };
635 
636     private static StreamCombinationTemplate sLevel3YUVReprocCombinations[] = {
637         new StreamCombinationTemplate(new StreamTemplate [] {
638                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
639                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.VGA),
640                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
641                 "In-app viewfinder analysis with ZSL and RAW",
642                 /*reprocessType*/ ReprocessType.YUV),
643         new StreamCombinationTemplate(new StreamTemplate [] {
644                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
645                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.VGA),
646                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM),
647                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
648                 "In-app viewfinder analysis with ZSL, RAW, and JPEG reprocessing output",
649                 /*reprocessType*/ ReprocessType.YUV),
650     };
651 
652     private static StreamCombinationTemplate sConcurrentStreamCombinations[] = {
653         new StreamCombinationTemplate(new StreamTemplate [] {
654                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.s1440p) },
655                 "In-app video / image processing"),
656         new StreamCombinationTemplate(new StreamTemplate [] {
657                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.s1440p) },
658                 "preview / preview to GPU"),
659         new StreamCombinationTemplate(new StreamTemplate [] {
660                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.s1440p) },
661                 "No view-finder still image capture"),
662         new StreamCombinationTemplate(new StreamTemplate [] {
663                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.s720p),
664                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.s1440p)},
665                 "Two-input in app video / image processing"),
666         new StreamCombinationTemplate(new StreamTemplate [] {
667                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.s720p),
668                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.s1440p)},
669                 "High resolution video recording with preview"),
670         new StreamCombinationTemplate(new StreamTemplate [] {
671                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.s720p),
672                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.s1440p)},
673                 "In-app video / image processing with preview"),
674         new StreamCombinationTemplate(new StreamTemplate [] {
675                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.s720p),
676                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.s1440p)},
677                 "In-app video / image processing with preview"),
678         new StreamCombinationTemplate(new StreamTemplate [] {
679                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.s720p),
680                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.s1440p)},
681                 "Standard stil image capture"),
682         new StreamCombinationTemplate(new StreamTemplate [] {
683                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.s720p),
684                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.s1440p)},
685                 "Standard still image capture"),
686     };
687 
688     private static StreamCombinationTemplate sConcurrentDepthOnlyStreamCombinations[] = {
689         new StreamCombinationTemplate(new StreamTemplate [] {
690                 new StreamTemplate(ImageFormat.DEPTH16, SizeThreshold.VGA) },
691                 "Depth capture for mesh based object rendering"),
692     };
693 
694     /**
695      * Helper builder class to generate a list of available mandatory stream combinations.
696      * @hide
697      */
698     public static final class Builder {
699         private Size mDisplaySize;
700         private List<Integer> mCapabilities;
701         private int mHwLevel, mCameraId;
702         private StreamConfigurationMap mStreamConfigMap;
703         private boolean mIsHiddenPhysicalCamera;
704 
705         private final Size kPreviewSizeBound = new Size(1920, 1088);
706 
707         /**
708          * Helper class to be used to generate the available mandatory stream combinations.
709          *
710          * @param cameraId Current camera id.
711          * @param hwLevel The camera HW level as reported by android.info.supportedHardwareLevel.
712          * @param displaySize The device display size.
713          * @param capabilities The camera device capabilities.
714          * @param sm The camera device stream configuration map.
715          */
Builder(int cameraId, int hwLevel, @NonNull Size displaySize, @NonNull List<Integer> capabilities, @NonNull StreamConfigurationMap sm)716         public Builder(int cameraId, int hwLevel, @NonNull Size displaySize,
717                 @NonNull List<Integer> capabilities, @NonNull StreamConfigurationMap sm) {
718             mCameraId = cameraId;
719             mDisplaySize = displaySize;
720             mCapabilities = capabilities;
721             mStreamConfigMap = sm;
722             mHwLevel = hwLevel;
723             mIsHiddenPhysicalCamera =
724                     CameraManager.isHiddenPhysicalCamera(Integer.toString(mCameraId));
725         }
726 
727         /**
728           * Retrieve a list of all available mandatory concurrent stream combinations.
729           * This method should only be called for devices which are listed in combinations returned
730           * by CameraManager.getConcurrentCameraIds.
731           *
732           * @return a non-modifiable list of supported mandatory concurrent stream combinations.
733           */
734         public @NonNull List<MandatoryStreamCombination>
getAvailableMandatoryConcurrentStreamCombinations()735                 getAvailableMandatoryConcurrentStreamCombinations() {
736             // Since concurrent streaming support is optional, we mandate these stream
737             // combinations regardless of camera device capabilities.
738 
739             StreamCombinationTemplate []chosenStreamCombinations = sConcurrentStreamCombinations;
740             if (!isColorOutputSupported()) {
741                 Log.v(TAG, "Device is not backward compatible, depth streams are mandatory!");
742                 chosenStreamCombinations = sConcurrentDepthOnlyStreamCombinations;
743             }
744             Size sizeVGAp = new Size(640, 480);
745             Size size720p = new Size(1280, 720);
746             Size size1440p = new Size(1920, 1440);
747 
748             ArrayList<MandatoryStreamCombination> availableConcurrentStreamCombinations =
749                     new ArrayList<MandatoryStreamCombination>();
750             availableConcurrentStreamCombinations.ensureCapacity(
751                     chosenStreamCombinations.length);
752             for (StreamCombinationTemplate combTemplate : chosenStreamCombinations) {
753                 ArrayList<MandatoryStreamInformation> streamsInfo =
754                         new ArrayList<MandatoryStreamInformation>();
755                 streamsInfo.ensureCapacity(combTemplate.mStreamTemplates.length);
756                 for (StreamTemplate template : combTemplate.mStreamTemplates) {
757                     MandatoryStreamInformation streamInfo;
758                     List<Size> sizes = new ArrayList<Size>();
759                     Size formatSize = null;
760                     switch (template.mSizeThreshold) {
761                         case s1440p:
762                             formatSize = size1440p;
763                             break;
764                         case VGA:
765                             formatSize = sizeVGAp;
766                             break;
767                         default:
768                             formatSize = size720p;
769                     }
770                     Size sizeChosen =
771                             getMinSize(formatSize,
772                                     getMaxSize(mStreamConfigMap.getOutputSizes(template.mFormat)));
773                     sizes.add(sizeChosen);
774                     try {
775                         streamInfo = new MandatoryStreamInformation(sizes, template.mFormat);
776                     } catch (IllegalArgumentException e) {
777                         String cause = "No available sizes found for format: " + template.mFormat
778                                 + " size threshold: " + template.mSizeThreshold + " combination: "
779                                 + combTemplate.mDescription;
780                         throw new RuntimeException(cause, e);
781                     }
782                     streamsInfo.add(streamInfo);
783                 }
784 
785                 MandatoryStreamCombination streamCombination;
786                 try {
787                     streamCombination = new MandatoryStreamCombination(streamsInfo,
788                             combTemplate.mDescription, /*isReprocess*/false);
789                 } catch (IllegalArgumentException e) {
790                     String cause =  "No stream information for mandatory combination: "
791                             + combTemplate.mDescription;
792                     throw new RuntimeException(cause, e);
793                 }
794                 availableConcurrentStreamCombinations.add(streamCombination);
795             }
796             return Collections.unmodifiableList(availableConcurrentStreamCombinations);
797         }
798 
799         /**
800          * Retrieve a list of all available mandatory stream combinations.
801          *
802          * @return a non-modifiable list of supported mandatory stream combinations or
803          *         null in case device is not backward compatible or the method encounters
804          *         an error.
805          */
806         public @Nullable List<MandatoryStreamCombination>
getAvailableMandatoryStreamCombinations()807             getAvailableMandatoryStreamCombinations() {
808             if (!isColorOutputSupported()) {
809                 Log.v(TAG, "Device is not backward compatible!");
810                 return null;
811             }
812 
813             if ((mCameraId < 0) && !isExternalCamera()) {
814                 Log.i(TAG, "Invalid camera id");
815                 return null;
816             }
817 
818             ArrayList<StreamCombinationTemplate> availableTemplates =
819                     new ArrayList<StreamCombinationTemplate> ();
820             if (isHardwareLevelAtLeastLegacy()) {
821                 availableTemplates.addAll(Arrays.asList(sLegacyCombinations));
822             }
823 
824             // External devices are identical to limited devices w.r.t. stream combinations.
825             if (isHardwareLevelAtLeastLimited() || isExternalCamera()) {
826                 availableTemplates.addAll(Arrays.asList(sLimitedCombinations));
827 
828                 if (isPrivateReprocessingSupported()) {
829                     availableTemplates.addAll(Arrays.asList(sLimitedPrivateReprocCombinations));
830                 }
831 
832                 if (isYUVReprocessingSupported()) {
833                     availableTemplates.addAll(Arrays.asList(sLimitedYUVReprocCombinations));
834                 }
835 
836             }
837 
838             if (isCapabilitySupported(
839                     CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE)) {
840                 availableTemplates.addAll(Arrays.asList(sBurstCombinations));
841             }
842 
843             if (isHardwareLevelAtLeastFull()) {
844                 availableTemplates.addAll(Arrays.asList(sFullCombinations));
845 
846                 if (isPrivateReprocessingSupported()) {
847                     availableTemplates.addAll(Arrays.asList(sFullPrivateReprocCombinations));
848                 }
849 
850                 if (isYUVReprocessingSupported()) {
851                     availableTemplates.addAll(Arrays.asList(sFullYUVReprocCombinations));
852                 }
853 
854             }
855 
856             if (isCapabilitySupported(
857                     CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
858                 availableTemplates.addAll(Arrays.asList(sRawCombinations));
859 
860                 if (isPrivateReprocessingSupported()) {
861                     availableTemplates.addAll(Arrays.asList(sRAWPrivateReprocCombinations));
862                 }
863 
864                 if (isYUVReprocessingSupported()) {
865                     availableTemplates.addAll(Arrays.asList(sRAWYUVReprocCombinations));
866                 }
867 
868             }
869 
870             if (isHardwareLevelAtLeastLevel3()) {
871                 availableTemplates.addAll(Arrays.asList(sLevel3Combinations));
872 
873                 if (isPrivateReprocessingSupported()) {
874                     availableTemplates.addAll(Arrays.asList(sLevel3PrivateReprocCombinations));
875                 }
876 
877                 if (isYUVReprocessingSupported()) {
878                     availableTemplates.addAll(Arrays.asList(sLevel3YUVReprocCombinations));
879                 }
880 
881             }
882 
883             return generateAvailableCombinations(availableTemplates);
884         }
885 
886         /**
887          * Helper method to generate the available stream combinations given the
888          * list of available combination templates.
889          *
890          * @param availableTemplates a list of templates supported by the camera device.
891          * @return a non-modifiable list of supported mandatory stream combinations or
892          *         null in case of errors.
893          */
generateAvailableCombinations( @onNull ArrayList<StreamCombinationTemplate> availableTemplates)894         private @Nullable List<MandatoryStreamCombination> generateAvailableCombinations(
895                 @NonNull ArrayList<StreamCombinationTemplate> availableTemplates) {
896             if (availableTemplates.isEmpty()) {
897                 Log.e(TAG, "No available stream templates!");
898                 return null;
899             }
900 
901             HashMap<Pair<SizeThreshold, Integer>, List<Size>> availableSizes =
902                 enumerateAvailableSizes();
903             if (availableSizes == null) {
904                 Log.e(TAG, "Available size enumeration failed!");
905                 return null;
906             }
907 
908             // RAW only uses MAXIMUM size threshold
909             Size[] rawSizes = mStreamConfigMap.getOutputSizes(ImageFormat.RAW_SENSOR);
910             ArrayList<Size> availableRawSizes = new ArrayList<Size>();
911             if (rawSizes != null) {
912                 availableRawSizes.ensureCapacity(rawSizes.length);
913                 availableRawSizes.addAll(Arrays.asList(rawSizes));
914             }
915 
916             Size maxPrivateInputSize = new Size(0, 0);
917             if (isPrivateReprocessingSupported()) {
918                 maxPrivateInputSize = getMaxSize(mStreamConfigMap.getInputSizes(
919                             ImageFormat.PRIVATE));
920             }
921 
922             Size maxYUVInputSize = new Size(0, 0);
923             if (isYUVReprocessingSupported()) {
924                 maxYUVInputSize = getMaxSize(mStreamConfigMap.getInputSizes(
925                             ImageFormat.YUV_420_888));
926             }
927 
928             // Generate the available mandatory stream combinations given the supported templates
929             // and size ranges.
930             ArrayList<MandatoryStreamCombination> availableStreamCombinations =
931                     new ArrayList<MandatoryStreamCombination>();
932             availableStreamCombinations.ensureCapacity(availableTemplates.size());
933             for (StreamCombinationTemplate combTemplate : availableTemplates) {
934                 ArrayList<MandatoryStreamInformation> streamsInfo =
935                         new ArrayList<MandatoryStreamInformation>();
936                 streamsInfo.ensureCapacity(combTemplate.mStreamTemplates.length);
937                 boolean isReprocessable = combTemplate.mReprocessType != ReprocessType.NONE;
938                 if (isReprocessable) {
939                     // The first and second streams in a reprocessable combination have the
940                     // same size and format. The first is the input and the second is the output
941                     // used for generating the subsequent input buffers.
942                     ArrayList<Size> inputSize = new ArrayList<Size>();
943                     int format;
944                     if (combTemplate.mReprocessType == ReprocessType.PRIVATE) {
945                         inputSize.add(maxPrivateInputSize);
946                         format = ImageFormat.PRIVATE;
947                     } else {
948                         inputSize.add(maxYUVInputSize);
949                         format = ImageFormat.YUV_420_888;
950                     }
951 
952                     streamsInfo.add(new MandatoryStreamInformation(inputSize, format,
953                                 /*isInput*/true));
954                     streamsInfo.add(new MandatoryStreamInformation(inputSize, format));
955                 }
956 
957                 for (StreamTemplate template : combTemplate.mStreamTemplates) {
958                     List<Size> sizes = null;
959                     if (template.mFormat == ImageFormat.RAW_SENSOR) {
960                         sizes = availableRawSizes;
961                     } else {
962                         Pair<SizeThreshold, Integer> pair;
963                         pair = new Pair<SizeThreshold, Integer>(template.mSizeThreshold,
964                                 new Integer(template.mFormat));
965                         sizes = availableSizes.get(pair);
966                     }
967 
968                     MandatoryStreamInformation streamInfo;
969                     try {
970                         streamInfo = new MandatoryStreamInformation(sizes, template.mFormat);
971                     } catch (IllegalArgumentException e) {
972                         Log.e(TAG, "No available sizes found for format: " + template.mFormat +
973                                 " size threshold: " + template.mSizeThreshold + " combination: " +
974                                 combTemplate.mDescription);
975                         return null;
976                     }
977 
978                     streamsInfo.add(streamInfo);
979                 }
980 
981                 MandatoryStreamCombination streamCombination;
982                 try {
983                     streamCombination = new MandatoryStreamCombination(streamsInfo,
984                             combTemplate.mDescription, isReprocessable);
985                 } catch (IllegalArgumentException e) {
986                     Log.e(TAG, "No stream information for mandatory combination: "
987                             + combTemplate.mDescription);
988                     return null;
989                 }
990 
991                 availableStreamCombinations.add(streamCombination);
992             }
993 
994             return Collections.unmodifiableList(availableStreamCombinations);
995         }
996 
997         /**
998          * Helper method to enumerate all available sizes according to size threshold and format.
999          */
1000         private @Nullable HashMap<Pair<SizeThreshold, Integer>, List<Size>>
enumerateAvailableSizes()1001             enumerateAvailableSizes() {
1002             final int[] formats = {
1003                 ImageFormat.PRIVATE,
1004                 ImageFormat.YUV_420_888,
1005                 ImageFormat.JPEG
1006             };
1007             Size recordingMaxSize = new Size(0, 0);
1008             Size previewMaxSize = new Size(0, 0);
1009             Size vgaSize = new Size(640, 480);
1010             // For external camera, or hidden physical camera, CamcorderProfile may not be
1011             // available, so get maximum recording size using stream configuration map.
1012             if (isExternalCamera() || mIsHiddenPhysicalCamera) {
1013                 recordingMaxSize = getMaxCameraRecordingSize();
1014             } else {
1015                 recordingMaxSize = getMaxRecordingSize();
1016             }
1017             if (recordingMaxSize == null) {
1018                 Log.e(TAG, "Failed to find maximum recording size!");
1019                 return null;
1020             }
1021 
1022             HashMap<Integer, Size[]> allSizes = new HashMap<Integer, Size[]>();
1023             for (int format : formats) {
1024                 Integer intFormat = new Integer(format);
1025                 allSizes.put(intFormat, mStreamConfigMap.getOutputSizes(format));
1026             }
1027 
1028             List<Size> previewSizes = getSizesWithinBound(
1029                     allSizes.get(new Integer(ImageFormat.PRIVATE)), kPreviewSizeBound);
1030             if ((previewSizes == null) || (previewSizes.isEmpty())) {
1031                 Log.e(TAG, "No preview sizes within preview size bound!");
1032                 return null;
1033             }
1034             List<Size> orderedPreviewSizes = getAscendingOrderSizes(previewSizes,
1035                     /*ascending*/false);
1036             previewMaxSize = getMaxPreviewSize(orderedPreviewSizes);
1037 
1038             HashMap<Pair<SizeThreshold, Integer>, List<Size>> availableSizes =
1039                     new HashMap<Pair<SizeThreshold, Integer>, List<Size>>();
1040 
1041             for (int format : formats) {
1042                 Integer intFormat = new Integer(format);
1043                 Size[] sizes = allSizes.get(intFormat);
1044                 Pair<SizeThreshold, Integer> pair = new Pair<SizeThreshold, Integer>(
1045                         SizeThreshold.VGA, intFormat);
1046                 availableSizes.put(pair, getSizesWithinBound(sizes, vgaSize));
1047 
1048                 pair = new Pair<SizeThreshold, Integer>(SizeThreshold.PREVIEW, intFormat);
1049                 availableSizes.put(pair, getSizesWithinBound(sizes, previewMaxSize));
1050 
1051                 pair = new Pair<SizeThreshold, Integer>(SizeThreshold.RECORD, intFormat);
1052                 availableSizes.put(pair, getSizesWithinBound(sizes, recordingMaxSize));
1053 
1054                 pair = new Pair<SizeThreshold, Integer>(SizeThreshold.MAXIMUM, intFormat);
1055                 availableSizes.put(pair, Arrays.asList(sizes));
1056             }
1057 
1058             return availableSizes;
1059         }
1060 
1061         /**
1062          * Compile a list of sizes smaller than or equal to given bound.
1063          * Return an empty list if there is no size smaller than or equal to the bound.
1064          */
getSizesWithinBound(@onNull Size[] sizes, @NonNull Size bound)1065         private static @Nullable List<Size> getSizesWithinBound(@NonNull Size[] sizes,
1066                 @NonNull Size bound) {
1067             ArrayList<Size> ret = new ArrayList<Size>();
1068             for (Size size : sizes) {
1069                 if (size.getWidth() <= bound.getWidth() && size.getHeight() <= bound.getHeight()) {
1070                     ret.add(size);
1071                 }
1072             }
1073 
1074             return ret;
1075         }
1076 
1077         /**
1078          * Return the lower size
1079          */
getMinSize(Size a, Size b)1080         public static @Nullable Size getMinSize(Size a, Size b) {
1081             if (a == null || b == null) {
1082                 throw new IllegalArgumentException("sizes was empty");
1083             }
1084             if (a.getWidth() * a.getHeight() < b.getHeight() * b.getWidth()) {
1085                 return a;
1086             }
1087             return b;
1088         }
1089         /**
1090          * Get the largest size by area.
1091          *
1092          * @param sizes an array of sizes, must have at least 1 element
1093          *
1094          * @return Largest Size
1095          *
1096          * @throws IllegalArgumentException if sizes was null or had 0 elements
1097          */
getMaxSize(@onNull Size... sizes)1098         public static @Nullable Size getMaxSize(@NonNull Size... sizes) {
1099             if (sizes == null || sizes.length == 0) {
1100                 throw new IllegalArgumentException("sizes was empty");
1101             }
1102 
1103             Size sz = sizes[0];
1104             for (Size size : sizes) {
1105                 if (size.getWidth() * size.getHeight() > sz.getWidth() * sz.getHeight()) {
1106                     sz = size;
1107                 }
1108             }
1109 
1110             return sz;
1111         }
1112 
1113         /**
1114          * Whether or not the hardware level reported by android.info.supportedHardwareLevel is
1115          * at least the desired one (but could be higher)
1116          */
isHardwareLevelAtLeast(int level)1117         private boolean isHardwareLevelAtLeast(int level) {
1118             final int[] sortedHwLevels = {
1119                 CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY,
1120                 CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL,
1121                 CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
1122                 CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
1123                 CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3
1124             };
1125             if (level == mHwLevel) {
1126                 return true;
1127             }
1128 
1129             for (int sortedlevel : sortedHwLevels) {
1130                 if (sortedlevel == level) {
1131                     return true;
1132                 } else if (sortedlevel == mHwLevel) {
1133                     return false;
1134                 }
1135             }
1136 
1137             return false;
1138         }
1139 
1140         /**
1141          * Whether or not the camera is an external camera.
1142          *
1143          * @return {@code true} if the device is external, {@code false} otherwise.
1144          */
isExternalCamera()1145         private boolean isExternalCamera() {
1146             return mHwLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL;
1147         }
1148 
1149         /**
1150          * Whether or not the hardware level is at least legacy.
1151          *
1152          * @return {@code true} if the device is {@code LEGACY}, {@code false} otherwise.
1153          */
isHardwareLevelAtLeastLegacy()1154         private boolean isHardwareLevelAtLeastLegacy() {
1155             return isHardwareLevelAtLeast(CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY);
1156         }
1157 
1158         /**
1159          * Whether or not the hardware level is at least limited.
1160          *
1161          * @return {@code true} if the device is {@code LIMITED} or {@code FULL},
1162          *         {@code false} otherwise (i.e. LEGACY).
1163          */
isHardwareLevelAtLeastLimited()1164         private boolean isHardwareLevelAtLeastLimited() {
1165             return isHardwareLevelAtLeast(CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED);
1166         }
1167 
1168         /**
1169          * Whether or not the hardware level is at least full.
1170          *
1171          * @return {@code true} if the device is {@code FULL}, {@code false} otherwise.
1172          */
isHardwareLevelAtLeastFull()1173         private boolean isHardwareLevelAtLeastFull() {
1174             return isHardwareLevelAtLeast(CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_FULL);
1175         }
1176 
1177         /**
1178          * Whether or not the hardware level is at least Level 3.
1179          *
1180          * @return {@code true} if the device is {@code LEVEL3}, {@code false} otherwise.
1181          */
isHardwareLevelAtLeastLevel3()1182         private boolean isHardwareLevelAtLeastLevel3() {
1183             return isHardwareLevelAtLeast(CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_3);
1184         }
1185 
1186         /**
1187          * Determine whether the current device supports a capability or not.
1188          *
1189          * @param capability (non-negative)
1190          *
1191          * @return {@code true} if the capability is supported, {@code false} otherwise.
1192          *
1193          */
isCapabilitySupported(int capability)1194         private boolean isCapabilitySupported(int capability) {
1195             return mCapabilities.contains(capability);
1196         }
1197 
1198         /**
1199          * Check whether the current device is backward compatible.
1200          */
isColorOutputSupported()1201         private boolean isColorOutputSupported() {
1202             return isCapabilitySupported(
1203                     CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE);
1204         }
1205 
1206         /**
1207          * Check whether the current device supports private reprocessing.
1208          */
isPrivateReprocessingSupported()1209         private boolean isPrivateReprocessingSupported() {
1210             return isCapabilitySupported(
1211                     CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING);
1212         }
1213 
1214         /**
1215          * Check whether the current device supports YUV reprocessing.
1216          */
isYUVReprocessingSupported()1217         private boolean isYUVReprocessingSupported() {
1218             return isCapabilitySupported(
1219                     CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING);
1220         }
1221 
1222         /**
1223          * Return the maximum supported video size using the camcorder profile information.
1224          *
1225          * @return Maximum supported video size.
1226          */
getMaxRecordingSize()1227         private @Nullable Size getMaxRecordingSize() {
1228             int quality =
1229                     CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_2160P) ?
1230                         CamcorderProfile.QUALITY_2160P :
1231                     CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_1080P) ?
1232                         CamcorderProfile.QUALITY_1080P :
1233                     CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_720P) ?
1234                         CamcorderProfile.QUALITY_720P :
1235                     CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_480P) ?
1236                         CamcorderProfile.QUALITY_480P :
1237                     CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_QVGA) ?
1238                         CamcorderProfile.QUALITY_QVGA :
1239                     CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_CIF) ?
1240                         CamcorderProfile.QUALITY_CIF :
1241                     CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_QCIF) ?
1242                         CamcorderProfile.QUALITY_QCIF :
1243                         -1;
1244 
1245             if (quality < 0) {
1246                 return null;
1247             }
1248 
1249             CamcorderProfile maxProfile = CamcorderProfile.get(mCameraId, quality);
1250             return new Size(maxProfile.videoFrameWidth, maxProfile.videoFrameHeight);
1251         }
1252 
1253         /**
1254          * Return the maximum supported video size for cameras using data from
1255          * the stream configuration map.
1256          *
1257          * @return Maximum supported video size.
1258          */
getMaxCameraRecordingSize()1259         private @NonNull Size getMaxCameraRecordingSize() {
1260             final Size FULLHD = new Size(1920, 1080);
1261 
1262             Size[] videoSizeArr = mStreamConfigMap.getOutputSizes(
1263                     android.media.MediaRecorder.class);
1264             List<Size> sizes = new ArrayList<Size>();
1265             for (Size sz: videoSizeArr) {
1266                 if (sz.getWidth() <= FULLHD.getWidth() && sz.getHeight() <= FULLHD.getHeight()) {
1267                     sizes.add(sz);
1268                 }
1269             }
1270             List<Size> videoSizes = getAscendingOrderSizes(sizes, /*ascending*/false);
1271             for (Size sz : videoSizes) {
1272                 long minFrameDuration = mStreamConfigMap.getOutputMinFrameDuration(
1273                         android.media.MediaRecorder.class, sz);
1274                 // Give some margin for rounding error
1275                 if (minFrameDuration > (1e9 / 30.1)) {
1276                     Log.i(TAG, "External camera " + mCameraId + " has max video size:" + sz);
1277                     return sz;
1278                 }
1279             }
1280             Log.w(TAG, "Camera " + mCameraId + " does not support any 30fps video output");
1281             return FULLHD; // doesn't matter what size is returned here
1282         }
1283 
getMaxPreviewSize(List<Size> orderedPreviewSizes)1284         private @NonNull Size getMaxPreviewSize(List<Size> orderedPreviewSizes) {
1285             if (orderedPreviewSizes != null) {
1286                 for (Size size : orderedPreviewSizes) {
1287                     if ((mDisplaySize.getWidth() >= size.getWidth()) &&
1288                             (mDisplaySize.getHeight() >= size.getHeight())) {
1289                         return size;
1290                     }
1291                 }
1292             }
1293 
1294             Log.w(TAG,"Camera " + mCameraId + " maximum preview size search failed with "
1295                     + "display size " + mDisplaySize);
1296             return kPreviewSizeBound;
1297         }
1298 
1299         /**
1300          * Size comparison method used by size comparators.
1301          */
compareSizes(int widthA, int heightA, int widthB, int heightB)1302         private static int compareSizes(int widthA, int heightA, int widthB, int heightB) {
1303             long left = widthA * (long) heightA;
1304             long right = widthB * (long) heightB;
1305             if (left == right) {
1306                 left = widthA;
1307                 right = widthB;
1308             }
1309             return (left < right) ? -1 : (left > right ? 1 : 0);
1310         }
1311 
1312         /**
1313          * Size comparator that compares the number of pixels it covers.
1314          *
1315          * <p>If two the areas of two sizes are same, compare the widths.</p>
1316          */
1317         public static class SizeComparator implements Comparator<Size> {
1318             @Override
compare(@onNull Size lhs, @NonNull Size rhs)1319             public int compare(@NonNull Size lhs, @NonNull Size rhs) {
1320                 return compareSizes(lhs.getWidth(), lhs.getHeight(), rhs.getWidth(),
1321                         rhs.getHeight());
1322             }
1323         }
1324 
1325         /**
1326          * Get a sorted list of sizes from a given size list.
1327          *
1328          * <p>
1329          * The size is compare by area it covers, if the areas are same, then
1330          * compare the widths.
1331          * </p>
1332          *
1333          * @param sizeList The input size list to be sorted
1334          * @param ascending True if the order is ascending, otherwise descending order
1335          * @return The ordered list of sizes
1336          */
getAscendingOrderSizes( @onNull final List<Size> sizeList, boolean ascending)1337         private static @NonNull List<Size> getAscendingOrderSizes(
1338                 @NonNull final List<Size> sizeList, boolean ascending) {
1339             Comparator<Size> comparator = new SizeComparator();
1340             List<Size> sortedSizes = new ArrayList<Size>();
1341             sortedSizes.addAll(sizeList);
1342             Collections.sort(sortedSizes, comparator);
1343             if (!ascending) {
1344                 Collections.reverse(sortedSizes);
1345             }
1346 
1347             return sortedSizes;
1348         }
1349     }
1350 }
1351