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

[camera] Fix CamcorderProfile Usages #4423

Merged
merged 50 commits into from
Oct 29, 2021
Merged
Show file tree
Hide file tree
Changes from 49 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
6976152
Identified deprecated usages and added initial fixes
camsim99 Oct 11, 2021
49722e4
Fix resolution feature tests
camsim99 Oct 11, 2021
f40abe2
fix mediarecorderbuilder test
camsim99 Oct 11, 2021
38d2310
suppress device orientation/unrelated errors
camsim99 Oct 11, 2021
09a2685
fix camera test
camsim99 Oct 11, 2021
accaed0
update android sdk versions in related build files
camsim99 Oct 12, 2021
a0441d9
add build checks for resolutionfeature:
camsim99 Oct 12, 2021
2383bf3
adjust mediarecorderbuilder and use robolectric
camsim99 Oct 13, 2021
09cbfa8
adjust camera and test
camsim99 Oct 13, 2021
252a6fe
format files
camsim99 Oct 13, 2021
f8fd254
restore device orientation (unrelated todo)
camsim99 Oct 13, 2021
f238289
changed error handling
camsim99 Oct 13, 2021
8532e97
add error check for media recorder
camsim99 Oct 13, 2021
724f891
add robolectric to resolutionfeature test
camsim99 Oct 15, 2021
b77b4fc
push latest changes to resolve camera test
camsim99 Oct 15, 2021
d6110ea
reformat and reorganize resolution feature test
camsim99 Oct 15, 2021
b7f7649
add missing imports
camsim99 Oct 15, 2021
c77ff4e
correct camera test
camsim99 Oct 16, 2021
162309b
removed print statement
camsim99 Oct 18, 2021
e74767a
change names
camsim99 Oct 18, 2021
3fd589c
Document gradle properties
camsim99 Oct 18, 2021
d3547d8
change bcprov jdk version
camsim99 Oct 18, 2021
c8194fc
modify enablejetifier
camsim99 Oct 19, 2021
84a243a
removie blacklist option
camsim99 Oct 19, 2021
95fac07
trying gradle modifications
camsim99 Oct 19, 2021
00a4e0d
upgrade gradle version:
camsim99 Oct 19, 2021
d59ff1f
upgrade gradle version and remove deprecations
camsim99 Oct 19, 2021
fb94483
downgrade robolectric version
camsim99 Oct 19, 2021
e0f71bc
remove robolectric req'd docker lines
camsim99 Oct 19, 2021
92e44c1
fix video_player and target api CI issues
camsim99 Oct 20, 2021
e54ec61
modify connectivity gradle verison
camsim99 Oct 20, 2021
bf9e4f7
circumvent android-lint problems for now
camsim99 Oct 20, 2021
e10031d
Bump plugin versions and update release notes
camsim99 Oct 20, 2021
ef23a8c
Revert mistake versionplugin bump
camsim99 Oct 20, 2021
f59241c
Merge branch 'master' into issue_89578_dev
camsim99 Oct 20, 2021
4fff3f3
Correct video_player version
camsim99 Oct 20, 2021
d2d58b3
Make first round of addressing Stuart's comments
camsim99 Oct 21, 2021
25550d0
rename on31, add back android-lint-artifcats
camsim99 Oct 21, 2021
4cbe753
Merge remote-tracking branch 'upstream/master' into issue_89578_dev
camsim99 Oct 22, 2021
7304068
Removed unneeded annotation
camsim99 Oct 25, 2021
3f50377
undo changes to other plugins
camsim99 Oct 25, 2021
5d6a93a
fix android platform test errors
camsim99 Oct 25, 2021
1e266e0
Remove undone changes from change logs
camsim99 Oct 25, 2021
9acf444
Merge remote-tracking branch 'upstream/master' into issue_89578_dev
camsim99 Oct 25, 2021
2cf0c68
undo changes to dart messenger
camsim99 Oct 25, 2021
e779b26
fix merge conflict
camsim99 Oct 28, 2021
e745349
undo accidental changelog modification
camsim99 Oct 28, 2021
d25fbac
Merge remote-tracking branch 'upstream/master' into issue_89578_dev
camsim99 Oct 29, 2021
979a37f
address nits
camsim99 Oct 29, 2021
11cc4e1
rename on31s
camsim99 Oct 29, 2021
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
3 changes: 3 additions & 0 deletions packages/camera/camera/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## 0.9.4+3

* Change Android compileSdkVersion to 31.
* Remove usages of deprecated Android API `CamcorderProfile`.
* Update gradle version to 7.0.2 on Android.
* Fix registerTexture and result being called on background thread on iOS.

## 0.9.4+2
Expand Down
9 changes: 5 additions & 4 deletions packages/camera/camera/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ buildscript {
}

dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
classpath 'com.android.tools.build:gradle:7.0.2'
}
}

Expand All @@ -27,9 +27,10 @@ project.getTasks().withType(JavaCompile){
apply plugin: 'com.android.library'

android {
compileSdkVersion 29
compileSdkVersion 31

defaultConfig {
targetSdkVersion 31
minSdkVersion 21
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down Expand Up @@ -60,7 +61,7 @@ android {
dependencies {
compileOnly 'androidx.annotation:annotation:1.1.0'
testImplementation 'junit:junit:4.12'
testImplementation 'org.mockito:mockito-inline:3.12.4'
testImplementation 'org.mockito:mockito-inline:4.0.0'
testImplementation 'androidx.test:core:1.3.0'
testImplementation 'org.robolectric:robolectric:4.3'
testImplementation 'org.robolectric:robolectric:4.5'
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
import android.media.CamcorderProfile;
import android.media.EncoderProfiles;
import android.media.Image;
import android.media.ImageReader;
import android.media.MediaRecorder;
Expand Down Expand Up @@ -199,8 +200,16 @@ private void prepareMediaRecorder(String outputFilePath) throws IOException {
((SensorOrientationFeature) cameraFeatures.getSensorOrientation())
.getLockedCaptureOrientation();

MediaRecorderBuilder mediaRecorderBuilder;

if (Build.VERSION.SDK_INT >= 31) {
mediaRecorderBuilder = new MediaRecorderBuilder(getRecordingProfile(), outputFilePath);
} else {
mediaRecorderBuilder = new MediaRecorderBuilder(getRecordingProfileLegacy(), outputFilePath);
}

mediaRecorder =
new MediaRecorderBuilder(getRecordingProfile(), outputFilePath)
mediaRecorderBuilder
.setEnableAudio(enableAudio)
.setMediaOrientation(
lockedOrientation == null
Expand Down Expand Up @@ -918,8 +927,12 @@ public float getMinZoomLevel() {
return cameraFeatures.getZoomLevel().getMinimumZoomLevel();
}

/** Shortcut to get current recording profile. */
CamcorderProfile getRecordingProfile() {
/** Shortcut to get current recording profile. Legacy method provides support for SDK < 31. */
CamcorderProfile getRecordingProfileLegacy() {
return cameraFeatures.getResolution().getRecordingProfileLegacy();
}

EncoderProfiles getRecordingProfile() {
return cameraFeatures.getResolution().getRecordingProfile();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@

package io.flutter.plugins.camera.features.resolution;

import android.annotation.TargetApi;
import android.hardware.camera2.CaptureRequest;
import android.media.CamcorderProfile;
import android.media.EncoderProfiles;
import android.os.Build;
import android.util.Size;
import androidx.annotation.VisibleForTesting;
import io.flutter.plugins.camera.CameraProperties;
import io.flutter.plugins.camera.features.CameraFeature;
import java.util.List;

/**
* Controls the resolutions configuration on the {@link android.hardware.camera2} API.
Expand All @@ -21,7 +25,8 @@
public class ResolutionFeature extends CameraFeature<ResolutionPreset> {
private Size captureSize;
private Size previewSize;
private CamcorderProfile recordingProfile;
private CamcorderProfile recordingProfileLegacy;
private EncoderProfiles recordingProfile;
private ResolutionPreset currentSetting;
private int cameraId;

Expand Down Expand Up @@ -51,7 +56,11 @@ public ResolutionFeature(
*
* @return Resolution information to configure the {@link android.hardware.camera2} API.
*/
public CamcorderProfile getRecordingProfile() {
public CamcorderProfile getRecordingProfileLegacy() {
return this.recordingProfileLegacy;
}

public EncoderProfiles getRecordingProfile() {
return this.recordingProfile;
}

Expand Down Expand Up @@ -100,19 +109,29 @@ public void updateBuilder(CaptureRequest.Builder requestBuilder) {
}

@VisibleForTesting
static Size computeBestPreviewSize(int cameraId, ResolutionPreset preset) {
static Size computeBestPreviewSize(int cameraId, ResolutionPreset preset)
throws IndexOutOfBoundsException {
if (preset.ordinal() > ResolutionPreset.high.ordinal()) {
preset = ResolutionPreset.high;
}
if (Build.VERSION.SDK_INT >= 31) {
EncoderProfiles profile =
getBestAvailableCamcorderProfileForResolutionPreset(cameraId, preset);
List<EncoderProfiles.VideoProfile> videoProfiles = profile.getVideoProfiles();
EncoderProfiles.VideoProfile defaultVideoProfile = videoProfiles.get(0);

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

/**
* Gets the best possible {@link android.media.CamcorderProfile} for the supplied {@link
* ResolutionPreset}.
* ResolutionPreset}. Supports SDK < 31.
*
* @param cameraId Camera identifier which indicates the device's camera for which to select a
* {@link android.media.CamcorderProfile}.
Expand All @@ -121,7 +140,7 @@ static Size computeBestPreviewSize(int cameraId, ResolutionPreset preset) {
* @return The best possible {@link android.media.CamcorderProfile} that matches the supplied
* {@link ResolutionPreset}.
*/
public static CamcorderProfile getBestAvailableCamcorderProfileForResolutionPreset(
public static CamcorderProfile getBestAvailableCamcorderProfileForResolutionPresetLegacy(
int cameraId, ResolutionPreset preset) {
if (cameraId < 0) {
throw new AssertionError(
Expand Down Expand Up @@ -164,13 +183,74 @@ public static CamcorderProfile getBestAvailableCamcorderProfileForResolutionPres
}
}

private void configureResolution(ResolutionPreset resolutionPreset, int cameraId) {
@TargetApi(Build.VERSION_CODES.S)
public static EncoderProfiles getBestAvailableCamcorderProfileForResolutionPreset(
int cameraId, ResolutionPreset preset) {
if (cameraId < 0) {
throw new AssertionError(
"getBestAvailableCamcorderProfileForResolutionPreset can only be used with valid (>=0) camera identifiers.");
}

String cameraIdString = Integer.toString(cameraId);

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.getAll(cameraIdString, CamcorderProfile.QUALITY_HIGH);
}
case ultraHigh:
if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_2160P)) {
return CamcorderProfile.getAll(cameraIdString, CamcorderProfile.QUALITY_2160P);
}
case veryHigh:
if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_1080P)) {
return CamcorderProfile.getAll(cameraIdString, CamcorderProfile.QUALITY_1080P);
}
case high:
if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_720P)) {
return CamcorderProfile.getAll(cameraIdString, CamcorderProfile.QUALITY_720P);
}
case medium:
if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_480P)) {
return CamcorderProfile.getAll(cameraIdString, CamcorderProfile.QUALITY_480P);
}
case low:
if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_QVGA)) {
return CamcorderProfile.getAll(cameraIdString, CamcorderProfile.QUALITY_QVGA);
}
default:
if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_LOW)) {
return CamcorderProfile.getAll(cameraIdString, CamcorderProfile.QUALITY_LOW);
}

throw new IllegalArgumentException(
"No capture session available for current capture session.");
}
}

private void configureResolution(ResolutionPreset resolutionPreset, int cameraId)
throws IndexOutOfBoundsException {
if (!checkIsSupported()) {
return;
}
recordingProfile =
getBestAvailableCamcorderProfileForResolutionPreset(cameraId, resolutionPreset);
captureSize = new Size(recordingProfile.videoFrameWidth, recordingProfile.videoFrameHeight);

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

EncoderProfiles.VideoProfile defaultVideoProfile = videoProfiles.get(0);
captureSize = new Size(defaultVideoProfile.getWidth(), defaultVideoProfile.getHeight());
} else {
@SuppressWarnings("deprecation")
CamcorderProfile camcorderProfile =
getBestAvailableCamcorderProfileForResolutionPresetLegacy(cameraId, resolutionPreset);
recordingProfileLegacy = camcorderProfile;
captureSize =
new Size(recordingProfileLegacy.videoFrameWidth, recordingProfileLegacy.videoFrameHeight);
}

previewSize = computeBestPreviewSize(cameraId, resolutionPreset);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,55 @@
package io.flutter.plugins.camera.media;

import android.media.CamcorderProfile;
import android.media.EncoderProfiles;
import android.media.MediaRecorder;
import android.os.Build;
import androidx.annotation.NonNull;
import java.io.IOException;

public class MediaRecorderBuilder {
@SuppressWarnings("deprecation")
static class MediaRecorderFactory {
MediaRecorder makeMediaRecorder() {
return new MediaRecorder();
}
}

private final String outputFilePath;
private final CamcorderProfile recordingProfile;
private final CamcorderProfile camcorderProfile;
private final EncoderProfiles encoderProfiles;
private final MediaRecorderFactory recorderFactory;

private boolean enableAudio;
private int mediaOrientation;

public MediaRecorderBuilder(
@NonNull CamcorderProfile recordingProfile, @NonNull String outputFilePath) {
this(recordingProfile, outputFilePath, new MediaRecorderFactory());
@NonNull CamcorderProfile camcorderProfile, @NonNull String outputFilePath) {
this(camcorderProfile, outputFilePath, new MediaRecorderFactory());
}

public MediaRecorderBuilder(
@NonNull EncoderProfiles encoderProfiles, @NonNull String outputFilePath) {
this(encoderProfiles, outputFilePath, new MediaRecorderFactory());
}

MediaRecorderBuilder(
@NonNull CamcorderProfile recordingProfile,
@NonNull CamcorderProfile camcorderProfile,
@NonNull String outputFilePath,
MediaRecorderFactory helper) {
this.outputFilePath = outputFilePath;
this.recordingProfile = recordingProfile;
this.camcorderProfile = camcorderProfile;
this.encoderProfiles = null;
this.recorderFactory = helper;
}

MediaRecorderBuilder(
@NonNull EncoderProfiles encoderProfiles,
@NonNull String outputFilePath,
MediaRecorderFactory helper) {
this.outputFilePath = outputFilePath;
this.encoderProfiles = encoderProfiles;
this.camcorderProfile = null;
this.recorderFactory = helper;
}

Expand All @@ -47,23 +67,43 @@ public MediaRecorderBuilder setMediaOrientation(int orientation) {
return this;
}

public MediaRecorder build() throws IOException {
public MediaRecorder build() throws IOException, NullPointerException, IndexOutOfBoundsException {
MediaRecorder mediaRecorder = recorderFactory.makeMediaRecorder();

// There's a fixed order that mediaRecorder expects. Only change these functions accordingly.
// You can find the specifics here: https://developer.android.com/reference/android/media/MediaRecorder.
if (enableAudio) mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
mediaRecorder.setOutputFormat(recordingProfile.fileFormat);
if (enableAudio) {
mediaRecorder.setAudioEncoder(recordingProfile.audioCodec);
mediaRecorder.setAudioEncodingBitRate(recordingProfile.audioBitRate);
mediaRecorder.setAudioSamplingRate(recordingProfile.audioSampleRate);

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

mediaRecorder.setOutputFormat(encoderProfiles.getRecommendedFileFormat());
if (enableAudio) {
mediaRecorder.setAudioEncoder(audioProfile.getCodec());
mediaRecorder.setAudioEncodingBitRate(audioProfile.getBitrate());
mediaRecorder.setAudioSamplingRate(audioProfile.getSampleRate());
}
mediaRecorder.setVideoEncoder(videoProfile.getCodec());
mediaRecorder.setVideoEncodingBitRate(videoProfile.getBitrate());
mediaRecorder.setVideoFrameRate(videoProfile.getFrameRate());
mediaRecorder.setVideoSize(videoProfile.getWidth(), videoProfile.getHeight());
mediaRecorder.setVideoSize(videoProfile.getWidth(), videoProfile.getHeight());
} else {
mediaRecorder.setOutputFormat(camcorderProfile.fileFormat);
if (enableAudio) {
mediaRecorder.setAudioEncoder(camcorderProfile.audioCodec);
mediaRecorder.setAudioEncodingBitRate(camcorderProfile.audioBitRate);
mediaRecorder.setAudioSamplingRate(camcorderProfile.audioSampleRate);
}
mediaRecorder.setVideoEncoder(camcorderProfile.videoCodec);
mediaRecorder.setVideoEncodingBitRate(camcorderProfile.videoBitRate);
mediaRecorder.setVideoFrameRate(camcorderProfile.videoFrameRate);
mediaRecorder.setVideoSize(
camcorderProfile.videoFrameWidth, camcorderProfile.videoFrameHeight);
}
mediaRecorder.setVideoEncoder(recordingProfile.videoCodec);
mediaRecorder.setVideoEncodingBitRate(recordingProfile.videoBitRate);
mediaRecorder.setVideoFrameRate(recordingProfile.videoFrameRate);
mediaRecorder.setVideoSize(recordingProfile.videoFrameWidth, recordingProfile.videoFrameHeight);

mediaRecorder.setOutputFile(outputFilePath);
mediaRecorder.setOrientationHint(this.mediaOrientation);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureRequest;
import android.media.CamcorderProfile;
import android.media.MediaRecorder;
import android.os.Build;
import android.os.Handler;
Expand Down Expand Up @@ -249,20 +248,6 @@ public void getMinZoomLevel() {
assertEquals(expectedMinZoomLevel, actualMinZoomLevel, 0);
}

@Test
public void getRecordingProfile() {
ResolutionFeature mockResolutionFeature =
mockCameraFeatureFactory.createResolutionFeature(mockCameraProperties, null, null);
CamcorderProfile mockCamcorderProfile = mock(CamcorderProfile.class);

when(mockResolutionFeature.getRecordingProfile()).thenReturn(mockCamcorderProfile);

CamcorderProfile actualRecordingProfile = camera.getRecordingProfile();

verify(mockResolutionFeature, times(1)).getRecordingProfile();
assertEquals(mockCamcorderProfile, actualRecordingProfile);
}

@Test
public void setExposureMode_shouldUpdateExposureLockFeature() {
ExposureLockFeature mockExposureLockFeature =
Expand Down
Loading