Skip to content

Commit caf48bc

Browse files
authored
[camera] Camera with MediaSettings: platform implementations (federated) (#5223)
## Platform implementations of federated plugin This is the `platform implementations` part of `camera` PR #3586. `camera_platform_interface: 2.6.0` merged and published in PR #3615. Now repeating steps 3,4 (see [Changing federated plugins](https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins)), because `camera/camera` depends on implementations `camera/camera_android`, `camera/camera_web` etc.
1 parent 764d997 commit caf48bc

File tree

67 files changed

+2703
-416
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+2703
-416
lines changed

packages/camera/camera_android/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.10.9
2+
3+
* Adds support to control video FPS and bitrate. See `CameraController.withSettings`.
4+
15
## 0.10.8+18
26

37
* Updates annotations lib to 1.7.1.

packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java

Lines changed: 82 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,14 @@
2929
import android.os.HandlerThread;
3030
import android.os.Looper;
3131
import android.util.Log;
32+
import android.util.Range;
3233
import android.util.Size;
3334
import android.view.Display;
3435
import android.view.Surface;
3536
import androidx.annotation.NonNull;
3637
import androidx.annotation.Nullable;
3738
import androidx.annotation.VisibleForTesting;
39+
import io.flutter.BuildConfig;
3840
import io.flutter.embedding.engine.systemchannels.PlatformChannel;
3941
import io.flutter.plugin.common.EventChannel;
4042
import io.flutter.plugin.common.MethodChannel;
@@ -52,6 +54,7 @@
5254
import io.flutter.plugins.camera.features.flash.FlashFeature;
5355
import io.flutter.plugins.camera.features.flash.FlashMode;
5456
import io.flutter.plugins.camera.features.focuspoint.FocusPointFeature;
57+
import io.flutter.plugins.camera.features.fpsrange.FpsRangeFeature;
5558
import io.flutter.plugins.camera.features.resolution.ResolutionFeature;
5659
import io.flutter.plugins.camera.features.resolution.ResolutionPreset;
5760
import io.flutter.plugins.camera.features.sensororientation.DeviceOrientationManager;
@@ -111,8 +114,7 @@ class Camera
111114
private int initialCameraFacing;
112115

113116
private final SurfaceTextureEntry flutterTexture;
114-
private final ResolutionPreset resolutionPreset;
115-
private final boolean enableAudio;
117+
private final VideoCaptureSettings videoCaptureSettings;
116118
private final Context applicationContext;
117119
final DartMessenger dartMessenger;
118120
private CameraProperties cameraProperties;
@@ -185,29 +187,80 @@ public void close() {
185187
}
186188
}
187189

190+
public static class VideoCaptureSettings {
191+
@NonNull public final ResolutionPreset resolutionPreset;
192+
public final boolean enableAudio;
193+
@Nullable public final Integer fps;
194+
@Nullable public final Integer videoBitrate;
195+
@Nullable public final Integer audioBitrate;
196+
197+
public VideoCaptureSettings(
198+
@NonNull ResolutionPreset resolutionPreset,
199+
boolean enableAudio,
200+
@Nullable Integer fps,
201+
@Nullable Integer videoBitrate,
202+
@Nullable Integer audioBitrate) {
203+
this.resolutionPreset = resolutionPreset;
204+
this.enableAudio = enableAudio;
205+
this.fps = fps;
206+
this.videoBitrate = videoBitrate;
207+
this.audioBitrate = audioBitrate;
208+
}
209+
210+
public VideoCaptureSettings(@NonNull ResolutionPreset resolutionPreset, boolean enableAudio) {
211+
this(resolutionPreset, enableAudio, null, null, null);
212+
}
213+
}
214+
188215
public Camera(
189216
final Activity activity,
190217
final SurfaceTextureEntry flutterTexture,
191218
final CameraFeatureFactory cameraFeatureFactory,
192219
final DartMessenger dartMessenger,
193220
final CameraProperties cameraProperties,
194-
final ResolutionPreset resolutionPreset,
195-
final boolean enableAudio) {
221+
final VideoCaptureSettings videoCaptureSettings) {
196222

197223
if (activity == null) {
198224
throw new IllegalStateException("No activity available!");
199225
}
200226
this.activity = activity;
201-
this.enableAudio = enableAudio;
202227
this.flutterTexture = flutterTexture;
203228
this.dartMessenger = dartMessenger;
204229
this.applicationContext = activity.getApplicationContext();
205230
this.cameraProperties = cameraProperties;
206231
this.cameraFeatureFactory = cameraFeatureFactory;
207-
this.resolutionPreset = resolutionPreset;
232+
this.videoCaptureSettings = videoCaptureSettings;
208233
this.cameraFeatures =
209234
CameraFeatures.init(
210-
cameraFeatureFactory, cameraProperties, activity, dartMessenger, resolutionPreset);
235+
cameraFeatureFactory,
236+
cameraProperties,
237+
activity,
238+
dartMessenger,
239+
videoCaptureSettings.resolutionPreset);
240+
241+
Integer recordingFps = null;
242+
243+
if (videoCaptureSettings.fps != null && videoCaptureSettings.fps.intValue() > 0) {
244+
recordingFps = videoCaptureSettings.fps;
245+
} else {
246+
247+
if (SdkCapabilityChecker.supportsEncoderProfiles()) {
248+
EncoderProfiles encoderProfiles = getRecordingProfile();
249+
if (encoderProfiles != null && encoderProfiles.getVideoProfiles().size() > 0) {
250+
recordingFps = encoderProfiles.getVideoProfiles().get(0).getFrameRate();
251+
}
252+
} else {
253+
CamcorderProfile camcorderProfile = getRecordingProfileLegacy();
254+
recordingFps = null != camcorderProfile ? camcorderProfile.videoFrameRate : null;
255+
}
256+
}
257+
258+
if (recordingFps != null && recordingFps.intValue() > 0) {
259+
260+
final FpsRangeFeature fpsRange = new FpsRangeFeature(cameraProperties);
261+
fpsRange.setValue(new Range<Integer>(recordingFps, recordingFps));
262+
this.cameraFeatures.setFpsRange(fpsRange);
263+
}
211264

212265
// Create capture callback.
213266
captureTimeouts = new CaptureTimeoutsWrapper(3000, 3000);
@@ -257,14 +310,28 @@ private void prepareMediaRecorder(String outputFilePath) throws IOException {
257310
// TODO(camsim99): Revert changes that allow legacy code to be used when recordingProfile is null
258311
// once this has largely been fixed on the Android side. https://github.com/flutter/flutter/issues/119668
259312
if (SdkCapabilityChecker.supportsEncoderProfiles() && getRecordingProfile() != null) {
260-
mediaRecorderBuilder = new MediaRecorderBuilder(getRecordingProfile(), outputFilePath);
313+
mediaRecorderBuilder =
314+
new MediaRecorderBuilder(
315+
getRecordingProfile(),
316+
new MediaRecorderBuilder.RecordingParameters(
317+
outputFilePath,
318+
videoCaptureSettings.fps,
319+
videoCaptureSettings.videoBitrate,
320+
videoCaptureSettings.audioBitrate));
261321
} else {
262-
mediaRecorderBuilder = new MediaRecorderBuilder(getRecordingProfileLegacy(), outputFilePath);
322+
mediaRecorderBuilder =
323+
new MediaRecorderBuilder(
324+
getRecordingProfileLegacy(),
325+
new MediaRecorderBuilder.RecordingParameters(
326+
outputFilePath,
327+
videoCaptureSettings.fps,
328+
videoCaptureSettings.videoBitrate,
329+
videoCaptureSettings.audioBitrate));
263330
}
264331

265332
mediaRecorder =
266333
mediaRecorderBuilder
267-
.setEnableAudio(enableAudio)
334+
.setEnableAudio(videoCaptureSettings.enableAudio)
268335
.setMediaOrientation(
269336
lockedOrientation == null
270337
? getDeviceOrientationManager().getVideoOrientation()
@@ -1314,7 +1381,11 @@ public void setDescriptionWhileRecording(
13141381
cameraProperties = properties;
13151382
cameraFeatures =
13161383
CameraFeatures.init(
1317-
cameraFeatureFactory, cameraProperties, activity, dartMessenger, resolutionPreset);
1384+
cameraFeatureFactory,
1385+
cameraProperties,
1386+
activity,
1387+
dartMessenger,
1388+
videoCaptureSettings.resolutionPreset);
13181389
cameraFeatures.setAutoFocus(
13191390
cameraFeatureFactory.createAutoFocusFeature(cameraProperties, true));
13201391
try {

packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,9 @@ private void instantiateCamera(MethodCall call, Result result) throws CameraAcce
388388
String cameraName = call.argument("cameraName");
389389
String preset = call.argument("resolutionPreset");
390390
boolean enableAudio = call.argument("enableAudio");
391+
Integer fps = call.argument("fps");
392+
Integer videoBitrate = call.argument("videoBitrate");
393+
Integer audioBitrate = call.argument("audioBitrate");
391394

392395
TextureRegistry.SurfaceTextureEntry flutterSurfaceTexture =
393396
textureRegistry.createSurfaceTexture();
@@ -405,8 +408,8 @@ private void instantiateCamera(MethodCall call, Result result) throws CameraAcce
405408
new CameraFeatureFactoryImpl(),
406409
dartMessenger,
407410
cameraProperties,
408-
resolutionPreset,
409-
enableAudio);
411+
new Camera.VideoCaptureSettings(
412+
resolutionPreset, enableAudio, fps, videoBitrate, audioBitrate));
410413

411414
Map<String, Object> reply = new HashMap<>();
412415
reply.put("cameraId", flutterSurfaceTexture.id());

packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java

Lines changed: 71 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import android.media.EncoderProfiles;
99
import android.media.MediaRecorder;
1010
import androidx.annotation.NonNull;
11+
import androidx.annotation.Nullable;
1112
import io.flutter.plugins.camera.SdkCapabilityChecker;
1213
import java.io.IOException;
1314

@@ -19,42 +20,64 @@ MediaRecorder makeMediaRecorder() {
1920
}
2021
}
2122

22-
private final String outputFilePath;
23+
public static class RecordingParameters {
24+
@NonNull public final String outputFilePath;
25+
@Nullable public final Integer fps;
26+
@Nullable public final Integer videoBitrate;
27+
@Nullable public final Integer audioBitrate;
28+
29+
public RecordingParameters(@NonNull String outputFilePath) {
30+
this(outputFilePath, null, null, null);
31+
}
32+
33+
public RecordingParameters(
34+
@NonNull String outputFilePath,
35+
@Nullable Integer fps,
36+
@Nullable Integer videoBitrate,
37+
@Nullable Integer audioBitrate) {
38+
this.outputFilePath = outputFilePath;
39+
this.fps = fps;
40+
this.videoBitrate = videoBitrate;
41+
this.audioBitrate = audioBitrate;
42+
}
43+
}
44+
2345
private final CamcorderProfile camcorderProfile;
2446
private final EncoderProfiles encoderProfiles;
2547
private final MediaRecorderFactory recorderFactory;
48+
@NonNull private final RecordingParameters parameters;
2649

2750
private boolean enableAudio;
2851
private int mediaOrientation;
2952

3053
public MediaRecorderBuilder(
31-
@NonNull CamcorderProfile camcorderProfile, @NonNull String outputFilePath) {
32-
this(camcorderProfile, outputFilePath, new MediaRecorderFactory());
54+
@NonNull CamcorderProfile camcorderProfile, @NonNull RecordingParameters parameters) {
55+
this(camcorderProfile, new MediaRecorderFactory(), parameters);
3356
}
3457

3558
public MediaRecorderBuilder(
36-
@NonNull EncoderProfiles encoderProfiles, @NonNull String outputFilePath) {
37-
this(encoderProfiles, outputFilePath, new MediaRecorderFactory());
59+
@NonNull EncoderProfiles encoderProfiles, @NonNull RecordingParameters parameters) {
60+
this(encoderProfiles, new MediaRecorderFactory(), parameters);
3861
}
3962

4063
MediaRecorderBuilder(
4164
@NonNull CamcorderProfile camcorderProfile,
42-
@NonNull String outputFilePath,
43-
MediaRecorderFactory helper) {
44-
this.outputFilePath = outputFilePath;
65+
MediaRecorderFactory helper,
66+
@NonNull RecordingParameters parameters) {
4567
this.camcorderProfile = camcorderProfile;
4668
this.encoderProfiles = null;
4769
this.recorderFactory = helper;
70+
this.parameters = parameters;
4871
}
4972

5073
MediaRecorderBuilder(
5174
@NonNull EncoderProfiles encoderProfiles,
52-
@NonNull String outputFilePath,
53-
MediaRecorderFactory helper) {
54-
this.outputFilePath = outputFilePath;
75+
MediaRecorderFactory helper,
76+
@NonNull RecordingParameters parameters) {
5577
this.encoderProfiles = encoderProfiles;
5678
this.camcorderProfile = null;
5779
this.recorderFactory = helper;
80+
this.parameters = parameters;
5881
}
5982

6083
@NonNull
@@ -79,34 +102,62 @@ public MediaRecorder build() throws IOException, NullPointerException, IndexOutO
79102
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
80103

81104
if (SdkCapabilityChecker.supportsEncoderProfiles() && encoderProfiles != null) {
105+
mediaRecorder.setOutputFormat(encoderProfiles.getRecommendedFileFormat());
106+
82107
EncoderProfiles.VideoProfile videoProfile = encoderProfiles.getVideoProfiles().get(0);
83-
EncoderProfiles.AudioProfile audioProfile = encoderProfiles.getAudioProfiles().get(0);
84108

85-
mediaRecorder.setOutputFormat(encoderProfiles.getRecommendedFileFormat());
86109
if (enableAudio) {
110+
EncoderProfiles.AudioProfile audioProfile = encoderProfiles.getAudioProfiles().get(0);
111+
87112
mediaRecorder.setAudioEncoder(audioProfile.getCodec());
88-
mediaRecorder.setAudioEncodingBitRate(audioProfile.getBitrate());
113+
mediaRecorder.setAudioEncodingBitRate(
114+
(parameters.audioBitrate != null && parameters.audioBitrate.intValue() > 0)
115+
? parameters.audioBitrate
116+
: audioProfile.getBitrate());
89117
mediaRecorder.setAudioSamplingRate(audioProfile.getSampleRate());
90118
}
119+
91120
mediaRecorder.setVideoEncoder(videoProfile.getCodec());
92-
mediaRecorder.setVideoEncodingBitRate(videoProfile.getBitrate());
93-
mediaRecorder.setVideoFrameRate(videoProfile.getFrameRate());
121+
122+
int videoBitrate =
123+
(parameters.videoBitrate != null && parameters.videoBitrate.intValue() > 0)
124+
? parameters.videoBitrate
125+
: videoProfile.getBitrate();
126+
127+
mediaRecorder.setVideoEncodingBitRate(videoBitrate);
128+
129+
int fps =
130+
(parameters.fps != null && parameters.fps.intValue() > 0)
131+
? parameters.fps
132+
: videoProfile.getFrameRate();
133+
134+
mediaRecorder.setVideoFrameRate(fps);
135+
94136
mediaRecorder.setVideoSize(videoProfile.getWidth(), videoProfile.getHeight());
95137
} else if (camcorderProfile != null) {
96138
mediaRecorder.setOutputFormat(camcorderProfile.fileFormat);
97139
if (enableAudio) {
98140
mediaRecorder.setAudioEncoder(camcorderProfile.audioCodec);
99-
mediaRecorder.setAudioEncodingBitRate(camcorderProfile.audioBitRate);
141+
mediaRecorder.setAudioEncodingBitRate(
142+
(parameters.audioBitrate != null && parameters.audioBitrate.intValue() > 0)
143+
? parameters.audioBitrate
144+
: camcorderProfile.audioBitRate);
100145
mediaRecorder.setAudioSamplingRate(camcorderProfile.audioSampleRate);
101146
}
102147
mediaRecorder.setVideoEncoder(camcorderProfile.videoCodec);
103-
mediaRecorder.setVideoEncodingBitRate(camcorderProfile.videoBitRate);
104-
mediaRecorder.setVideoFrameRate(camcorderProfile.videoFrameRate);
148+
mediaRecorder.setVideoEncodingBitRate(
149+
(parameters.videoBitrate != null && parameters.videoBitrate.intValue() > 0)
150+
? parameters.videoBitrate
151+
: camcorderProfile.videoBitRate);
152+
mediaRecorder.setVideoFrameRate(
153+
(parameters.fps != null && parameters.fps.intValue() > 0)
154+
? parameters.fps
155+
: camcorderProfile.videoFrameRate);
105156
mediaRecorder.setVideoSize(
106157
camcorderProfile.videoFrameWidth, camcorderProfile.videoFrameHeight);
107158
}
108159

109-
mediaRecorder.setOutputFile(outputFilePath);
160+
mediaRecorder.setOutputFile(parameters.outputFilePath);
110161
mediaRecorder.setOrientationHint(this.mediaOrientation);
111162

112163
mediaRecorder.prepare();

0 commit comments

Comments
 (0)