Merge "Filter suggested resolution with attachedSurfaceResolution class." into androidx-main
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java
index 5d53087..32e8f86 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java
@@ -41,6 +41,7 @@
import android.media.CamcorderProfile;
import android.media.MediaRecorder;
import android.util.Pair;
+import android.util.Range;
import android.util.Rational;
import android.util.Size;
import android.view.Surface;
@@ -209,10 +210,64 @@
return SurfaceConfig.transformSurfaceConfig(imageFormat, size, mSurfaceSizeDefinition);
}
+ static int getMaxFramerate(CameraCharacteristicsCompat characteristics, int imageFormat,
+ Size size) {
+ int maxFramerate = 0;
+ try {
+ maxFramerate = (int) (1000000000.0
+ / characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
+ .getOutputMinFrameDuration(imageFormat,
+ size));
+ } catch (Exception e) {
+ //TODO
+ //this try catch is in place for the rare that a surface config has a size
+ // incompatible for getOutputMinFrameDuration... put into a Quirk
+ }
+ return maxFramerate;
+ }
+
+ /**
+ * @param newTargetFramerate an incoming framerate range
+ * @param storedTargetFramerate a stored framerate range to be modified
+ * @return adjusted target frame rate
+ *
+ * If the two ranges are both nonnull and disjoint of each other, then the range that was
+ * already stored will be used
+ */
+ private Range<Integer> getUpdatedTargetFramerate(Range<Integer> newTargetFramerate,
+ Range<Integer> storedTargetFramerate) {
+ Range<Integer> updatedTarget = storedTargetFramerate;
+
+ if (storedTargetFramerate == null) {
+ // if stored value was null before, set it to the new value
+ updatedTarget = newTargetFramerate;
+ } else if (newTargetFramerate != null) {
+ try {
+ // get intersection of existing target fps
+ updatedTarget =
+ storedTargetFramerate
+ .intersect(newTargetFramerate);
+ } catch (IllegalArgumentException e) {
+ // no intersection, keep the previously stored value
+ updatedTarget = storedTargetFramerate;
+ }
+ }
+ return updatedTarget;
+ }
+
+ /**
+ * @param currentMaxFps the previously stored Max FPS
+ * @param imageFormat the image format of the incoming surface
+ * @param size the size of the incoming surface
+ */
+ private int getUpdatedMaximumFps(int currentMaxFps, int imageFormat, Size size) {
+ return Math.min(currentMaxFps, getMaxFramerate(mCharacteristics, imageFormat, size));
+ }
+
/**
* Finds the suggested stream specifications of the newly added UseCaseConfig.
*
- * @param existingSurfaces the existing surfaces.
+ * @param attachedSurfaces the existing surfaces.
* @param newUseCaseConfigs newly added UseCaseConfig.
* @return the suggested stream specifications, which is a mapping from UseCaseConfig to the
* suggested stream specification.
@@ -222,13 +277,13 @@
*/
@NonNull
Map<UseCaseConfig<?>, StreamSpec> getSuggestedStreamSpecifications(
- @NonNull List<AttachedSurfaceInfo> existingSurfaces,
+ @NonNull List<AttachedSurfaceInfo> attachedSurfaces,
@NonNull List<UseCaseConfig<?>> newUseCaseConfigs) {
// Refresh Preview Size based on current display configurations.
refreshPreviewSize();
List<SurfaceConfig> surfaceConfigs = new ArrayList<>();
- for (AttachedSurfaceInfo scc : existingSurfaces) {
- surfaceConfigs.add(scc.getSurfaceConfig());
+ for (AttachedSurfaceInfo attachedSurface : attachedSurfaces) {
+ surfaceConfigs.add(attachedSurface.getSurfaceConfig());
}
// Use the small size (640x480) for new use cases to check whether there is any possible
@@ -244,20 +299,31 @@
throw new IllegalArgumentException(
"No supported surface combination is found for camera device - Id : "
+ mCameraId + ". May be attempting to bind too many use cases. "
- + "Existing surfaces: " + existingSurfaces + " New configs: "
+ + "Existing surfaces: " + attachedSurfaces + " New configs: "
+ newUseCaseConfigs);
}
+ Range<Integer> targetFramerateForConfig = null;
+ int existingSurfaceFrameRateCeiling = Integer.MAX_VALUE;
+
+ for (AttachedSurfaceInfo attachedSurfaceInfo : attachedSurfaces) {
+ // init target fps range for new configs from existing surfaces
+ targetFramerateForConfig = getUpdatedTargetFramerate(
+ attachedSurfaceInfo.getTargetFrameRate(),
+ targetFramerateForConfig);
+ //get the fps ceiling for existing surfaces
+ existingSurfaceFrameRateCeiling = getUpdatedMaximumFps(
+ existingSurfaceFrameRateCeiling,
+ attachedSurfaceInfo.getImageFormat(), attachedSurfaceInfo.getSize());
+ }
+
// Get the index order list by the use case priority for finding stream configuration
- List<Integer> useCasesPriorityOrder =
- getUseCasesPriorityOrder(
- newUseCaseConfigs);
+ 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));
+ List<Size> supportedOutputSizes = getSupportedOutputSizes(newUseCaseConfigs.get(index));
supportedOutputSizesList.add(supportedOutputSizes);
}
@@ -266,13 +332,27 @@
getAllPossibleSizeArrangements(
supportedOutputSizesList);
- Map<UseCaseConfig<?>, StreamSpec> suggestedStreamSpecMap = null;
+ // update target fps for new configs using new use cases' priority order
+ for (Integer index : useCasesPriorityOrder) {
+ targetFramerateForConfig =
+ getUpdatedTargetFramerate(
+ newUseCaseConfigs.get(index).getTargetFramerate(null),
+ targetFramerateForConfig);
+ }
+
+ Map<UseCaseConfig<?>, StreamSpec> suggestedStreamSpecMap;
+ List<Size> savedSizes = null;
+ int savedConfigMaxFps = Integer.MAX_VALUE;
+
// 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<>();
- for (AttachedSurfaceInfo sc : existingSurfaces) {
- surfaceConfigList.add(sc.getSurfaceConfig());
+ int currentConfigFramerateCeiling = existingSurfaceFrameRateCeiling;
+ boolean isConfigFrameRateAcceptable = true;
+
+ for (AttachedSurfaceInfo attachedSurfaceInfo : attachedSurfaces) {
+ surfaceConfigList.add(attachedSurfaceInfo.getSurfaceConfig());
}
// Attach SurfaceConfig of new use cases
@@ -280,29 +360,66 @@
Size size = possibleSizeList.get(i);
UseCaseConfig<?> newUseCase =
newUseCaseConfigs.get(useCasesPriorityOrder.get(i));
+ // add new use case/size config to list of surfaces
surfaceConfigList.add(
SurfaceConfig.transformSurfaceConfig(newUseCase.getInputFormat(), size,
mSurfaceSizeDefinition));
+
+ // get the maximum fps of the new surface and update the maximum fps of the
+ // proposed configuration
+ currentConfigFramerateCeiling = getUpdatedMaximumFps(
+ currentConfigFramerateCeiling,
+ newUseCase.getInputFormat(),
+ size);
+ }
+ if (targetFramerateForConfig != null) {
+ if (existingSurfaceFrameRateCeiling > currentConfigFramerateCeiling
+ && currentConfigFramerateCeiling < targetFramerateForConfig.getLower()) {
+ // if the max fps before adding new use cases supports our target fps range
+ // BUT the max fps of the new configuration is below
+ // our target fps range, we'll want to check the next configuration until we
+ // get one that supports our target FPS
+ isConfigFrameRateAcceptable = false;
+ }
}
- // Check whether the SurfaceConfig combination can be supported
+ // only change the saved config if you get another that has a better max fps
if (checkSupported(surfaceConfigList)) {
- suggestedStreamSpecMap = new HashMap<>();
- for (UseCaseConfig<?> useCaseConfig : newUseCaseConfigs) {
- suggestedStreamSpecMap.put(
- useCaseConfig,
- StreamSpec.builder(possibleSizeList.get(useCasesPriorityOrder.indexOf(
- newUseCaseConfigs.indexOf(useCaseConfig)))).build());
+ // if the config is supported by the device but doesn't meet the target framerate,
+ // save the config
+ if (savedConfigMaxFps == Integer.MAX_VALUE) {
+ savedConfigMaxFps = currentConfigFramerateCeiling;
+ savedSizes = possibleSizeList;
+ } else if (savedConfigMaxFps < currentConfigFramerateCeiling) {
+ // only change the saved config if the max fps is better
+ savedConfigMaxFps = currentConfigFramerateCeiling;
+ savedSizes = possibleSizeList;
}
- break;
+
+ // if we have a configuration where the max fps is acceptable for our target, break
+ if (isConfigFrameRateAcceptable) {
+ savedConfigMaxFps = currentConfigFramerateCeiling;
+ savedSizes = possibleSizeList;
+ break;
+ }
}
}
- if (suggestedStreamSpecMap == null) {
+
+ // Map the saved supported SurfaceConfig combination
+ if (savedSizes != null) {
+ suggestedStreamSpecMap = new HashMap<>();
+ for (UseCaseConfig<?> useCaseConfig : newUseCaseConfigs) {
+ suggestedStreamSpecMap.put(
+ useCaseConfig,
+ StreamSpec.builder(savedSizes.get(useCasesPriorityOrder.indexOf(
+ newUseCaseConfigs.indexOf(useCaseConfig)))).build());
+ }
+ } else {
throw new IllegalArgumentException(
"No supported surface combination is found for camera device - Id : "
+ mCameraId + " and Hardware level: " + mHardwareLevel
+ ". May be the specified resolution is too large and not supported."
- + " Existing surfaces: " + existingSurfaces
+ + " Existing surfaces: " + attachedSurfaces
+ " New configs: " + newUseCaseConfigs);
}
return suggestedStreamSpecMap;
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedOutputSizesCollectorTest.kt b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedOutputSizesCollectorTest.kt
index 0691c5d..e7c1d634 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedOutputSizesCollectorTest.kt
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedOutputSizesCollectorTest.kt
@@ -60,6 +60,8 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Mockito
import org.robolectric.ParameterizedRobolectricTestRunner
import org.robolectric.Shadows
@@ -1242,6 +1244,35 @@
Mockito.`when`(it.getOutputSizes(MediaRecorder::class.java))
.thenReturn(supportedSizes)
+ // setup to return different minimum frame durations depending on resolution
+ // minimum frame durations were designated only for the purpose of testing
+ Mockito.`when`(it.getOutputMinFrameDuration(anyInt(), eq(Size(4032, 3024))))
+ .thenReturn(50000000L) // 20 fps, size maximum
+
+ Mockito.`when`(it.getOutputMinFrameDuration(anyInt(), eq(Size(3840, 2160))))
+ .thenReturn(40000000L) // 25, size record
+
+ Mockito.`when`(it.getOutputMinFrameDuration(anyInt(), eq(Size(1920, 1440))))
+ .thenReturn(30000000L) // 30
+
+ Mockito.`when`(it.getOutputMinFrameDuration(anyInt(), eq(Size(1920, 1080))))
+ .thenReturn(28000000L) // 35
+
+ Mockito.`when`(it.getOutputMinFrameDuration(anyInt(), eq(Size(1280, 960))))
+ .thenReturn(25000000L) // 40
+
+ Mockito.`when`(it.getOutputMinFrameDuration(anyInt(), eq(Size(1280, 720))))
+ .thenReturn(22000000L) // 45, size preview/display
+
+ Mockito.`when`(it.getOutputMinFrameDuration(anyInt(), eq(Size(960, 544))))
+ .thenReturn(20000000L) // 50
+
+ Mockito.`when`(it.getOutputMinFrameDuration(anyInt(), eq(Size(800, 450))))
+ .thenReturn(16666000L) // 60fps
+
+ Mockito.`when`(it.getOutputMinFrameDuration(anyInt(), eq(Size(640, 480))))
+ .thenReturn(16666000L) // 60fps
+
// Sets up the supported high resolution sizes
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Mockito.`when`(it.getHighResolutionOutputSizes(ArgumentMatchers.anyInt()))
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.kt b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.kt
index 3a0fcb1..760a818 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.kt
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.kt
@@ -15,6 +15,7 @@
* limitations under the License.
*/
package androidx.camera.camera2.internal
+
import android.content.Context
import android.graphics.ImageFormat
import android.hardware.camera2.CameraCharacteristics
@@ -22,6 +23,7 @@
import android.media.CamcorderProfile
import android.os.Build
import android.util.Pair
+import android.util.Range
import android.util.Rational
import android.util.Size
import android.view.Surface
@@ -30,6 +32,7 @@
import androidx.camera.camera2.Camera2Config
import androidx.camera.camera2.internal.SupportedOutputSizesCollectorTest.Companion.createUseCaseByResolutionSelector
import androidx.camera.camera2.internal.SupportedOutputSizesCollectorTest.Companion.setupCamera
+import androidx.camera.camera2.internal.compat.CameraAccessExceptionCompat
import androidx.camera.camera2.internal.compat.CameraManagerCompat
import androidx.camera.core.AspectRatio
import androidx.camera.core.CameraSelector.LensFacing
@@ -41,6 +44,7 @@
import androidx.camera.core.Preview
import androidx.camera.core.SurfaceRequest
import androidx.camera.core.UseCase
+import androidx.camera.core.impl.AttachedSurfaceInfo
import androidx.camera.core.impl.CameraDeviceSurfaceManager
import androidx.camera.core.impl.CameraFactory
import androidx.camera.core.impl.MutableStateObservable
@@ -51,6 +55,7 @@
import androidx.camera.core.impl.SurfaceConfig.ConfigSize
import androidx.camera.core.impl.SurfaceConfig.ConfigType
import androidx.camera.core.impl.UseCaseConfig
+import androidx.camera.core.impl.UseCaseConfig.OPTION_TARGET_FRAME_RATE
import androidx.camera.core.impl.UseCaseConfigFactory
import androidx.camera.core.impl.utils.AspectRatioUtil.ASPECT_RATIO_4_3
import androidx.camera.core.impl.utils.AspectRatioUtil.hasMatchingAspectRatio
@@ -163,6 +168,8 @@
targetRotation: Int,
preferredAspectRatio: Int,
preferredResolution: Size?,
+ targetFrameRate: Range<Int>?,
+ surfaceOccupancyPriority: Int,
maxResolution: Size?,
highResolutionEnabled: Boolean,
defaultResolution: Size?,
@@ -174,6 +181,8 @@
targetRotation,
preferredAspectRatio,
preferredResolution,
+ targetFrameRate,
+ surfaceOccupancyPriority,
maxResolution,
defaultResolution,
supportedResolutions,
@@ -188,6 +197,8 @@
targetRotation: Int,
preferredAspectRatio: Int,
preferredResolution: Size?,
+ targetFrameRate: Range<Int>?,
+ surfaceOccupancyPriority: Int,
maxResolution: Size?,
highResolutionEnabled: Boolean,
defaultResolution: Size?,
@@ -215,6 +226,8 @@
targetRotation: Int,
preferredAspectRatio: Int,
preferredResolution: Size?,
+ targetFrameRate: Range<Int>?,
+ surfaceOccupancyPriority: Int,
maxResolution: Size?,
highResolutionEnabled: Boolean,
defaultResolution: Size?,
@@ -2667,6 +2680,336 @@
assertThat(suggestedStreamSpecMap[useCase]?.resolution).isEqualTo(Size(8000, 4500))
}
+ @Test
+ @Throws(CameraUnavailableException::class, CameraAccessExceptionCompat::class)
+ fun getSupportedOutputSizes_single_valid_targetFPS() {
+ // a valid target means the device is capable of that fps
+ setupCameraAndInitCameraX(
+ hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+ )
+ val supportedSurfaceCombination = SupportedSurfaceCombination(
+ context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
+ )
+
+ // use case with target fps
+ val useCase1 = createUseCaseByLegacyApi(
+ FAKE_USE_CASE,
+ targetFrameRate = Range<Int>(25, 30)
+ )
+
+ val suggestedStreamSpecMap = getSuggestedStreamSpecMap(
+ supportedSurfaceCombination,
+ useCase1
+ )
+ // single selected size should be equal to 3840 x 2160
+ assertThat(suggestedStreamSpecMap[useCase1]!!.resolution).isEqualTo(Size(3840, 2160))
+ }
+
+ @Test
+ @Throws(CameraUnavailableException::class, CameraAccessExceptionCompat::class)
+ fun getSupportedOutputSizes_single_invalid_targetFPS() {
+ // an invalid target means the device would neve be able to reach that fps
+ setupCameraAndInitCameraX(
+ hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+ )
+ val supportedSurfaceCombination = SupportedSurfaceCombination(
+ context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
+ )
+
+ // use case with target fps
+ val useCase1 = createUseCaseByLegacyApi(
+ FAKE_USE_CASE,
+ targetFrameRate = Range<Int>(65, 70)
+ )
+
+ val suggestedStreamSpecMap = getSuggestedStreamSpecMap(
+ supportedSurfaceCombination,
+ useCase1
+ )
+ // single selected size should be equal to 3840 x 2160
+ assertThat(suggestedStreamSpecMap[useCase1]!!.resolution).isEqualTo(Size(800, 450))
+ }
+
+ @Test
+ @Throws(CameraUnavailableException::class, CameraAccessExceptionCompat::class)
+ fun getSupportedOutputSizes_multiple_targetFPS_first_is_larger() {
+ // a valid target means the device is capable of that fps
+ setupCameraAndInitCameraX(
+ hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL
+ )
+ val supportedSurfaceCombination = SupportedSurfaceCombination(
+ context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
+ )
+
+ val useCase1 = createUseCaseByLegacyApi(
+ FAKE_USE_CASE,
+ targetFrameRate = Range<Int>(30, 35),
+ surfaceOccupancyPriority = 1
+ )
+
+ val useCase2 = createUseCaseByLegacyApi(
+ FAKE_USE_CASE,
+ targetFrameRate = Range<Int>(15, 25)
+ )
+
+ val suggestedStreamSpecMap = getSuggestedStreamSpecMap(
+ supportedSurfaceCombination,
+ useCase1,
+ useCase2
+ )
+ // both selected size should be no larger than 1920 x 1080
+ assertThat(sizeIsAtMost(suggestedStreamSpecMap[useCase1]!!.resolution, Size(1920, 1445)))
+ .isTrue()
+ assertThat(sizeIsAtMost(suggestedStreamSpecMap[useCase2]!!.resolution, Size(1920, 1445)))
+ .isTrue()
+ }
+
+ @Test
+ @Throws(CameraUnavailableException::class, CameraAccessExceptionCompat::class)
+ fun getSupportedOutputSizes_multiple_targetFPS_first_is_smaller() {
+ // a valid target means the device is capable of that fps
+ setupCameraAndInitCameraX(
+ hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+ )
+ val supportedSurfaceCombination = SupportedSurfaceCombination(
+ context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
+ )
+
+ val useCase1 = createUseCaseByLegacyApi(
+ FAKE_USE_CASE,
+ targetFrameRate = Range<Int>(30, 35),
+ surfaceOccupancyPriority = 1
+ )
+
+ val useCase2 = createUseCaseByLegacyApi(
+ FAKE_USE_CASE,
+ targetFrameRate = Range<Int>(45, 50)
+ )
+
+ val suggestedStreamSpecMap = getSuggestedStreamSpecMap(
+ supportedSurfaceCombination,
+ useCase1,
+ useCase2
+ )
+ // both selected size should be no larger than 1920 x 1440
+ assertThat(sizeIsAtMost(suggestedStreamSpecMap[useCase1]!!.resolution, Size(1920, 1440)))
+ .isTrue()
+ assertThat(sizeIsAtMost(suggestedStreamSpecMap[useCase2]!!.resolution, Size(1920, 1440)))
+ .isTrue()
+ }
+
+ @Test
+ @Throws(CameraUnavailableException::class, CameraAccessExceptionCompat::class)
+ fun getSupportedOutputSizes_multiple_targetFPS_intersect() {
+ // first and second new use cases have target fps that intersect each other
+ setupCameraAndInitCameraX(
+ hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+ )
+ val supportedSurfaceCombination = SupportedSurfaceCombination(
+ context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
+ )
+
+ val useCase1 = createUseCaseByLegacyApi(
+ FAKE_USE_CASE,
+ targetFrameRate = Range<Int>(30, 40),
+ surfaceOccupancyPriority = 1
+ )
+
+ val useCase2 = createUseCaseByLegacyApi(
+ FAKE_USE_CASE,
+ targetFrameRate = Range<Int>(35, 45)
+ )
+
+ val suggestedStreamSpecMap = getSuggestedStreamSpecMap(
+ supportedSurfaceCombination,
+ useCase1,
+ useCase2
+ )
+ // effective target fps becomes 35-40
+ // both selected size should be no larger than 1920 x 1080
+ assertThat(sizeIsAtMost(suggestedStreamSpecMap[useCase1]!!.resolution, Size(1920, 1080)))
+ .isTrue()
+ assertThat(sizeIsAtMost(suggestedStreamSpecMap[useCase2]!!.resolution, Size(1920, 1080)))
+ .isTrue()
+ }
+ @Test
+ @Throws(CameraUnavailableException::class, CameraAccessExceptionCompat::class)
+ fun getSupportedOutputSizes_multiple_cases_first_has_targetFPS() {
+ // first new use case has a target fps, second new use case does not
+ setupCameraAndInitCameraX(
+ hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+ )
+ val supportedSurfaceCombination = SupportedSurfaceCombination(
+ context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
+ )
+
+ val useCase1 = createUseCaseByLegacyApi(
+ FAKE_USE_CASE,
+ targetFrameRate = Range<Int>(30, 35),
+ surfaceOccupancyPriority = 1
+ )
+
+ val useCase2 = createUseCaseByLegacyApi(
+ FAKE_USE_CASE
+ )
+
+ val suggestedStreamSpecMap = getSuggestedStreamSpecMap(
+ supportedSurfaceCombination,
+ useCase1,
+ useCase2
+ )
+ // both selected size should be no larger than 1920 x 1440
+ assertThat(sizeIsAtMost(suggestedStreamSpecMap[useCase1]!!.resolution, Size(1920, 1440)))
+ .isTrue()
+ assertThat(sizeIsAtMost(suggestedStreamSpecMap[useCase2]!!.resolution, Size(1920, 1440)))
+ .isTrue()
+ }
+
+ @Test
+ @Throws(CameraUnavailableException::class, CameraAccessExceptionCompat::class)
+ fun getSupportedOutputSizes_multiple_cases_second_has_targetFPS() {
+ // second new use case does not have a target fps, first new use case does not
+ setupCameraAndInitCameraX(
+ hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+ )
+ val supportedSurfaceCombination = SupportedSurfaceCombination(
+ context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
+ )
+
+ val useCase1 = createUseCaseByLegacyApi(
+ FAKE_USE_CASE
+ )
+ val useCase2 = createUseCaseByLegacyApi(
+ FAKE_USE_CASE,
+ targetFrameRate = Range<Int>(30, 35)
+ )
+
+ val suggestedStreamSpecMap = getSuggestedStreamSpecMap(
+ supportedSurfaceCombination,
+ useCase1,
+ useCase2
+ )
+ // both selected size should be no larger than 1920 x 1440
+ assertThat(sizeIsAtMost(suggestedStreamSpecMap[useCase1]!!.resolution, Size(1920, 1440)))
+ .isTrue()
+ assertThat(sizeIsAtMost(suggestedStreamSpecMap[useCase2]!!.resolution, Size(1920, 1440)))
+ .isTrue()
+ }
+
+ @Test
+ @Throws(CameraUnavailableException::class, CameraAccessExceptionCompat::class)
+ fun getSupportedOutputSizes_attached_with_targetFPS_no_new_targetFPS() {
+ // existing surface with target fps + new use case without a target fps
+ setupCameraAndInitCameraX(
+ hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+ )
+ val supportedSurfaceCombination = SupportedSurfaceCombination(
+ context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
+ )
+
+ // existing surface w/ target fps
+ val attachedSurfaceInfo = AttachedSurfaceInfo.create(
+ SurfaceConfig.create(
+ ConfigType.JPEG,
+ ConfigSize.PREVIEW
+ ), ImageFormat.JPEG,
+ Size(1280, 720), Range(40, 50)
+ )
+
+ // new use case with no target fps
+ val useCase1 = createUseCaseByLegacyApi(FAKE_USE_CASE)
+
+ val suggestedStreamSpecMap = getSuggestedStreamSpecMap(
+ supportedSurfaceCombination,
+ useCase1,
+ attachedSurfaces = listOf(attachedSurfaceInfo)
+ )
+ // size should be no larger than 1280 x 960
+ assertThat(sizeIsAtMost(suggestedStreamSpecMap[useCase1]!!.resolution, Size(1280, 960)))
+ .isTrue()
+ }
+ @Test
+ @Throws(CameraUnavailableException::class, CameraAccessExceptionCompat::class)
+ fun getSupportedOutputSizes_attached_with_targetFPS_and_new_targetFPS_no_intersect() {
+ // existing surface with target fps + new use case with target fps that does not intersect
+ setupCameraAndInitCameraX(
+ hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+ )
+ val supportedSurfaceCombination = SupportedSurfaceCombination(
+ context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
+ )
+
+ // existing surface w/ target fps
+ val attachedSurfaceInfo = AttachedSurfaceInfo.create(
+ SurfaceConfig.create(
+ ConfigType.JPEG,
+ ConfigSize.PREVIEW
+ ), ImageFormat.JPEG,
+ Size(1280, 720), Range(40, 50)
+ )
+
+ // new use case with target fps
+ val useCase1 = createUseCaseByLegacyApi(
+ FAKE_USE_CASE,
+ targetFrameRate = Range<Int>(30, 35)
+
+ )
+
+ val suggestedStreamSpecMap = getSuggestedStreamSpecMap(
+ supportedSurfaceCombination,
+ useCase1,
+ attachedSurfaces = listOf(attachedSurfaceInfo)
+ )
+ // size of new surface should be no larger than 1280 x 960
+ assertThat(sizeIsAtMost(suggestedStreamSpecMap[useCase1]!!.resolution, Size(1280, 960)))
+ .isTrue()
+ }
+
+ @Test
+ @Throws(CameraUnavailableException::class, CameraAccessExceptionCompat::class)
+ fun getSupportedOutputSizes_attached_with_targetFPS_and_new_targetFPS_with_intersect() {
+ // existing surface with target fps + new use case with target fps that intersect each other
+ setupCameraAndInitCameraX(
+ hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+ )
+ val supportedSurfaceCombination = SupportedSurfaceCombination(
+ context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
+ )
+
+ // existing surface w/ target fps
+ val attachedSurfaceInfo = AttachedSurfaceInfo.create(
+ SurfaceConfig.create(
+ ConfigType.JPEG,
+ ConfigSize.PREVIEW
+ ), ImageFormat.JPEG,
+ Size(1280, 720), Range(40, 50)
+ )
+
+ // new use case with target fps
+ val useCase1 = createUseCaseByLegacyApi(
+ FAKE_USE_CASE,
+ targetFrameRate = Range<Int>(45, 50)
+
+ )
+
+ val suggestedStreamSpecMap = getSuggestedStreamSpecMap(
+ supportedSurfaceCombination,
+ useCase1,
+ attachedSurfaces = listOf(attachedSurfaceInfo)
+ )
+ // size of new surface should be no larger than 1280 x 720
+ assertThat(sizeIsAtMost(suggestedStreamSpecMap[useCase1]!!.resolution, Size(1280, 720)))
+ .isTrue()
+ }
+
+ /**
+ * Helper function that returns whether size is <= maxSize
+ *
+ */
+ private fun sizeIsAtMost(size: Size, maxSize: Size): Boolean {
+ return (size.height * size.width) <= (maxSize.height * maxSize.width)
+ }
+
/**
* Sets up camera according to the specified settings and initialize [CameraX].
*
@@ -2786,6 +3129,7 @@
private fun getSuggestedStreamSpecMap(
supportedSurfaceCombination: SupportedSurfaceCombination,
vararg useCases: UseCase,
+ attachedSurfaces: List<AttachedSurfaceInfo>? = null,
cameraFactory: CameraFactory = this.cameraFactory!!,
cameraId: String = DEFAULT_CAMERA_ID,
useCaseConfigFactory: UseCaseConfigFactory = this.useCaseConfigFactory!!
@@ -2798,8 +3142,9 @@
)
// Uses the use case config list to get suggested stream specs
val useCaseConfigStreamSpecMap = supportedSurfaceCombination
- .getSuggestedStreamSpecifications(emptyList(), mutableListOf<UseCaseConfig<*>?>()
- .apply { addAll(useCaseToConfigMap.values) }
+ .getSuggestedStreamSpecifications(
+ attachedSurfaces ?: emptyList(),
+ mutableListOf<UseCaseConfig<*>?>().apply { addAll(useCaseToConfigMap.values) }
)
val useCaseStreamSpecMap = mutableMapOf<UseCase, StreamSpec?>()
// Maps the use cases to the suggestion resolutions
@@ -2851,6 +3196,8 @@
targetRotation: Int = UNKNOWN_ROTATION,
targetAspectRatio: Int = UNKNOWN_ASPECT_RATIO,
targetResolution: Size? = null,
+ targetFrameRate: Range<Int>? = null,
+ surfaceOccupancyPriority: Int = -1,
maxResolution: Size? = null,
defaultResolution: Size? = null,
supportedResolutions: List<Pair<Int, Array<Size>>>? = null,
@@ -2868,6 +3215,10 @@
if (targetAspectRatio != UNKNOWN_ASPECT_RATIO) {
builder.setTargetAspectRatio(targetAspectRatio)
}
+ if (surfaceOccupancyPriority >= 0) {
+ builder.setSurfaceOccupancyPriority(surfaceOccupancyPriority)
+ }
+ builder.mutableConfig.insertOption(OPTION_TARGET_FRAME_RATE, targetFrameRate)
targetResolution?.let { builder.setTargetResolution(it) }
maxResolution?.let { builder.setMaxResolution(it) }
defaultResolution?.let { builder.setDefaultResolution(it) }
@@ -2920,6 +3271,8 @@
targetRotation: Int = UNKNOWN_ROTATION,
preferredAspectRatio: Int = UNKNOWN_ASPECT_RATIO,
preferredResolution: Size? = null,
+ targetFrameRate: Range<Int>? = null,
+ surfaceOccupancyPriority: Int = -1,
maxResolution: Size? = null,
highResolutionEnabled: Boolean = false,
defaultResolution: Size? = null,