blob: 90b261b28588365e0dc2de1581a7e548cff3e152 [file] [log] [blame]
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.camera.camera2.internal;
import static androidx.camera.core.impl.utils.AspectRatioUtil.ASPECT_RATIO_16_9;
import static androidx.camera.core.impl.utils.AspectRatioUtil.ASPECT_RATIO_3_4;
import static androidx.camera.core.impl.utils.AspectRatioUtil.ASPECT_RATIO_4_3;
import static androidx.camera.core.impl.utils.AspectRatioUtil.ASPECT_RATIO_9_16;
import static androidx.camera.core.impl.utils.AspectRatioUtil.hasMatchingAspectRatio;
import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.os.Build;
import android.util.Rational;
import android.util.Size;
import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
import androidx.camera.camera2.internal.compat.workaround.ExcludedSupportedSizesContainer;
import androidx.camera.camera2.internal.compat.workaround.ResolutionCorrector;
import androidx.camera.camera2.internal.compat.workaround.TargetAspectRatio;
import androidx.camera.core.AspectRatio;
import androidx.camera.core.Logger;
import androidx.camera.core.ResolutionSelector;
import androidx.camera.core.impl.ImageFormatConstants;
import androidx.camera.core.impl.ImageOutputConfig;
import androidx.camera.core.impl.SizeCoordinate;
import androidx.camera.core.impl.SurfaceConfig;
import androidx.camera.core.impl.utils.AspectRatioUtil;
import androidx.camera.core.impl.utils.CameraOrientationUtil;
import androidx.camera.core.impl.utils.CompareSizesByArea;
import androidx.camera.core.internal.utils.SizeUtil;
import androidx.core.util.Preconditions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* The supported output sizes collector to help collect the available resolution candidate list
* according to the use case config and the following settings in {@link ResolutionSelector}:
*
* 1. Preferred aspect ratio
* 2. Preferred resolution
* 3. Max resolution
* 4. Is high resolution enabled
*
* The problematic resolutions retrieved from {@link ExcludedSupportedSizesContainer} will also
* be execulded.
*/
@RequiresApi(21)
final class SupportedOutputSizesCollector {
private static final String TAG = "SupportedOutputSizesCollector";
private final String mCameraId;
@NonNull
private final CameraCharacteristicsCompat mCharacteristics;
@NonNull
private final DisplayInfoManager mDisplayInfoManager;
private final ResolutionCorrector mResolutionCorrector = new ResolutionCorrector();
private final Map<Integer, Size[]> mOutputSizesCache = new HashMap<>();
private final Map<Integer, Size[]> mHighResolutionOutputSizesCache = new HashMap<>();
private final Map<Integer, Size> mMaxSizeCache = new HashMap<>();
private final ExcludedSupportedSizesContainer mExcludedSupportedSizesContainer;
private final Map<Integer, List<Size>> mExcludedSizeListCache = new HashMap<>();
private final boolean mIsSensorLandscapeResolution;
private final boolean mIsBurstCaptureSupported;
private final Size mActiveArraySize;
private final int mSensorOrientation;
private final int mLensFacing;
SupportedOutputSizesCollector(@NonNull String cameraId,
@NonNull CameraCharacteristicsCompat cameraCharacteristics,
@NonNull DisplayInfoManager displayInfoManager) {
mCameraId = cameraId;
mCharacteristics = cameraCharacteristics;
mDisplayInfoManager = displayInfoManager;
mExcludedSupportedSizesContainer = new ExcludedSupportedSizesContainer(cameraId);
mIsSensorLandscapeResolution = isSensorLandscapeResolution(mCharacteristics);
mIsBurstCaptureSupported = isBurstCaptureSupported();
Rect rect = mCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
mActiveArraySize = rect != null ? new Size(rect.width(), rect.height()) : null;
mSensorOrientation = mCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
mLensFacing = mCharacteristics.get(CameraCharacteristics.LENS_FACING);
}
/**
* Collects and sorts the resolution candidate list by the following steps:
*
* 1. Collects the candidate list by the high resolution enable setting.
* 2. Filters out the candidate list according to the min size bound, max resolution or
* excluded resolution quirk.
* 3. Sorts the candidate list according to the rules of legacy resolution API or new
* Resolution API.
* 4. Forces select specific resolutions according to ResolutionCorrector workaround.
*/
@NonNull
List<Size> getSupportedOutputSizes(@NonNull ResolutionSelector resolutionSelector,
int imageFormat, @Nullable Size miniBoundingSize,
@Nullable Size[] customizedSupportSizes) {
// 1. Collects the candidate list by the high resolution enable setting.
List<Size> resolutionCandidateList = collectResolutionCandidateList(resolutionSelector,
imageFormat, customizedSupportSizes);
// 2. Filters out the candidate list according to the min size bound, max resolution or
// excluded resolution quirk.
resolutionCandidateList = filterOutResolutionCandidateListBySettings(
resolutionCandidateList, resolutionSelector, imageFormat);
// 3. Sorts the candidate list according to the rules of new Resolution API.
resolutionCandidateList = sortResolutionCandidateListByResolutionSelector(
resolutionCandidateList, resolutionSelector,
mDisplayInfoManager.getMaxSizeDisplay().getRotation(), miniBoundingSize);
// 4. Forces select specific resolutions according to ResolutionCorrector workaround.
resolutionCandidateList = mResolutionCorrector.insertOrPrioritize(
SurfaceConfig.getConfigType(imageFormat), resolutionCandidateList);
return resolutionCandidateList;
}
/**
* Collects the resolution candidate list.
*
* 1. Customized supported resolutions list will be returned when it exists
* 2. Otherwise, the sizes retrieved from {@link StreamConfigurationMap#getOutputSizes(int)}
* will be the base of the resolution candidate list.
* 3. High resolution sizes retrieved from
* {@link StreamConfigurationMap#getHighResolutionOutputSizes(int)} will be included when
* {@link ResolutionSelector#isHighResolutionEnabled()} returns true.
*
* The returned list will be sorted in descending order and duplicate items will be removed.
*/
@NonNull
private List<Size> collectResolutionCandidateList(
@NonNull ResolutionSelector resolutionSelector, int imageFormat,
@Nullable Size[] customizedSupportedSizes) {
Size[] outputSizes = customizedSupportedSizes;
if (outputSizes == null) {
boolean highResolutionEnabled = resolutionSelector.isHighResolutionEnabled();
outputSizes = getAllOutputSizesByFormat(imageFormat, highResolutionEnabled);
}
// Sort the output sizes. The Comparator result must be reversed to have a descending order
// result.
Arrays.sort(outputSizes, new CompareSizesByArea(true));
// Removes the duplicate items
List<Size> resultList = new ArrayList<>();
for (Size size: outputSizes) {
if (!resultList.contains(size)) {
resultList.add(size);
}
}
if (resultList.isEmpty()) {
throw new IllegalArgumentException(
"Resolution candidate list is empty when collecting by the settings!");
}
return resultList;
}
/**
* Filters out the resolution candidate list by the max resolution setting.
*
* The input size list should have been sorted in descending order.
*/
private List<Size> filterOutResolutionCandidateListBySettings(
@NonNull List<Size> resolutionCandidateList,
@NonNull ResolutionSelector resolutionSelector, int imageFormat) {
// Retrieves the max resolution setting. When ResolutionSelector is used, all resolution
// selection logic should depend on ResolutionSelector's settings.
Size maxResolution = resolutionSelector.getMaxResolution();
// Filter out the resolution candidate list by the max resolution. Sizes that any edge
// exceeds the max resolution will be filtered out.
List<Size> resultList;
if (maxResolution == null) {
resultList = new ArrayList<>(resolutionCandidateList);
} else {
resultList = new ArrayList<>();
for (Size outputSize : resolutionCandidateList) {
if (!SizeUtil.isLongerInAnyEdge(outputSize, maxResolution)) {
resultList.add(outputSize);
}
}
}
resultList = excludeProblematicSizes(resultList, imageFormat);
if (resultList.isEmpty()) {
throw new IllegalArgumentException(
"Resolution candidate list is empty after filtering out by the settings!");
}
return resultList;
}
/**
* Sorts the resolution candidate list according to the new ResolutionSelector API logic.
*
* The list will be sorted by the following order:
* 1. size of preferred resolution
* 2. a resolution with preferred aspect ratio, is not smaller than, and is closest to the
* preferred resolution.
* 3. resolutions with preferred aspect ratio and is smaller than the preferred resolution
* size in descending order of resolution area size.
* 4. Other sizes sorted by CompareAspectRatiosByMappingAreaInFullFovAspectRatioSpace and
* area size.
*/
@NonNull
private List<Size> sortResolutionCandidateListByResolutionSelector(
@NonNull List<Size> resolutionCandidateList,
@NonNull ResolutionSelector resolutionSelector,
@ImageOutputConfig.RotationValue int targetRotation,
@Nullable Size miniBoundingSize) {
Rational aspectRatio = getTargetAspectRatioByResolutionSelector(resolutionSelector);
Preconditions.checkNotNull(aspectRatio, "ResolutionSelector should also have aspect ratio"
+ " value.");
Size targetSize = getTargetSizeByResolutionSelector(resolutionSelector, targetRotation,
mSensorOrientation, mLensFacing);
List<Size> resultList = sortResolutionCandidateListByTargetAspectRatioAndSize(
resolutionCandidateList, aspectRatio, miniBoundingSize);
// Moves the target size to the first position if it exists in the resolution candidate
// list and there is no quirk that needs to select specific aspect ratio sizes in priority.
if (resultList.contains(targetSize) && canResolutionBeMovedToHead(targetSize)) {
resultList.remove(targetSize);
resultList.add(0, targetSize);
}
return resultList;
}
@NonNull
private Size[] getAllOutputSizesByFormat(int imageFormat, boolean highResolutionEnabled) {
Size[] outputs = mOutputSizesCache.get(imageFormat);
if (outputs == null) {
outputs = doGetOutputSizesByFormat(imageFormat);
mOutputSizesCache.put(imageFormat, outputs);
}
Size[] highResolutionOutputs = null;
// A device that does not support the BURST_CAPTURE capability,
// StreamConfigurationMap#getHighResolutionOutputSizes() will return null.
if (highResolutionEnabled && mIsBurstCaptureSupported) {
highResolutionOutputs = mHighResolutionOutputSizesCache.get(imageFormat);
// High resolution output sizes list may be empty. If it is empty and cached in the
// map, don't need to query it again.
if (highResolutionOutputs == null && !mHighResolutionOutputSizesCache.containsKey(
imageFormat)) {
highResolutionOutputs = doGetHighResolutionOutputSizesByFormat(imageFormat);
mHighResolutionOutputSizesCache.put(imageFormat, highResolutionOutputs);
}
}
// Combines output sizes if high resolution sizes list is not empty.
if (highResolutionOutputs != null) {
Size[] allOutputs = Arrays.copyOf(highResolutionOutputs,
highResolutionOutputs.length + outputs.length);
System.arraycopy(outputs, 0, allOutputs, highResolutionOutputs.length, outputs.length);
outputs = allOutputs;
}
return outputs;
}
@NonNull
private Size[] doGetOutputSizesByFormat(int imageFormat) {
Size[] outputSizes;
StreamConfigurationMap map =
mCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (map == null) {
throw new IllegalArgumentException("Can not retrieve SCALER_STREAM_CONFIGURATION_MAP");
}
if (Build.VERSION.SDK_INT < 23
&& imageFormat == ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE) {
// This is a little tricky that 0x22 that is internal defined in
// StreamConfigurationMap.java to be equal to ImageFormat.PRIVATE that is public
// after Android level 23 but not public in Android L. Use {@link SurfaceTexture}
// or {@link MediaCodec} will finally mapped to 0x22 in StreamConfigurationMap to
// retrieve the output sizes information.
outputSizes = map.getOutputSizes(SurfaceTexture.class);
} else {
outputSizes = map.getOutputSizes(imageFormat);
}
if (outputSizes == null) {
throw new IllegalArgumentException(
"Can not get supported output size for the format: " + imageFormat);
}
return outputSizes;
}
@Nullable
private Size[] doGetHighResolutionOutputSizesByFormat(int imageFormat) {
if (Build.VERSION.SDK_INT < 23) {
return null;
}
Size[] outputSizes;
StreamConfigurationMap map =
mCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (map == null) {
throw new IllegalArgumentException("Can not retrieve SCALER_STREAM_CONFIGURATION_MAP");
}
outputSizes = Api23Impl.getHighResolutionOutputSizes(map, imageFormat);
return outputSizes;
}
/**
* Returns the target aspect ratio value corrected by quirks.
*
* The final aspect ratio is determined by the following order:
* 1. The aspect ratio returned by {@link TargetAspectRatio} if it is
* {@link TargetAspectRatio#RATIO_4_3}, {@link TargetAspectRatio#RATIO_16_9} or
* {@link TargetAspectRatio#RATIO_MAX_JPEG}.
* 2. The use case's original aspect ratio if {@link TargetAspectRatio} returns
* {@link TargetAspectRatio#RATIO_ORIGINAL} and the use case has target aspect ratio setting.
*
* @param resolutionSelector the resolution selector of the use case.
*/
@Nullable
private Rational getTargetAspectRatioByResolutionSelector(
@NonNull ResolutionSelector resolutionSelector) {
Rational outputRatio = getTargetAspectRatioFromQuirk();
if (outputRatio == null) {
@AspectRatio.Ratio int aspectRatio = resolutionSelector.getPreferredAspectRatio();
switch (aspectRatio) {
case AspectRatio.RATIO_4_3:
outputRatio = mIsSensorLandscapeResolution ? ASPECT_RATIO_4_3
: ASPECT_RATIO_3_4;
break;
case AspectRatio.RATIO_16_9:
outputRatio = mIsSensorLandscapeResolution ? ASPECT_RATIO_16_9
: ASPECT_RATIO_9_16;
break;
default:
Logger.e(TAG, "Undefined target aspect ratio: " + aspectRatio);
}
}
return outputRatio;
}
/**
* Returns the restricted target aspect ratio value from quirk. The returned value can be
* null which means that no quirk to restrict the use case to use a specific target aspect
* ratio value.
*/
@Nullable
private Rational getTargetAspectRatioFromQuirk() {
Rational outputRatio = null;
// Gets the corrected aspect ratio due to device constraints or null if no correction is
// needed.
@TargetAspectRatio.Ratio int targetAspectRatio =
new TargetAspectRatio().get(mCameraId, mCharacteristics);
switch (targetAspectRatio) {
case TargetAspectRatio.RATIO_4_3:
outputRatio = mIsSensorLandscapeResolution ? ASPECT_RATIO_4_3 : ASPECT_RATIO_3_4;
break;
case TargetAspectRatio.RATIO_16_9:
outputRatio = mIsSensorLandscapeResolution ? ASPECT_RATIO_16_9 : ASPECT_RATIO_9_16;
break;
case TargetAspectRatio.RATIO_MAX_JPEG:
Size maxJpegSize = fetchMaxNormalOutputSize(ImageFormat.JPEG);
outputRatio = new Rational(maxJpegSize.getWidth(), maxJpegSize.getHeight());
break;
case TargetAspectRatio.RATIO_ORIGINAL:
break;
}
return outputRatio;
}
@Nullable
static Size getTargetSizeByResolutionSelector(@NonNull ResolutionSelector resolutionSelector,
int targetRotation, int sensorOrientation, int lensFacing) {
Size targetSize = resolutionSelector.getPreferredResolution();
// Calibrate targetSize by the target rotation value if it is set by the Android View
// coordinate orientation.
if (resolutionSelector.getSizeCoordinate() == SizeCoordinate.ANDROID_VIEW) {
targetSize = flipSizeByRotation(targetSize, targetRotation, lensFacing,
sensorOrientation);
}
return targetSize;
}
private static boolean isRotationNeeded(int targetRotation, int lensFacing,
int sensorOrientation) {
int relativeRotationDegrees =
CameraOrientationUtil.surfaceRotationToDegrees(targetRotation);
// Currently this assumes that a back-facing camera is always opposite to the screen.
// This may not be the case for all devices, so in the future we may need to handle that
// scenario.
boolean isOppositeFacingScreen = CameraCharacteristics.LENS_FACING_BACK == lensFacing;
int sensorRotationDegrees = CameraOrientationUtil.getRelativeImageRotation(
relativeRotationDegrees,
sensorOrientation,
isOppositeFacingScreen);
return sensorRotationDegrees == 90 || sensorRotationDegrees == 270;
}
@NonNull
private List<Size> excludeProblematicSizes(@NonNull List<Size> resolutionCandidateList,
int imageFormat) {
List<Size> excludedSizes = fetchExcludedSizes(imageFormat);
resolutionCandidateList.removeAll(excludedSizes);
return resolutionCandidateList;
}
@NonNull
private List<Size> fetchExcludedSizes(int imageFormat) {
List<Size> excludedSizes = mExcludedSizeListCache.get(imageFormat);
if (excludedSizes == null) {
excludedSizes = mExcludedSupportedSizesContainer.get(imageFormat);
mExcludedSizeListCache.put(imageFormat, excludedSizes);
}
return excludedSizes;
}
/**
* Sorts the resolution candidate list according to the target aspect ratio and size settings.
*
* 1. The resolution candidate list will be grouped by aspect ratio.
* 2. Each group only keeps one size which is not smaller than the target size.
* 3. The aspect ratios of groups will be sorted against to the target aspect ratio setting by
* CompareAspectRatiosByMappingAreaInFullFovAspectRatioSpace.
* 4. Concatenate all sizes as the result list
*/
@NonNull
private List<Size> sortResolutionCandidateListByTargetAspectRatioAndSize(
@NonNull List<Size> resolutionCandidateList, @NonNull Rational aspectRatio,
@Nullable Size miniBoundingSize) {
// Rearrange the supported size to put the ones with the same aspect ratio in the front
// of the list and put others in the end from large to small. Some low end devices may
// not able to get an supported resolution that match the preferred aspect ratio.
// Group output sizes by aspect ratio.
Map<Rational, List<Size>> aspectRatioSizeListMap =
groupSizesByAspectRatio(resolutionCandidateList);
// If the target resolution is set, use it to remove unnecessary larger sizes.
if (miniBoundingSize != null) {
// Remove unnecessary larger sizes from each aspect ratio size list
for (Rational key : aspectRatioSizeListMap.keySet()) {
removeSupportedSizesByMiniBoundingSize(aspectRatioSizeListMap.get(key),
miniBoundingSize);
}
}
// Sort the aspect ratio key set by the target aspect ratio.
List<Rational> aspectRatios = new ArrayList<>(aspectRatioSizeListMap.keySet());
Rational fullFovRatio = mActiveArraySize != null ? new Rational(
mActiveArraySize.getWidth(), mActiveArraySize.getHeight()) : null;
Collections.sort(aspectRatios,
new AspectRatioUtil.CompareAspectRatiosByMappingAreaInFullFovAspectRatioSpace(
aspectRatio, fullFovRatio));
List<Size> resultList = new ArrayList<>();
// Put available sizes into final result list by aspect ratio distance to target ratio.
for (Rational rational : aspectRatios) {
for (Size size : aspectRatioSizeListMap.get(rational)) {
// A size may exist in multiple groups in mod16 condition. Keep only one in
// the final list.
if (!resultList.contains(size)) {
resultList.add(size);
}
}
}
return resultList;
}
/**
* Returns {@code true} if the input resolution can be moved to the head of resolution
* candidate list.
*
* The resolution possibly can't be moved to head due to some quirks that sizes of
* specific aspect ratio must be used to avoid problems.
*/
private boolean canResolutionBeMovedToHead(@NonNull Size resolution) {
@TargetAspectRatio.Ratio int targetAspectRatio =
new TargetAspectRatio().get(mCameraId, mCharacteristics);
switch (targetAspectRatio) {
case TargetAspectRatio.RATIO_4_3:
return hasMatchingAspectRatio(resolution, ASPECT_RATIO_4_3);
case TargetAspectRatio.RATIO_16_9:
return hasMatchingAspectRatio(resolution, ASPECT_RATIO_16_9);
case TargetAspectRatio.RATIO_MAX_JPEG:
Size maxJpegSize = fetchMaxNormalOutputSize(ImageFormat.JPEG);
Rational maxJpegRatio = new Rational(maxJpegSize.getWidth(),
maxJpegSize.getHeight());
return hasMatchingAspectRatio(resolution, maxJpegRatio);
}
return true;
}
private Size fetchMaxNormalOutputSize(int imageFormat) {
Size size = mMaxSizeCache.get(imageFormat);
if (size != null) {
return size;
}
Size maxSize = getMaxNormalOutputSizeByFormat(imageFormat);
mMaxSizeCache.put(imageFormat, maxSize);
return maxSize;
}
/**
* Gets max normal supported output size for specific image format.
*
* <p>Normal supported output sizes mean the sizes retrieved by the
* {@link StreamConfigurationMap#getOutputSizes(int)}. The high resolution sizes retrieved by
* the {@link StreamConfigurationMap#getHighResolutionOutputSizes(int)} are not included.
*
* @param imageFormat the image format info
* @return the max normal supported output size for the image format
*/
private Size getMaxNormalOutputSizeByFormat(int imageFormat) {
Size[] outputSizes = getAllOutputSizesByFormat(imageFormat, false);
return SizeUtil.getMaxSize(Arrays.asList(outputSizes));
}
private boolean isBurstCaptureSupported() {
int[] availableCapabilities =
mCharacteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
if (availableCapabilities != null) {
for (int capability : availableCapabilities) {
if (capability
== CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE) {
return true;
}
}
}
return false;
}
//////////////////////////////////////////////////////////////////////////////////////////
// The following functions can be reused by the legacy resolution selection logic. The can be
// changed as private function after the legacy resolution API is completely removed.
//////////////////////////////////////////////////////////////////////////////////////////
// Use target rotation to calibrate the size.
@Nullable
static Size flipSizeByRotation(@Nullable Size size, int targetRotation, int lensFacing,
int sensorOrientation) {
Size outputSize = size;
// Calibrates the size with the display and sensor rotation degrees values.
if (size != null && isRotationNeeded(targetRotation, lensFacing, sensorOrientation)) {
outputSize = new Size(/* width= */size.getHeight(), /* height= */size.getWidth());
}
return outputSize;
}
static Map<Rational, List<Size>> groupSizesByAspectRatio(List<Size> sizes) {
Map<Rational, List<Size>> aspectRatioSizeListMap = new HashMap<>();
List<Rational> aspectRatioKeys = getResolutionListGroupingAspectRatioKeys(sizes);
for (Rational aspectRatio: aspectRatioKeys) {
aspectRatioSizeListMap.put(aspectRatio, new ArrayList<>());
}
for (Size outputSize : sizes) {
for (Rational key : aspectRatioSizeListMap.keySet()) {
// Put the size into all groups that is matched in mod16 condition since a size
// may match multiple aspect ratio in mod16 algorithm.
if (hasMatchingAspectRatio(outputSize, key)) {
aspectRatioSizeListMap.get(key).add(outputSize);
}
}
}
return aspectRatioSizeListMap;
}
/**
* Returns the grouping aspect ratio keys of the input resolution list.
*
* <p>Some sizes might be mod16 case. When grouping, those sizes will be grouped into an
* existing aspect ratio group if the aspect ratio can match by the mod16 rule.
*/
@NonNull
static List<Rational> getResolutionListGroupingAspectRatioKeys(
@NonNull List<Size> resolutionCandidateList) {
List<Rational> aspectRatios = new ArrayList<>();
// Adds the default 4:3 and 16:9 items first to avoid their mod16 sizes to create
// additional items.
aspectRatios.add(ASPECT_RATIO_4_3);
aspectRatios.add(ASPECT_RATIO_16_9);
// Tries to find the aspect ratio which the target size belongs to.
for (Size size : resolutionCandidateList) {
Rational newRatio = new Rational(size.getWidth(), size.getHeight());
boolean aspectRatioFound = aspectRatios.contains(newRatio);
// The checking size might be a mod16 size which can be mapped to an existing aspect
// ratio group.
if (!aspectRatioFound) {
boolean hasMatchingAspectRatio = false;
for (Rational aspectRatio : aspectRatios) {
if (hasMatchingAspectRatio(size, aspectRatio)) {
hasMatchingAspectRatio = true;
break;
}
}
if (!hasMatchingAspectRatio) {
aspectRatios.add(newRatio);
}
}
}
return aspectRatios;
}
/**
* Removes unnecessary sizes by target size.
*
* <p>If the target resolution is set, a size that is equal to or closest to the target
* resolution will be selected. If the list includes more than one size equal to or larger
* than the target resolution, only one closest size needs to be kept. The other larger sizes
* can be removed so that they won't be selected to use.
*
* @param supportedSizesList The list should have been sorted in descending order.
* @param miniBoundingSize The target size used to remove unnecessary sizes.
*/
static void removeSupportedSizesByMiniBoundingSize(List<Size> supportedSizesList,
Size miniBoundingSize) {
if (supportedSizesList == null || supportedSizesList.isEmpty()) {
return;
}
int indexBigEnough = -1;
List<Size> removeSizes = new ArrayList<>();
// Get the index of the item that is equal to or closest to the target size.
for (int i = 0; i < supportedSizesList.size(); i++) {
Size outputSize = supportedSizesList.get(i);
if (outputSize.getWidth() >= miniBoundingSize.getWidth()
&& outputSize.getHeight() >= miniBoundingSize.getHeight()) {
// New big enough item closer to the target size is found. Adding the previous
// one into the sizes list that will be removed.
if (indexBigEnough >= 0) {
removeSizes.add(supportedSizesList.get(indexBigEnough));
}
indexBigEnough = i;
} else {
break;
}
}
// Remove the unnecessary items that are larger than the item closest to the target size.
supportedSizesList.removeAll(removeSizes);
}
static boolean isSensorLandscapeResolution(
@NonNull CameraCharacteristicsCompat characteristicsCompat) {
Size pixelArraySize =
characteristicsCompat.get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE);
// Make the default value is true since usually the sensor resolution is landscape.
return pixelArraySize == null || pixelArraySize.getWidth() >= pixelArraySize.getHeight();
}
//////////////////////////////////////////////////////////////////////////////////////////
// The above functions can be reused by the legacy resolution selection logic. The can be
// changed as private function after the legacy resolution API is completely removed.
//////////////////////////////////////////////////////////////////////////////////////////
@RequiresApi(23)
private static class Api23Impl {
private Api23Impl() {
// This class is not instantiable.
}
@DoNotInline
static Size[] getHighResolutionOutputSizes(StreamConfigurationMap streamConfigurationMap,
int format) {
return streamConfigurationMap.getHighResolutionOutputSizes(format);
}
}
}