This repository was archived by the owner on Feb 22, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9.8k
[camera] android-rework part 5: Android FPS range, resolution and sensor orientation features #3799
Merged
fluttergithubbot
merged 26 commits into
flutter:master
from
Baseflow:camera-android/fps_resolution_sensor_features
Jun 22, 2021
Merged
Changes from all commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
2b7aa9b
Base classes to support Android camera features
mvanbeusekom f780742
Fixed formatting
mvanbeusekom 76bc5bd
Applied feedback from PR
mvanbeusekom 0bbed99
Added Android FPS range, resolution and sensor orientation features
mvanbeusekom 1ba738d
Use mockito-inline
mvanbeusekom de4e70f
Merge remote-tracking branch 'upstream/master' into camera-android/fp…
mvanbeusekom 728346a
Fix issue Pixel 4A
mvanbeusekom c014fe3
Merge remote-tracking branch 'upstream/master' into camera-android/fp…
mvanbeusekom 84f5e73
Added API documentation
mvanbeusekom f763f77
Processed feedback on PR
mvanbeusekom 4a7c73a
Fix formatting
mvanbeusekom a890919
Fix formatting
mvanbeusekom 55a6702
Only exclude 60 FPS limit for Pixel 4a
mvanbeusekom cd53321
Removed redundant empty line
mvanbeusekom 35831d3
Fixed comment
mvanbeusekom a9f3142
Test Pixel 4a workaround
mvanbeusekom 551800e
Add tests for orientation updates
mvanbeusekom 68cbc56
Fix formatting
mvanbeusekom 1b137c2
Fix formatting
mvanbeusekom 6514a00
Added missing license header
mvanbeusekom 7f0180e
Accept cameraName as String
mvanbeusekom 24af367
Format
mvanbeusekom 8313dd0
Removed obsolete comment
mvanbeusekom a39c2e1
update method structure in class to a more logical order
mvanbeusekom 3eecfe9
Merge remote-tracking branch 'origin/master' into camera-android/fps_…
mvanbeusekom 7299b1d
Fix formatting
mvanbeusekom File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
87 changes: 87 additions & 0 deletions
87
...ra/android/src/main/java/io/flutter/plugins/camera/features/fpsrange/FpsRangeFeature.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
// Copyright 2013 The Flutter Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
package io.flutter.plugins.camera.features.fpsrange; | ||
|
||
import android.hardware.camera2.CaptureRequest; | ||
import android.os.Build; | ||
import android.util.Range; | ||
import io.flutter.plugins.camera.CameraProperties; | ||
import io.flutter.plugins.camera.features.CameraFeature; | ||
|
||
/** | ||
* Controls the frames per seconds (FPS) range configuration on the {@link android.hardware.camera2} | ||
* API. | ||
*/ | ||
public class FpsRangeFeature extends CameraFeature<Range<Integer>> { | ||
private static final Range<Integer> MAX_PIXEL4A_RANGE = new Range<>(30, 30); | ||
private Range<Integer> currentSetting; | ||
|
||
/** | ||
* Creates a new instance of the {@link FpsRangeFeature}. | ||
* | ||
* @param cameraProperties Collection of characteristics for the current camera device. | ||
*/ | ||
public FpsRangeFeature(CameraProperties cameraProperties) { | ||
super(cameraProperties); | ||
|
||
if (isPixel4A()) { | ||
// HACK: There is a bug in the Pixel 4A where it cannot support 60fps modes | ||
// even though they are reported as supported by | ||
// `getControlAutoExposureAvailableTargetFpsRanges`. | ||
// For max device compatibility we will keep FPS under 60 even if they report they are | ||
// capable of achieving 60 fps. Highest working FPS is 30. | ||
// https://issuetracker.google.com/issues/189237151 | ||
currentSetting = MAX_PIXEL4A_RANGE; | ||
} else { | ||
Range<Integer>[] ranges = cameraProperties.getControlAutoExposureAvailableTargetFpsRanges(); | ||
|
||
if (ranges != null) { | ||
for (Range<Integer> range : ranges) { | ||
int upper = range.getUpper(); | ||
|
||
if (upper >= 10) { | ||
if (currentSetting == null || upper > currentSetting.getUpper()) { | ||
currentSetting = range; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
private boolean isPixel4A() { | ||
return Build.BRAND.equals("google") && Build.MODEL.equals("Pixel 4a"); | ||
} | ||
|
||
@Override | ||
public String getDebugName() { | ||
return "FpsRangeFeature"; | ||
} | ||
|
||
@Override | ||
public Range<Integer> getValue() { | ||
return currentSetting; | ||
} | ||
|
||
@Override | ||
public void setValue(Range<Integer> value) { | ||
this.currentSetting = value; | ||
} | ||
|
||
// Always supported | ||
@Override | ||
public boolean checkIsSupported() { | ||
return true; | ||
} | ||
|
||
@Override | ||
public void updateBuilder(CaptureRequest.Builder requestBuilder) { | ||
if (!checkIsSupported()) { | ||
return; | ||
} | ||
|
||
requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, currentSetting); | ||
} | ||
} |
176 changes: 176 additions & 0 deletions
176
...ndroid/src/main/java/io/flutter/plugins/camera/features/resolution/ResolutionFeature.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
// Copyright 2013 The Flutter Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
package io.flutter.plugins.camera.features.resolution; | ||
|
||
import android.hardware.camera2.CaptureRequest; | ||
import android.media.CamcorderProfile; | ||
import android.util.Size; | ||
import androidx.annotation.VisibleForTesting; | ||
import io.flutter.plugins.camera.CameraProperties; | ||
import io.flutter.plugins.camera.features.CameraFeature; | ||
|
||
/** | ||
* Controls the resolutions configuration on the {@link android.hardware.camera2} API. | ||
* | ||
* <p>The {@link ResolutionFeature} is responsible for converting the platform independent {@link | ||
* ResolutionPreset} into a {@link android.media.CamcorderProfile} which contains all the properties | ||
* required to configure the resolution using the {@link android.hardware.camera2} API. | ||
*/ | ||
public class ResolutionFeature extends CameraFeature<ResolutionPreset> { | ||
private Size captureSize; | ||
private Size previewSize; | ||
private CamcorderProfile recordingProfile; | ||
private ResolutionPreset currentSetting; | ||
private int cameraId; | ||
|
||
/** | ||
* Creates a new instance of the {@link ResolutionFeature}. | ||
* | ||
* @param cameraProperties Collection of characteristics for the current camera device. | ||
* @param resolutionPreset Platform agnostic enum containing resolution information. | ||
* @param cameraName Camera identifier of the camera for which to configure the resolution. | ||
*/ | ||
public ResolutionFeature( | ||
CameraProperties cameraProperties, ResolutionPreset resolutionPreset, String cameraName) { | ||
mvanbeusekom marked this conversation as resolved.
Show resolved
Hide resolved
|
||
super(cameraProperties); | ||
this.currentSetting = resolutionPreset; | ||
try { | ||
this.cameraId = Integer.parseInt(cameraName, 10); | ||
} catch (NumberFormatException e) { | ||
this.cameraId = -1; | ||
return; | ||
} | ||
configureResolution(resolutionPreset, cameraId); | ||
} | ||
|
||
/** | ||
* Gets the {@link android.media.CamcorderProfile} containing the information to configure the | ||
* resolution using the {@link android.hardware.camera2} API. | ||
* | ||
* @return Resolution information to configure the {@link android.hardware.camera2} API. | ||
*/ | ||
public CamcorderProfile getRecordingProfile() { | ||
return this.recordingProfile; | ||
} | ||
|
||
/** | ||
* Gets the optimal preview size based on the configured resolution. | ||
* | ||
* @return The optimal preview size. | ||
*/ | ||
public Size getPreviewSize() { | ||
return this.previewSize; | ||
} | ||
|
||
/** | ||
* Gets the optimal capture size based on the configured resolution. | ||
* | ||
* @return The optimal capture size. | ||
*/ | ||
public Size getCaptureSize() { | ||
return this.captureSize; | ||
} | ||
|
||
@Override | ||
public String getDebugName() { | ||
return "ResolutionFeature"; | ||
} | ||
|
||
@Override | ||
public ResolutionPreset getValue() { | ||
return currentSetting; | ||
} | ||
|
||
@Override | ||
public void setValue(ResolutionPreset value) { | ||
this.currentSetting = value; | ||
configureResolution(currentSetting, cameraId); | ||
} | ||
|
||
@Override | ||
public boolean checkIsSupported() { | ||
return cameraId >= 0; | ||
} | ||
|
||
@Override | ||
public void updateBuilder(CaptureRequest.Builder requestBuilder) { | ||
// No-op: when setting a resolution there is no need to update the request builder. | ||
} | ||
|
||
@VisibleForTesting | ||
static Size computeBestPreviewSize(int cameraId, ResolutionPreset preset) { | ||
if (preset.ordinal() > ResolutionPreset.high.ordinal()) { | ||
preset = ResolutionPreset.high; | ||
} | ||
|
||
CamcorderProfile profile = | ||
getBestAvailableCamcorderProfileForResolutionPreset(cameraId, preset); | ||
return new Size(profile.videoFrameWidth, profile.videoFrameHeight); | ||
} | ||
|
||
/** | ||
* Gets the best possible {@link android.media.CamcorderProfile} for the supplied {@link | ||
* ResolutionPreset}. | ||
* | ||
* @param cameraId Camera identifier which indicates the device's camera for which to select a | ||
* {@link android.media.CamcorderProfile}. | ||
* @param preset The {@link ResolutionPreset} for which is to be translated to a {@link | ||
* android.media.CamcorderProfile}. | ||
* @return The best possible {@link android.media.CamcorderProfile} that matches the supplied | ||
* {@link ResolutionPreset}. | ||
*/ | ||
public static CamcorderProfile getBestAvailableCamcorderProfileForResolutionPreset( | ||
int cameraId, ResolutionPreset preset) { | ||
if (cameraId < 0) { | ||
throw new AssertionError( | ||
"getBestAvailableCamcorderProfileForResolutionPreset can only be used with valid (>=0) camera identifiers."); | ||
} | ||
|
||
switch (preset) { | ||
// All of these cases deliberately fall through to get the best available profile. | ||
case max: | ||
if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_HIGH)) { | ||
return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_HIGH); | ||
} | ||
case ultraHigh: | ||
if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_2160P)) { | ||
return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_2160P); | ||
} | ||
case veryHigh: | ||
if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_1080P)) { | ||
return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_1080P); | ||
} | ||
case high: | ||
if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_720P)) { | ||
return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_720P); | ||
} | ||
case medium: | ||
if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_480P)) { | ||
return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_480P); | ||
} | ||
case low: | ||
if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_QVGA)) { | ||
return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_QVGA); | ||
} | ||
default: | ||
if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_LOW)) { | ||
return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_LOW); | ||
} else { | ||
throw new IllegalArgumentException( | ||
"No capture session available for current capture session."); | ||
} | ||
} | ||
} | ||
|
||
private void configureResolution(ResolutionPreset resolutionPreset, int cameraId) { | ||
if (!checkIsSupported()) { | ||
return; | ||
} | ||
recordingProfile = | ||
getBestAvailableCamcorderProfileForResolutionPreset(cameraId, resolutionPreset); | ||
captureSize = new Size(recordingProfile.videoFrameWidth, recordingProfile.videoFrameHeight); | ||
previewSize = computeBestPreviewSize(cameraId, resolutionPreset); | ||
} | ||
mvanbeusekom marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} |
15 changes: 15 additions & 0 deletions
15
...android/src/main/java/io/flutter/plugins/camera/features/resolution/ResolutionPreset.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// Copyright 2013 The Flutter Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
package io.flutter.plugins.camera.features.resolution; | ||
|
||
// Mirrors camera.dart | ||
public enum ResolutionPreset { | ||
low, | ||
medium, | ||
high, | ||
veryHigh, | ||
ultraHigh, | ||
max, | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.