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

[camera_android] Default to legacy recording profile when EncoderProfiles unavailable #7073

Merged
merged 7 commits into from
Feb 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/camera/camera_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.10.4

* Temporarily fixes issue with requested video profiles being null by falling back to deprecated behavior in that case.

## 0.10.3

* Adds back use of Optional type.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,11 @@ private void prepareMediaRecorder(String outputFilePath) throws IOException {

MediaRecorderBuilder mediaRecorderBuilder;

if (Build.VERSION.SDK_INT >= 31) {
mediaRecorderBuilder = new MediaRecorderBuilder(getRecordingProfile(), outputFilePath);
// TODO(camsim99): Revert changes that allow legacy code to be used when recordingProfile is null
// once this has largely been fixed on the Android side. https://github.com/flutter/flutter/issues/119668
EncoderProfiles recordingProfile = getRecordingProfile();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && recordingProfile != null) {
mediaRecorderBuilder = new MediaRecorderBuilder(recordingProfile, outputFilePath);
} else {
mediaRecorderBuilder = new MediaRecorderBuilder(getRecordingProfileLegacy(), outputFilePath);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,19 +114,23 @@ static Size computeBestPreviewSize(int cameraId, ResolutionPreset preset)
if (preset.ordinal() > ResolutionPreset.high.ordinal()) {
preset = ResolutionPreset.high;
}
if (Build.VERSION.SDK_INT >= 31) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
EncoderProfiles profile =
getBestAvailableCamcorderProfileForResolutionPreset(cameraId, preset);
List<EncoderProfiles.VideoProfile> videoProfiles = profile.getVideoProfiles();
EncoderProfiles.VideoProfile defaultVideoProfile = videoProfiles.get(0);

return new Size(defaultVideoProfile.getWidth(), defaultVideoProfile.getHeight());
} else {
@SuppressWarnings("deprecation")
CamcorderProfile profile =
getBestAvailableCamcorderProfileForResolutionPresetLegacy(cameraId, preset);
return new Size(profile.videoFrameWidth, profile.videoFrameHeight);
if (defaultVideoProfile != null) {
return new Size(defaultVideoProfile.getWidth(), defaultVideoProfile.getHeight());
}
}

@SuppressWarnings("deprecation")
// TODO(camsim99): Suppression is currently safe because legacy code is used as a fallback for SDK >= S.
// This should be removed when reverting that fallback behavior: https://github.com/flutter/flutter/issues/119668.
CamcorderProfile profile =
getBestAvailableCamcorderProfileForResolutionPresetLegacy(cameraId, preset);
return new Size(profile.videoFrameWidth, profile.videoFrameHeight);
}

/**
Expand Down Expand Up @@ -234,15 +238,24 @@ private void configureResolution(ResolutionPreset resolutionPreset, int cameraId
if (!checkIsSupported()) {
return;
}
boolean captureSizeCalculated = false;

if (Build.VERSION.SDK_INT >= 31) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
recordingProfileLegacy = null;
recordingProfile =
getBestAvailableCamcorderProfileForResolutionPreset(cameraId, resolutionPreset);
List<EncoderProfiles.VideoProfile> videoProfiles = recordingProfile.getVideoProfiles();

EncoderProfiles.VideoProfile defaultVideoProfile = videoProfiles.get(0);
captureSize = new Size(defaultVideoProfile.getWidth(), defaultVideoProfile.getHeight());
} else {

if (defaultVideoProfile != null) {
captureSizeCalculated = true;
captureSize = new Size(defaultVideoProfile.getWidth(), defaultVideoProfile.getHeight());
}
}

if (!captureSizeCalculated) {
recordingProfile = null;
@SuppressWarnings("deprecation")
CamcorderProfile camcorderProfile =
getBestAvailableCamcorderProfileForResolutionPresetLegacy(cameraId, resolutionPreset);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public MediaRecorder build() throws IOException, NullPointerException, IndexOutO
if (enableAudio) mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);

if (Build.VERSION.SDK_INT >= 31) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && encoderProfiles != null) {
EncoderProfiles.VideoProfile videoProfile = encoderProfiles.getVideoProfiles().get(0);
EncoderProfiles.AudioProfile audioProfile = encoderProfiles.getAudioProfiles().get(0);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,27 @@
package io.flutter.plugins.camera.features.resolution;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;

import android.media.CamcorderProfile;
import android.media.EncoderProfiles;
import android.util.Size;
import io.flutter.plugins.camera.CameraProperties;
import java.util.ArrayList;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockedStatic;
import org.mockito.stubbing.Answer;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

Expand Down Expand Up @@ -329,4 +336,95 @@ public void computeBestPreviewSize_shouldUseQVGAWhenResolutionPresetLow() {

mockedStaticProfile.verify(() -> CamcorderProfile.getAll("1", CamcorderProfile.QUALITY_QVGA));
}

@Config(minSdk = 31)
@Test
public void computeBestPreviewSize_shouldUseLegacyBehaviorWhenEncoderProfilesNull() {
try (MockedStatic<ResolutionFeature> mockedResolutionFeature =
mockStatic(ResolutionFeature.class)) {
mockedResolutionFeature
.when(
() ->
ResolutionFeature.getBestAvailableCamcorderProfileForResolutionPreset(
anyInt(), any(ResolutionPreset.class)))
.thenAnswer(
(Answer<EncoderProfiles>)
invocation -> {
EncoderProfiles mockEncoderProfiles = mock(EncoderProfiles.class);
List<EncoderProfiles.VideoProfile> videoProfiles =
new ArrayList<EncoderProfiles.VideoProfile>() {
{
add(null);
}
};
when(mockEncoderProfiles.getVideoProfiles()).thenReturn(videoProfiles);
return mockEncoderProfiles;
});
mockedResolutionFeature
.when(
() ->
ResolutionFeature.getBestAvailableCamcorderProfileForResolutionPresetLegacy(
anyInt(), any(ResolutionPreset.class)))
.thenAnswer(
(Answer<CamcorderProfile>)
invocation -> {
CamcorderProfile mockCamcorderProfile = mock(CamcorderProfile.class);
mockCamcorderProfile.videoFrameWidth = 10;
mockCamcorderProfile.videoFrameHeight = 50;
return mockCamcorderProfile;
});
mockedResolutionFeature
.when(() -> ResolutionFeature.computeBestPreviewSize(1, ResolutionPreset.max))
.thenCallRealMethod();

Size testPreviewSize = ResolutionFeature.computeBestPreviewSize(1, ResolutionPreset.max);
assertEquals(testPreviewSize.getWidth(), 10);
assertEquals(testPreviewSize.getHeight(), 50);
}
}

@Config(minSdk = 31)
@Test
public void resolutionFeatureShouldUseLegacyBehaviorWhenEncoderProfilesNull() {
beforeLegacy();
try (MockedStatic<ResolutionFeature> mockedResolutionFeature =
mockStatic(ResolutionFeature.class)) {
mockedResolutionFeature
.when(
() ->
ResolutionFeature.getBestAvailableCamcorderProfileForResolutionPreset(
anyInt(), any(ResolutionPreset.class)))
.thenAnswer(
(Answer<EncoderProfiles>)
invocation -> {
EncoderProfiles mockEncoderProfiles = mock(EncoderProfiles.class);
List<EncoderProfiles.VideoProfile> videoProfiles =
new ArrayList<EncoderProfiles.VideoProfile>() {
{
add(null);
}
};
when(mockEncoderProfiles.getVideoProfiles()).thenReturn(videoProfiles);
return mockEncoderProfiles;
});
mockedResolutionFeature
.when(
() ->
ResolutionFeature.getBestAvailableCamcorderProfileForResolutionPresetLegacy(
anyInt(), any(ResolutionPreset.class)))
.thenAnswer(
(Answer<CamcorderProfile>)
invocation -> {
CamcorderProfile mockCamcorderProfile = mock(CamcorderProfile.class);
return mockCamcorderProfile;
});

CameraProperties mockCameraProperties = mock(CameraProperties.class);
ResolutionFeature resolutionFeature =
new ResolutionFeature(mockCameraProperties, ResolutionPreset.max, cameraName);

assertNotNull(resolutionFeature.getRecordingProfileLegacy());
assertNull(resolutionFeature.getRecordingProfile());
}
}
}
2 changes: 1 addition & 1 deletion packages/camera/camera_android/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: camera_android
description: Android implementation of the camera plugin.
repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera_android
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
version: 0.10.3
version: 0.10.4

environment:
sdk: ">=2.14.0 <3.0.0"
Expand Down