Skip to content

Instantly share code, notes, and snippets.

@wafer-li
Created July 15, 2021 11:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wafer-li/5c2c5565dbfea669174341fb5567a2df to your computer and use it in GitHub Desktop.
Save wafer-li/5c2c5565dbfea669174341fb5567a2df to your computer and use it in GitHub Desktop.
CameraX Custom SurfaceCombination
@SuppressLint("RestrictedApi")
class App : Application(), CameraXConfig.Provider {
override fun getCameraXConfig(): CameraXConfig {
val defaultConfig = Camera2Config.defaultConfig()
val provider = CameraDeviceSurfaceManager.Provider { context, cameraManager, availableCameraIds ->
Camera2DeviceSurfaceManagerModified(context, cameraManager, availableCameraIds)
}
return CameraXConfig.Builder.fromConfig(defaultConfig)
.setDeviceSurfaceManagerProvider(provider)
.build()
}
}
/*
* Copyright 2019 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.
*/
import android.annotation.SuppressLint;
import android.content.Context;
import android.media.CamcorderProfile;
import android.util.Size;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.camera.camera2.internal.compat.CameraManagerCompat;
import androidx.camera.core.CameraUnavailableException;
import androidx.camera.core.impl.CameraDeviceSurfaceManager;
import androidx.camera.core.impl.SurfaceConfig;
import androidx.camera.core.impl.UseCaseConfig;
import androidx.core.util.Preconditions;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Camera device manager to provide the guaranteed supported stream capabilities related info for
* all camera devices
*
* <p>{@link android.hardware.camera2.CameraDevice#createCaptureSession} defines the default
* guaranteed stream combinations for different hardware level devices. It defines what combination
* of surface configuration type and size pairs can be supported for different hardware level camera
* devices. This structure is used to store the guaranteed supported stream capabilities related
* info.
*/
@SuppressLint("RestrictedApi")
public final class Camera2DeviceSurfaceManagerModified implements CameraDeviceSurfaceManager {
private static final String TAG = "Camera2DeviceSurfaceManagerModified";
private final Map<String, SupportedSurfaceCombinationModified> mCameraSupportedSurfaceCombinationMap =
new HashMap<>();
private final CamcorderProfileHelper mCamcorderProfileHelper;
/**
* Creates a new, initialized Camera2DeviceSurfaceManagerModified.
*/
public Camera2DeviceSurfaceManagerModified(@NonNull Context context, @Nullable Object cameraManager,
@NonNull Set<String> availableCameraIds) throws CameraUnavailableException {
this(context, new CamcorderProfileHelper() {
@Override
public boolean hasProfile(int cameraId, int quality) {
return CamcorderProfile.hasProfile(cameraId, quality);
}
@Override
public CamcorderProfile get(int cameraId, int quality) {
return CamcorderProfile.get(cameraId, quality);
}
}, cameraManager, availableCameraIds);
}
Camera2DeviceSurfaceManagerModified(@NonNull Context context, @NonNull CamcorderProfileHelper camcorderProfileHelper,
@Nullable Object cameraManager, @NonNull Set<String> availableCameraIds) throws CameraUnavailableException {
Preconditions.checkNotNull(camcorderProfileHelper);
mCamcorderProfileHelper = camcorderProfileHelper;
CameraManagerCompat cameraManagerCompat;
if (cameraManager instanceof CameraManagerCompat) {
cameraManagerCompat = (CameraManagerCompat) cameraManager;
} else {
cameraManagerCompat = CameraManagerCompat.from(context);
}
init(context, cameraManagerCompat, availableCameraIds);
}
/**
* Prepare necessary resources for the surface manager.
*/
private void init(@NonNull Context context, @NonNull CameraManagerCompat cameraManager,
@NonNull Set<String> availableCameraIds) throws CameraUnavailableException {
Preconditions.checkNotNull(context);
for (String cameraId : availableCameraIds) {
mCameraSupportedSurfaceCombinationMap.put(cameraId,
new SupportedSurfaceCombinationModified(context, cameraId, cameraManager, mCamcorderProfileHelper));
}
}
/**
* Check whether the input surface configuration list is under the capability of any combination
* of this object.
*
* @param cameraId the camera id of the camera device to be compared
* @param surfaceConfigList the surface configuration list to be compared
* @return the check result that whether it could be supported
* @throws IllegalStateException if not initialized
*/
@Override
public boolean checkSupported(@NonNull String cameraId, @Nullable List<SurfaceConfig> surfaceConfigList) {
if (surfaceConfigList == null || surfaceConfigList.isEmpty()) {
return true;
}
SupportedSurfaceCombinationModified supportedSurfaceCombinationModified =
mCameraSupportedSurfaceCombinationMap.get(cameraId);
boolean isSupported = false;
if (supportedSurfaceCombinationModified != null) {
isSupported = supportedSurfaceCombinationModified.checkSupported(surfaceConfigList);
}
return isSupported;
}
/**
* Transform to a SurfaceConfig object with cameraId, image format and size info
*
* @param cameraId the camera id of the camera device to transform the object
* @param imageFormat the image format info for the surface configuration object
* @param size the size info for the surface configuration object
* @return new {@link SurfaceConfig} object
* @throws IllegalStateException if not initialized
*/
@Nullable
@Override
public SurfaceConfig transformSurfaceConfig(@NonNull String cameraId, int imageFormat, @NonNull Size size) {
SupportedSurfaceCombinationModified supportedSurfaceCombinationModified =
mCameraSupportedSurfaceCombinationMap.get(cameraId);
SurfaceConfig surfaceConfig = null;
if (supportedSurfaceCombinationModified != null) {
surfaceConfig = supportedSurfaceCombinationModified.transformSurfaceConfig(imageFormat, size);
}
return surfaceConfig;
}
/**
* Retrieves a map of suggested resolutions for the given list of use cases.
*
* @param cameraId the camera id of the camera device used by the use cases
* @param existingSurfaces list of surfaces already configured and used by the camera. The
* resolutions for these surface can not change.
* @param newUseCaseConfigs list of configurations of the use cases that will be given a
* suggested resolution
* @return map of suggested resolutions for given use cases
* @throws IllegalStateException if not initialized
* @throws IllegalArgumentException if {@code newUseCaseConfigs} is an empty list, if
* there isn't a supported combination of surfaces
* available, or if the {@code cameraId}
* is not a valid id.
*/
@NonNull
@Override
public Map<UseCaseConfig<?>, Size> getSuggestedResolutions(@NonNull String cameraId,
@NonNull List<SurfaceConfig> existingSurfaces, @NonNull List<UseCaseConfig<?>> newUseCaseConfigs) {
Preconditions.checkArgument(!newUseCaseConfigs.isEmpty(), "No new use cases to be bound.");
// Use the small size (640x480) for new use cases to check whether there is any possible
// supported combination first
List<SurfaceConfig> surfaceConfigs = new ArrayList<>(existingSurfaces);
for (UseCaseConfig<?> useCaseConfig : newUseCaseConfigs) {
surfaceConfigs.add(transformSurfaceConfig(cameraId, useCaseConfig.getInputFormat(), new Size(640, 480)));
}
SupportedSurfaceCombinationModified supportedSurfaceCombinationModified =
mCameraSupportedSurfaceCombinationMap.get(cameraId);
if (supportedSurfaceCombinationModified == null) {
throw new IllegalArgumentException("No such camera id in supported combination list: " + cameraId);
}
if (!supportedSurfaceCombinationModified.checkSupported(surfaceConfigs)) {
throw new IllegalArgumentException("No supported surface combination is found for camera device - Id : "
+ cameraId
+ ". May be attempting to bind too many use cases. "
+ "Existing surfaces: "
+ existingSurfaces
+ " New configs: "
+ newUseCaseConfigs);
}
return supportedSurfaceCombinationModified.getSuggestedResolutions(existingSurfaces, newUseCaseConfigs);
}
}
/*
* Copyright 2019 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.
*/
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.ImageFormat;
import android.graphics.Point;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.CamcorderProfile;
import android.media.MediaRecorder;
import android.os.Build;
import android.util.Pair;
import android.util.Rational;
import android.util.Size;
import android.view.Surface;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.camera.camera2.internal.CameraUnavailableExceptionHelper;
import androidx.camera.camera2.internal.compat.CameraAccessExceptionCompat;
import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
import androidx.camera.camera2.internal.compat.CameraManagerCompat;
import androidx.camera.camera2.internal.compat.workaround.ExcludedSupportedSizesContainer;
import androidx.camera.camera2.internal.compat.workaround.ExtraSupportedSurfaceCombinationsContainer;
import androidx.camera.camera2.internal.compat.workaround.TargetAspectRatio;
import androidx.camera.core.AspectRatio;
import androidx.camera.core.CameraUnavailableException;
import androidx.camera.core.Logger;
import androidx.camera.core.impl.ImageFormatConstants;
import androidx.camera.core.impl.ImageOutputConfig;
import androidx.camera.core.impl.SurfaceCombination;
import androidx.camera.core.impl.SurfaceConfig;
import androidx.camera.core.impl.SurfaceConfig.ConfigSize;
import androidx.camera.core.impl.SurfaceConfig.ConfigType;
import androidx.camera.core.impl.SurfaceSizeDefinition;
import androidx.camera.core.impl.UseCaseConfig;
import androidx.camera.core.impl.utils.CameraOrientationUtil;
import androidx.core.util.Preconditions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Camera device supported surface configuration combinations
*
* <p>{@link android.hardware.camera2.CameraDevice#createCaptureSession} defines the default
* guaranteed stream combinations for different hardware level devices. It defines what combination
* of surface configuration type and size pairs can be supported for different hardware level camera
* devices. This structure is used to store a list of surface combinations that are guaranteed to
* support for this camera device.
*/
@SuppressLint("RestrictedApi")
final class SupportedSurfaceCombinationModified {
private static final String TAG = "SupportedSurfaceCombination";
private static final Size MAX_PREVIEW_SIZE = new Size(1920, 1080);
private static final Size DEFAULT_SIZE = new Size(640, 480);
private static final Size ZERO_SIZE = new Size(0, 0);
private static final Size QUALITY_1080P_SIZE = new Size(1920, 1080);
private static final Size QUALITY_480P_SIZE = new Size(720, 480);
private static final int ALIGN16 = 16;
private static final Rational ASPECT_RATIO_4_3 = new Rational(4, 3);
private static final Rational ASPECT_RATIO_3_4 = new Rational(3, 4);
private static final Rational ASPECT_RATIO_16_9 = new Rational(16, 9);
private static final Rational ASPECT_RATIO_9_16 = new Rational(9, 16);
private final List<SurfaceCombination> mSurfaceCombinations = new ArrayList<>();
private final Map<Integer, Size> mMaxSizeCache = new HashMap<>();
private final String mCameraId;
private final CamcorderProfileHelper mCamcorderProfileHelper;
private final CameraCharacteristicsCompat mCharacteristics;
private final ExcludedSupportedSizesContainer mExcludedSupportedSizesContainer;
private final ExtraSupportedSurfaceCombinationsContainer mExtraSupportedSurfaceCombinationsContainer;
private final int mHardwareLevel;
private final boolean mIsSensorLandscapeResolution;
private final Map<Integer, List<Size>> mExcludedSizeListCache = new HashMap<>();
private boolean mIsRawSupported = false;
private boolean mIsBurstCaptureSupported = false;
private SurfaceSizeDefinition mSurfaceSizeDefinition;
private Map<Integer, Size[]> mOutputSizesCache = new HashMap<>();
SupportedSurfaceCombinationModified(@NonNull Context context, @NonNull String cameraId,
@NonNull CameraManagerCompat cameraManagerCompat, @NonNull CamcorderProfileHelper camcorderProfileHelper)
throws CameraUnavailableException {
mCameraId = Preconditions.checkNotNull(cameraId);
mCamcorderProfileHelper = Preconditions.checkNotNull(camcorderProfileHelper);
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mExcludedSupportedSizesContainer = new ExcludedSupportedSizesContainer(cameraId);
mExtraSupportedSurfaceCombinationsContainer = new ExtraSupportedSurfaceCombinationsContainer(cameraId);
try {
mCharacteristics = cameraManagerCompat.getCameraCharacteristicsCompat(mCameraId);
Integer keyValue = mCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
mHardwareLevel = keyValue != null ? keyValue : CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY;
mIsSensorLandscapeResolution = isSensorLandscapeResolution();
} catch (CameraAccessExceptionCompat e) {
throw CameraUnavailableExceptionHelper.createFrom(e);
}
generateSupportedCombinationList();
generateSurfaceSizeDefinition(windowManager);
checkCustomization();
}
String getCameraId() {
return mCameraId;
}
boolean isRawSupported() {
return mIsRawSupported;
}
boolean isBurstCaptureSupported() {
return mIsBurstCaptureSupported;
}
/**
* Check whether the input surface configuration list is under the capability of any combination
* of this object.
*
* @param surfaceConfigList the surface configuration list to be compared
* @return the check result that whether it could be supported
*/
boolean checkSupported(List<SurfaceConfig> surfaceConfigList) {
boolean isSupported = false;
for (SurfaceCombination surfaceCombination : mSurfaceCombinations) {
isSupported = surfaceCombination.isSupported(surfaceConfigList);
if (isSupported) {
break;
}
}
return isSupported;
}
/**
* Transform to a SurfaceConfig object with image format and size info
*
* @param imageFormat the image format info for the surface configuration object
* @param size the size info for the surface configuration object
* @return new {@link SurfaceConfig} object
*/
SurfaceConfig transformSurfaceConfig(int imageFormat, Size size) {
ConfigType configType;
ConfigSize configSize = ConfigSize.NOT_SUPPORT;
/*
* PRIV refers to any target whose available sizes are found using
* StreamConfigurationMap.getOutputSizes(Class) with no direct application-visible format,
* YUV refers to a target Surface using the ImageFormat.YUV_420_888 format, JPEG refers to
* the ImageFormat.JPEG format, and RAW refers to the ImageFormat.RAW_SENSOR format.
*/
if (imageFormat == ImageFormat.YUV_420_888) {
configType = ConfigType.YUV;
} else if (imageFormat == ImageFormat.JPEG) {
configType = ConfigType.JPEG;
} else if (imageFormat == ImageFormat.RAW_SENSOR) {
configType = ConfigType.RAW;
} else {
configType = ConfigType.PRIV;
}
Size maxSize = fetchMaxSize(imageFormat);
// Compare with surface size definition to determine the surface configuration size
if (size.getWidth() * size.getHeight()
<= mSurfaceSizeDefinition.getAnalysisSize().getWidth() * mSurfaceSizeDefinition.getAnalysisSize().getHeight()) {
configSize = ConfigSize.ANALYSIS;
} else if (size.getWidth() * size.getHeight()
<= mSurfaceSizeDefinition.getPreviewSize().getWidth() * mSurfaceSizeDefinition.getPreviewSize().getHeight()) {
configSize = ConfigSize.PREVIEW;
} else if (size.getWidth() * size.getHeight()
<= mSurfaceSizeDefinition.getRecordSize().getWidth() * mSurfaceSizeDefinition.getRecordSize().getHeight()) {
configSize = ConfigSize.RECORD;
} else if (size.getWidth() * size.getHeight() <= maxSize.getWidth() * maxSize.getHeight()) {
configSize = ConfigSize.MAXIMUM;
}
return SurfaceConfig.create(configType, configSize);
}
Map<UseCaseConfig<?>, Size> getSuggestedResolutions(List<SurfaceConfig> existingSurfaces,
List<UseCaseConfig<?>> newUseCaseConfigs) {
Map<UseCaseConfig<?>, Size> suggestedResolutionsMap = new HashMap<>();
// Get the index order list by the use case priority for finding stream configuration
List<Integer> useCasesPriorityOrder = getUseCasesPriorityOrder(newUseCaseConfigs);
List<List<Size>> supportedOutputSizesList = new ArrayList<>();
// Collect supported output sizes for all use cases
for (Integer index : useCasesPriorityOrder) {
List<Size> supportedOutputSizes = getSupportedOutputSizes(newUseCaseConfigs.get(index));
supportedOutputSizesList.add(supportedOutputSizes);
}
// Get all possible size arrangements
List<List<Size>> allPossibleSizeArrangements = getAllPossibleSizeArrangements(supportedOutputSizesList);
// Transform use cases to SurfaceConfig list and find the first (best) workable combination
for (List<Size> possibleSizeList : allPossibleSizeArrangements) {
// Attach SurfaceConfig of original use cases since it will impact the new use cases
List<SurfaceConfig> surfaceConfigList = new ArrayList<>(existingSurfaces);
// Attach SurfaceConfig of new use cases
for (int i = 0; i < possibleSizeList.size(); i++) {
Size size = possibleSizeList.get(i);
UseCaseConfig<?> newUseCase = newUseCaseConfigs.get(useCasesPriorityOrder.get(i));
surfaceConfigList.add(transformSurfaceConfig(newUseCase.getInputFormat(), size));
}
// Check whether the SurfaceConfig combination can be supported
if (checkSupported(surfaceConfigList)) {
for (UseCaseConfig<?> useCaseConfig : newUseCaseConfigs) {
suggestedResolutionsMap.put(useCaseConfig,
possibleSizeList.get(useCasesPriorityOrder.indexOf(newUseCaseConfigs.indexOf(useCaseConfig))));
}
break;
}
}
return suggestedResolutionsMap;
}
private Rational getTargetAspectRatio(@NonNull ImageOutputConfig imageOutputConfig) {
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(imageOutputConfig, 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 = fetchMaxSize(ImageFormat.JPEG);
outputRatio = new Rational(maxJpegSize.getWidth(), maxJpegSize.getHeight());
break;
case TargetAspectRatio.RATIO_ORIGINAL:
Size targetSize = getTargetSize(imageOutputConfig);
if (imageOutputConfig.hasTargetAspectRatio()) {
@AspectRatio.Ratio
int aspectRatio = imageOutputConfig.getTargetAspectRatio();
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);
}
} else if (targetSize != null) {
// Target size is calculated from the target resolution. If target size is not
// null, sizes which aspect ratio is nearest to the aspect ratio of target size
// will be selected in priority.
outputRatio = new Rational(targetSize.getWidth(), targetSize.getHeight());
}
break;
default:
// Unhandled event.
}
return outputRatio;
}
SurfaceSizeDefinition getSurfaceSizeDefinition() {
return mSurfaceSizeDefinition;
}
private Size fetchMaxSize(int imageFormat) {
Size size = mMaxSizeCache.get(imageFormat);
if (size != null) {
return size;
}
Size maxSize = getMaxOutputSizeByFormat(imageFormat);
mMaxSizeCache.put(imageFormat, maxSize);
return maxSize;
}
private List<Integer> getUseCasesPriorityOrder(List<UseCaseConfig<?>> newUseCaseConfigs) {
List<Integer> priorityOrder = new ArrayList<>();
/*
* Once the stream resource is occupied by one use case, it will impact the other use cases.
* Therefore, we need to define the priority for stream resource usage. For the use cases
* with the higher priority, we will try to find the best one for them in priority as
* possible.
*/
List<Integer> priorityValueList = new ArrayList<>();
for (UseCaseConfig<?> config : newUseCaseConfigs) {
int priority = config.getSurfaceOccupancyPriority(0);
if (!priorityValueList.contains(priority)) {
priorityValueList.add(priority);
}
}
Collections.sort(priorityValueList);
// Reverse the priority value list in descending order since larger value means higher
// priority
Collections.reverse(priorityValueList);
for (int priorityValue : priorityValueList) {
for (UseCaseConfig<?> config : newUseCaseConfigs) {
if (priorityValue == config.getSurfaceOccupancyPriority(0)) {
priorityOrder.add(newUseCaseConfigs.indexOf(config));
}
}
}
return priorityOrder;
}
@NonNull
@VisibleForTesting
List<Size> getSupportedOutputSizes(@NonNull UseCaseConfig<?> config) {
int imageFormat = config.getInputFormat();
ImageOutputConfig imageOutputConfig = (ImageOutputConfig) config;
Size[] outputSizes = getCustomizedSupportSizesFromConfig(imageFormat, imageOutputConfig);
if (outputSizes == null) {
outputSizes = getAllOutputSizesByFormat(imageFormat);
}
List<Size> outputSizeCandidates = new ArrayList<>();
Size maxSize = imageOutputConfig.getMaxResolution(null);
Size maxOutputSizeByFormat = getMaxOutputSizeByFormat(imageFormat);
// Set maxSize as the max resolution setting or the max supported output size for the
// image format, whichever is smaller.
if (maxSize == null || getArea(maxOutputSizeByFormat) < getArea(maxSize)) {
maxSize = maxOutputSizeByFormat;
}
// Sort the output sizes. The Comparator result must be reversed to have a descending order
// result.
Arrays.sort(outputSizes, new CompareSizesByArea(true));
Size targetSize = getTargetSize(imageOutputConfig);
Size minSize = DEFAULT_SIZE;
int defaultSizeArea = getArea(DEFAULT_SIZE);
int maxSizeArea = getArea(maxSize);
// When maxSize is smaller than 640x480, set minSize as 0x0. It means the min size bound
// will be ignored. Otherwise, set the minimal size according to min(DEFAULT_SIZE,
// TARGET_RESOLUTION).
if (maxSizeArea < defaultSizeArea) {
minSize = ZERO_SIZE;
} else if (targetSize != null && getArea(targetSize) < defaultSizeArea) {
minSize = targetSize;
}
// Filter out the ones that exceed the maximum size and the minimum size. The output
// sizes candidates list won't have duplicated items.
for (Size outputSize : outputSizes) {
if (getArea(outputSize) <= getArea(maxSize)
&& getArea(outputSize) >= getArea(minSize)
&& !outputSizeCandidates.contains(outputSize)) {
outputSizeCandidates.add(outputSize);
}
}
if (outputSizeCandidates.isEmpty()) {
throw new IllegalArgumentException(
"Can not get supported output size under supported maximum for the format: " + imageFormat);
}
Rational aspectRatio = getTargetAspectRatio(imageOutputConfig);
// Check the default resolution if the target resolution is not set
targetSize = targetSize == null ? imageOutputConfig.getDefaultResolution(null) : targetSize;
List<Size> supportedResolutions = new ArrayList<>();
Map<Rational, List<Size>> aspectRatioSizeListMap = new HashMap<>();
if (aspectRatio == null) {
// If no target aspect ratio is set, all sizes can be added to the result list
// directly. No need to sort again since the source list has been sorted previously.
supportedResolutions.addAll(outputSizeCandidates);
// If the target resolution is set, use it to remove unnecessary larger sizes.
if (targetSize != null) {
removeSupportedSizesByTargetSize(supportedResolutions, targetSize);
}
} else {
// 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.
aspectRatioSizeListMap = groupSizesByAspectRatio(outputSizeCandidates);
// If the target resolution is set, use it to remove unnecessary larger sizes.
if (targetSize != null) {
// Remove unnecessary larger sizes from each aspect ratio size list
for (Rational key : aspectRatioSizeListMap.keySet()) {
removeSupportedSizesByTargetSize(aspectRatioSizeListMap.get(key), targetSize);
}
}
// Sort the aspect ratio key set by the target aspect ratio.
List<Rational> aspectRatios = new ArrayList<>(aspectRatioSizeListMap.keySet());
Collections.sort(aspectRatios, new CompareAspectRatiosByDistanceToTargetRatio(aspectRatio));
// 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 (!supportedResolutions.contains(size)) {
supportedResolutions.add(size);
}
}
}
}
return supportedResolutions;
}
@Nullable
private Size getTargetSize(@NonNull ImageOutputConfig imageOutputConfig) {
int targetRotation = imageOutputConfig.getTargetRotation(Surface.ROTATION_0);
// Calibrate targetSize by the target rotation value.
Size targetSize = imageOutputConfig.getTargetResolution(null);
targetSize = flipSizeByRotation(targetSize, targetRotation);
return targetSize;
}
// Use target rotation to calibrate the size.
@Nullable
private Size flipSizeByRotation(@Nullable Size size, int targetRotation) {
Size outputSize = size;
// Calibrates the size with the display and sensor rotation degrees values.
if (size != null && isRotationNeeded(targetRotation)) {
outputSize = new Size(/* width= */size.getHeight(), /* height= */size.getWidth());
}
return outputSize;
}
private boolean isRotationNeeded(int targetRotation) {
Integer sensorOrientation = mCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
Preconditions.checkNotNull(sensorOrientation,
"Camera HAL in bad state, unable to " + "retrieve the SENSOR_ORIENTATION");
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.
Integer lensFacing = mCharacteristics.get(CameraCharacteristics.LENS_FACING);
Preconditions.checkNotNull(lensFacing, "Camera HAL in bad state, unable to retrieve the " + "LENS_FACING");
boolean isOppositeFacingScreen = CameraCharacteristics.LENS_FACING_BACK == lensFacing;
int sensorRotationDegrees =
CameraOrientationUtil.getRelativeImageRotation(relativeRotationDegrees, sensorOrientation,
isOppositeFacingScreen);
return sensorRotationDegrees == 90 || sensorRotationDegrees == 270;
}
private boolean isSensorLandscapeResolution() {
Size pixelArraySize = mCharacteristics.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() : true;
}
static boolean hasMatchingAspectRatio(Size resolution, Rational aspectRatio) {
boolean isMatch = false;
if (aspectRatio == null) {
isMatch = false;
} else if (aspectRatio.equals(new Rational(resolution.getWidth(), resolution.getHeight()))) {
isMatch = true;
} else if (getArea(resolution) >= getArea(DEFAULT_SIZE)) {
// Only do mod 16 calculation if the size is equal to or larger than 640x480. It is
// because the aspect ratio will be affected critically by mod 16 calculation if the
// size is small. It may result in unexpected outcome such like 256x144 will be
// considered as 18.5:9.
isMatch = isPossibleMod16FromAspectRatio(resolution, aspectRatio);
}
return isMatch;
}
/**
* For codec performance improvement, OEMs may make the supported sizes to be mod16 alignment
* . It means that the width or height of the supported size will be multiple of 16. The
* result number after applying mod16 alignment can be the larger or smaller number that is
* multiple of 16 and is closest to the original number. For example, a standard 16:9
* supported size is 1920x1080. It may become 1920x1088 on some devices because 1088 is
* multiple of 16. This function uses the target aspect ratio to calculate the possible
* original width or height inversely. And then, checks whether the possibly original width or
* height is in the range that the mod16 aligned height or width can support.
*/
private static boolean isPossibleMod16FromAspectRatio(Size resolution, Rational aspectRatio) {
int width = resolution.getWidth();
int height = resolution.getHeight();
Rational invAspectRatio = new Rational(/* numerator= */aspectRatio.getDenominator(),
/* denominator= */aspectRatio.getNumerator());
if (width % 16 == 0 && height % 16 == 0) {
return ratioIntersectsMod16Segment(Math.max(0, height - ALIGN16), width, aspectRatio)
|| ratioIntersectsMod16Segment(Math.max(0, width - ALIGN16), height, invAspectRatio);
} else if (width % 16 == 0) {
return ratioIntersectsMod16Segment(height, width, aspectRatio);
} else if (height % 16 == 0) {
return ratioIntersectsMod16Segment(width, height, invAspectRatio);
}
return false;
}
private static int getArea(Size size) {
return size.getWidth() * size.getHeight();
}
private static boolean ratioIntersectsMod16Segment(int height, int mod16Width, Rational aspectRatio) {
Preconditions.checkArgument(mod16Width % 16 == 0);
double aspectRatioWidth = height * aspectRatio.getNumerator() / (double) aspectRatio.getDenominator();
return aspectRatioWidth > Math.max(0, mod16Width - ALIGN16) && aspectRatioWidth < (mod16Width + ALIGN16);
}
private Map<Rational, List<Size>> groupSizesByAspectRatio(List<Size> sizes) {
Map<Rational, List<Size>> aspectRatioSizeListMap = new HashMap<>();
// Add 4:3 and 16:9 entries first. Most devices should mainly have supported sizes of
// these two aspect ratios. Adding them first can avoid that if the first one 4:3 or 16:9
// size is a mod16 alignment size, the aspect ratio key may be different from the 4:3 or
// 16:9 value.
aspectRatioSizeListMap.put(ASPECT_RATIO_4_3, new ArrayList<>());
aspectRatioSizeListMap.put(ASPECT_RATIO_16_9, new ArrayList<>());
for (Size outputSize : sizes) {
Rational matchedKey = null;
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)) {
matchedKey = key;
List<Size> sizeList = aspectRatioSizeListMap.get(matchedKey);
if (!sizeList.contains(outputSize)) {
sizeList.add(outputSize);
}
}
}
// Create new item if no matching group is found.
if (matchedKey == null) {
aspectRatioSizeListMap.put(new Rational(outputSize.getWidth(), outputSize.getHeight()),
new ArrayList<>(Collections.singleton(outputSize)));
}
}
return aspectRatioSizeListMap;
}
/**
* 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 targetSize The target size used to remove unnecessary sizes.
*/
private void removeSupportedSizesByTargetSize(List<Size> supportedSizesList, Size targetSize) {
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() >= targetSize.getWidth() && outputSize.getHeight() >= targetSize.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);
}
private List<List<Size>> getAllPossibleSizeArrangements(List<List<Size>> supportedOutputSizesList) {
int totalArrangementsCount = 1;
for (List<Size> supportedOutputSizes : supportedOutputSizesList) {
totalArrangementsCount *= supportedOutputSizes.size();
}
// If totalArrangementsCount is 0 means that there may some problem to get
// supportedOutputSizes
// for some use case
if (totalArrangementsCount == 0) {
throw new IllegalArgumentException("Failed to find supported resolutions.");
}
List<List<Size>> allPossibleSizeArrangements = new ArrayList<>();
// Initialize allPossibleSizeArrangements for the following operations
for (int i = 0; i < totalArrangementsCount; i++) {
List<Size> sizeList = new ArrayList<>();
allPossibleSizeArrangements.add(sizeList);
}
/*
* Try to list out all possible arrangements by attaching all possible size of each column
* in sequence. We have generated supportedOutputSizesList by the priority order for
* different use cases. And the supported outputs sizes for each use case are also arranged
* from large to small. Therefore, the earlier size arrangement in the result list will be
* the better one to choose if finally it won't exceed the camera device's stream
* combination capability.
*/
int currentRunCount = totalArrangementsCount;
int nextRunCount = currentRunCount / supportedOutputSizesList.get(0).size();
for (int currentIndex = 0; currentIndex < supportedOutputSizesList.size(); currentIndex++) {
List<Size> supportedOutputSizes = supportedOutputSizesList.get(currentIndex);
for (int i = 0; i < totalArrangementsCount; i++) {
List<Size> surfaceConfigList = allPossibleSizeArrangements.get(i);
surfaceConfigList.add(supportedOutputSizes.get((i % currentRunCount) / nextRunCount));
}
if (currentIndex < supportedOutputSizesList.size() - 1) {
currentRunCount = nextRunCount;
nextRunCount = currentRunCount / supportedOutputSizesList.get(currentIndex + 1).size();
}
}
return allPossibleSizeArrangements;
}
@NonNull
private Size[] excludeProblematicSizes(@NonNull Size[] outputSizes, int imageFormat) {
List<Size> excludedSizes = fetchExcludedSizes(imageFormat);
List<Size> resultSizesList = new ArrayList<>(Arrays.asList(outputSizes));
resultSizesList.removeAll(excludedSizes);
return resultSizesList.toArray(new Size[0]);
}
@Nullable
private Size[] getCustomizedSupportSizesFromConfig(int imageFormat, @NonNull ImageOutputConfig config) {
Size[] outputSizes = null;
// Try to retrieve customized supported resolutions from config.
List<Pair<Integer, Size[]>> formatResolutionsPairList = config.getSupportedResolutions(null);
if (formatResolutionsPairList != null) {
for (Pair<Integer, Size[]> formatResolutionPair : formatResolutionsPairList) {
if (formatResolutionPair.first == imageFormat) {
outputSizes = formatResolutionPair.second;
break;
}
}
}
if (outputSizes != null) {
outputSizes = excludeProblematicSizes(outputSizes, imageFormat);
// Sort the output sizes. The Comparator result must be reversed to have a descending
// order result.
Arrays.sort(outputSizes, new CompareSizesByArea(true));
}
return outputSizes;
}
@NonNull
private Size[] getAllOutputSizesByFormat(int imageFormat) {
Size[] outputs = mOutputSizesCache.get(imageFormat);
if (outputs == null) {
outputs = doGetAllOutputSizesByFormat(imageFormat);
mOutputSizesCache.put(imageFormat, outputs);
}
return outputs;
}
@NonNull
private Size[] doGetAllOutputSizesByFormat(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);
}
outputSizes = excludeProblematicSizes(outputSizes, imageFormat);
// Sort the output sizes. The Comparator result must be reversed to have a descending order
// result.
Arrays.sort(outputSizes, new CompareSizesByArea(true));
return outputSizes;
}
/**
* Get max supported output size for specific image format
*
* @param imageFormat the image format info
* @return the max supported output size for the image format
*/
Size getMaxOutputSizeByFormat(int imageFormat) {
Size[] outputSizes = getAllOutputSizesByFormat(imageFormat);
return Collections.max(Arrays.asList(outputSizes), new CompareSizesByArea());
}
List<SurfaceCombination> getLegacySupportedCombinationList() {
List<SurfaceCombination> combinationList = new ArrayList<>();
// (PRIV, MAXIMUM)
SurfaceCombination surfaceCombination1 = new SurfaceCombination();
surfaceCombination1.addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.MAXIMUM));
combinationList.add(surfaceCombination1);
// (JPEG, MAXIMUM)
SurfaceCombination surfaceCombination2 = new SurfaceCombination();
surfaceCombination2.addSurfaceConfig(SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
combinationList.add(surfaceCombination2);
// (YUV, MAXIMUM)
SurfaceCombination surfaceCombination3 = new SurfaceCombination();
surfaceCombination3.addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
combinationList.add(surfaceCombination3);
// Below two combinations are all supported in the combination
// (PRIV, PREVIEW) + (JPEG, MAXIMUM)
SurfaceCombination surfaceCombination4 = new SurfaceCombination();
surfaceCombination4.addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
surfaceCombination4.addSurfaceConfig(SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
combinationList.add(surfaceCombination4);
// (YUV, PREVIEW) + (JPEG, MAXIMUM)
SurfaceCombination surfaceCombination5 = new SurfaceCombination();
surfaceCombination5.addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
surfaceCombination5.addSurfaceConfig(SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
combinationList.add(surfaceCombination5);
// (PRIV, PREVIEW) + (PRIV, PREVIEW)
SurfaceCombination surfaceCombination6 = new SurfaceCombination();
surfaceCombination6.addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
surfaceCombination6.addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
combinationList.add(surfaceCombination6);
// (PRIV, PREVIEW) + (YUV, PREVIEW)
SurfaceCombination surfaceCombination7 = new SurfaceCombination();
surfaceCombination7.addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
surfaceCombination7.addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
combinationList.add(surfaceCombination7);
// (PRIV, PREVIEW) + (PRIV, PREVIEW) + (JPEG, MAXIMUM)
SurfaceCombination surfaceCombination8 = new SurfaceCombination();
surfaceCombination8.addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
surfaceCombination8.addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
surfaceCombination8.addSurfaceConfig(SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
combinationList.add(surfaceCombination8);
return combinationList;
}
List<SurfaceCombination> getLimitedSupportedCombinationList() {
List<SurfaceCombination> combinationList = new ArrayList<>();
// (PRIV, PREVIEW) + (PRIV, RECORD)
SurfaceCombination surfaceCombination1 = new SurfaceCombination();
surfaceCombination1.addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
surfaceCombination1.addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.RECORD));
combinationList.add(surfaceCombination1);
// (PRIV, PREVIEW) + (YUV, RECORD)
SurfaceCombination surfaceCombination2 = new SurfaceCombination();
surfaceCombination2.addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
surfaceCombination2.addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.RECORD));
combinationList.add(surfaceCombination2);
// (YUV, PREVIEW) + (YUV, RECORD)
SurfaceCombination surfaceCombination3 = new SurfaceCombination();
surfaceCombination3.addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
surfaceCombination3.addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.RECORD));
combinationList.add(surfaceCombination3);
// (PRIV, PREVIEW) + (PRIV, RECORD) + (JPEG, RECORD)
SurfaceCombination surfaceCombination4 = new SurfaceCombination();
surfaceCombination4.addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
surfaceCombination4.addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.RECORD));
surfaceCombination4.addSurfaceConfig(SurfaceConfig.create(ConfigType.JPEG, ConfigSize.RECORD));
combinationList.add(surfaceCombination4);
// (PRIV, PREVIEW) + (YUV, RECORD) + (JPEG, RECORD)
SurfaceCombination surfaceCombination5 = new SurfaceCombination();
surfaceCombination5.addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
surfaceCombination5.addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.RECORD));
surfaceCombination5.addSurfaceConfig(SurfaceConfig.create(ConfigType.JPEG, ConfigSize.RECORD));
combinationList.add(surfaceCombination5);
// (YUV, PREVIEW) + (YUV, PREVIEW) + (JPEG, MAXIMUM)
SurfaceCombination surfaceCombination6 = new SurfaceCombination();
surfaceCombination6.addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
surfaceCombination6.addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
surfaceCombination6.addSurfaceConfig(SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
combinationList.add(surfaceCombination6);
return combinationList;
}
List<SurfaceCombination> getFullSupportedCombinationList() {
List<SurfaceCombination> combinationList = new ArrayList<>();
// (PRIV, PREVIEW) + (PRIV, MAXIMUM)
SurfaceCombination surfaceCombination1 = new SurfaceCombination();
surfaceCombination1.addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
surfaceCombination1.addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.MAXIMUM));
combinationList.add(surfaceCombination1);
// (PRIV, PREVIEW) + (YUV, MAXIMUM)
SurfaceCombination surfaceCombination2 = new SurfaceCombination();
surfaceCombination2.addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
surfaceCombination2.addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
combinationList.add(surfaceCombination2);
// (YUV, PREVIEW) + (YUV, MAXIMUM)
SurfaceCombination surfaceCombination3 = new SurfaceCombination();
surfaceCombination3.addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
surfaceCombination3.addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
combinationList.add(surfaceCombination3);
// (PRIV, PREVIEW) + (PRIV, PREVIEW) + (JPEG, MAXIMUM)
SurfaceCombination surfaceCombination4 = new SurfaceCombination();
surfaceCombination4.addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
surfaceCombination4.addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
surfaceCombination4.addSurfaceConfig(SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
combinationList.add(surfaceCombination4);
// (YUV, ANALYSIS) + (PRIV, PREVIEW) + (YUV, MAXIMUM)
SurfaceCombination surfaceCombination5 = new SurfaceCombination();
surfaceCombination5.addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.ANALYSIS));
surfaceCombination5.addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
surfaceCombination5.addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
combinationList.add(surfaceCombination5);
// (YUV, ANALYSIS) + (YUV, PREVIEW) + (YUV, MAXIMUM)
SurfaceCombination surfaceCombination6 = new SurfaceCombination();
surfaceCombination6.addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.ANALYSIS));
surfaceCombination6.addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
surfaceCombination6.addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
combinationList.add(surfaceCombination6);
return combinationList;
}
List<SurfaceCombination> getRAWSupportedCombinationList() {
List<SurfaceCombination> combinationList = new ArrayList<>();
// (RAW, MAXIMUM)
SurfaceCombination surfaceCombination1 = new SurfaceCombination();
surfaceCombination1.addSurfaceConfig(SurfaceConfig.create(ConfigType.RAW, ConfigSize.MAXIMUM));
combinationList.add(surfaceCombination1);
// (PRIV, PREVIEW) + (RAW, MAXIMUM)
SurfaceCombination surfaceCombination2 = new SurfaceCombination();
surfaceCombination2.addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
surfaceCombination2.addSurfaceConfig(SurfaceConfig.create(ConfigType.RAW, ConfigSize.MAXIMUM));
combinationList.add(surfaceCombination2);
// (YUV, PREVIEW) + (RAW, MAXIMUM)
SurfaceCombination surfaceCombination3 = new SurfaceCombination();
surfaceCombination3.addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
surfaceCombination3.addSurfaceConfig(SurfaceConfig.create(ConfigType.RAW, ConfigSize.MAXIMUM));
combinationList.add(surfaceCombination3);
// (PRIV, PREVIEW) + (PRIV, PREVIEW) + (RAW, MAXIMUM)
SurfaceCombination surfaceCombination4 = new SurfaceCombination();
surfaceCombination4.addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
surfaceCombination4.addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
surfaceCombination4.addSurfaceConfig(SurfaceConfig.create(ConfigType.RAW, ConfigSize.MAXIMUM));
combinationList.add(surfaceCombination4);
// (PRIV, PREVIEW) + (YUV, PREVIEW) + (RAW, MAXIMUM)
SurfaceCombination surfaceCombination5 = new SurfaceCombination();
surfaceCombination5.addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
surfaceCombination5.addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
surfaceCombination5.addSurfaceConfig(SurfaceConfig.create(ConfigType.RAW, ConfigSize.MAXIMUM));
combinationList.add(surfaceCombination5);
// (YUV, PREVIEW) + (YUV, PREVIEW) + (RAW, MAXIMUM)
SurfaceCombination surfaceCombination6 = new SurfaceCombination();
surfaceCombination6.addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
surfaceCombination6.addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
surfaceCombination6.addSurfaceConfig(SurfaceConfig.create(ConfigType.RAW, ConfigSize.MAXIMUM));
combinationList.add(surfaceCombination6);
// (PRIV, PREVIEW) + (JPEG, MAXIMUM) + (RAW, MAXIMUM)
SurfaceCombination surfaceCombination7 = new SurfaceCombination();
surfaceCombination7.addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
surfaceCombination7.addSurfaceConfig(SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
surfaceCombination7.addSurfaceConfig(SurfaceConfig.create(ConfigType.RAW, ConfigSize.MAXIMUM));
combinationList.add(surfaceCombination7);
// (YUV, PREVIEW) + (JPEG, MAXIMUM) + (RAW, MAXIMUM)
SurfaceCombination surfaceCombination8 = new SurfaceCombination();
surfaceCombination8.addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
surfaceCombination8.addSurfaceConfig(SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
surfaceCombination8.addSurfaceConfig(SurfaceConfig.create(ConfigType.RAW, ConfigSize.MAXIMUM));
combinationList.add(surfaceCombination8);
return combinationList;
}
List<SurfaceCombination> getBurstSupportedCombinationList() {
List<SurfaceCombination> combinationList = new ArrayList<>();
// (PRIV, PREVIEW) + (PRIV, MAXIMUM)
SurfaceCombination surfaceCombination1 = new SurfaceCombination();
surfaceCombination1.addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
surfaceCombination1.addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.MAXIMUM));
combinationList.add(surfaceCombination1);
// (PRIV, PREVIEW) + (YUV, MAXIMUM)
SurfaceCombination surfaceCombination2 = new SurfaceCombination();
surfaceCombination2.addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
surfaceCombination2.addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
combinationList.add(surfaceCombination2);
// (YUV, PREVIEW) + (YUV, MAXIMUM)
SurfaceCombination surfaceCombination3 = new SurfaceCombination();
surfaceCombination3.addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
surfaceCombination3.addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
combinationList.add(surfaceCombination3);
return combinationList;
}
List<SurfaceCombination> getLevel3SupportedCombinationList() {
List<SurfaceCombination> combinationList = new ArrayList<>();
// (PRIV, PREVIEW) + (PRIV, ANALYSIS) + (YUV, MAXIMUM) + (RAW, MAXIMUM)
SurfaceCombination surfaceCombination1 = new SurfaceCombination();
surfaceCombination1.addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
surfaceCombination1.addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.ANALYSIS));
surfaceCombination1.addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
surfaceCombination1.addSurfaceConfig(SurfaceConfig.create(ConfigType.RAW, ConfigSize.MAXIMUM));
combinationList.add(surfaceCombination1);
// (PRIV, PREVIEW) + (PRIV, ANALYSIS) + (JPEG, MAXIMUM) + (RAW, MAXIMUM)
SurfaceCombination surfaceCombination2 = new SurfaceCombination();
surfaceCombination2.addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
surfaceCombination2.addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.ANALYSIS));
surfaceCombination2.addSurfaceConfig(SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
surfaceCombination2.addSurfaceConfig(SurfaceConfig.create(ConfigType.RAW, ConfigSize.MAXIMUM));
combinationList.add(surfaceCombination2);
return combinationList;
}
private void generateSupportedCombinationList() {
mSurfaceCombinations.addAll(getLegacySupportedCombinationList());
if (mHardwareLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
|| mHardwareLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL
|| mHardwareLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3) {
mSurfaceCombinations.addAll(getLimitedSupportedCombinationList());
}
if (mHardwareLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL
|| mHardwareLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3) {
mSurfaceCombinations.addAll(getFullSupportedCombinationList());
}
int[] availableCapabilities = mCharacteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
if (availableCapabilities != null) {
for (int capability : availableCapabilities) {
if (capability == CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW) {
mIsRawSupported = true;
} else if (capability == CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE) {
mIsBurstCaptureSupported = true;
}
}
}
if (mIsRawSupported) {
mSurfaceCombinations.addAll(getRAWSupportedCombinationList());
}
if (mIsBurstCaptureSupported && mHardwareLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED) {
mSurfaceCombinations.addAll(getBurstSupportedCombinationList());
}
if (mHardwareLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3) {
mSurfaceCombinations.addAll(getLevel3SupportedCombinationList());
}
mSurfaceCombinations.addAll(mExtraSupportedSurfaceCombinationsContainer.get());
}
private void checkCustomization() {
// Modification
// Add the following SurfaceConfig to bypass the legacy camera hardware limitation.
final SurfaceCombination surfaceCombination = new SurfaceCombination();
surfaceCombination.addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
surfaceCombination.addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
surfaceCombination.addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
mSurfaceCombinations.add(surfaceCombination);
// TODO(b/119466260): Integrate found feasible stream combinations into supported list
}
// Utility classes and methods:
// *********************************************************************************************
private void generateSurfaceSizeDefinition(WindowManager windowManager) {
Size analysisSize = new Size(640, 480);
Size previewSize = getPreviewSize(windowManager);
Size recordSize = getRecordSize();
mSurfaceSizeDefinition = SurfaceSizeDefinition.create(analysisSize, previewSize, recordSize);
}
/**
* PREVIEW refers to the best size match to the device's screen resolution, or to 1080p
* (1920x1080), whichever is smaller.
*/
@SuppressWarnings("deprecation") /* defaultDisplay */
@NonNull
public static Size getPreviewSize(@NonNull WindowManager windowManager) {
Point displaySize = new Point();
windowManager.getDefaultDisplay().getRealSize(displaySize);
Size displayViewSize;
if (displaySize.x > displaySize.y) {
displayViewSize = new Size(displaySize.x, displaySize.y);
} else {
displayViewSize = new Size(displaySize.y, displaySize.x);
}
// Limit the max preview size to under min(display size, 1080P) by comparing the area size
Size previewSize = Collections.min(
Arrays.asList(new Size(displayViewSize.getWidth(), displayViewSize.getHeight()), MAX_PREVIEW_SIZE),
new CompareSizesByArea());
return previewSize;
}
/**
* RECORD refers to the camera device's maximum supported recording resolution, as determined by
* CamcorderProfile.
*/
@NonNull
private Size getRecordSize() {
int cameraId;
try {
cameraId = Integer.parseInt(mCameraId);
} catch (NumberFormatException e) {
// The camera Id is not an integer because the camera may be a removable device. Use
// StreamConfigurationMap to determine the RECORD size.
return getRecordSizeFromStreamConfigurationMap();
}
CamcorderProfile profile = null;
if (mCamcorderProfileHelper.hasProfile(cameraId, CamcorderProfile.QUALITY_HIGH)) {
profile = mCamcorderProfileHelper.get(cameraId, CamcorderProfile.QUALITY_HIGH);
}
if (profile != null) {
return new Size(profile.videoFrameWidth, profile.videoFrameHeight);
}
return getRecordSizeByHasProfile(cameraId);
}
/**
* Return the maximum supported video size for cameras using data from the stream
* configuration map.
*
* @return Maximum supported video size.
*/
@NonNull
private Size getRecordSizeFromStreamConfigurationMap() {
StreamConfigurationMap map = mCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (map == null) {
throw new IllegalArgumentException("Can not retrieve SCALER_STREAM_CONFIGURATION_MAP");
}
Size[] videoSizeArr = map.getOutputSizes(MediaRecorder.class);
if (videoSizeArr == null) {
return QUALITY_480P_SIZE;
}
Arrays.sort(videoSizeArr, new CompareSizesByArea(true));
for (Size size : videoSizeArr) {
// Returns the largest supported size under 1080P
if (size.getWidth() <= QUALITY_1080P_SIZE.getWidth() && size.getHeight() <= QUALITY_1080P_SIZE.getHeight()) {
return size;
}
}
return QUALITY_480P_SIZE;
}
/**
* Return the maximum supported video size for cameras by
* {@link CamcorderProfile#hasProfile(int, int)}.
*
* @return Maximum supported video size.
*/
@NonNull
private Size getRecordSizeByHasProfile(int cameraId) {
Size recordSize = QUALITY_480P_SIZE;
CamcorderProfile profile = null;
// Check whether 4KDCI, 2160P, 2K, 1080P, 720P, 480P (sorted by size) are supported by
// CamcorderProfile
if (mCamcorderProfileHelper.hasProfile(cameraId, CamcorderProfile.QUALITY_2160P)) {
profile = mCamcorderProfileHelper.get(cameraId, CamcorderProfile.QUALITY_2160P);
} else if (mCamcorderProfileHelper.hasProfile(cameraId, CamcorderProfile.QUALITY_1080P)) {
profile = mCamcorderProfileHelper.get(cameraId, CamcorderProfile.QUALITY_1080P);
} else if (mCamcorderProfileHelper.hasProfile(cameraId, CamcorderProfile.QUALITY_720P)) {
profile = mCamcorderProfileHelper.get(cameraId, CamcorderProfile.QUALITY_720P);
} else if (mCamcorderProfileHelper.hasProfile(cameraId, CamcorderProfile.QUALITY_480P)) {
profile = mCamcorderProfileHelper.get(cameraId, CamcorderProfile.QUALITY_480P);
}
if (profile != null) {
recordSize = new Size(profile.videoFrameWidth, profile.videoFrameHeight);
}
return recordSize;
}
@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;
}
/** Comparator based on area of the given {@link Size} objects. */
static final class CompareSizesByArea implements Comparator<Size> {
private boolean mReverse = false;
CompareSizesByArea() {
}
CompareSizesByArea(boolean reverse) {
mReverse = reverse;
}
@Override
public int compare(Size lhs, Size rhs) {
// We cast here to ensure the multiplications won't overflow
int result = Long.signum((long) lhs.getWidth() * lhs.getHeight() - (long) rhs.getWidth() * rhs.getHeight());
if (mReverse) {
result *= -1;
}
return result;
}
}
/** Comparator based on how close they are to the target aspect ratio. */
static final class CompareAspectRatiosByDistanceToTargetRatio implements Comparator<Rational> {
private Rational mTargetRatio;
CompareAspectRatiosByDistanceToTargetRatio(Rational targetRatio) {
mTargetRatio = targetRatio;
}
@Override
public int compare(Rational lhs, Rational rhs) {
if (lhs.equals(rhs)) {
return 0;
}
final Float lhsRatioDelta = Math.abs(lhs.floatValue() - mTargetRatio.floatValue());
final Float rhsRatioDelta = Math.abs(rhs.floatValue() - mTargetRatio.floatValue());
int result = (int) Math.signum(lhsRatioDelta - rhsRatioDelta);
return result;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment