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