[camerax] Implement resolution configuration (#3799)
Adds resolution configuration for all camera use cases. Also makes minor updates to related documentation.
Fixes https://github.com/flutter/flutter/issues/120462.
diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md
index 1d2e9c6..61aff80 100644
--- a/packages/camera/camera_android_camerax/CHANGELOG.md
+++ b/packages/camera/camera_android_camerax/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.5.0+17
+
+* Implements resolution configuration for all camera use cases.
+
## 0.5.0+16
* Adds pub topics to package metadata.
diff --git a/packages/camera/camera_android_camerax/README.md b/packages/camera/camera_android_camerax/README.md
index dd6e563..1642b81 100644
--- a/packages/camera/camera_android_camerax/README.md
+++ b/packages/camera/camera_android_camerax/README.md
@@ -24,16 +24,18 @@
## Missing features and limitations
-### Resolution configuration \[[Issue #120462][120462]\]
-Any specified `ResolutionPreset` wll go unused in favor of CameraX defaults and
-`onCameraResolutionChanged` is unimplemented.
+### 240p resolution configuration for video recording
+
+240p resolution configuration for video recording is unsupported by CameraX,
+and thus, the plugin will fall back to 480p if configured with a
+`ResolutionPreset`.
### Locking/Unlocking capture orientation \[[Issue #125915][125915]\]
`lockCaptureOrientation` & `unLockCaptureOrientation` are unimplemented.
-### Flash mode configuration \[[Issue #120715][120715]\]
+### Torch mode \[[Issue #120715][120715]\]
Calling `setFlashMode` with mode `FlashMode.torch` currently does nothing.
diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FallbackStrategyHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FallbackStrategyHostApiImpl.java
index 0309d54..da9f58a 100644
--- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FallbackStrategyHostApiImpl.java
+++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FallbackStrategyHostApiImpl.java
@@ -9,7 +9,7 @@
import androidx.camera.video.FallbackStrategy;
import androidx.camera.video.Quality;
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.FallbackStrategyHostApi;
-import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoQualityConstraint;
+import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoQuality;
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoResolutionFallbackRule;
/**
@@ -28,20 +28,18 @@
public static class FallbackStrategyProxy {
/** Creates an instance of {@link FallbackStrategy}. */
public @NonNull FallbackStrategy create(
- @NonNull VideoQualityConstraint videoQualityConstraint,
- @NonNull VideoResolutionFallbackRule fallbackRule) {
- Quality videoQuality =
- QualitySelectorHostApiImpl.getQualityFromVideoQualityConstraint(videoQualityConstraint);
+ @NonNull VideoQuality videoQuality, @NonNull VideoResolutionFallbackRule fallbackRule) {
+ Quality quality = QualitySelectorHostApiImpl.getQualityFromVideoQuality(videoQuality);
switch (fallbackRule) {
case HIGHER_QUALITY_OR_LOWER_THAN:
- return FallbackStrategy.higherQualityOrLowerThan(videoQuality);
+ return FallbackStrategy.higherQualityOrLowerThan(quality);
case HIGHER_QUALITY_THAN:
- return FallbackStrategy.higherQualityThan(videoQuality);
+ return FallbackStrategy.higherQualityThan(quality);
case LOWER_QUALITY_OR_HIGHER_THAN:
- return FallbackStrategy.lowerQualityOrHigherThan(videoQuality);
+ return FallbackStrategy.lowerQualityOrHigherThan(quality);
case LOWER_QUALITY_THAN:
- return FallbackStrategy.lowerQualityThan(videoQuality);
+ return FallbackStrategy.lowerQualityThan(quality);
}
throw new IllegalArgumentException(
"Specified fallback rule " + fallbackRule + " unrecognized.");
@@ -75,9 +73,8 @@
@Override
public void create(
@NonNull Long identifier,
- @NonNull VideoQualityConstraint videoQualityConstraint,
+ @NonNull VideoQuality videoQuality,
@NonNull VideoResolutionFallbackRule fallbackRule) {
- instanceManager.addDartCreatedInstance(
- proxy.create(videoQualityConstraint, fallbackRule), identifier);
+ instanceManager.addDartCreatedInstance(proxy.create(videoQuality, fallbackRule), identifier);
}
}
diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java
index d7d378d..fe9a935 100644
--- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java
+++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java
@@ -112,7 +112,7 @@
*
* <p>See https://developer.android.com/reference/androidx/camera/video/Quality.
*/
- public enum VideoQualityConstraint {
+ public enum VideoQuality {
SD(0),
HD(1),
FHD(2),
@@ -122,12 +122,16 @@
final int index;
- private VideoQualityConstraint(final int index) {
+ private VideoQuality(final int index) {
this.index = index;
}
}
- /** Fallback rules for selecting video resolution. */
+ /**
+ * Fallback rules for selecting video resolution.
+ *
+ * <p>See https://developer.android.com/reference/androidx/camera/video/FallbackStrategy.
+ */
public enum VideoResolutionFallbackRule {
HIGHER_QUALITY_OR_LOWER_THAN(0),
HIGHER_QUALITY_THAN(1),
@@ -472,6 +476,59 @@
}
}
+ /**
+ * Convenience class for sending lists of [Quality]s.
+ *
+ * <p>Generated class from Pigeon that represents data sent in messages.
+ */
+ public static final class VideoQualityData {
+ private @NonNull VideoQuality quality;
+
+ public @NonNull VideoQuality getQuality() {
+ return quality;
+ }
+
+ public void setQuality(@NonNull VideoQuality setterArg) {
+ if (setterArg == null) {
+ throw new IllegalStateException("Nonnull field \"quality\" is null.");
+ }
+ this.quality = setterArg;
+ }
+
+ /** Constructor is non-public to enforce null safety; use Builder. */
+ VideoQualityData() {}
+
+ public static final class Builder {
+
+ private @Nullable VideoQuality quality;
+
+ public @NonNull Builder setQuality(@NonNull VideoQuality setterArg) {
+ this.quality = setterArg;
+ return this;
+ }
+
+ public @NonNull VideoQualityData build() {
+ VideoQualityData pigeonReturn = new VideoQualityData();
+ pigeonReturn.setQuality(quality);
+ return pigeonReturn;
+ }
+ }
+
+ @NonNull
+ ArrayList<Object> toList() {
+ ArrayList<Object> toListResult = new ArrayList<Object>(1);
+ toListResult.add(quality == null ? null : quality.index);
+ return toListResult;
+ }
+
+ static @NonNull VideoQualityData fromList(@NonNull ArrayList<Object> list) {
+ VideoQualityData pigeonResult = new VideoQualityData();
+ Object quality = list.get(0);
+ pigeonResult.setQuality(quality == null ? null : VideoQuality.values()[(int) quality]);
+ return pigeonResult;
+ }
+ }
+
public interface Result<T> {
@SuppressWarnings("UnknownNullness")
void success(T result);
@@ -2991,6 +3048,8 @@
switch (type) {
case (byte) 128:
return ResolutionInfo.fromList((ArrayList<Object>) readValue(buffer));
+ case (byte) 129:
+ return VideoQualityData.fromList((ArrayList<Object>) readValue(buffer));
default:
return super.readValueOfType(type, buffer);
}
@@ -3001,6 +3060,9 @@
if (value instanceof ResolutionInfo) {
stream.write(128);
writeValue(stream, ((ResolutionInfo) value).toList());
+ } else if (value instanceof VideoQualityData) {
+ stream.write(129);
+ writeValue(stream, ((VideoQualityData) value).toList());
} else {
super.writeValue(stream, value);
}
@@ -3012,12 +3074,11 @@
void create(
@NonNull Long identifier,
- @NonNull List<Long> videoQualityConstraintIndexList,
+ @NonNull List<VideoQualityData> videoQualityDataList,
@Nullable Long fallbackStrategyId);
@NonNull
- ResolutionInfo getResolution(
- @NonNull Long cameraInfoId, @NonNull VideoQualityConstraint quality);
+ ResolutionInfo getResolution(@NonNull Long cameraInfoId, @NonNull VideoQuality quality);
/** The codec used by QualitySelectorHostApi. */
static @NonNull MessageCodec<Object> getCodec() {
@@ -3039,12 +3100,13 @@
ArrayList<Object> wrapped = new ArrayList<Object>();
ArrayList<Object> args = (ArrayList<Object>) message;
Number identifierArg = (Number) args.get(0);
- List<Long> videoQualityConstraintIndexListArg = (List<Long>) args.get(1);
+ List<VideoQualityData> videoQualityDataListArg =
+ (List<VideoQualityData>) args.get(1);
Number fallbackStrategyIdArg = (Number) args.get(2);
try {
api.create(
(identifierArg == null) ? null : identifierArg.longValue(),
- videoQualityConstraintIndexListArg,
+ videoQualityDataListArg,
(fallbackStrategyIdArg == null) ? null : fallbackStrategyIdArg.longValue());
wrapped.add(0, null);
} catch (Throwable exception) {
@@ -3069,8 +3131,8 @@
ArrayList<Object> wrapped = new ArrayList<Object>();
ArrayList<Object> args = (ArrayList<Object>) message;
Number cameraInfoIdArg = (Number) args.get(0);
- VideoQualityConstraint qualityArg =
- args.get(1) == null ? null : VideoQualityConstraint.values()[(int) args.get(1)];
+ VideoQuality qualityArg =
+ args.get(1) == null ? null : VideoQuality.values()[(int) args.get(1)];
try {
ResolutionInfo output =
api.getResolution(
@@ -3094,7 +3156,7 @@
void create(
@NonNull Long identifier,
- @NonNull VideoQualityConstraint quality,
+ @NonNull VideoQuality quality,
@NonNull VideoResolutionFallbackRule fallbackRule);
/** The codec used by FallbackStrategyHostApi. */
@@ -3117,8 +3179,8 @@
ArrayList<Object> wrapped = new ArrayList<Object>();
ArrayList<Object> args = (ArrayList<Object>) message;
Number identifierArg = (Number) args.get(0);
- VideoQualityConstraint qualityArg =
- args.get(1) == null ? null : VideoQualityConstraint.values()[(int) args.get(1)];
+ VideoQuality qualityArg =
+ args.get(1) == null ? null : VideoQuality.values()[(int) args.get(1)];
VideoResolutionFallbackRule fallbackRuleArg =
args.get(2) == null
? null
diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/QualitySelectorHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/QualitySelectorHostApiImpl.java
index c747c75..6752254 100644
--- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/QualitySelectorHostApiImpl.java
+++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/QualitySelectorHostApiImpl.java
@@ -13,7 +13,8 @@
import androidx.camera.video.QualitySelector;
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.QualitySelectorHostApi;
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.ResolutionInfo;
-import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoQualityConstraint;
+import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoQuality;
+import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoQualityData;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -34,12 +35,12 @@
public static class QualitySelectorProxy {
/** Creates an instance of {@link QualitySelector}. */
public @NonNull QualitySelector create(
- @NonNull List<Long> videoQualityConstraintIndexList,
+ @NonNull List<VideoQualityData> videoQualityDataList,
@Nullable FallbackStrategy fallbackStrategy) {
- // Convert each index of VideoQualityConstraint to Quality.
+ // Convert each index of VideoQuality to Quality.
List<Quality> qualityList = new ArrayList<Quality>();
- for (Long qualityIndex : videoQualityConstraintIndexList) {
- qualityList.add(getQualityConstant(qualityIndex));
+ for (VideoQualityData videoQualityData : videoQualityDataList) {
+ qualityList.add(getQualityFromVideoQuality(videoQualityData.getQuality()));
}
boolean fallbackStrategySpecified = fallbackStrategy != null;
@@ -57,12 +58,6 @@
? QualitySelector.fromOrderedList(qualityList, fallbackStrategy)
: QualitySelector.fromOrderedList(qualityList);
}
-
- /** Converts from index of {@link VideoQualityConstraint} to {@link Quality}. */
- private Quality getQualityConstant(@NonNull Long qualityIndex) {
- VideoQualityConstraint quality = VideoQualityConstraint.values()[qualityIndex.intValue()];
- return getQualityFromVideoQualityConstraint(quality);
- }
}
/**
@@ -93,11 +88,11 @@
@Override
public void create(
@NonNull Long identifier,
- @NonNull List<Long> videoQualityConstraintIndexList,
+ @NonNull List<VideoQualityData> videoQualityDataList,
@Nullable Long fallbackStrategyIdentifier) {
instanceManager.addDartCreatedInstance(
proxy.create(
- videoQualityConstraintIndexList,
+ videoQualityDataList,
fallbackStrategyIdentifier == null
? null
: Objects.requireNonNull(instanceManager.getInstance(fallbackStrategyIdentifier))),
@@ -110,11 +105,11 @@
*/
@Override
public @NonNull ResolutionInfo getResolution(
- @NonNull Long cameraInfoIdentifier, @NonNull VideoQualityConstraint quality) {
+ @NonNull Long cameraInfoIdentifier, @NonNull VideoQuality quality) {
final Size result =
QualitySelector.getResolution(
Objects.requireNonNull(instanceManager.getInstance(cameraInfoIdentifier)),
- getQualityFromVideoQualityConstraint(quality));
+ getQualityFromVideoQuality(quality));
return new ResolutionInfo.Builder()
.setWidth(Long.valueOf(result.getWidth()))
.setHeight(Long.valueOf(result.getHeight()))
@@ -122,12 +117,11 @@
}
/**
- * Converts the specified {@link VideoQualityConstraint} to a {@link Quality} that is understood
+ * Converts the specified {@link VideoQuality to a {@link Quality} that is understood
* by CameraX.
*/
- public static @NonNull Quality getQualityFromVideoQualityConstraint(
- @NonNull VideoQualityConstraint videoQualityConstraint) {
- switch (videoQualityConstraint) {
+ public static @NonNull Quality getQualityFromVideoQuality(@NonNull VideoQuality videoQuality) {
+ switch (videoQuality) {
case SD:
return Quality.SD;
case HD:
@@ -142,8 +136,6 @@
return Quality.HIGHEST;
}
throw new IllegalArgumentException(
- "VideoQualityConstraint "
- + videoQualityConstraint
- + " is unhandled by QualitySelectorHostApiImpl.");
+ "VideoQuality " + videoQuality + " is unhandled by QualitySelectorHostApiImpl.");
}
}
diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/FallbackStrategyTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/FallbackStrategyTest.java
index 4a2eeef..a68384f 100644
--- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/FallbackStrategyTest.java
+++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/FallbackStrategyTest.java
@@ -11,7 +11,7 @@
import androidx.camera.video.FallbackStrategy;
import androidx.camera.video.Quality;
-import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoQualityConstraint;
+import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoQuality;
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoResolutionFallbackRule;
import org.junit.After;
import org.junit.Before;
@@ -48,11 +48,11 @@
try (MockedStatic<FallbackStrategy> mockedFallbackStrategy =
mockStatic(FallbackStrategy.class)) {
- for (VideoQualityConstraint videoQualityConstraint : VideoQualityConstraint.values()) {
+ for (VideoQuality videoQuality : VideoQuality.values()) {
for (VideoResolutionFallbackRule fallbackRule : VideoResolutionFallbackRule.values()) {
- // Determine expected Quality based on videoQualityConstraint being tested.
+ // Determine expected Quality based on videoQuality being tested.
Quality convertedQuality = null;
- switch (videoQualityConstraint) {
+ switch (videoQuality) {
case SD:
convertedQuality = Quality.SD;
break;
@@ -72,10 +72,7 @@
convertedQuality = Quality.HIGHEST;
break;
default:
- fail(
- "The VideoQualityConstraint "
- + videoQualityConstraint.toString()
- + "is unhandled by this test.");
+ fail("The VideoQuality " + videoQuality.toString() + "is unhandled by this test.");
}
// Set Quality as final local variable to avoid error about using non-final (or effecitvely final) local variables in lambda expressions.
final Quality expectedQuality = convertedQuality;
@@ -108,7 +105,7 @@
+ fallbackRule.toString()
+ "is unhandled by this test.");
}
- hostApi.create(instanceIdentifier, videoQualityConstraint, fallbackRule);
+ hostApi.create(instanceIdentifier, videoQuality, fallbackRule);
assertEquals(instanceManager.getInstance(instanceIdentifier), mockFallbackStrategy);
// Clear/reset FallbackStrategy mock and InstanceManager.
diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/QualitySelectorTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/QualitySelectorTest.java
index 5519583..83617a7 100644
--- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/QualitySelectorTest.java
+++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/QualitySelectorTest.java
@@ -14,7 +14,8 @@
import androidx.camera.video.Quality;
import androidx.camera.video.QualitySelector;
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.ResolutionInfo;
-import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoQualityConstraint;
+import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoQuality;
+import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoQualityData;
import java.util.Arrays;
import java.util.List;
import org.junit.After;
@@ -47,10 +48,9 @@
@Test
public void hostApiCreate_createsExpectedQualitySelectorWhenOneQualitySpecified() {
- final Long expectedVideoQualityConstraintIndex =
- Long.valueOf(VideoQualityConstraint.UHD.ordinal());
- final List<Long> videoQualityConstraintList =
- Arrays.asList(expectedVideoQualityConstraintIndex);
+ final VideoQualityData expectedVideoQualityData =
+ new VideoQualityData.Builder().setQuality(VideoQuality.UHD).build();
+ final List<VideoQualityData> videoQualityDataList = Arrays.asList(expectedVideoQualityData);
final FallbackStrategy mockFallbackStrategy = mock(FallbackStrategy.class);
final long fallbackStrategyIdentifier = 9;
final QualitySelectorHostApiImpl hostApi = new QualitySelectorHostApiImpl(instanceManager);
@@ -69,7 +69,7 @@
// Test with no fallback strategy.
long instanceIdentifier = 0;
- hostApi.create(instanceIdentifier, videoQualityConstraintList, null);
+ hostApi.create(instanceIdentifier, videoQualityDataList, null);
assertEquals(
instanceManager.getInstance(instanceIdentifier),
@@ -77,7 +77,7 @@
// Test with fallback strategy.
instanceIdentifier = 1;
- hostApi.create(instanceIdentifier, videoQualityConstraintList, fallbackStrategyIdentifier);
+ hostApi.create(instanceIdentifier, videoQualityDataList, fallbackStrategyIdentifier);
assertEquals(
instanceManager.getInstance(instanceIdentifier), mockQualitySelectorWithFallbackStrategy);
@@ -86,14 +86,11 @@
@Test
public void hostApiCreate_createsExpectedQualitySelectorWhenOrderedListOfQualitiesSpecified() {
- final List<Long> expectedIndices =
+ final List<VideoQualityData> videoQualityDataList =
Arrays.asList(
- Long.valueOf(VideoQualityConstraint.UHD.ordinal()),
- Long.valueOf(VideoQualityConstraint.HIGHEST.ordinal()));
- final List<VideoQualityConstraint> videoQualityConstraintList =
- Arrays.asList(VideoQualityConstraint.UHD, VideoQualityConstraint.HIGHEST);
- final List<Quality> expectedVideoQualityConstraintList =
- Arrays.asList(Quality.UHD, Quality.HIGHEST);
+ new VideoQualityData.Builder().setQuality(VideoQuality.UHD).build(),
+ new VideoQualityData.Builder().setQuality(VideoQuality.HIGHEST).build());
+ final List<Quality> expectedVideoQualityList = Arrays.asList(Quality.UHD, Quality.HIGHEST);
final FallbackStrategy mockFallbackStrategy = mock(FallbackStrategy.class);
final long fallbackStrategyIdentifier = 9;
final QualitySelectorHostApiImpl hostApi = new QualitySelectorHostApiImpl(instanceManager);
@@ -102,20 +99,18 @@
try (MockedStatic<QualitySelector> mockedQualitySelector = mockStatic(QualitySelector.class)) {
mockedQualitySelector
- .when(() -> QualitySelector.fromOrderedList(expectedVideoQualityConstraintList))
+ .when(() -> QualitySelector.fromOrderedList(expectedVideoQualityList))
.thenAnswer(
(Answer<QualitySelector>) invocation -> mockQualitySelectorWithoutFallbackStrategy);
mockedQualitySelector
.when(
- () ->
- QualitySelector.fromOrderedList(
- expectedVideoQualityConstraintList, mockFallbackStrategy))
+ () -> QualitySelector.fromOrderedList(expectedVideoQualityList, mockFallbackStrategy))
.thenAnswer(
(Answer<QualitySelector>) invocation -> mockQualitySelectorWithFallbackStrategy);
// Test with no fallback strategy.
long instanceIdentifier = 0;
- hostApi.create(instanceIdentifier, expectedIndices, null);
+ hostApi.create(instanceIdentifier, videoQualityDataList, null);
assertEquals(
instanceManager.getInstance(instanceIdentifier),
@@ -123,7 +118,7 @@
// Test with fallback strategy.
instanceIdentifier = 1;
- hostApi.create(instanceIdentifier, expectedIndices, fallbackStrategyIdentifier);
+ hostApi.create(instanceIdentifier, videoQualityDataList, fallbackStrategyIdentifier);
assertEquals(
instanceManager.getInstance(instanceIdentifier), mockQualitySelectorWithFallbackStrategy);
@@ -134,7 +129,7 @@
public void getResolution_returnsExpectedResolutionInfo() {
final CameraInfo mockCameraInfo = mock(CameraInfo.class);
final long cameraInfoIdentifier = 6;
- final VideoQualityConstraint videoQualityConstraint = VideoQualityConstraint.FHD;
+ final VideoQuality videoQuality = VideoQuality.FHD;
final Size sizeResult = new Size(30, 40);
final QualitySelectorHostApiImpl hostApi = new QualitySelectorHostApiImpl(instanceManager);
@@ -145,8 +140,7 @@
.when(() -> QualitySelector.getResolution(mockCameraInfo, Quality.FHD))
.thenAnswer((Answer<Size>) invocation -> sizeResult);
- final ResolutionInfo result =
- hostApi.getResolution(cameraInfoIdentifier, videoQualityConstraint);
+ final ResolutionInfo result = hostApi.getResolution(cameraInfoIdentifier, videoQuality);
assertEquals(result.getWidth(), Long.valueOf(sizeResult.getWidth()));
assertEquals(result.getHeight(), Long.valueOf(sizeResult.getHeight()));
diff --git a/packages/camera/camera_android_camerax/example/integration_test/integration_test.dart b/packages/camera/camera_android_camerax/example/integration_test/integration_test.dart
index b6cba8d..b016cf0 100644
--- a/packages/camera/camera_android_camerax/example/integration_test/integration_test.dart
+++ b/packages/camera/camera_android_camerax/example/integration_test/integration_test.dart
@@ -2,11 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'dart:async';
import 'dart:io';
import 'dart:ui';
import 'package:camera_android_camerax/camera_android_camerax.dart';
import 'package:camera_android_camerax_example/camera_controller.dart';
+import 'package:camera_android_camerax_example/camera_image.dart';
import 'package:camera_platform_interface/camera_platform_interface.dart';
import 'package:flutter/painting.dart';
import 'package:flutter_test/flutter_test.dart';
@@ -19,6 +21,46 @@
CameraPlatform.instance = AndroidCameraCameraX();
});
+ final Map<ResolutionPreset, Size> presetExpectedSizes =
+ <ResolutionPreset, Size>{
+ ResolutionPreset.low: const Size(240, 320),
+ ResolutionPreset.medium: const Size(480, 720),
+ ResolutionPreset.high: const Size(720, 1280),
+ ResolutionPreset.veryHigh: const Size(1080, 1920),
+ ResolutionPreset.ultraHigh: const Size(2160, 3840),
+ // Don't bother checking for max here since it could be anything.
+ };
+
+ /// Verify that [actual] has dimensions that are at most as large as
+ /// [expectedSize]. Allows for a mismatch in portrait vs landscape. Returns
+ /// whether the dimensions exactly match.
+ bool assertExpectedDimensions(Size expectedSize, Size actual) {
+ expect(actual.shortestSide, lessThanOrEqualTo(expectedSize.shortestSide));
+ expect(actual.longestSide, lessThanOrEqualTo(expectedSize.longestSide));
+ return actual.shortestSide == expectedSize.shortestSide &&
+ actual.longestSide == expectedSize.longestSide;
+ }
+
+ // This tests that the capture is no bigger than the preset, since we have
+ // automatic code to fall back to smaller sizes when we need to. Returns
+ // whether the image is exactly the desired resolution.
+ Future<bool> testCaptureImageResolution(
+ CameraController controller, ResolutionPreset preset) async {
+ final Size expectedSize = presetExpectedSizes[preset]!;
+
+ // Take Picture
+ final XFile file = await controller.takePicture();
+
+ // Load picture
+ final File fileImage = File(file.path);
+ final Image image = await decodeImageFromList(fileImage.readAsBytesSync());
+
+ // Verify image dimensions are as expected
+ expect(image, isNotNull);
+ return assertExpectedDimensions(
+ expectedSize, Size(image.height.toDouble(), image.width.toDouble()));
+ }
+
testWidgets('availableCameras only supports valid back or front cameras',
(WidgetTester tester) async {
final List<CameraDescription> availableCameras =
@@ -31,27 +73,103 @@
}
});
- testWidgets('takePictures stores a valid image in memory',
+ testWidgets('Capture specific image resolutions',
(WidgetTester tester) async {
- final List<CameraDescription> availableCameras =
+ final List<CameraDescription> cameras =
await CameraPlatform.instance.availableCameras();
- if (availableCameras.isEmpty) {
+ if (cameras.isEmpty) {
return;
}
- for (final CameraDescription cameraDescription in availableCameras) {
- final CameraController controller =
- CameraController(cameraDescription, ResolutionPreset.high);
- await controller.initialize();
+ for (final CameraDescription cameraDescription in cameras) {
+ bool previousPresetExactlySupported = true;
+ for (final MapEntry<ResolutionPreset, Size> preset
+ in presetExpectedSizes.entries) {
+ final CameraController controller =
+ CameraController(cameraDescription, preset.key);
+ await controller.initialize();
+ final bool presetExactlySupported =
+ await testCaptureImageResolution(controller, preset.key);
+ // Ensures that if a lower resolution was used for previous (lower)
+ // resolution preset, then the current (higher) preset also is adjusted,
+ // as it demands a hgher resolution.
+ expect(
+ previousPresetExactlySupported || !presetExactlySupported, isTrue,
+ reason:
+ 'The camera took higher resolution pictures at a lower resolution.');
+ previousPresetExactlySupported = presetExactlySupported;
+ await controller.dispose();
+ }
+ }
+ });
- // Take Picture
- final XFile file = await controller.takePicture();
+ testWidgets('Preview takes expected resolution from preset',
+ (WidgetTester tester) async {
+ final List<CameraDescription> cameras =
+ await CameraPlatform.instance.availableCameras();
+ if (cameras.isEmpty) {
+ return;
+ }
+ for (final CameraDescription cameraDescription in cameras) {
+ bool previousPresetExactlySupported = true;
+ for (final MapEntry<ResolutionPreset, Size> preset
+ in presetExpectedSizes.entries) {
+ final CameraController controller =
+ CameraController(cameraDescription, preset.key);
- // Try loading picture
- final File fileImage = File(file.path);
- final Image image =
- await decodeImageFromList(fileImage.readAsBytesSync());
+ await controller.initialize();
- expect(image, isNotNull);
+ while (controller.value.previewSize == null) {
+ // Wait for preview size to update.
+ }
+
+ final bool presetExactlySupported = assertExpectedDimensions(
+ preset.value, controller.value.previewSize!);
+ // Ensures that if a lower resolution was used for previous (lower)
+ // resolution preset, then the current (higher) preset also is adjusted,
+ // as it demands a hgher resolution.
+ expect(
+ previousPresetExactlySupported || !presetExactlySupported, isTrue,
+ reason: 'The preview has a lower resolution than that specified.');
+ previousPresetExactlySupported = presetExactlySupported;
+ await controller.dispose();
+ }
+ }
+ });
+
+ testWidgets('Images from streaming have expected resolution from preset',
+ (WidgetTester tester) async {
+ final List<CameraDescription> cameras =
+ await CameraPlatform.instance.availableCameras();
+ if (cameras.isEmpty) {
+ return;
+ }
+ for (final CameraDescription cameraDescription in cameras) {
+ bool previousPresetExactlySupported = true;
+ for (final MapEntry<ResolutionPreset, Size> preset
+ in presetExpectedSizes.entries) {
+ final CameraController controller =
+ CameraController(cameraDescription, preset.key);
+ final Completer<CameraImage> imageCompleter = Completer<CameraImage>();
+ await controller.initialize();
+ await controller.startImageStream((CameraImage image) {
+ imageCompleter.complete(image);
+ controller.stopImageStream();
+ });
+
+ final CameraImage image = await imageCompleter.future;
+ final bool presetExactlySupported = assertExpectedDimensions(
+ preset.value,
+ Size(image.height.toDouble(), image.width.toDouble()));
+ // Ensures that if a lower resolution was used for previous (lower)
+ // resolution preset, then the current (higher) preset also is adjusted,
+ // as it demands a hgher resolution.
+ expect(
+ previousPresetExactlySupported || !presetExactlySupported, isTrue,
+ reason: 'The preview has a lower resolution than that specified.');
+ previousPresetExactlySupported = presetExactlySupported;
+
+ await controller.dispose();
+ }
}
});
}
diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart
index 7c5bf6b..fd42cc6 100644
--- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart
+++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart
@@ -16,6 +16,7 @@
import 'camera_state.dart';
import 'camerax_library.g.dart';
import 'exposure_state.dart';
+import 'fallback_strategy.dart';
import 'image_analysis.dart';
import 'image_capture.dart';
import 'image_proxy.dart';
@@ -25,8 +26,11 @@
import 'plane_proxy.dart';
import 'preview.dart';
import 'process_camera_provider.dart';
+import 'quality_selector.dart';
import 'recorder.dart';
import 'recording.dart';
+import 'resolution_selector.dart';
+import 'resolution_strategy.dart';
import 'surface.dart';
import 'system_services.dart';
import 'use_case.dart';
@@ -224,35 +228,42 @@
// Start listening for device orientation changes preceding camera creation.
startListeningForDeviceOrientationChange(
cameraIsFrontFacing, cameraDescription.sensorOrientation);
+ // Determine ResolutionSelector and QualitySelector based on
+ // resolutionPreset for camera UseCases.
+ final ResolutionSelector? presetResolutionSelector =
+ _getResolutionSelectorFromPreset(resolutionPreset);
+ final QualitySelector? presetQualitySelector =
+ _getQualitySelectorFromPreset(resolutionPreset);
// Retrieve a fresh ProcessCameraProvider instance.
processCameraProvider ??= await ProcessCameraProvider.getInstance();
processCameraProvider!.unbindAll();
- // TODO(camsim99): Implement resolution configuration for UseCases
- // configured here. https://github.com/flutter/flutter/issues/120462
-
// Configure Preview instance.
final int targetRotation =
_getTargetRotation(cameraDescription.sensorOrientation);
- preview = createPreview(targetRotation);
+ preview = createPreview(
+ targetRotation: targetRotation,
+ resolutionSelector: presetResolutionSelector);
final int flutterSurfaceTextureId = await preview!.setSurfaceProvider();
// Configure ImageCapture instance.
- imageCapture = createImageCapture(null);
+ imageCapture = createImageCapture(presetResolutionSelector);
+
+ // Configure ImageAnalysis instance.
+ // Defaults to YUV_420_888 image format.
+ imageAnalysis = createImageAnalysis(presetResolutionSelector);
// Configure VideoCapture and Recorder instances.
- // TODO(gmackall): Enable video capture resolution configuration in createRecorder().
- recorder = createRecorder();
+ recorder = createRecorder(presetQualitySelector);
videoCapture = await createVideoCapture(recorder!);
// Bind configured UseCases to ProcessCameraProvider instance & mark Preview
// instance as bound but not paused. Video capture is bound at first use
// instead of here.
- camera = await processCameraProvider!
- .bindToLifecycle(cameraSelector!, <UseCase>[preview!, imageCapture!]);
- await _updateLiveCameraState(flutterSurfaceTextureId);
- cameraInfo = await camera!.getCameraInfo();
+ camera = await processCameraProvider!.bindToLifecycle(
+ cameraSelector!, <UseCase>[preview!, imageCapture!, imageAnalysis!]);
+ await _updateCameraInfoAndLiveCameraState(flutterSurfaceTextureId);
_previewIsPaused = false;
return flutterSurfaceTextureId;
@@ -325,6 +336,14 @@
return _cameraEvents(cameraId).whereType<CameraInitializedEvent>();
}
+ /// The camera's resolution has changed.
+ ///
+ /// This stream currently has no events being added to it from this plugin.
+ @override
+ Stream<CameraResolutionChangedEvent> onCameraResolutionChanged(int cameraId) {
+ return _cameraEvents(cameraId).whereType<CameraResolutionChangedEvent>();
+ }
+
/// The camera started to close.
@override
Stream<CameraClosingEvent> onCameraClosing(int cameraId) {
@@ -484,6 +503,10 @@
/// Configures and starts a video recording. Returns silently without doing
/// anything if there is currently an active recording.
+ ///
+ /// Note that the preset resolution is used to configure the recording, but
+ /// 240p ([ResolutionPreset.low]) is unsupported and will fallback to
+ /// configure the recording as the next highest available quality.
@override
Future<void> startVideoRecording(int cameraId,
{Duration? maxVideoDuration}) async {
@@ -568,7 +591,7 @@
Stream<CameraImageData> onStreamedFrameAvailable(int cameraId,
{CameraImageStreamOptions? options}) {
cameraImageDataStreamController = StreamController<CameraImageData>(
- onListen: _onFrameStreamListen,
+ onListen: () => _onFrameStreamListen(cameraId),
onCancel: _onFrameStreamCancel,
);
return cameraImageDataStreamController!.stream;
@@ -592,13 +615,12 @@
camera = await processCameraProvider!
.bindToLifecycle(cameraSelector!, <UseCase>[preview!]);
- await _updateLiveCameraState(cameraId);
- cameraInfo = await camera!.getCameraInfo();
+ await _updateCameraInfoAndLiveCameraState(cameraId);
}
/// Configures the [imageAnalysis] instance for image streaming and binds it
/// to camera lifecycle controlled by the [processCameraProvider].
- Future<void> _configureAndBindImageAnalysisToLifecycle() async {
+ Future<void> _configureAndBindImageAnalysisToLifecycle(int cameraId) async {
// Create Analyzer that can read image data for image streaming.
final WeakReference<AndroidCameraCameraX> weakThis =
WeakReference<AndroidCameraCameraX>(this);
@@ -634,21 +656,7 @@
? Analyzer.detached(analyze: analyze)
: Analyzer(analyze: analyze);
- // TODO(camsim99): Support resolution configuration.
- // Defaults to YUV_420_888 image format.
- imageAnalysis ??= createImageAnalysis();
- unawaited(imageAnalysis!.setAnalyzer(analyzer));
-
- if (await processCameraProvider!.isBound(imageAnalysis!)) {
- // No need to bind imageAnalysis to lifecycle again.
- return;
- }
-
- // TODO(camsim99): Reset live camera state observers here when
- // https://github.com/flutter/packages/pull/3419 lands.
- camera = await processCameraProvider!
- .bindToLifecycle(cameraSelector!, <UseCase>[imageAnalysis!]);
- cameraInfo = await camera!.getCameraInfo();
+ await imageAnalysis!.setAnalyzer(analyzer);
}
/// Unbinds [useCase] from camera lifecycle controlled by the
@@ -666,8 +674,8 @@
/// The [onListen] callback for the stream controller used for image
/// streaming.
- Future<void> _onFrameStreamListen() async {
- await _configureAndBindImageAnalysisToLifecycle();
+ Future<void> _onFrameStreamListen(int cameraId) async {
+ await _configureAndBindImageAnalysisToLifecycle(cameraId);
}
/// The [onCancel] callback for the stream controller used for image
@@ -695,15 +703,16 @@
// Methods concerning camera state:
- /// Adds observers to the [LiveData] of the [CameraState] of the current
+ /// Updates [cameraInfo] to the information corresponding to [camera] and
+ /// adds observers to the [LiveData] of the [CameraState] of the current
/// [camera], saved as [liveCameraState].
///
/// If a previous [liveCameraState] was stored, existing observers are
/// removed, as well.
- Future<void> _updateLiveCameraState(int cameraId) async {
- final CameraInfo cameraInfo = await camera!.getCameraInfo();
+ Future<void> _updateCameraInfoAndLiveCameraState(int cameraId) async {
+ cameraInfo = await camera!.getCameraInfo();
await liveCameraState?.removeObservers();
- liveCameraState = await cameraInfo.getCameraState();
+ liveCameraState = await cameraInfo!.getCameraState();
await liveCameraState!.observe(_createCameraClosingObserver(cameraId));
}
@@ -774,6 +783,110 @@
}
}
+ /// Returns the [ResolutionSelector] that maps to the specified resolution
+ /// preset for camera [UseCase]s.
+ ///
+ /// If the specified [preset] is unavailable, the camera will fall back to the
+ /// closest lower resolution available.
+ ResolutionSelector? _getResolutionSelectorFromPreset(
+ ResolutionPreset? preset) {
+ const int fallbackRule = ResolutionStrategy.fallbackRuleClosestLower;
+
+ Size? boundSize;
+ ResolutionStrategy? resolutionStrategy;
+ switch (preset) {
+ case ResolutionPreset.low:
+ boundSize = const Size(320, 240);
+ break;
+ case ResolutionPreset.medium:
+ boundSize = const Size(720, 480);
+ break;
+ case ResolutionPreset.high:
+ boundSize = const Size(1280, 720);
+ break;
+ case ResolutionPreset.veryHigh:
+ boundSize = const Size(1920, 1080);
+ break;
+ case ResolutionPreset.ultraHigh:
+ boundSize = const Size(3840, 2160);
+ break;
+ case ResolutionPreset.max:
+ // Automatically set strategy to choose highest available.
+ resolutionStrategy = _shouldCreateDetachedObjectForTesting
+ ? ResolutionStrategy.detachedHighestAvailableStrategy()
+ : ResolutionStrategy.highestAvailableStrategy();
+ break;
+ case null:
+ // If no preset is specified, default to CameraX's default behavior
+ // for each UseCase.
+ return null;
+ }
+
+ if (_shouldCreateDetachedObjectForTesting) {
+ resolutionStrategy ??= ResolutionStrategy.detached(
+ boundSize: boundSize, fallbackRule: fallbackRule);
+ return ResolutionSelector.detached(
+ resolutionStrategy: resolutionStrategy);
+ }
+
+ resolutionStrategy ??=
+ ResolutionStrategy(boundSize: boundSize!, fallbackRule: fallbackRule);
+ return ResolutionSelector(
+ resolutionStrategy: ResolutionStrategy(
+ boundSize: boundSize!, fallbackRule: fallbackRule));
+ }
+
+ /// Returns the [QualitySelector] that maps to the specified resolution
+ /// preset for the camera used only for video capture.
+ ///
+ /// If the specified [preset] is unavailable, the camera will fall back to the
+ /// closest lower resolution available.
+ QualitySelector? _getQualitySelectorFromPreset(ResolutionPreset? preset) {
+ VideoQuality? videoQuality;
+ switch (preset) {
+ case ResolutionPreset.low:
+ // 240p is not supported by CameraX.
+ case ResolutionPreset.medium:
+ videoQuality = VideoQuality.SD;
+ break;
+ case ResolutionPreset.high:
+ videoQuality = VideoQuality.HD;
+ break;
+ case ResolutionPreset.veryHigh:
+ videoQuality = VideoQuality.FHD;
+ break;
+ case ResolutionPreset.ultraHigh:
+ videoQuality = VideoQuality.UHD;
+ break;
+ case ResolutionPreset.max:
+ videoQuality = VideoQuality.highest;
+ break;
+ case null:
+ // If no preset is specified, default to CameraX's default behavior
+ // for each UseCase.
+ return null;
+ }
+
+ // We will choose the next highest video quality if the one desired
+ // is unavailable.
+ const VideoResolutionFallbackRule fallbackRule =
+ VideoResolutionFallbackRule.lowerQualityThan;
+ final FallbackStrategy fallbackStrategy =
+ _shouldCreateDetachedObjectForTesting
+ ? FallbackStrategy.detached(
+ quality: videoQuality, fallbackRule: fallbackRule)
+ : FallbackStrategy(
+ quality: videoQuality, fallbackRule: fallbackRule);
+
+ return _shouldCreateDetachedObjectForTesting
+ ? QualitySelector.detached(qualityList: <VideoQualityData>[
+ VideoQualityData(quality: videoQuality)
+ ], fallbackStrategy: fallbackStrategy)
+ : QualitySelector.from(
+ quality: VideoQualityData(quality: videoQuality),
+ fallbackStrategy: fallbackStrategy);
+ }
+
// Methods for calls that need to be tested:
/// Requests camera permissions.
@@ -804,23 +917,26 @@
}
/// Returns a [Preview] configured with the specified target rotation and
- /// resolution.
+ /// specified [ResolutionSelector].
@visibleForTesting
- Preview createPreview(int targetRotation) {
- return Preview(targetRotation: targetRotation);
+ Preview createPreview(
+ {required int targetRotation, ResolutionSelector? resolutionSelector}) {
+ return Preview(
+ targetRotation: targetRotation, resolutionSelector: resolutionSelector);
}
/// Returns an [ImageCapture] configured with specified flash mode and
- /// target resolution.
+ /// the specified [ResolutionSelector].
@visibleForTesting
- ImageCapture createImageCapture(int? flashMode) {
- return ImageCapture(targetFlashMode: flashMode);
+ ImageCapture createImageCapture(ResolutionSelector? resolutionSelector) {
+ return ImageCapture(resolutionSelector: resolutionSelector);
}
- /// Returns a [Recorder] for use in video capture.
+ /// Returns a [Recorder] for use in video capture configured with the
+ /// specified [QualitySelector].
@visibleForTesting
- Recorder createRecorder() {
- return Recorder();
+ Recorder createRecorder(QualitySelector? qualitySelector) {
+ return Recorder(qualitySelector: qualitySelector);
}
/// Returns a [VideoCapture] associated with the provided [Recorder].
@@ -829,9 +945,10 @@
return VideoCapture.withOutput(recorder);
}
- /// Returns an [ImageAnalysis] configured with specified target resolution.
+ /// Returns an [ImageAnalysis] configured with the specified
+ /// [ResolutionSelector].
@visibleForTesting
- ImageAnalysis createImageAnalysis() {
- return ImageAnalysis();
+ ImageAnalysis createImageAnalysis(ResolutionSelector? resolutionSelector) {
+ return ImageAnalysis(resolutionSelector: resolutionSelector);
}
}
diff --git a/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart b/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart
index b97d193..dda3a81 100644
--- a/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart
+++ b/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart
@@ -49,7 +49,7 @@
/// These are pre-defined quality constants that are universally used for video.
///
/// See https://developer.android.com/reference/androidx/camera/video/Quality.
-enum VideoQualityConstraint {
+enum VideoQuality {
SD,
HD,
FHD,
@@ -59,6 +59,8 @@
}
/// Fallback rules for selecting video resolution.
+///
+/// See https://developer.android.com/reference/androidx/camera/video/FallbackStrategy.
enum VideoResolutionFallbackRule {
higherQualityOrLowerThan,
higherQualityThan,
@@ -186,6 +188,28 @@
}
}
+/// Convenience class for sending lists of [Quality]s.
+class VideoQualityData {
+ VideoQualityData({
+ required this.quality,
+ });
+
+ VideoQuality quality;
+
+ Object encode() {
+ return <Object?>[
+ quality.index,
+ ];
+ }
+
+ static VideoQualityData decode(Object result) {
+ result as List<Object?>;
+ return VideoQualityData(
+ quality: VideoQuality.values[result[0]! as int],
+ );
+ }
+}
+
class InstanceManagerHostApi {
/// Constructor for [InstanceManagerHostApi]. The [binaryMessenger] named argument is
/// available for dependency injection. If it is left null, the default
@@ -2478,6 +2502,9 @@
if (value is ResolutionInfo) {
buffer.putUint8(128);
writeValue(buffer, value.encode());
+ } else if (value is VideoQualityData) {
+ buffer.putUint8(129);
+ writeValue(buffer, value.encode());
} else {
super.writeValue(buffer, value);
}
@@ -2488,6 +2515,8 @@
switch (type) {
case 128:
return ResolutionInfo.decode(readValue(buffer)!);
+ case 129:
+ return VideoQualityData.decode(readValue(buffer)!);
default:
return super.readValueOfType(type, buffer);
}
@@ -2506,14 +2535,14 @@
Future<void> create(
int arg_identifier,
- List<int?> arg_videoQualityConstraintIndexList,
+ List<VideoQualityData?> arg_videoQualityDataList,
int? arg_fallbackStrategyId) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.QualitySelectorHostApi.create', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList = await channel.send(<Object?>[
arg_identifier,
- arg_videoQualityConstraintIndexList,
+ arg_videoQualityDataList,
arg_fallbackStrategyId
]) as List<Object?>?;
if (replyList == null) {
@@ -2533,7 +2562,7 @@
}
Future<ResolutionInfo> getResolution(
- int arg_cameraInfoId, VideoQualityConstraint arg_quality) async {
+ int arg_cameraInfoId, VideoQuality arg_quality) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.QualitySelectorHostApi.getResolution', codec,
binaryMessenger: _binaryMessenger);
@@ -2571,7 +2600,7 @@
static const MessageCodec<Object?> codec = StandardMessageCodec();
- Future<void> create(int arg_identifier, VideoQualityConstraint arg_quality,
+ Future<void> create(int arg_identifier, VideoQuality arg_quality,
VideoResolutionFallbackRule arg_fallbackRule) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.FallbackStrategyHostApi.create', codec,
diff --git a/packages/camera/camera_android_camerax/lib/src/fallback_strategy.dart b/packages/camera/camera_android_camerax/lib/src/fallback_strategy.dart
index ee098fa..ad2589d 100644
--- a/packages/camera/camera_android_camerax/lib/src/fallback_strategy.dart
+++ b/packages/camera/camera_android_camerax/lib/src/fallback_strategy.dart
@@ -40,7 +40,7 @@
late final _FallbackStrategyHostApiImpl _api;
/// The input quality used to specify this fallback strategy relative to.
- final VideoQualityConstraint quality;
+ final VideoQuality quality;
/// The fallback rule that this strategy will follow.
final VideoResolutionFallbackRule fallbackRule;
@@ -72,9 +72,7 @@
/// Creates a [FallbackStrategy] instance with the specified video [quality]
/// and [fallbackRule].
- void createFromInstance(
- FallbackStrategy instance,
- VideoQualityConstraint quality,
+ void createFromInstance(FallbackStrategy instance, VideoQuality quality,
VideoResolutionFallbackRule fallbackRule) {
final int identifier = instanceManager.addDartCreatedInstance(instance,
onCopy: (FallbackStrategy original) {
diff --git a/packages/camera/camera_android_camerax/lib/src/quality_selector.dart b/packages/camera/camera_android_camerax/lib/src/quality_selector.dart
index 303d4ae..6daff7d 100644
--- a/packages/camera/camera_android_camerax/lib/src/quality_selector.dart
+++ b/packages/camera/camera_android_camerax/lib/src/quality_selector.dart
@@ -22,9 +22,9 @@
QualitySelector.from(
{BinaryMessenger? binaryMessenger,
InstanceManager? instanceManager,
- required VideoQualityConstraint quality,
+ required VideoQualityData quality,
this.fallbackStrategy})
- : qualityList = <VideoQualityConstraint>[quality],
+ : qualityList = <VideoQualityData>[quality],
super.detached(
binaryMessenger: binaryMessenger,
instanceManager: instanceManager) {
@@ -65,7 +65,7 @@
late final _QualitySelectorHostApiImpl _api;
/// Desired qualities for this selector instance.
- final List<VideoQualityConstraint> qualityList;
+ final List<VideoQualityData> qualityList;
/// Desired fallback strategy for this selector instance.
final FallbackStrategy? fallbackStrategy;
@@ -73,7 +73,7 @@
/// Retrieves the corresponding resolution from the input [quality] for the
/// camera represented by [cameraInfo].
static Future<ResolutionInfo> getResolution(
- CameraInfo cameraInfo, VideoQualityConstraint quality,
+ CameraInfo cameraInfo, VideoQuality quality,
{BinaryMessenger? binaryMessenger, InstanceManager? instanceManager}) {
final _QualitySelectorHostApiImpl api = _QualitySelectorHostApiImpl(
binaryMessenger: binaryMessenger, instanceManager: instanceManager);
@@ -107,10 +107,8 @@
/// Creates a [QualitySelector] instance with the desired qualities and
/// fallback strategy specified.
- void createFromInstance(
- QualitySelector instance,
- List<VideoQualityConstraint> qualityList,
- FallbackStrategy? fallbackStrategy) {
+ void createFromInstance(QualitySelector instance,
+ List<VideoQualityData> qualityList, FallbackStrategy? fallbackStrategy) {
final int identifier = instanceManager.addDartCreatedInstance(instance,
onCopy: (QualitySelector original) {
return QualitySelector.detached(
@@ -120,13 +118,10 @@
fallbackStrategy: original.fallbackStrategy,
);
});
- final List<int> qualityIndices = qualityList
- .map<int>((VideoQualityConstraint quality) => quality.index)
- .toList();
create(
identifier,
- qualityIndices,
+ qualityList,
fallbackStrategy == null
? null
: instanceManager.getIdentifier(fallbackStrategy));
@@ -135,7 +130,7 @@
/// Retrieves the corresponding resolution from the input [quality] for the
/// camera represented by [cameraInfo].
Future<ResolutionInfo> getResolutionFromInstance(
- CameraInfo cameraInfo, VideoQualityConstraint quality) async {
+ CameraInfo cameraInfo, VideoQuality quality) async {
final int? cameraInfoIdentifier = instanceManager.getIdentifier(cameraInfo);
if (cameraInfoIdentifier == null) {
diff --git a/packages/camera/camera_android_camerax/lib/src/recorder.dart b/packages/camera/camera_android_camerax/lib/src/recorder.dart
index 1395362..1fd93ec 100644
--- a/packages/camera/camera_android_camerax/lib/src/recorder.dart
+++ b/packages/camera/camera_android_camerax/lib/src/recorder.dart
@@ -59,13 +59,13 @@
return QualitySelector.fromOrderedList(
binaryMessenger: binaryMessenger,
instanceManager: instanceManager,
- qualityList: const <VideoQualityConstraint>[
- VideoQualityConstraint.FHD,
- VideoQualityConstraint.HD,
- VideoQualityConstraint.SD
+ qualityList: <VideoQualityData>[
+ VideoQualityData(quality: VideoQuality.FHD),
+ VideoQualityData(quality: VideoQuality.HD),
+ VideoQualityData(quality: VideoQuality.SD),
],
fallbackStrategy: FallbackStrategy(
- quality: VideoQualityConstraint.FHD,
+ quality: VideoQuality.FHD,
fallbackRule: VideoResolutionFallbackRule.higherQualityOrLowerThan),
);
}
diff --git a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart
index 02ea78c..9ebf34b 100644
--- a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart
+++ b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart
@@ -102,7 +102,7 @@
/// These are pre-defined quality constants that are universally used for video.
///
/// See https://developer.android.com/reference/androidx/camera/video/Quality.
-enum VideoQualityConstraint {
+enum VideoQuality {
SD, // 480p
HD, // 720p
FHD, // 1080p
@@ -111,6 +111,11 @@
highest,
}
+/// Convenience class for sending lists of [Quality]s.
+class VideoQualityData {
+ late VideoQuality quality;
+}
+
/// Fallback rules for selecting video resolution.
///
/// See https://developer.android.com/reference/androidx/camera/video/FallbackStrategy.
@@ -401,17 +406,14 @@
@HostApi(dartHostTestHandler: 'TestQualitySelectorHostApi')
abstract class QualitySelectorHostApi {
- // TODO(camsim99): Change qualityList to List<VideoQualityConstraint> when
- // enums are supported for collection types.
- void create(int identifier, List<int> videoQualityConstraintIndexList,
+ void create(int identifier, List<VideoQualityData> videoQualityDataList,
int? fallbackStrategyId);
- ResolutionInfo getResolution(
- int cameraInfoId, VideoQualityConstraint quality);
+ ResolutionInfo getResolution(int cameraInfoId, VideoQuality quality);
}
@HostApi(dartHostTestHandler: 'TestFallbackStrategyHostApi')
abstract class FallbackStrategyHostApi {
- void create(int identifier, VideoQualityConstraint quality,
+ void create(int identifier, VideoQuality quality,
VideoResolutionFallbackRule fallbackRule);
}
diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml
index 0e57c52..54c895b 100644
--- a/packages/camera/camera_android_camerax/pubspec.yaml
+++ b/packages/camera/camera_android_camerax/pubspec.yaml
@@ -2,7 +2,7 @@
description: Android implementation of the camera plugin using the CameraX library.
repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android_camerax
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
-version: 0.5.0+16
+version: 0.5.0+17
environment:
sdk: ">=2.19.0 <4.0.0"
diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart
index a0e60b6..bece928 100644
--- a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart
+++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart
@@ -14,6 +14,7 @@
import 'package:camera_android_camerax/src/camera_state_error.dart';
import 'package:camera_android_camerax/src/camerax_library.g.dart';
import 'package:camera_android_camerax/src/exposure_state.dart';
+import 'package:camera_android_camerax/src/fallback_strategy.dart';
import 'package:camera_android_camerax/src/image_analysis.dart';
import 'package:camera_android_camerax/src/image_capture.dart';
import 'package:camera_android_camerax/src/image_proxy.dart';
@@ -23,8 +24,11 @@
import 'package:camera_android_camerax/src/plane_proxy.dart';
import 'package:camera_android_camerax/src/preview.dart';
import 'package:camera_android_camerax/src/process_camera_provider.dart';
+import 'package:camera_android_camerax/src/quality_selector.dart';
import 'package:camera_android_camerax/src/recorder.dart';
import 'package:camera_android_camerax/src/recording.dart';
+import 'package:camera_android_camerax/src/resolution_selector.dart';
+import 'package:camera_android_camerax/src/resolution_strategy.dart';
import 'package:camera_android_camerax/src/system_services.dart';
import 'package:camera_android_camerax/src/use_case.dart';
import 'package:camera_android_camerax/src/video_capture.dart';
@@ -181,9 +185,11 @@
when(camera.testPreview.setSurfaceProvider())
.thenAnswer((_) async => testSurfaceTextureId);
when(mockProcessCameraProvider.bindToLifecycle(
- camera.mockBackCameraSelector,
- <UseCase>[camera.testPreview, camera.testImageCapture]))
- .thenAnswer((_) async => mockCamera);
+ camera.mockBackCameraSelector, <UseCase>[
+ camera.testPreview,
+ camera.testImageCapture,
+ camera.testImageAnalysis
+ ])).thenAnswer((_) async => mockCamera);
when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo);
when(mockCameraInfo.getCameraState())
.thenAnswer((_) async => mockLiveCameraState);
@@ -245,9 +251,11 @@
camera.processCameraProvider = mockProcessCameraProvider;
when(mockProcessCameraProvider.bindToLifecycle(
- camera.mockBackCameraSelector,
- <UseCase>[camera.testPreview, camera.testImageCapture]))
- .thenAnswer((_) async => mockCamera);
+ camera.mockBackCameraSelector, <UseCase>[
+ camera.testPreview,
+ camera.testImageCapture,
+ camera.testImageAnalysis
+ ])).thenAnswer((_) async => mockCamera);
when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo);
when(mockCameraInfo.getCameraState())
.thenAnswer((_) async => MockLiveCameraState());
@@ -257,14 +265,191 @@
enableAudio: enableAudio);
// Verify expected UseCases were bound.
- verify(camera.processCameraProvider!.bindToLifecycle(camera.cameraSelector!,
- <UseCase>[camera.testPreview, camera.testImageCapture]));
+ verify(camera.processCameraProvider!.bindToLifecycle(
+ camera.cameraSelector!, <UseCase>[
+ camera.testPreview,
+ camera.testImageCapture,
+ camera.testImageAnalysis
+ ]));
// Verify the camera's CameraInfo instance got updated.
expect(camera.cameraInfo, equals(mockCameraInfo));
});
test(
+ 'createCamera properly sets preset resolution for non-video capture use cases',
+ () async {
+ final FakeAndroidCameraCameraX camera =
+ FakeAndroidCameraCameraX(shouldCreateDetachedObjectForTesting: true);
+ final MockProcessCameraProvider mockProcessCameraProvider =
+ MockProcessCameraProvider();
+ const CameraLensDirection testLensDirection = CameraLensDirection.back;
+ const int testSensorOrientation = 90;
+ const CameraDescription testCameraDescription = CameraDescription(
+ name: 'cameraName',
+ lensDirection: testLensDirection,
+ sensorOrientation: testSensorOrientation);
+ const bool enableAudio = true;
+ final MockCamera mockCamera = MockCamera();
+ final MockCameraInfo mockCameraInfo = MockCameraInfo();
+
+ camera.processCameraProvider = mockProcessCameraProvider;
+
+ when(mockProcessCameraProvider.bindToLifecycle(
+ camera.mockBackCameraSelector, <UseCase>[
+ camera.testPreview,
+ camera.testImageCapture,
+ camera.testImageAnalysis
+ ])).thenAnswer((_) async => mockCamera);
+ when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo);
+ when(mockCameraInfo.getCameraState())
+ .thenAnswer((_) async => MockLiveCameraState());
+ camera.processCameraProvider = mockProcessCameraProvider;
+
+ // Test non-null resolution presets.
+ for (final ResolutionPreset resolutionPreset in ResolutionPreset.values) {
+ await camera.createCamera(testCameraDescription, resolutionPreset,
+ enableAudio: enableAudio);
+
+ Size? expectedBoundSize;
+ ResolutionStrategy? expectedResolutionStrategy;
+ switch (resolutionPreset) {
+ case ResolutionPreset.low:
+ expectedBoundSize = const Size(320, 240);
+ break;
+ case ResolutionPreset.medium:
+ expectedBoundSize = const Size(720, 480);
+ break;
+ case ResolutionPreset.high:
+ expectedBoundSize = const Size(1280, 720);
+ break;
+ case ResolutionPreset.veryHigh:
+ expectedBoundSize = const Size(1920, 1080);
+ break;
+ case ResolutionPreset.ultraHigh:
+ expectedBoundSize = const Size(3840, 2160);
+ break;
+ case ResolutionPreset.max:
+ expectedResolutionStrategy =
+ ResolutionStrategy.detachedHighestAvailableStrategy();
+ break;
+ }
+
+ // We expect the strategy to be the highest available or correspond to the
+ // expected bound size, with fallback to the closest and highest available
+ // resolution.
+ expectedResolutionStrategy ??= ResolutionStrategy.detached(
+ boundSize: expectedBoundSize,
+ fallbackRule: ResolutionStrategy.fallbackRuleClosestLower);
+
+ expect(camera.preview!.resolutionSelector!.resolutionStrategy!.boundSize,
+ equals(expectedResolutionStrategy.boundSize));
+ expect(
+ camera
+ .imageCapture!.resolutionSelector!.resolutionStrategy!.boundSize,
+ equals(expectedResolutionStrategy.boundSize));
+ expect(
+ camera
+ .imageAnalysis!.resolutionSelector!.resolutionStrategy!.boundSize,
+ equals(expectedResolutionStrategy.boundSize));
+ expect(
+ camera.preview!.resolutionSelector!.resolutionStrategy!.fallbackRule,
+ equals(expectedResolutionStrategy.fallbackRule));
+ expect(
+ camera.imageCapture!.resolutionSelector!.resolutionStrategy!
+ .fallbackRule,
+ equals(expectedResolutionStrategy.fallbackRule));
+ expect(
+ camera.imageAnalysis!.resolutionSelector!.resolutionStrategy!
+ .fallbackRule,
+ equals(expectedResolutionStrategy.fallbackRule));
+ }
+
+ // Test null case.
+ await camera.createCamera(testCameraDescription, null);
+ expect(camera.preview!.resolutionSelector, isNull);
+ expect(camera.imageCapture!.resolutionSelector, isNull);
+ expect(camera.imageAnalysis!.resolutionSelector, isNull);
+ });
+
+ test(
+ 'createCamera properly sets preset resolution for video capture use case',
+ () async {
+ final FakeAndroidCameraCameraX camera =
+ FakeAndroidCameraCameraX(shouldCreateDetachedObjectForTesting: true);
+ final MockProcessCameraProvider mockProcessCameraProvider =
+ MockProcessCameraProvider();
+ const CameraLensDirection testLensDirection = CameraLensDirection.back;
+ const int testSensorOrientation = 90;
+ const CameraDescription testCameraDescription = CameraDescription(
+ name: 'cameraName',
+ lensDirection: testLensDirection,
+ sensorOrientation: testSensorOrientation);
+ const bool enableAudio = true;
+ final MockCamera mockCamera = MockCamera();
+ final MockCameraInfo mockCameraInfo = MockCameraInfo();
+
+ camera.processCameraProvider = mockProcessCameraProvider;
+
+ when(mockProcessCameraProvider.bindToLifecycle(
+ camera.mockBackCameraSelector, <UseCase>[
+ camera.testPreview,
+ camera.testImageCapture,
+ camera.testImageAnalysis
+ ])).thenAnswer((_) async => mockCamera);
+ when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo);
+ when(mockCameraInfo.getCameraState())
+ .thenAnswer((_) async => MockLiveCameraState());
+ camera.processCameraProvider = mockProcessCameraProvider;
+
+ // Test non-null resolution presets.
+ for (final ResolutionPreset resolutionPreset in ResolutionPreset.values) {
+ await camera.createCamera(testCameraDescription, resolutionPreset,
+ enableAudio: enableAudio);
+
+ VideoQuality? expectedVideoQuality;
+ switch (resolutionPreset) {
+ case ResolutionPreset.low:
+ // 240p is not supported by CameraX.
+ case ResolutionPreset.medium:
+ expectedVideoQuality = VideoQuality.SD;
+ break;
+ case ResolutionPreset.high:
+ expectedVideoQuality = VideoQuality.HD;
+ break;
+ case ResolutionPreset.veryHigh:
+ expectedVideoQuality = VideoQuality.FHD;
+ break;
+ case ResolutionPreset.ultraHigh:
+ expectedVideoQuality = VideoQuality.UHD;
+ break;
+ case ResolutionPreset.max:
+ expectedVideoQuality = VideoQuality.highest;
+ break;
+ }
+
+ const VideoResolutionFallbackRule expectedFallbackRule =
+ VideoResolutionFallbackRule.lowerQualityThan;
+ final FallbackStrategy expectedFallbackStrategy =
+ FallbackStrategy.detached(
+ quality: expectedVideoQuality,
+ fallbackRule: expectedFallbackRule);
+
+ expect(camera.recorder!.qualitySelector!.qualityList.length, equals(1));
+ expect(camera.recorder!.qualitySelector!.qualityList.first.quality,
+ equals(expectedVideoQuality));
+ expect(camera.recorder!.qualitySelector!.fallbackStrategy!.quality,
+ equals(expectedFallbackStrategy.quality));
+ expect(camera.recorder!.qualitySelector!.fallbackStrategy!.fallbackRule,
+ equals(expectedFallbackStrategy.fallbackRule));
+ }
+
+ // Test null case.
+ await camera.createCamera(testCameraDescription, null);
+ expect(camera.recorder!.qualitySelector, isNull);
+ });
+
+ test(
'initializeCamera throws a CameraException when createCamera has not been called before initializedCamera',
() async {
final AndroidCameraCameraX camera = AndroidCameraCameraX();
@@ -313,9 +498,11 @@
.thenAnswer((_) async => cameraId);
when(camera.processCameraProvider!.bindToLifecycle(
- camera.mockBackCameraSelector,
- <UseCase>[camera.testPreview, camera.testImageCapture]))
- .thenAnswer((_) async => mockCamera);
+ camera.mockBackCameraSelector, <UseCase>[
+ camera.testPreview,
+ camera.testImageCapture,
+ camera.testImageAnalysis
+ ])).thenAnswer((_) async => mockCamera);
when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo);
when(mockCameraInfo.getCameraState())
.thenAnswer((_) async => MockLiveCameraState());
@@ -917,15 +1104,19 @@
final MockProcessCameraProvider mockProcessCameraProvider =
MockProcessCameraProvider();
final MockCamera mockCamera = MockCamera();
+ final MockCameraInfo mockCameraInfo = MockCameraInfo();
const int cameraId = 22;
camera.processCameraProvider = mockProcessCameraProvider;
camera.cameraSelector = MockCameraSelector();
+ camera.imageAnalysis = MockImageAnalysis();
when(mockProcessCameraProvider.bindToLifecycle(any, any))
.thenAnswer((_) => Future<Camera>.value(mockCamera));
when(mockCamera.getCameraInfo())
- .thenAnswer((_) => Future<CameraInfo>.value(MockCameraInfo()));
+ .thenAnswer((_) => Future<CameraInfo>.value(mockCameraInfo));
+ when(mockCameraInfo.getCameraState())
+ .thenAnswer((_) async => MockLiveCameraState());
final CameraImageData mockCameraImageData = MockCameraImageData();
final Stream<CameraImageData> imageStream =
@@ -947,15 +1138,19 @@
final MockProcessCameraProvider mockProcessCameraProvider =
MockProcessCameraProvider();
final MockCamera mockCamera = MockCamera();
+ final MockCameraInfo mockCameraInfo = MockCameraInfo();
const int cameraId = 22;
camera.processCameraProvider = mockProcessCameraProvider;
camera.cameraSelector = MockCameraSelector();
+ camera.imageAnalysis = MockImageAnalysis();
when(mockProcessCameraProvider.bindToLifecycle(any, any))
.thenAnswer((_) => Future<Camera>.value(mockCamera));
when(mockCamera.getCameraInfo())
- .thenAnswer((_) => Future<CameraInfo>.value(MockCameraInfo()));
+ .thenAnswer((_) => Future<CameraInfo>.value(mockCameraInfo));
+ when(mockCameraInfo.getCameraState())
+ .thenAnswer((_) async => MockLiveCameraState());
final CameraImageData mockCameraImageData = MockCameraImageData();
final Stream<CameraImageData> imageStream =
@@ -988,6 +1183,7 @@
final ProcessCameraProvider mockProcessCameraProvider =
MockProcessCameraProvider();
final CameraSelector mockCameraSelector = MockCameraSelector();
+ final MockImageAnalysis mockImageAnalysis = MockImageAnalysis();
final Camera mockCamera = MockCamera();
final CameraInfo mockCameraInfo = MockCameraInfo();
final MockImageProxy mockImageProxy = MockImageProxy();
@@ -1002,13 +1198,16 @@
camera.processCameraProvider = mockProcessCameraProvider;
camera.cameraSelector = mockCameraSelector;
+ camera.imageAnalysis = mockImageAnalysis;
- when(mockProcessCameraProvider.isBound(camera.mockImageAnalysis))
+ when(mockProcessCameraProvider.isBound(mockImageAnalysis))
.thenAnswer((_) async => Future<bool>.value(false));
- when(mockProcessCameraProvider.bindToLifecycle(
- mockCameraSelector, <UseCase>[camera.mockImageAnalysis]))
+ when(mockProcessCameraProvider
+ .bindToLifecycle(mockCameraSelector, <UseCase>[mockImageAnalysis]))
.thenAnswer((_) async => mockCamera);
when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo);
+ when(mockCameraInfo.getCameraState())
+ .thenAnswer((_) async => MockLiveCameraState());
when(mockImageProxy.getPlanes())
.thenAnswer((_) => Future<List<PlaneProxy>>.value(mockPlanes));
when(mockPlane.buffer).thenReturn(buffer);
@@ -1029,12 +1228,8 @@
// Test ImageAnalysis use case is bound to ProcessCameraProvider.
final Analyzer capturedAnalyzer =
- verify(camera.mockImageAnalysis.setAnalyzer(captureAny)).captured.single
+ verify(mockImageAnalysis.setAnalyzer(captureAny)).captured.single
as Analyzer;
- await untilCalled(
- mockProcessCameraProvider.isBound(camera.mockImageAnalysis));
- await untilCalled(mockProcessCameraProvider.bindToLifecycle(
- mockCameraSelector, <UseCase>[camera.mockImageAnalysis]));
await capturedAnalyzer.analyze(mockImageProxy);
final CameraImageData imageData = await imageDataCompleter.future;
@@ -1048,9 +1243,6 @@
expect(imageData.height, equals(imageHeight));
expect(imageData.width, equals(imageWidth));
- // Verify camera and cameraInfo were properly updated.
- expect(camera.camera, equals(mockCamera));
- expect(camera.cameraInfo, equals(mockCameraInfo));
await onStreamedFrameAvailableSubscription.cancel();
});
@@ -1063,26 +1255,19 @@
final ProcessCameraProvider mockProcessCameraProvider =
MockProcessCameraProvider();
final CameraSelector mockCameraSelector = MockCameraSelector();
- final Camera mockCamera = MockCamera();
+ final MockImageAnalysis mockImageAnalysis = MockImageAnalysis();
camera.processCameraProvider = mockProcessCameraProvider;
camera.cameraSelector = mockCameraSelector;
-
- when(mockProcessCameraProvider.bindToLifecycle(
- mockCameraSelector, <UseCase>[camera.mockImageAnalysis]))
- .thenAnswer((_) async => mockCamera);
- when(mockCamera.getCameraInfo()).thenAnswer((_) async => MockCameraInfo());
+ camera.imageAnalysis = mockImageAnalysis;
final StreamSubscription<CameraImageData> imageStreamSubscription = camera
.onStreamedFrameAvailable(cameraId)
.listen((CameraImageData data) {});
- when(mockProcessCameraProvider.isBound(camera.mockImageAnalysis))
- .thenAnswer((_) async => Future<bool>.value(true));
-
await imageStreamSubscription.cancel();
- verify(camera.mockImageAnalysis.clearAnalyzer());
+ verify(mockImageAnalysis.clearAnalyzer());
});
}
@@ -1101,7 +1286,7 @@
final MockCameraSelector mockFrontCameraSelector = MockCameraSelector();
final MockRecorder testRecorder = MockRecorder();
final MockVideoCapture testVideoCapture = MockVideoCapture();
- final MockImageAnalysis mockImageAnalysis = MockImageAnalysis();
+ final MockImageAnalysis testImageAnalysis = MockImageAnalysis();
@override
Future<void> requestCameraPermissions(bool enableAudio) async {
@@ -1127,17 +1312,21 @@
}
@override
- Preview createPreview(int targetRotation) {
+ Preview createPreview(
+ {required int targetRotation, ResolutionSelector? resolutionSelector}) {
+ when(testPreview.resolutionSelector).thenReturn(resolutionSelector);
return testPreview;
}
@override
- ImageCapture createImageCapture(int? flashMode) {
+ ImageCapture createImageCapture(ResolutionSelector? resolutionSelector) {
+ when(testImageCapture.resolutionSelector).thenReturn(resolutionSelector);
return testImageCapture;
}
@override
- Recorder createRecorder() {
+ Recorder createRecorder(QualitySelector? qualitySelector) {
+ when(testRecorder.qualitySelector).thenReturn(qualitySelector);
return testRecorder;
}
@@ -1147,7 +1336,8 @@
}
@override
- ImageAnalysis createImageAnalysis() {
- return mockImageAnalysis;
+ ImageAnalysis createImageAnalysis(ResolutionSelector? resolutionSelector) {
+ when(testImageAnalysis.resolutionSelector).thenReturn(resolutionSelector);
+ return testImageAnalysis;
}
}
diff --git a/packages/camera/camera_android_camerax/test/fallback_strategy_test.dart b/packages/camera/camera_android_camerax/test/fallback_strategy_test.dart
index 8c6960c..f54e40d 100644
--- a/packages/camera/camera_android_camerax/test/fallback_strategy_test.dart
+++ b/packages/camera/camera_android_camerax/test/fallback_strategy_test.dart
@@ -33,14 +33,14 @@
);
FallbackStrategy.detached(
- quality: VideoQualityConstraint.UHD,
+ quality: VideoQuality.UHD,
fallbackRule: VideoResolutionFallbackRule.higherQualityThan,
instanceManager: instanceManager,
);
verifyNever(mockApi.create(
argThat(isA<int>()),
- argThat(isA<VideoQualityConstraint>()),
+ argThat(isA<VideoQuality>()),
argThat(isA<VideoResolutionFallbackRule>()),
));
});
@@ -55,7 +55,7 @@
onWeakReferenceRemoved: (_) {},
);
- const VideoQualityConstraint quality = VideoQualityConstraint.HD;
+ const VideoQuality quality = VideoQuality.HD;
const VideoResolutionFallbackRule fallbackRule =
VideoResolutionFallbackRule.lowerQualityThan;
diff --git a/packages/camera/camera_android_camerax/test/fallback_strategy_test.mocks.dart b/packages/camera/camera_android_camerax/test/fallback_strategy_test.mocks.dart
index 02d185a..8ba811c 100644
--- a/packages/camera/camera_android_camerax/test/fallback_strategy_test.mocks.dart
+++ b/packages/camera/camera_android_camerax/test/fallback_strategy_test.mocks.dart
@@ -33,7 +33,7 @@
@override
void create(
int? identifier,
- _i3.VideoQualityConstraint? quality,
+ _i3.VideoQuality? quality,
_i3.VideoResolutionFallbackRule? fallbackRule,
) =>
super.noSuchMethod(
diff --git a/packages/camera/camera_android_camerax/test/quality_selector_test.dart b/packages/camera/camera_android_camerax/test/quality_selector_test.dart
index 8c05aa7..bfaa568 100644
--- a/packages/camera/camera_android_camerax/test/quality_selector_test.dart
+++ b/packages/camera/camera_android_camerax/test/quality_selector_test.dart
@@ -41,7 +41,9 @@
);
QualitySelector.detached(
- qualityList: const <VideoQualityConstraint>[VideoQualityConstraint.FHD],
+ qualityList: <VideoQualityData>[
+ VideoQualityData(quality: VideoQuality.UHD)
+ ],
fallbackStrategy: MockFallbackStrategy(),
instanceManager: instanceManager,
);
@@ -60,7 +62,8 @@
onWeakReferenceRemoved: (_) {},
);
- const VideoQualityConstraint quality = VideoQualityConstraint.FHD;
+ const VideoQuality videoQuality = VideoQuality.FHD;
+ final VideoQualityData quality = VideoQualityData(quality: videoQuality);
final FallbackStrategy fallbackStrategy = MockFallbackStrategy();
const int fallbackStrategyIdentifier = 9;
@@ -76,11 +79,15 @@
instanceManager: instanceManager,
);
- verify(mockApi.create(
+ final VerificationResult verificationResult = verify(mockApi.create(
instanceManager.getIdentifier(instance),
- <int>[2],
+ captureAny,
fallbackStrategyIdentifier,
));
+ final List<VideoQualityData?> videoQualityData =
+ verificationResult.captured.single as List<VideoQualityData?>;
+ expect(videoQualityData.length, equals(1));
+ expect(videoQualityData.first!.quality, equals(videoQuality));
});
test('quality list constructor calls create on the Java side', () {
@@ -93,9 +100,9 @@
onWeakReferenceRemoved: (_) {},
);
- const List<VideoQualityConstraint> qualityList = <VideoQualityConstraint>[
- VideoQualityConstraint.FHD,
- VideoQualityConstraint.highest
+ final List<VideoQualityData> qualityList = <VideoQualityData>[
+ VideoQualityData(quality: VideoQuality.FHD),
+ VideoQualityData(quality: VideoQuality.highest),
];
final FallbackStrategy fallbackStrategy = MockFallbackStrategy();
@@ -113,11 +120,16 @@
instanceManager: instanceManager,
);
- verify(mockApi.create(
+ final VerificationResult verificationResult = verify(mockApi.create(
instanceManager.getIdentifier(instance),
- <int>[2, 5],
+ captureAny,
fallbackStrategyIdentifier,
));
+ final List<VideoQualityData?> videoQualityData =
+ verificationResult.captured.single as List<VideoQualityData?>;
+ expect(videoQualityData.length, equals(2));
+ expect(videoQualityData.first!.quality, equals(VideoQuality.FHD));
+ expect(videoQualityData.last!.quality, equals(VideoQuality.highest));
});
test('getResolution returns expected resolution info', () async {
@@ -131,7 +143,9 @@
final QualitySelector instance = QualitySelector.detached(
instanceManager: instanceManager,
- qualityList: const <VideoQualityConstraint>[VideoQualityConstraint.HD],
+ qualityList: <VideoQualityData>[
+ VideoQualityData(quality: VideoQuality.HD)
+ ],
fallbackStrategy: MockFallbackStrategy(),
);
const int instanceIdentifier = 0;
@@ -153,7 +167,7 @@
onCopy: (_) => MockCameraInfo(),
);
- const VideoQualityConstraint quality = VideoQualityConstraint.FHD;
+ const VideoQuality quality = VideoQuality.FHD;
final ResolutionInfo expectedResult =
ResolutionInfo(width: 34, height: 23);
diff --git a/packages/camera/camera_android_camerax/test/quality_selector_test.mocks.dart b/packages/camera/camera_android_camerax/test/quality_selector_test.mocks.dart
index ba08711..65741af 100644
--- a/packages/camera/camera_android_camerax/test/quality_selector_test.mocks.dart
+++ b/packages/camera/camera_android_camerax/test/quality_selector_test.mocks.dart
@@ -135,10 +135,10 @@
}
@override
- _i4.VideoQualityConstraint get quality => (super.noSuchMethod(
+ _i4.VideoQuality get quality => (super.noSuchMethod(
Invocation.getter(#quality),
- returnValue: _i4.VideoQualityConstraint.SD,
- ) as _i4.VideoQualityConstraint);
+ returnValue: _i4.VideoQuality.SD,
+ ) as _i4.VideoQuality);
@override
_i4.VideoResolutionFallbackRule get fallbackRule => (super.noSuchMethod(
Invocation.getter(#fallbackRule),
@@ -158,7 +158,7 @@
@override
void create(
int? identifier,
- List<int?>? videoQualityConstraintIndexList,
+ List<_i4.VideoQualityData?>? videoQualityDataList,
int? fallbackStrategyId,
) =>
super.noSuchMethod(
@@ -166,7 +166,7 @@
#create,
[
identifier,
- videoQualityConstraintIndexList,
+ videoQualityDataList,
fallbackStrategyId,
],
),
@@ -175,7 +175,7 @@
@override
_i4.ResolutionInfo getResolution(
int? cameraInfoId,
- _i4.VideoQualityConstraint? quality,
+ _i4.VideoQuality? quality,
) =>
(super.noSuchMethod(
Invocation.method(
diff --git a/packages/camera/camera_android_camerax/test/recorder_test.dart b/packages/camera/camera_android_camerax/test/recorder_test.dart
index 994c0f9..c7b189f 100644
--- a/packages/camera/camera_android_camerax/test/recorder_test.dart
+++ b/packages/camera/camera_android_camerax/test/recorder_test.dart
@@ -84,16 +84,21 @@
final QualitySelector defaultQualitySelector =
Recorder.getDefaultQualitySelector();
+ final List<VideoQuality> expectedVideoQualities = <VideoQuality>[
+ VideoQuality.FHD,
+ VideoQuality.HD,
+ VideoQuality.SD
+ ];
- expect(
- defaultQualitySelector.qualityList,
- equals(const <VideoQualityConstraint>[
- VideoQualityConstraint.FHD,
- VideoQualityConstraint.HD,
- VideoQualityConstraint.SD
- ]));
+ expect(defaultQualitySelector.qualityList.length, equals(3));
+ for (int i = 0; i < 3; i++) {
+ final VideoQuality currentVideoQuality =
+ defaultQualitySelector.qualityList[i].quality;
+ expect(currentVideoQuality, equals(expectedVideoQualities[i]));
+ }
+
expect(defaultQualitySelector.fallbackStrategy!.quality,
- equals(VideoQualityConstraint.FHD));
+ equals(VideoQuality.FHD));
expect(defaultQualitySelector.fallbackStrategy!.fallbackRule,
equals(VideoResolutionFallbackRule.higherQualityOrLowerThan));
diff --git a/packages/camera/camera_android_camerax/test/recorder_test.mocks.dart b/packages/camera/camera_android_camerax/test/recorder_test.mocks.dart
index c7850c7..7a678e2 100644
--- a/packages/camera/camera_android_camerax/test/recorder_test.mocks.dart
+++ b/packages/camera/camera_android_camerax/test/recorder_test.mocks.dart
@@ -57,10 +57,10 @@
}
@override
- List<_i2.VideoQualityConstraint> get qualityList => (super.noSuchMethod(
+ List<_i2.VideoQualityData> get qualityList => (super.noSuchMethod(
Invocation.getter(#qualityList),
- returnValue: <_i2.VideoQualityConstraint>[],
- ) as List<_i2.VideoQualityConstraint>);
+ returnValue: <_i2.VideoQualityData>[],
+ ) as List<_i2.VideoQualityData>);
}
/// A class which mocks [TestInstanceManagerHostApi].
@@ -94,7 +94,7 @@
@override
void create(
int? identifier,
- _i2.VideoQualityConstraint? quality,
+ _i2.VideoQuality? quality,
_i2.VideoResolutionFallbackRule? fallbackRule,
) =>
super.noSuchMethod(
@@ -183,7 +183,7 @@
@override
void create(
int? identifier,
- List<int?>? videoQualityConstraintIndexList,
+ List<_i2.VideoQualityData?>? videoQualityDataList,
int? fallbackStrategyId,
) =>
super.noSuchMethod(
@@ -191,7 +191,7 @@
#create,
[
identifier,
- videoQualityConstraintIndexList,
+ videoQualityDataList,
fallbackStrategyId,
],
),
@@ -200,7 +200,7 @@
@override
_i2.ResolutionInfo getResolution(
int? cameraInfoId,
- _i2.VideoQualityConstraint? quality,
+ _i2.VideoQuality? quality,
) =>
(super.noSuchMethod(
Invocation.method(
diff --git a/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart b/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart
index d4c935e..c649a73 100644
--- a/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart
+++ b/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart
@@ -1587,6 +1587,9 @@
if (value is ResolutionInfo) {
buffer.putUint8(128);
writeValue(buffer, value.encode());
+ } else if (value is VideoQualityData) {
+ buffer.putUint8(129);
+ writeValue(buffer, value.encode());
} else {
super.writeValue(buffer, value);
}
@@ -1597,6 +1600,8 @@
switch (type) {
case 128:
return ResolutionInfo.decode(readValue(buffer)!);
+ case 129:
+ return VideoQualityData.decode(readValue(buffer)!);
default:
return super.readValueOfType(type, buffer);
}
@@ -1608,11 +1613,10 @@
TestDefaultBinaryMessengerBinding.instance;
static const MessageCodec<Object?> codec = _TestQualitySelectorHostApiCodec();
- void create(int identifier, List<int?> videoQualityConstraintIndexList,
+ void create(int identifier, List<VideoQualityData?> videoQualityDataList,
int? fallbackStrategyId);
- ResolutionInfo getResolution(
- int cameraInfoId, VideoQualityConstraint quality);
+ ResolutionInfo getResolution(int cameraInfoId, VideoQuality quality);
static void setup(TestQualitySelectorHostApi? api,
{BinaryMessenger? binaryMessenger}) {
@@ -1633,12 +1637,12 @@
final int? arg_identifier = (args[0] as int?);
assert(arg_identifier != null,
'Argument for dev.flutter.pigeon.QualitySelectorHostApi.create was null, expected non-null int.');
- final List<int?>? arg_videoQualityConstraintIndexList =
- (args[1] as List<Object?>?)?.cast<int?>();
- assert(arg_videoQualityConstraintIndexList != null,
- 'Argument for dev.flutter.pigeon.QualitySelectorHostApi.create was null, expected non-null List<int?>.');
+ final List<VideoQualityData?>? arg_videoQualityDataList =
+ (args[1] as List<Object?>?)?.cast<VideoQualityData?>();
+ assert(arg_videoQualityDataList != null,
+ 'Argument for dev.flutter.pigeon.QualitySelectorHostApi.create was null, expected non-null List<VideoQualityData?>.');
final int? arg_fallbackStrategyId = (args[2] as int?);
- api.create(arg_identifier!, arg_videoQualityConstraintIndexList!,
+ api.create(arg_identifier!, arg_videoQualityDataList!,
arg_fallbackStrategyId);
return <Object?>[];
});
@@ -1661,11 +1665,10 @@
final int? arg_cameraInfoId = (args[0] as int?);
assert(arg_cameraInfoId != null,
'Argument for dev.flutter.pigeon.QualitySelectorHostApi.getResolution was null, expected non-null int.');
- final VideoQualityConstraint? arg_quality = args[1] == null
- ? null
- : VideoQualityConstraint.values[args[1] as int];
+ final VideoQuality? arg_quality =
+ args[1] == null ? null : VideoQuality.values[args[1] as int];
assert(arg_quality != null,
- 'Argument for dev.flutter.pigeon.QualitySelectorHostApi.getResolution was null, expected non-null VideoQualityConstraint.');
+ 'Argument for dev.flutter.pigeon.QualitySelectorHostApi.getResolution was null, expected non-null VideoQuality.');
final ResolutionInfo output =
api.getResolution(arg_cameraInfoId!, arg_quality!);
return <Object?>[output];
@@ -1680,7 +1683,7 @@
TestDefaultBinaryMessengerBinding.instance;
static const MessageCodec<Object?> codec = StandardMessageCodec();
- void create(int identifier, VideoQualityConstraint quality,
+ void create(int identifier, VideoQuality quality,
VideoResolutionFallbackRule fallbackRule);
static void setup(TestFallbackStrategyHostApi? api,
@@ -1702,11 +1705,10 @@
final int? arg_identifier = (args[0] as int?);
assert(arg_identifier != null,
'Argument for dev.flutter.pigeon.FallbackStrategyHostApi.create was null, expected non-null int.');
- final VideoQualityConstraint? arg_quality = args[1] == null
- ? null
- : VideoQualityConstraint.values[args[1] as int];
+ final VideoQuality? arg_quality =
+ args[1] == null ? null : VideoQuality.values[args[1] as int];
assert(arg_quality != null,
- 'Argument for dev.flutter.pigeon.FallbackStrategyHostApi.create was null, expected non-null VideoQualityConstraint.');
+ 'Argument for dev.flutter.pigeon.FallbackStrategyHostApi.create was null, expected non-null VideoQuality.');
final VideoResolutionFallbackRule? arg_fallbackRule = args[2] == null
? null
: VideoResolutionFallbackRule.values[args[2] as int];