diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index 1d2e9c665c5..61aff809f22 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 dd6e5633880..1642b815690 100644 --- a/packages/camera/camera_android_camerax/README.md +++ b/packages/camera/camera_android_camerax/README.md @@ -24,16 +24,18 @@ dependencies: ## 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 0309d5476a0..da9f58a3f71 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 class FallbackStrategyHostApiImpl implements FallbackStrategyHostApi { 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 @@ public FallbackStrategyHostApiImpl(@NonNull InstanceManager instanceManager) { @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 d7d378d158b..fe9a935989f 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 @@ private LiveDataSupportedType(final int index) { * *

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 @@ public enum VideoQualityConstraint { 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. + * + *

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 @@ ArrayList toList() { } } + /** + * Convenience class for sending lists of [Quality]s. + * + *

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 toList() { + ArrayList toListResult = new ArrayList(1); + toListResult.add(quality == null ? null : quality.index); + return toListResult; + } + + static @NonNull VideoQualityData fromList(@NonNull ArrayList list) { + VideoQualityData pigeonResult = new VideoQualityData(); + Object quality = list.get(0); + pigeonResult.setQuality(quality == null ? null : VideoQuality.values()[(int) quality]); + return pigeonResult; + } + } + public interface Result { @SuppressWarnings("UnknownNullness") void success(T result); @@ -2991,6 +3048,8 @@ protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { switch (type) { case (byte) 128: return ResolutionInfo.fromList((ArrayList) readValue(buffer)); + case (byte) 129: + return VideoQualityData.fromList((ArrayList) readValue(buffer)); default: return super.readValueOfType(type, buffer); } @@ -3001,6 +3060,9 @@ protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { 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 @@ public interface QualitySelectorHostApi { void create( @NonNull Long identifier, - @NonNull List videoQualityConstraintIndexList, + @NonNull List 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 getCodec() { @@ -3039,12 +3100,13 @@ static void setup( ArrayList wrapped = new ArrayList(); ArrayList args = (ArrayList) message; Number identifierArg = (Number) args.get(0); - List videoQualityConstraintIndexListArg = (List) args.get(1); + List videoQualityDataListArg = + (List) 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 @@ static void setup( ArrayList wrapped = new ArrayList(); ArrayList args = (ArrayList) 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 @@ public interface FallbackStrategyHostApi { void create( @NonNull Long identifier, - @NonNull VideoQualityConstraint quality, + @NonNull VideoQuality quality, @NonNull VideoResolutionFallbackRule fallbackRule); /** The codec used by FallbackStrategyHostApi. */ @@ -3117,8 +3179,8 @@ static void setup( ArrayList wrapped = new ArrayList(); ArrayList args = (ArrayList) 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 c747c75962b..675225454c5 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 class QualitySelectorHostApiImpl implements QualitySelectorHostApi { public static class QualitySelectorProxy { /** Creates an instance of {@link QualitySelector}. */ public @NonNull QualitySelector create( - @NonNull List videoQualityConstraintIndexList, + @NonNull List videoQualityDataList, @Nullable FallbackStrategy fallbackStrategy) { - // Convert each index of VideoQualityConstraint to Quality. + // Convert each index of VideoQuality to Quality. List qualityList = new ArrayList(); - 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 @@ public static class QualitySelectorProxy { ? 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 @@ public QualitySelectorHostApiImpl(@NonNull InstanceManager instanceManager) { @Override public void create( @NonNull Long identifier, - @NonNull List videoQualityConstraintIndexList, + @NonNull List videoQualityDataList, @Nullable Long fallbackStrategyIdentifier) { instanceManager.addDartCreatedInstance( proxy.create( - videoQualityConstraintIndexList, + videoQualityDataList, fallbackStrategyIdentifier == null ? null : Objects.requireNonNull(instanceManager.getInstance(fallbackStrategyIdentifier))), @@ -110,11 +105,11 @@ public void create( */ @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 @@ public void create( } /** - * 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 @@ public void create( 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 4a2eeef7f13..a68384fc720 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 @@ public void hostApiCreate_makesCallToCreateExpectedFallbackStrategy() { try (MockedStatic 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 @@ public void hostApiCreate_makesCallToCreateExpectedFallbackStrategy() { 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 @@ public void hostApiCreate_makesCallToCreateExpectedFallbackStrategy() { + 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 55195831f98..83617a713d8 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 @@ public void tearDown() { @Test public void hostApiCreate_createsExpectedQualitySelectorWhenOneQualitySpecified() { - final Long expectedVideoQualityConstraintIndex = - Long.valueOf(VideoQualityConstraint.UHD.ordinal()); - final List videoQualityConstraintList = - Arrays.asList(expectedVideoQualityConstraintIndex); + final VideoQualityData expectedVideoQualityData = + new VideoQualityData.Builder().setQuality(VideoQuality.UHD).build(); + final List videoQualityDataList = Arrays.asList(expectedVideoQualityData); final FallbackStrategy mockFallbackStrategy = mock(FallbackStrategy.class); final long fallbackStrategyIdentifier = 9; final QualitySelectorHostApiImpl hostApi = new QualitySelectorHostApiImpl(instanceManager); @@ -69,7 +69,7 @@ public void hostApiCreate_createsExpectedQualitySelectorWhenOneQualitySpecified( // 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 @@ public void hostApiCreate_createsExpectedQualitySelectorWhenOneQualitySpecified( // 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 @@ public void hostApiCreate_createsExpectedQualitySelectorWhenOneQualitySpecified( @Test public void hostApiCreate_createsExpectedQualitySelectorWhenOrderedListOfQualitiesSpecified() { - final List expectedIndices = + final List videoQualityDataList = Arrays.asList( - Long.valueOf(VideoQualityConstraint.UHD.ordinal()), - Long.valueOf(VideoQualityConstraint.HIGHEST.ordinal())); - final List videoQualityConstraintList = - Arrays.asList(VideoQualityConstraint.UHD, VideoQualityConstraint.HIGHEST); - final List expectedVideoQualityConstraintList = - Arrays.asList(Quality.UHD, Quality.HIGHEST); + new VideoQualityData.Builder().setQuality(VideoQuality.UHD).build(), + new VideoQualityData.Builder().setQuality(VideoQuality.HIGHEST).build()); + final List 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 @@ public void hostApiCreate_createsExpectedQualitySelectorWhenOrderedListOfQualiti try (MockedStatic mockedQualitySelector = mockStatic(QualitySelector.class)) { mockedQualitySelector - .when(() -> QualitySelector.fromOrderedList(expectedVideoQualityConstraintList)) + .when(() -> QualitySelector.fromOrderedList(expectedVideoQualityList)) .thenAnswer( (Answer) invocation -> mockQualitySelectorWithoutFallbackStrategy); mockedQualitySelector .when( - () -> - QualitySelector.fromOrderedList( - expectedVideoQualityConstraintList, mockFallbackStrategy)) + () -> QualitySelector.fromOrderedList(expectedVideoQualityList, mockFallbackStrategy)) .thenAnswer( (Answer) 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 @@ public void hostApiCreate_createsExpectedQualitySelectorWhenOrderedListOfQualiti // 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 hostApiCreate_createsExpectedQualitySelectorWhenOrderedListOfQualiti 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 @@ public void getResolution_returnsExpectedResolutionInfo() { .when(() -> QualitySelector.getResolution(mockCameraInfo, Quality.FHD)) .thenAnswer((Answer) 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 b6cba8da981..b016cf080da 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 @@ void main() { CameraPlatform.instance = AndroidCameraCameraX(); }); + final Map presetExpectedSizes = + { + 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 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 availableCameras = @@ -31,27 +73,103 @@ void main() { } }); - testWidgets('takePictures stores a valid image in memory', + testWidgets('Capture specific image resolutions', (WidgetTester tester) async { - final List availableCameras = + final List 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 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(); + } + } + }); + + testWidgets('Preview takes expected resolution from preset', + (WidgetTester tester) async { + final List cameras = + await CameraPlatform.instance.availableCameras(); + if (cameras.isEmpty) { + return; + } + for (final CameraDescription cameraDescription in cameras) { + bool previousPresetExactlySupported = true; + for (final MapEntry preset + in presetExpectedSizes.entries) { + final CameraController controller = + CameraController(cameraDescription, preset.key); + + await controller.initialize(); + + 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(); + } + } + }); - // Take Picture - final XFile file = await controller.takePicture(); + testWidgets('Images from streaming have expected resolution from preset', + (WidgetTester tester) async { + final List cameras = + await CameraPlatform.instance.availableCameras(); + if (cameras.isEmpty) { + return; + } + for (final CameraDescription cameraDescription in cameras) { + bool previousPresetExactlySupported = true; + for (final MapEntry preset + in presetExpectedSizes.entries) { + final CameraController controller = + CameraController(cameraDescription, preset.key); + final Completer imageCompleter = Completer(); + await controller.initialize(); + await controller.startImageStream((CameraImage image) { + imageCompleter.complete(image); + controller.stopImageStream(); + }); - // Try loading picture - final File fileImage = File(file.path); - final Image image = - await decodeImageFromList(fileImage.readAsBytesSync()); + 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; - expect(image, isNotNull); + 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 7c5bf6b37e6..fd42cc69566 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_selector.dart'; 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 'pending_recording.dart'; 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 @@ class AndroidCameraCameraX extends CameraPlatform { // 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!, [preview!, imageCapture!]); - await _updateLiveCameraState(flutterSurfaceTextureId); - cameraInfo = await camera!.getCameraInfo(); + camera = await processCameraProvider!.bindToLifecycle( + cameraSelector!, [preview!, imageCapture!, imageAnalysis!]); + await _updateCameraInfoAndLiveCameraState(flutterSurfaceTextureId); _previewIsPaused = false; return flutterSurfaceTextureId; @@ -325,6 +336,14 @@ class AndroidCameraCameraX extends CameraPlatform { return _cameraEvents(cameraId).whereType(); } + /// The camera's resolution has changed. + /// + /// This stream currently has no events being added to it from this plugin. + @override + Stream onCameraResolutionChanged(int cameraId) { + return _cameraEvents(cameraId).whereType(); + } + /// The camera started to close. @override Stream onCameraClosing(int cameraId) { @@ -484,6 +503,10 @@ class AndroidCameraCameraX extends CameraPlatform { /// 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 startVideoRecording(int cameraId, {Duration? maxVideoDuration}) async { @@ -568,7 +591,7 @@ class AndroidCameraCameraX extends CameraPlatform { Stream onStreamedFrameAvailable(int cameraId, {CameraImageStreamOptions? options}) { cameraImageDataStreamController = StreamController( - onListen: _onFrameStreamListen, + onListen: () => _onFrameStreamListen(cameraId), onCancel: _onFrameStreamCancel, ); return cameraImageDataStreamController!.stream; @@ -592,13 +615,12 @@ class AndroidCameraCameraX extends CameraPlatform { camera = await processCameraProvider! .bindToLifecycle(cameraSelector!, [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 _configureAndBindImageAnalysisToLifecycle() async { + Future _configureAndBindImageAnalysisToLifecycle(int cameraId) async { // Create Analyzer that can read image data for image streaming. final WeakReference weakThis = WeakReference(this); @@ -634,21 +656,7 @@ class AndroidCameraCameraX extends CameraPlatform { ? 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!, [imageAnalysis!]); - cameraInfo = await camera!.getCameraInfo(); + await imageAnalysis!.setAnalyzer(analyzer); } /// Unbinds [useCase] from camera lifecycle controlled by the @@ -666,8 +674,8 @@ class AndroidCameraCameraX extends CameraPlatform { /// The [onListen] callback for the stream controller used for image /// streaming. - Future _onFrameStreamListen() async { - await _configureAndBindImageAnalysisToLifecycle(); + Future _onFrameStreamListen(int cameraId) async { + await _configureAndBindImageAnalysisToLifecycle(cameraId); } /// The [onCancel] callback for the stream controller used for image @@ -695,15 +703,16 @@ class AndroidCameraCameraX extends CameraPlatform { // 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 _updateLiveCameraState(int cameraId) async { - final CameraInfo cameraInfo = await camera!.getCameraInfo(); + Future _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 @@ class AndroidCameraCameraX extends CameraPlatform { } } + /// 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(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 @@ class AndroidCameraCameraX extends CameraPlatform { } /// 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 @@ class AndroidCameraCameraX extends CameraPlatform { 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 b97d19360d8..dda3a81442d 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 @@ enum LiveDataSupportedType { /// 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 @@ enum VideoQualityConstraint { } /// Fallback rules for selecting video resolution. +/// +/// See https://developer.android.com/reference/androidx/camera/video/FallbackStrategy. enum VideoResolutionFallbackRule { higherQualityOrLowerThan, higherQualityThan, @@ -186,6 +188,28 @@ class ExposureCompensationRange { } } +/// Convenience class for sending lists of [Quality]s. +class VideoQualityData { + VideoQualityData({ + required this.quality, + }); + + VideoQuality quality; + + Object encode() { + return [ + quality.index, + ]; + } + + static VideoQualityData decode(Object result) { + result as List; + 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 @@ class _QualitySelectorHostApiCodec extends StandardMessageCodec { 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 @@ class _QualitySelectorHostApiCodec extends StandardMessageCodec { 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 @@ class QualitySelectorHostApi { Future create( int arg_identifier, - List arg_videoQualityConstraintIndexList, + List arg_videoQualityDataList, int? arg_fallbackStrategyId) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.QualitySelectorHostApi.create', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([ arg_identifier, - arg_videoQualityConstraintIndexList, + arg_videoQualityDataList, arg_fallbackStrategyId ]) as List?; if (replyList == null) { @@ -2533,7 +2562,7 @@ class QualitySelectorHostApi { } Future getResolution( - int arg_cameraInfoId, VideoQualityConstraint arg_quality) async { + int arg_cameraInfoId, VideoQuality arg_quality) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.QualitySelectorHostApi.getResolution', codec, binaryMessenger: _binaryMessenger); @@ -2571,7 +2600,7 @@ class FallbackStrategyHostApi { static const MessageCodec codec = StandardMessageCodec(); - Future create(int arg_identifier, VideoQualityConstraint arg_quality, + Future create(int arg_identifier, VideoQuality arg_quality, VideoResolutionFallbackRule arg_fallbackRule) async { final BasicMessageChannel channel = BasicMessageChannel( '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 ee098fafd14..ad2589dae72 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 @@ class FallbackStrategy extends JavaObject { 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 @@ class _FallbackStrategyHostApiImpl extends FallbackStrategyHostApi { /// 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 303d4aecaf5..6daff7d0682 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 @@ class QualitySelector extends JavaObject { QualitySelector.from( {BinaryMessenger? binaryMessenger, InstanceManager? instanceManager, - required VideoQualityConstraint quality, + required VideoQualityData quality, this.fallbackStrategy}) - : qualityList = [quality], + : qualityList = [quality], super.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager) { @@ -65,7 +65,7 @@ class QualitySelector extends JavaObject { late final _QualitySelectorHostApiImpl _api; /// Desired qualities for this selector instance. - final List qualityList; + final List qualityList; /// Desired fallback strategy for this selector instance. final FallbackStrategy? fallbackStrategy; @@ -73,7 +73,7 @@ class QualitySelector extends JavaObject { /// Retrieves the corresponding resolution from the input [quality] for the /// camera represented by [cameraInfo]. static Future 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 @@ class _QualitySelectorHostApiImpl extends QualitySelectorHostApi { /// Creates a [QualitySelector] instance with the desired qualities and /// fallback strategy specified. - void createFromInstance( - QualitySelector instance, - List qualityList, - FallbackStrategy? fallbackStrategy) { + void createFromInstance(QualitySelector instance, + List qualityList, FallbackStrategy? fallbackStrategy) { final int identifier = instanceManager.addDartCreatedInstance(instance, onCopy: (QualitySelector original) { return QualitySelector.detached( @@ -120,13 +118,10 @@ class _QualitySelectorHostApiImpl extends QualitySelectorHostApi { fallbackStrategy: original.fallbackStrategy, ); }); - final List qualityIndices = qualityList - .map((VideoQualityConstraint quality) => quality.index) - .toList(); create( identifier, - qualityIndices, + qualityList, fallbackStrategy == null ? null : instanceManager.getIdentifier(fallbackStrategy)); @@ -135,7 +130,7 @@ class _QualitySelectorHostApiImpl extends QualitySelectorHostApi { /// Retrieves the corresponding resolution from the input [quality] for the /// camera represented by [cameraInfo]. Future 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 13953621552..1fd93ec1b6e 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 @@ class Recorder extends JavaObject { return QualitySelector.fromOrderedList( binaryMessenger: binaryMessenger, instanceManager: instanceManager, - qualityList: const [ - VideoQualityConstraint.FHD, - VideoQualityConstraint.HD, - VideoQualityConstraint.SD + qualityList: [ + 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 02ea78c049a..9ebf34b73be 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 @@ class ExposureCompensationRange { /// 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 @@ enum VideoQualityConstraint { 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 @@ abstract class PlaneProxyFlutterApi { @HostApi(dartHostTestHandler: 'TestQualitySelectorHostApi') abstract class QualitySelectorHostApi { - // TODO(camsim99): Change qualityList to List when - // enums are supported for collection types. - void create(int identifier, List videoQualityConstraintIndexList, + void create(int identifier, List 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 0e57c52a292..54c895b4fa9 100644 --- a/packages/camera/camera_android_camerax/pubspec.yaml +++ b/packages/camera/camera_android_camerax/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_android_camerax 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 a0e60b60be8..bece9281bb8 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.dart'; 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/pending_recording.dart'; 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 @@ void main() { when(camera.testPreview.setSurfaceProvider()) .thenAnswer((_) async => testSurfaceTextureId); when(mockProcessCameraProvider.bindToLifecycle( - camera.mockBackCameraSelector, - [camera.testPreview, camera.testImageCapture])) - .thenAnswer((_) async => mockCamera); + camera.mockBackCameraSelector, [ + 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 @@ void main() { camera.processCameraProvider = mockProcessCameraProvider; when(mockProcessCameraProvider.bindToLifecycle( - camera.mockBackCameraSelector, - [camera.testPreview, camera.testImageCapture])) - .thenAnswer((_) async => mockCamera); + camera.mockBackCameraSelector, [ + camera.testPreview, + camera.testImageCapture, + camera.testImageAnalysis + ])).thenAnswer((_) async => mockCamera); when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo); when(mockCameraInfo.getCameraState()) .thenAnswer((_) async => MockLiveCameraState()); @@ -257,13 +265,190 @@ void main() { enableAudio: enableAudio); // Verify expected UseCases were bound. - verify(camera.processCameraProvider!.bindToLifecycle(camera.cameraSelector!, - [camera.testPreview, camera.testImageCapture])); + verify(camera.processCameraProvider!.bindToLifecycle( + camera.cameraSelector!, [ + 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, [ + 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, [ + 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 { @@ -313,9 +498,11 @@ void main() { .thenAnswer((_) async => cameraId); when(camera.processCameraProvider!.bindToLifecycle( - camera.mockBackCameraSelector, - [camera.testPreview, camera.testImageCapture])) - .thenAnswer((_) async => mockCamera); + camera.mockBackCameraSelector, [ + 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 @@ void main() { 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.value(mockCamera)); when(mockCamera.getCameraInfo()) - .thenAnswer((_) => Future.value(MockCameraInfo())); + .thenAnswer((_) => Future.value(mockCameraInfo)); + when(mockCameraInfo.getCameraState()) + .thenAnswer((_) async => MockLiveCameraState()); final CameraImageData mockCameraImageData = MockCameraImageData(); final Stream imageStream = @@ -947,15 +1138,19 @@ void main() { 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.value(mockCamera)); when(mockCamera.getCameraInfo()) - .thenAnswer((_) => Future.value(MockCameraInfo())); + .thenAnswer((_) => Future.value(mockCameraInfo)); + when(mockCameraInfo.getCameraState()) + .thenAnswer((_) async => MockLiveCameraState()); final CameraImageData mockCameraImageData = MockCameraImageData(); final Stream imageStream = @@ -988,6 +1183,7 @@ void main() { 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 @@ void main() { camera.processCameraProvider = mockProcessCameraProvider; camera.cameraSelector = mockCameraSelector; + camera.imageAnalysis = mockImageAnalysis; - when(mockProcessCameraProvider.isBound(camera.mockImageAnalysis)) + when(mockProcessCameraProvider.isBound(mockImageAnalysis)) .thenAnswer((_) async => Future.value(false)); - when(mockProcessCameraProvider.bindToLifecycle( - mockCameraSelector, [camera.mockImageAnalysis])) + when(mockProcessCameraProvider + .bindToLifecycle(mockCameraSelector, [mockImageAnalysis])) .thenAnswer((_) async => mockCamera); when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo); + when(mockCameraInfo.getCameraState()) + .thenAnswer((_) async => MockLiveCameraState()); when(mockImageProxy.getPlanes()) .thenAnswer((_) => Future>.value(mockPlanes)); when(mockPlane.buffer).thenReturn(buffer); @@ -1029,12 +1228,8 @@ void main() { // 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, [camera.mockImageAnalysis])); await capturedAnalyzer.analyze(mockImageProxy); final CameraImageData imageData = await imageDataCompleter.future; @@ -1048,9 +1243,6 @@ void main() { 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 @@ void main() { 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, [camera.mockImageAnalysis])) - .thenAnswer((_) async => mockCamera); - when(mockCamera.getCameraInfo()).thenAnswer((_) async => MockCameraInfo()); + camera.imageAnalysis = mockImageAnalysis; final StreamSubscription imageStreamSubscription = camera .onStreamedFrameAvailable(cameraId) .listen((CameraImageData data) {}); - when(mockProcessCameraProvider.isBound(camera.mockImageAnalysis)) - .thenAnswer((_) async => Future.value(true)); - await imageStreamSubscription.cancel(); - verify(camera.mockImageAnalysis.clearAnalyzer()); + verify(mockImageAnalysis.clearAnalyzer()); }); } @@ -1101,7 +1286,7 @@ class FakeAndroidCameraCameraX extends AndroidCameraCameraX { final MockCameraSelector mockFrontCameraSelector = MockCameraSelector(); final MockRecorder testRecorder = MockRecorder(); final MockVideoCapture testVideoCapture = MockVideoCapture(); - final MockImageAnalysis mockImageAnalysis = MockImageAnalysis(); + final MockImageAnalysis testImageAnalysis = MockImageAnalysis(); @override Future requestCameraPermissions(bool enableAudio) async { @@ -1127,17 +1312,21 @@ class FakeAndroidCameraCameraX extends AndroidCameraCameraX { } @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 @@ class FakeAndroidCameraCameraX extends AndroidCameraCameraX { } @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 8c6960c082c..f54e40df62d 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 @@ void main() { ); FallbackStrategy.detached( - quality: VideoQualityConstraint.UHD, + quality: VideoQuality.UHD, fallbackRule: VideoResolutionFallbackRule.higherQualityThan, instanceManager: instanceManager, ); verifyNever(mockApi.create( argThat(isA()), - argThat(isA()), + argThat(isA()), argThat(isA()), )); }); @@ -55,7 +55,7 @@ void main() { 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 02d185a7222..8ba811c7b46 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 @@ class MockTestFallbackStrategyHostApi extends _i1.Mock @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 8c05aa7be43..bfaa568c254 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 @@ void main() { ); QualitySelector.detached( - qualityList: const [VideoQualityConstraint.FHD], + qualityList: [ + VideoQualityData(quality: VideoQuality.UHD) + ], fallbackStrategy: MockFallbackStrategy(), instanceManager: instanceManager, ); @@ -60,7 +62,8 @@ void main() { 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 @@ void main() { instanceManager: instanceManager, ); - verify(mockApi.create( + final VerificationResult verificationResult = verify(mockApi.create( instanceManager.getIdentifier(instance), - [2], + captureAny, fallbackStrategyIdentifier, )); + final List videoQualityData = + verificationResult.captured.single as List; + 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 @@ void main() { onWeakReferenceRemoved: (_) {}, ); - const List qualityList = [ - VideoQualityConstraint.FHD, - VideoQualityConstraint.highest + final List qualityList = [ + VideoQualityData(quality: VideoQuality.FHD), + VideoQualityData(quality: VideoQuality.highest), ]; final FallbackStrategy fallbackStrategy = MockFallbackStrategy(); @@ -113,11 +120,16 @@ void main() { instanceManager: instanceManager, ); - verify(mockApi.create( + final VerificationResult verificationResult = verify(mockApi.create( instanceManager.getIdentifier(instance), - [2, 5], + captureAny, fallbackStrategyIdentifier, )); + final List videoQualityData = + verificationResult.captured.single as List; + 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 @@ void main() { final QualitySelector instance = QualitySelector.detached( instanceManager: instanceManager, - qualityList: const [VideoQualityConstraint.HD], + qualityList: [ + VideoQualityData(quality: VideoQuality.HD) + ], fallbackStrategy: MockFallbackStrategy(), ); const int instanceIdentifier = 0; @@ -153,7 +167,7 @@ void main() { 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 ba08711ba73..65741af996a 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 @@ class MockFallbackStrategy extends _i1.Mock implements _i9.FallbackStrategy { } @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 @@ class MockTestQualitySelectorHostApi extends _i1.Mock @override void create( int? identifier, - List? videoQualityConstraintIndexList, + List<_i4.VideoQualityData?>? videoQualityDataList, int? fallbackStrategyId, ) => super.noSuchMethod( @@ -166,7 +166,7 @@ class MockTestQualitySelectorHostApi extends _i1.Mock #create, [ identifier, - videoQualityConstraintIndexList, + videoQualityDataList, fallbackStrategyId, ], ), @@ -175,7 +175,7 @@ class MockTestQualitySelectorHostApi extends _i1.Mock @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 994c0f91b83..c7b189f6130 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 @@ void main() { final QualitySelector defaultQualitySelector = Recorder.getDefaultQualitySelector(); + final List expectedVideoQualities = [ + VideoQuality.FHD, + VideoQuality.HD, + VideoQuality.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.qualityList, - equals(const [ - VideoQualityConstraint.FHD, - VideoQualityConstraint.HD, - VideoQualityConstraint.SD - ])); 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 c7850c70e53..7a678e28645 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 @@ class MockQualitySelector extends _i1.Mock implements _i4.QualitySelector { } @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 @@ class MockTestFallbackStrategyHostApi extends _i1.Mock @override void create( int? identifier, - _i2.VideoQualityConstraint? quality, + _i2.VideoQuality? quality, _i2.VideoResolutionFallbackRule? fallbackRule, ) => super.noSuchMethod( @@ -183,7 +183,7 @@ class MockTestQualitySelectorHostApi extends _i1.Mock @override void create( int? identifier, - List? videoQualityConstraintIndexList, + List<_i2.VideoQualityData?>? videoQualityDataList, int? fallbackStrategyId, ) => super.noSuchMethod( @@ -191,7 +191,7 @@ class MockTestQualitySelectorHostApi extends _i1.Mock #create, [ identifier, - videoQualityConstraintIndexList, + videoQualityDataList, fallbackStrategyId, ], ), @@ -200,7 +200,7 @@ class MockTestQualitySelectorHostApi extends _i1.Mock @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 d4c935e819c..c649a735215 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 @@ class _TestQualitySelectorHostApiCodec extends StandardMessageCodec { 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 @@ class _TestQualitySelectorHostApiCodec extends StandardMessageCodec { 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 @@ abstract class TestQualitySelectorHostApi { TestDefaultBinaryMessengerBinding.instance; static const MessageCodec codec = _TestQualitySelectorHostApiCodec(); - void create(int identifier, List videoQualityConstraintIndexList, + void create(int identifier, List 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 @@ abstract class TestQualitySelectorHostApi { 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? arg_videoQualityConstraintIndexList = - (args[1] as List?)?.cast(); - assert(arg_videoQualityConstraintIndexList != null, - 'Argument for dev.flutter.pigeon.QualitySelectorHostApi.create was null, expected non-null List.'); + final List? arg_videoQualityDataList = + (args[1] as List?)?.cast(); + assert(arg_videoQualityDataList != null, + 'Argument for dev.flutter.pigeon.QualitySelectorHostApi.create was null, expected non-null List.'); final int? arg_fallbackStrategyId = (args[2] as int?); - api.create(arg_identifier!, arg_videoQualityConstraintIndexList!, + api.create(arg_identifier!, arg_videoQualityDataList!, arg_fallbackStrategyId); return []; }); @@ -1661,11 +1665,10 @@ abstract class TestQualitySelectorHostApi { 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 [output]; @@ -1680,7 +1683,7 @@ abstract class TestFallbackStrategyHostApi { TestDefaultBinaryMessengerBinding.instance; static const MessageCodec 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 @@ abstract class TestFallbackStrategyHostApi { 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];