Skip to content

Commit c6fe5fa

Browse files
authored
[camerax] Implement resolution configuration (#3799)
Adds resolution configuration for all camera use cases. Also makes minor updates to related documentation. Fixes flutter/flutter#120462.
1 parent 913c946 commit c6fe5fa

23 files changed

+791
-273
lines changed

packages/camera/camera_android_camerax/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.5.0+17
2+
3+
* Implements resolution configuration for all camera use cases.
4+
15
## 0.5.0+16
26

37
* Adds pub topics to package metadata.

packages/camera/camera_android_camerax/README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,18 @@ dependencies:
2424
2525
## Missing features and limitations
2626
27-
### Resolution configuration \[[Issue #120462][120462]\]
2827
29-
Any specified `ResolutionPreset` wll go unused in favor of CameraX defaults and
30-
`onCameraResolutionChanged` is unimplemented.
28+
### 240p resolution configuration for video recording
29+
30+
240p resolution configuration for video recording is unsupported by CameraX,
31+
and thus, the plugin will fall back to 480p if configured with a
32+
`ResolutionPreset`.
3133

3234
### Locking/Unlocking capture orientation \[[Issue #125915][125915]\]
3335

3436
`lockCaptureOrientation` & `unLockCaptureOrientation` are unimplemented.
3537

36-
### Flash mode configuration \[[Issue #120715][120715]\]
38+
### Torch mode \[[Issue #120715][120715]\]
3739

3840
Calling `setFlashMode` with mode `FlashMode.torch` currently does nothing.
3941

packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FallbackStrategyHostApiImpl.java

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import androidx.camera.video.FallbackStrategy;
1010
import androidx.camera.video.Quality;
1111
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.FallbackStrategyHostApi;
12-
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoQualityConstraint;
12+
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoQuality;
1313
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoResolutionFallbackRule;
1414

1515
/**
@@ -28,20 +28,18 @@ public class FallbackStrategyHostApiImpl implements FallbackStrategyHostApi {
2828
public static class FallbackStrategyProxy {
2929
/** Creates an instance of {@link FallbackStrategy}. */
3030
public @NonNull FallbackStrategy create(
31-
@NonNull VideoQualityConstraint videoQualityConstraint,
32-
@NonNull VideoResolutionFallbackRule fallbackRule) {
33-
Quality videoQuality =
34-
QualitySelectorHostApiImpl.getQualityFromVideoQualityConstraint(videoQualityConstraint);
31+
@NonNull VideoQuality videoQuality, @NonNull VideoResolutionFallbackRule fallbackRule) {
32+
Quality quality = QualitySelectorHostApiImpl.getQualityFromVideoQuality(videoQuality);
3533

3634
switch (fallbackRule) {
3735
case HIGHER_QUALITY_OR_LOWER_THAN:
38-
return FallbackStrategy.higherQualityOrLowerThan(videoQuality);
36+
return FallbackStrategy.higherQualityOrLowerThan(quality);
3937
case HIGHER_QUALITY_THAN:
40-
return FallbackStrategy.higherQualityThan(videoQuality);
38+
return FallbackStrategy.higherQualityThan(quality);
4139
case LOWER_QUALITY_OR_HIGHER_THAN:
42-
return FallbackStrategy.lowerQualityOrHigherThan(videoQuality);
40+
return FallbackStrategy.lowerQualityOrHigherThan(quality);
4341
case LOWER_QUALITY_THAN:
44-
return FallbackStrategy.lowerQualityThan(videoQuality);
42+
return FallbackStrategy.lowerQualityThan(quality);
4543
}
4644
throw new IllegalArgumentException(
4745
"Specified fallback rule " + fallbackRule + " unrecognized.");
@@ -75,9 +73,8 @@ public FallbackStrategyHostApiImpl(@NonNull InstanceManager instanceManager) {
7573
@Override
7674
public void create(
7775
@NonNull Long identifier,
78-
@NonNull VideoQualityConstraint videoQualityConstraint,
76+
@NonNull VideoQuality videoQuality,
7977
@NonNull VideoResolutionFallbackRule fallbackRule) {
80-
instanceManager.addDartCreatedInstance(
81-
proxy.create(videoQualityConstraint, fallbackRule), identifier);
78+
instanceManager.addDartCreatedInstance(proxy.create(videoQuality, fallbackRule), identifier);
8279
}
8380
}

packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java

Lines changed: 75 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ private LiveDataSupportedType(final int index) {
112112
*
113113
* <p>See https://developer.android.com/reference/androidx/camera/video/Quality.
114114
*/
115-
public enum VideoQualityConstraint {
115+
public enum VideoQuality {
116116
SD(0),
117117
HD(1),
118118
FHD(2),
@@ -122,12 +122,16 @@ public enum VideoQualityConstraint {
122122

123123
final int index;
124124

125-
private VideoQualityConstraint(final int index) {
125+
private VideoQuality(final int index) {
126126
this.index = index;
127127
}
128128
}
129129

130-
/** Fallback rules for selecting video resolution. */
130+
/**
131+
* Fallback rules for selecting video resolution.
132+
*
133+
* <p>See https://developer.android.com/reference/androidx/camera/video/FallbackStrategy.
134+
*/
131135
public enum VideoResolutionFallbackRule {
132136
HIGHER_QUALITY_OR_LOWER_THAN(0),
133137
HIGHER_QUALITY_THAN(1),
@@ -472,6 +476,59 @@ ArrayList<Object> toList() {
472476
}
473477
}
474478

479+
/**
480+
* Convenience class for sending lists of [Quality]s.
481+
*
482+
* <p>Generated class from Pigeon that represents data sent in messages.
483+
*/
484+
public static final class VideoQualityData {
485+
private @NonNull VideoQuality quality;
486+
487+
public @NonNull VideoQuality getQuality() {
488+
return quality;
489+
}
490+
491+
public void setQuality(@NonNull VideoQuality setterArg) {
492+
if (setterArg == null) {
493+
throw new IllegalStateException("Nonnull field \"quality\" is null.");
494+
}
495+
this.quality = setterArg;
496+
}
497+
498+
/** Constructor is non-public to enforce null safety; use Builder. */
499+
VideoQualityData() {}
500+
501+
public static final class Builder {
502+
503+
private @Nullable VideoQuality quality;
504+
505+
public @NonNull Builder setQuality(@NonNull VideoQuality setterArg) {
506+
this.quality = setterArg;
507+
return this;
508+
}
509+
510+
public @NonNull VideoQualityData build() {
511+
VideoQualityData pigeonReturn = new VideoQualityData();
512+
pigeonReturn.setQuality(quality);
513+
return pigeonReturn;
514+
}
515+
}
516+
517+
@NonNull
518+
ArrayList<Object> toList() {
519+
ArrayList<Object> toListResult = new ArrayList<Object>(1);
520+
toListResult.add(quality == null ? null : quality.index);
521+
return toListResult;
522+
}
523+
524+
static @NonNull VideoQualityData fromList(@NonNull ArrayList<Object> list) {
525+
VideoQualityData pigeonResult = new VideoQualityData();
526+
Object quality = list.get(0);
527+
pigeonResult.setQuality(quality == null ? null : VideoQuality.values()[(int) quality]);
528+
return pigeonResult;
529+
}
530+
}
531+
475532
public interface Result<T> {
476533
@SuppressWarnings("UnknownNullness")
477534
void success(T result);
@@ -2991,6 +3048,8 @@ protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) {
29913048
switch (type) {
29923049
case (byte) 128:
29933050
return ResolutionInfo.fromList((ArrayList<Object>) readValue(buffer));
3051+
case (byte) 129:
3052+
return VideoQualityData.fromList((ArrayList<Object>) readValue(buffer));
29943053
default:
29953054
return super.readValueOfType(type, buffer);
29963055
}
@@ -3001,6 +3060,9 @@ protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) {
30013060
if (value instanceof ResolutionInfo) {
30023061
stream.write(128);
30033062
writeValue(stream, ((ResolutionInfo) value).toList());
3063+
} else if (value instanceof VideoQualityData) {
3064+
stream.write(129);
3065+
writeValue(stream, ((VideoQualityData) value).toList());
30043066
} else {
30053067
super.writeValue(stream, value);
30063068
}
@@ -3012,12 +3074,11 @@ public interface QualitySelectorHostApi {
30123074

30133075
void create(
30143076
@NonNull Long identifier,
3015-
@NonNull List<Long> videoQualityConstraintIndexList,
3077+
@NonNull List<VideoQualityData> videoQualityDataList,
30163078
@Nullable Long fallbackStrategyId);
30173079

30183080
@NonNull
3019-
ResolutionInfo getResolution(
3020-
@NonNull Long cameraInfoId, @NonNull VideoQualityConstraint quality);
3081+
ResolutionInfo getResolution(@NonNull Long cameraInfoId, @NonNull VideoQuality quality);
30213082

30223083
/** The codec used by QualitySelectorHostApi. */
30233084
static @NonNull MessageCodec<Object> getCodec() {
@@ -3039,12 +3100,13 @@ static void setup(
30393100
ArrayList<Object> wrapped = new ArrayList<Object>();
30403101
ArrayList<Object> args = (ArrayList<Object>) message;
30413102
Number identifierArg = (Number) args.get(0);
3042-
List<Long> videoQualityConstraintIndexListArg = (List<Long>) args.get(1);
3103+
List<VideoQualityData> videoQualityDataListArg =
3104+
(List<VideoQualityData>) args.get(1);
30433105
Number fallbackStrategyIdArg = (Number) args.get(2);
30443106
try {
30453107
api.create(
30463108
(identifierArg == null) ? null : identifierArg.longValue(),
3047-
videoQualityConstraintIndexListArg,
3109+
videoQualityDataListArg,
30483110
(fallbackStrategyIdArg == null) ? null : fallbackStrategyIdArg.longValue());
30493111
wrapped.add(0, null);
30503112
} catch (Throwable exception) {
@@ -3069,8 +3131,8 @@ static void setup(
30693131
ArrayList<Object> wrapped = new ArrayList<Object>();
30703132
ArrayList<Object> args = (ArrayList<Object>) message;
30713133
Number cameraInfoIdArg = (Number) args.get(0);
3072-
VideoQualityConstraint qualityArg =
3073-
args.get(1) == null ? null : VideoQualityConstraint.values()[(int) args.get(1)];
3134+
VideoQuality qualityArg =
3135+
args.get(1) == null ? null : VideoQuality.values()[(int) args.get(1)];
30743136
try {
30753137
ResolutionInfo output =
30763138
api.getResolution(
@@ -3094,7 +3156,7 @@ public interface FallbackStrategyHostApi {
30943156

30953157
void create(
30963158
@NonNull Long identifier,
3097-
@NonNull VideoQualityConstraint quality,
3159+
@NonNull VideoQuality quality,
30983160
@NonNull VideoResolutionFallbackRule fallbackRule);
30993161

31003162
/** The codec used by FallbackStrategyHostApi. */
@@ -3117,8 +3179,8 @@ static void setup(
31173179
ArrayList<Object> wrapped = new ArrayList<Object>();
31183180
ArrayList<Object> args = (ArrayList<Object>) message;
31193181
Number identifierArg = (Number) args.get(0);
3120-
VideoQualityConstraint qualityArg =
3121-
args.get(1) == null ? null : VideoQualityConstraint.values()[(int) args.get(1)];
3182+
VideoQuality qualityArg =
3183+
args.get(1) == null ? null : VideoQuality.values()[(int) args.get(1)];
31223184
VideoResolutionFallbackRule fallbackRuleArg =
31233185
args.get(2) == null
31243186
? null

packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/QualitySelectorHostApiImpl.java

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
import androidx.camera.video.QualitySelector;
1414
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.QualitySelectorHostApi;
1515
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.ResolutionInfo;
16-
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoQualityConstraint;
16+
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoQuality;
17+
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoQualityData;
1718
import java.util.ArrayList;
1819
import java.util.List;
1920
import java.util.Objects;
@@ -34,12 +35,12 @@ public class QualitySelectorHostApiImpl implements QualitySelectorHostApi {
3435
public static class QualitySelectorProxy {
3536
/** Creates an instance of {@link QualitySelector}. */
3637
public @NonNull QualitySelector create(
37-
@NonNull List<Long> videoQualityConstraintIndexList,
38+
@NonNull List<VideoQualityData> videoQualityDataList,
3839
@Nullable FallbackStrategy fallbackStrategy) {
39-
// Convert each index of VideoQualityConstraint to Quality.
40+
// Convert each index of VideoQuality to Quality.
4041
List<Quality> qualityList = new ArrayList<Quality>();
41-
for (Long qualityIndex : videoQualityConstraintIndexList) {
42-
qualityList.add(getQualityConstant(qualityIndex));
42+
for (VideoQualityData videoQualityData : videoQualityDataList) {
43+
qualityList.add(getQualityFromVideoQuality(videoQualityData.getQuality()));
4344
}
4445

4546
boolean fallbackStrategySpecified = fallbackStrategy != null;
@@ -57,12 +58,6 @@ public static class QualitySelectorProxy {
5758
? QualitySelector.fromOrderedList(qualityList, fallbackStrategy)
5859
: QualitySelector.fromOrderedList(qualityList);
5960
}
60-
61-
/** Converts from index of {@link VideoQualityConstraint} to {@link Quality}. */
62-
private Quality getQualityConstant(@NonNull Long qualityIndex) {
63-
VideoQualityConstraint quality = VideoQualityConstraint.values()[qualityIndex.intValue()];
64-
return getQualityFromVideoQualityConstraint(quality);
65-
}
6661
}
6762

6863
/**
@@ -93,11 +88,11 @@ public QualitySelectorHostApiImpl(@NonNull InstanceManager instanceManager) {
9388
@Override
9489
public void create(
9590
@NonNull Long identifier,
96-
@NonNull List<Long> videoQualityConstraintIndexList,
91+
@NonNull List<VideoQualityData> videoQualityDataList,
9792
@Nullable Long fallbackStrategyIdentifier) {
9893
instanceManager.addDartCreatedInstance(
9994
proxy.create(
100-
videoQualityConstraintIndexList,
95+
videoQualityDataList,
10196
fallbackStrategyIdentifier == null
10297
? null
10398
: Objects.requireNonNull(instanceManager.getInstance(fallbackStrategyIdentifier))),
@@ -110,24 +105,23 @@ public void create(
110105
*/
111106
@Override
112107
public @NonNull ResolutionInfo getResolution(
113-
@NonNull Long cameraInfoIdentifier, @NonNull VideoQualityConstraint quality) {
108+
@NonNull Long cameraInfoIdentifier, @NonNull VideoQuality quality) {
114109
final Size result =
115110
QualitySelector.getResolution(
116111
Objects.requireNonNull(instanceManager.getInstance(cameraInfoIdentifier)),
117-
getQualityFromVideoQualityConstraint(quality));
112+
getQualityFromVideoQuality(quality));
118113
return new ResolutionInfo.Builder()
119114
.setWidth(Long.valueOf(result.getWidth()))
120115
.setHeight(Long.valueOf(result.getHeight()))
121116
.build();
122117
}
123118

124119
/**
125-
* Converts the specified {@link VideoQualityConstraint} to a {@link Quality} that is understood
120+
* Converts the specified {@link VideoQuality to a {@link Quality} that is understood
126121
* by CameraX.
127122
*/
128-
public static @NonNull Quality getQualityFromVideoQualityConstraint(
129-
@NonNull VideoQualityConstraint videoQualityConstraint) {
130-
switch (videoQualityConstraint) {
123+
public static @NonNull Quality getQualityFromVideoQuality(@NonNull VideoQuality videoQuality) {
124+
switch (videoQuality) {
131125
case SD:
132126
return Quality.SD;
133127
case HD:
@@ -142,8 +136,6 @@ public void create(
142136
return Quality.HIGHEST;
143137
}
144138
throw new IllegalArgumentException(
145-
"VideoQualityConstraint "
146-
+ videoQualityConstraint
147-
+ " is unhandled by QualitySelectorHostApiImpl.");
139+
"VideoQuality " + videoQuality + " is unhandled by QualitySelectorHostApiImpl.");
148140
}
149141
}

packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/FallbackStrategyTest.java

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
import androidx.camera.video.FallbackStrategy;
1313
import androidx.camera.video.Quality;
14-
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoQualityConstraint;
14+
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoQuality;
1515
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoResolutionFallbackRule;
1616
import org.junit.After;
1717
import org.junit.Before;
@@ -48,11 +48,11 @@ public void hostApiCreate_makesCallToCreateExpectedFallbackStrategy() {
4848

4949
try (MockedStatic<FallbackStrategy> mockedFallbackStrategy =
5050
mockStatic(FallbackStrategy.class)) {
51-
for (VideoQualityConstraint videoQualityConstraint : VideoQualityConstraint.values()) {
51+
for (VideoQuality videoQuality : VideoQuality.values()) {
5252
for (VideoResolutionFallbackRule fallbackRule : VideoResolutionFallbackRule.values()) {
53-
// Determine expected Quality based on videoQualityConstraint being tested.
53+
// Determine expected Quality based on videoQuality being tested.
5454
Quality convertedQuality = null;
55-
switch (videoQualityConstraint) {
55+
switch (videoQuality) {
5656
case SD:
5757
convertedQuality = Quality.SD;
5858
break;
@@ -72,10 +72,7 @@ public void hostApiCreate_makesCallToCreateExpectedFallbackStrategy() {
7272
convertedQuality = Quality.HIGHEST;
7373
break;
7474
default:
75-
fail(
76-
"The VideoQualityConstraint "
77-
+ videoQualityConstraint.toString()
78-
+ "is unhandled by this test.");
75+
fail("The VideoQuality " + videoQuality.toString() + "is unhandled by this test.");
7976
}
8077
// Set Quality as final local variable to avoid error about using non-final (or effecitvely final) local variables in lambda expressions.
8178
final Quality expectedQuality = convertedQuality;
@@ -108,7 +105,7 @@ public void hostApiCreate_makesCallToCreateExpectedFallbackStrategy() {
108105
+ fallbackRule.toString()
109106
+ "is unhandled by this test.");
110107
}
111-
hostApi.create(instanceIdentifier, videoQualityConstraint, fallbackRule);
108+
hostApi.create(instanceIdentifier, videoQuality, fallbackRule);
112109
assertEquals(instanceManager.getInstance(instanceIdentifier), mockFallbackStrategy);
113110

114111
// Clear/reset FallbackStrategy mock and InstanceManager.

0 commit comments

Comments
 (0)