1 /*
2  * Copyright 2022 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 androidx.camera.camera2.internal.compat;
18 
19 import android.graphics.ImageFormat;
20 import android.graphics.PixelFormat;
21 import android.hardware.camera2.params.StreamConfigurationMap;
22 import android.os.Build;
23 import android.util.Range;
24 import android.util.Size;
25 
26 import androidx.camera.camera2.internal.compat.workaround.OutputSizesCorrector;
27 import androidx.camera.core.Logger;
28 
29 import org.jspecify.annotations.NonNull;
30 import org.jspecify.annotations.Nullable;
31 
32 import java.util.HashMap;
33 import java.util.Map;
34 
35 /**
36  * Helper for accessing features in {@link StreamConfigurationMap} in a backwards compatible
37  * fashion.
38  */
39 public class StreamConfigurationMapCompat {
40     private static final String TAG = "StreamConfigurationMapCompat";
41 
42     private final StreamConfigurationMapCompatImpl mImpl;
43     private final OutputSizesCorrector mOutputSizesCorrector;
44     private final Map<Integer, Size[]> mCachedFormatOutputSizes = new HashMap<>();
45     private final Map<Integer, Size[]> mCachedFormatHighResolutionOutputSizes = new HashMap<>();
46     private final Map<Class<?>, Size[]> mCachedClassOutputSizes = new HashMap<>();
47 
StreamConfigurationMapCompat(@onNull StreamConfigurationMap map, @NonNull OutputSizesCorrector outputSizesCorrector)48     private StreamConfigurationMapCompat(@NonNull StreamConfigurationMap map,
49             @NonNull OutputSizesCorrector outputSizesCorrector) {
50         if (Build.VERSION.SDK_INT >= 23) {
51             mImpl = new StreamConfigurationMapCompatApi23Impl(map);
52         } else {
53             mImpl = new StreamConfigurationMapCompatBaseImpl(map);
54         }
55         mOutputSizesCorrector = outputSizesCorrector;
56     }
57 
58     /**
59      * Provides a backward-compatible wrapper for {@link StreamConfigurationMap}.
60      *
61      * @param map {@link StreamConfigurationMap} class to wrap
62      * @param outputSizesCorrector {@link OutputSizesCorrector} which can apply the related
63      *                                                         workarounds when output sizes are
64      *                                                         retrieved.
65      * @return wrapped class
66      */
toStreamConfigurationMapCompat( @onNull StreamConfigurationMap map, @NonNull OutputSizesCorrector outputSizesCorrector)67     static @NonNull StreamConfigurationMapCompat toStreamConfigurationMapCompat(
68             @NonNull StreamConfigurationMap map,
69             @NonNull OutputSizesCorrector outputSizesCorrector) {
70         return new StreamConfigurationMapCompat(map, outputSizesCorrector);
71     }
72 
73 
74     /**
75      * Get the image format output formats in this stream configuration.
76      *
77      * <p>All image formats returned by this function will be defined in either ImageFormat or in
78      * PixelFormat.
79      *
80      * @return an array of integer format
81      * @see ImageFormat
82      * @see PixelFormat
83      */
getOutputFormats()84     public int @Nullable [] getOutputFormats() {
85         int[] result = mImpl.getOutputFormats();
86         return result == null ? null : result.clone();
87     }
88 
89     /**
90      * Get a list of sizes compatible with the requested image {@code format}.
91      *
92      * <p>Output sizes related quirks will be applied onto the returned sizes list.
93      *
94      * @param format an image format from {@link ImageFormat} or {@link PixelFormat}
95      * @return an array of supported sizes, or {@code null} if the {@code format} is not a
96      * supported output
97      * @see ImageFormat
98      * @see PixelFormat
99      */
getOutputSizes(int format)100     public Size @Nullable [] getOutputSizes(int format) {
101         if (mCachedFormatOutputSizes.containsKey(format)) {
102             Size[] cachedOutputSizes = mCachedFormatOutputSizes.get(format);
103             return cachedOutputSizes == null ? null : mCachedFormatOutputSizes.get(format).clone();
104         }
105 
106         Size[] outputSizes = null;
107         try {
108             // b/378508360: try-catch to workaround the exception when using
109             // StreamConfigurationMap provided by Robolectric.
110             outputSizes = mImpl.getOutputSizes(format);
111         } catch (Throwable t) {
112             Logger.w(TAG, "Failed to get output sizes for " + format, t);
113         }
114 
115         if (outputSizes == null || outputSizes.length == 0) {
116             Logger.w(TAG, "Retrieved output sizes array is null or empty for format " + format);
117             return outputSizes;
118         }
119 
120         outputSizes = mOutputSizesCorrector.applyQuirks(outputSizes, format);
121         mCachedFormatOutputSizes.put(format, outputSizes);
122         return outputSizes.clone();
123     }
124 
125     /**
126      * Get a list of sizes compatible with {@code klass} to use as an output.
127      *
128      * <p>Output sizes related quirks will be applied onto the returned sizes list.
129      *
130      * @param klass a non-{@code null} {@link Class} object reference
131      * @return an array of supported sizes for {@link ImageFormat#PRIVATE} format,
132      * or {@code null} iff the {@code klass} is not a supported output.
133      * @throws NullPointerException if {@code klass} was {@code null}
134      */
getOutputSizes(@onNull Class<T> klass)135     public <T> Size @Nullable [] getOutputSizes(@NonNull Class<T> klass) {
136         if (mCachedClassOutputSizes.containsKey(klass)) {
137             Size[] cachedOutputSizes = mCachedClassOutputSizes.get(klass);
138             return cachedOutputSizes == null ? null : mCachedClassOutputSizes.get(klass).clone();
139         }
140 
141         Size[] outputSizes = null;
142         try {
143             // b/378508360: try-catch to workaround the exception when using
144             // StreamConfigurationMap provided by Robolectric.
145             outputSizes = mImpl.getOutputSizes(klass);
146         } catch (Throwable t) {
147             Logger.w(TAG, "Fail to get output sizes for " + klass, t);
148         }
149 
150         if (outputSizes == null || outputSizes.length == 0) {
151             Logger.w(TAG, "Retrieved output sizes array is null or empty for class " + klass);
152             return outputSizes;
153         }
154 
155         outputSizes = mOutputSizesCorrector.applyQuirks(outputSizes, klass);
156         mCachedClassOutputSizes.put(klass, outputSizes);
157         return outputSizes.clone();
158     }
159 
160     /**
161      * Get a list of high resolution sizes compatible with the requested image {@code format}.
162      *
163      * @param format an image format from {@link ImageFormat} or {@link PixelFormat}
164      * @return an array of supported sizes, or {@code null} if the {@code format} is not a
165      * supported output
166      * @see ImageFormat
167      * @see PixelFormat
168      */
getHighResolutionOutputSizes(int format)169     public Size @Nullable [] getHighResolutionOutputSizes(int format) {
170         if (mCachedFormatHighResolutionOutputSizes.containsKey(format)) {
171             Size[] cachedOutputSizes = mCachedFormatHighResolutionOutputSizes.get(format);
172             return cachedOutputSizes == null ? null : mCachedFormatHighResolutionOutputSizes.get(
173                     format).clone();
174         }
175 
176         Size[] outputSizes = mImpl.getHighResolutionOutputSizes(format);
177 
178         // High resolution output sizes can be null.
179         if (outputSizes != null && outputSizes.length > 0) {
180             outputSizes = mOutputSizesCorrector.applyQuirks(outputSizes, format);
181         }
182 
183         mCachedFormatHighResolutionOutputSizes.put(format, outputSizes);
184         return outputSizes != null ? outputSizes.clone() : null;
185     }
186 
187     /** Get a list of supported high speed video recording FPS ranges. */
188     @Nullable
getHighSpeedVideoFpsRanges()189     public Range<Integer>[] getHighSpeedVideoFpsRanges() {
190         return mImpl.getHighSpeedVideoFpsRanges();
191     }
192 
193     /** Get the frame per second ranges (fpsMin, fpsMax) for input high speed video size. */
194     @Nullable
getHighSpeedVideoFpsRangesFor(@onNull Size size)195     public Range<Integer>[] getHighSpeedVideoFpsRangesFor(@NonNull Size size)
196             throws IllegalArgumentException {
197         return mImpl.getHighSpeedVideoFpsRangesFor(size);
198     }
199 
200     /** Get a list of supported high speed video recording sizes. */
201     @Nullable
getHighSpeedVideoSizes()202     public Size[] getHighSpeedVideoSizes() {
203         return mImpl.getHighSpeedVideoSizes();
204     }
205 
206     /** Get the supported video sizes for an input high speed FPS range. */
207     @Nullable
getHighSpeedVideoSizesFor(@onNull Range<Integer> fpsRange)208     public Size[] getHighSpeedVideoSizesFor(@NonNull Range<Integer> fpsRange)
209             throws IllegalArgumentException {
210         return mImpl.getHighSpeedVideoSizesFor(fpsRange);
211     }
212 
213     /**
214      * Returns the {@link StreamConfigurationMap} represented by this object.
215      */
toStreamConfigurationMap()216     public @NonNull StreamConfigurationMap toStreamConfigurationMap() {
217         return mImpl.unwrap();
218     }
219 
220     interface StreamConfigurationMapCompatImpl {
221 
getOutputFormats()222         int @Nullable [] getOutputFormats();
223 
getOutputSizes(int format)224         Size @Nullable [] getOutputSizes(int format);
225 
getOutputSizes(@onNull Class<T> klass)226         <T> Size @Nullable [] getOutputSizes(@NonNull Class<T> klass);
227 
getHighResolutionOutputSizes(int format)228         Size @Nullable [] getHighResolutionOutputSizes(int format);
229 
230         @Nullable
getHighSpeedVideoFpsRanges()231         Range<Integer>[] getHighSpeedVideoFpsRanges();
232 
233         @Nullable
getHighSpeedVideoFpsRangesFor(@onNull Size size)234         Range<Integer>[] getHighSpeedVideoFpsRangesFor(@NonNull Size size)
235                 throws IllegalArgumentException;
236 
237         @Nullable
getHighSpeedVideoSizes()238         Size[] getHighSpeedVideoSizes();
239 
240         @Nullable
getHighSpeedVideoSizesFor(@onNull Range<Integer> fpsRange)241         Size[] getHighSpeedVideoSizesFor(@NonNull Range<Integer> fpsRange)
242                 throws IllegalArgumentException;
243 
244         /**
245          * Returns the underlying {@link StreamConfigurationMap} instance.
246          */
unwrap()247         @NonNull StreamConfigurationMap unwrap();
248     }
249 }
250