Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 0232846

Browse files
authored
[camera] android-rework part 5: Android FPS range, resolution and sensor orientation features (#3799)
1 parent e864172 commit 0232846

File tree

11 files changed

+1497
-2
lines changed

11 files changed

+1497
-2
lines changed

packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import java.util.HashMap;
1717
import java.util.Map;
1818

19-
class DartMessenger {
19+
public class DartMessenger {
2020
@NonNull private final Handler handler;
2121
@Nullable private MethodChannel cameraChannel;
2222
@Nullable private MethodChannel deviceChannel;
@@ -48,7 +48,7 @@ enum CameraEventType {
4848
this.handler = handler;
4949
}
5050

51-
void sendDeviceOrientationChangeEvent(PlatformChannel.DeviceOrientation orientation) {
51+
public void sendDeviceOrientationChangeEvent(PlatformChannel.DeviceOrientation orientation) {
5252
assert (orientation != null);
5353
this.send(
5454
DeviceEventType.ORIENTATION_CHANGED,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.camera.features.fpsrange;
6+
7+
import android.hardware.camera2.CaptureRequest;
8+
import android.os.Build;
9+
import android.util.Range;
10+
import io.flutter.plugins.camera.CameraProperties;
11+
import io.flutter.plugins.camera.features.CameraFeature;
12+
13+
/**
14+
* Controls the frames per seconds (FPS) range configuration on the {@link android.hardware.camera2}
15+
* API.
16+
*/
17+
public class FpsRangeFeature extends CameraFeature<Range<Integer>> {
18+
private static final Range<Integer> MAX_PIXEL4A_RANGE = new Range<>(30, 30);
19+
private Range<Integer> currentSetting;
20+
21+
/**
22+
* Creates a new instance of the {@link FpsRangeFeature}.
23+
*
24+
* @param cameraProperties Collection of characteristics for the current camera device.
25+
*/
26+
public FpsRangeFeature(CameraProperties cameraProperties) {
27+
super(cameraProperties);
28+
29+
if (isPixel4A()) {
30+
// HACK: There is a bug in the Pixel 4A where it cannot support 60fps modes
31+
// even though they are reported as supported by
32+
// `getControlAutoExposureAvailableTargetFpsRanges`.
33+
// For max device compatibility we will keep FPS under 60 even if they report they are
34+
// capable of achieving 60 fps. Highest working FPS is 30.
35+
// https://issuetracker.google.com/issues/189237151
36+
currentSetting = MAX_PIXEL4A_RANGE;
37+
} else {
38+
Range<Integer>[] ranges = cameraProperties.getControlAutoExposureAvailableTargetFpsRanges();
39+
40+
if (ranges != null) {
41+
for (Range<Integer> range : ranges) {
42+
int upper = range.getUpper();
43+
44+
if (upper >= 10) {
45+
if (currentSetting == null || upper > currentSetting.getUpper()) {
46+
currentSetting = range;
47+
}
48+
}
49+
}
50+
}
51+
}
52+
}
53+
54+
private boolean isPixel4A() {
55+
return Build.BRAND.equals("google") && Build.MODEL.equals("Pixel 4a");
56+
}
57+
58+
@Override
59+
public String getDebugName() {
60+
return "FpsRangeFeature";
61+
}
62+
63+
@Override
64+
public Range<Integer> getValue() {
65+
return currentSetting;
66+
}
67+
68+
@Override
69+
public void setValue(Range<Integer> value) {
70+
this.currentSetting = value;
71+
}
72+
73+
// Always supported
74+
@Override
75+
public boolean checkIsSupported() {
76+
return true;
77+
}
78+
79+
@Override
80+
public void updateBuilder(CaptureRequest.Builder requestBuilder) {
81+
if (!checkIsSupported()) {
82+
return;
83+
}
84+
85+
requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, currentSetting);
86+
}
87+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.camera.features.resolution;
6+
7+
import android.hardware.camera2.CaptureRequest;
8+
import android.media.CamcorderProfile;
9+
import android.util.Size;
10+
import androidx.annotation.VisibleForTesting;
11+
import io.flutter.plugins.camera.CameraProperties;
12+
import io.flutter.plugins.camera.features.CameraFeature;
13+
14+
/**
15+
* Controls the resolutions configuration on the {@link android.hardware.camera2} API.
16+
*
17+
* <p>The {@link ResolutionFeature} is responsible for converting the platform independent {@link
18+
* ResolutionPreset} into a {@link android.media.CamcorderProfile} which contains all the properties
19+
* required to configure the resolution using the {@link android.hardware.camera2} API.
20+
*/
21+
public class ResolutionFeature extends CameraFeature<ResolutionPreset> {
22+
private Size captureSize;
23+
private Size previewSize;
24+
private CamcorderProfile recordingProfile;
25+
private ResolutionPreset currentSetting;
26+
private int cameraId;
27+
28+
/**
29+
* Creates a new instance of the {@link ResolutionFeature}.
30+
*
31+
* @param cameraProperties Collection of characteristics for the current camera device.
32+
* @param resolutionPreset Platform agnostic enum containing resolution information.
33+
* @param cameraName Camera identifier of the camera for which to configure the resolution.
34+
*/
35+
public ResolutionFeature(
36+
CameraProperties cameraProperties, ResolutionPreset resolutionPreset, String cameraName) {
37+
super(cameraProperties);
38+
this.currentSetting = resolutionPreset;
39+
try {
40+
this.cameraId = Integer.parseInt(cameraName, 10);
41+
} catch (NumberFormatException e) {
42+
this.cameraId = -1;
43+
return;
44+
}
45+
configureResolution(resolutionPreset, cameraId);
46+
}
47+
48+
/**
49+
* Gets the {@link android.media.CamcorderProfile} containing the information to configure the
50+
* resolution using the {@link android.hardware.camera2} API.
51+
*
52+
* @return Resolution information to configure the {@link android.hardware.camera2} API.
53+
*/
54+
public CamcorderProfile getRecordingProfile() {
55+
return this.recordingProfile;
56+
}
57+
58+
/**
59+
* Gets the optimal preview size based on the configured resolution.
60+
*
61+
* @return The optimal preview size.
62+
*/
63+
public Size getPreviewSize() {
64+
return this.previewSize;
65+
}
66+
67+
/**
68+
* Gets the optimal capture size based on the configured resolution.
69+
*
70+
* @return The optimal capture size.
71+
*/
72+
public Size getCaptureSize() {
73+
return this.captureSize;
74+
}
75+
76+
@Override
77+
public String getDebugName() {
78+
return "ResolutionFeature";
79+
}
80+
81+
@Override
82+
public ResolutionPreset getValue() {
83+
return currentSetting;
84+
}
85+
86+
@Override
87+
public void setValue(ResolutionPreset value) {
88+
this.currentSetting = value;
89+
configureResolution(currentSetting, cameraId);
90+
}
91+
92+
@Override
93+
public boolean checkIsSupported() {
94+
return cameraId >= 0;
95+
}
96+
97+
@Override
98+
public void updateBuilder(CaptureRequest.Builder requestBuilder) {
99+
// No-op: when setting a resolution there is no need to update the request builder.
100+
}
101+
102+
@VisibleForTesting
103+
static Size computeBestPreviewSize(int cameraId, ResolutionPreset preset) {
104+
if (preset.ordinal() > ResolutionPreset.high.ordinal()) {
105+
preset = ResolutionPreset.high;
106+
}
107+
108+
CamcorderProfile profile =
109+
getBestAvailableCamcorderProfileForResolutionPreset(cameraId, preset);
110+
return new Size(profile.videoFrameWidth, profile.videoFrameHeight);
111+
}
112+
113+
/**
114+
* Gets the best possible {@link android.media.CamcorderProfile} for the supplied {@link
115+
* ResolutionPreset}.
116+
*
117+
* @param cameraId Camera identifier which indicates the device's camera for which to select a
118+
* {@link android.media.CamcorderProfile}.
119+
* @param preset The {@link ResolutionPreset} for which is to be translated to a {@link
120+
* android.media.CamcorderProfile}.
121+
* @return The best possible {@link android.media.CamcorderProfile} that matches the supplied
122+
* {@link ResolutionPreset}.
123+
*/
124+
public static CamcorderProfile getBestAvailableCamcorderProfileForResolutionPreset(
125+
int cameraId, ResolutionPreset preset) {
126+
if (cameraId < 0) {
127+
throw new AssertionError(
128+
"getBestAvailableCamcorderProfileForResolutionPreset can only be used with valid (>=0) camera identifiers.");
129+
}
130+
131+
switch (preset) {
132+
// All of these cases deliberately fall through to get the best available profile.
133+
case max:
134+
if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_HIGH)) {
135+
return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_HIGH);
136+
}
137+
case ultraHigh:
138+
if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_2160P)) {
139+
return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_2160P);
140+
}
141+
case veryHigh:
142+
if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_1080P)) {
143+
return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_1080P);
144+
}
145+
case high:
146+
if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_720P)) {
147+
return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_720P);
148+
}
149+
case medium:
150+
if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_480P)) {
151+
return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_480P);
152+
}
153+
case low:
154+
if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_QVGA)) {
155+
return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_QVGA);
156+
}
157+
default:
158+
if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_LOW)) {
159+
return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_LOW);
160+
} else {
161+
throw new IllegalArgumentException(
162+
"No capture session available for current capture session.");
163+
}
164+
}
165+
}
166+
167+
private void configureResolution(ResolutionPreset resolutionPreset, int cameraId) {
168+
if (!checkIsSupported()) {
169+
return;
170+
}
171+
recordingProfile =
172+
getBestAvailableCamcorderProfileForResolutionPreset(cameraId, resolutionPreset);
173+
captureSize = new Size(recordingProfile.videoFrameWidth, recordingProfile.videoFrameHeight);
174+
previewSize = computeBestPreviewSize(cameraId, resolutionPreset);
175+
}
176+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.camera.features.resolution;
6+
7+
// Mirrors camera.dart
8+
public enum ResolutionPreset {
9+
low,
10+
medium,
11+
high,
12+
veryHigh,
13+
ultraHigh,
14+
max,
15+
}

0 commit comments

Comments
 (0)