Skip to content

Commit

Permalink
[camera] Camera with MediaSettings: platform implementations (federat…
Browse files Browse the repository at this point in the history
…ed) (flutter#5223)

This is the `platform implementations` part of `camera` PR flutter#3586.

`camera_platform_interface: 2.6.0` merged and published in PR flutter#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.
  • Loading branch information
PROGrand authored and TecHaxter committed May 22, 2024
1 parent 37e4d12 commit 92b29a2
Show file tree
Hide file tree
Showing 67 changed files with 2,734 additions and 448 deletions.
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.9

* Adds support to control video FPS and bitrate. See `CameraController.withSettings`.

## 0.10.8+18

* Updates annotations lib to 1.7.1.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@
import android.os.HandlerThread;
import android.os.Looper;
import android.util.Log;
import android.util.Range;
import android.util.Size;
import android.view.Display;
import android.view.Surface;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import io.flutter.BuildConfig;
import io.flutter.embedding.engine.systemchannels.PlatformChannel;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodChannel;
Expand All @@ -52,6 +54,7 @@
import io.flutter.plugins.camera.features.flash.FlashFeature;
import io.flutter.plugins.camera.features.flash.FlashMode;
import io.flutter.plugins.camera.features.focuspoint.FocusPointFeature;
import io.flutter.plugins.camera.features.fpsrange.FpsRangeFeature;
import io.flutter.plugins.camera.features.resolution.ResolutionFeature;
import io.flutter.plugins.camera.features.resolution.ResolutionPreset;
import io.flutter.plugins.camera.features.sensororientation.DeviceOrientationManager;
Expand Down Expand Up @@ -111,8 +114,7 @@ class Camera
private int initialCameraFacing;

private final SurfaceTextureEntry flutterTexture;
private final ResolutionPreset resolutionPreset;
private final boolean enableAudio;
private final VideoCaptureSettings videoCaptureSettings;
private final Context applicationContext;
final DartMessenger dartMessenger;
private CameraProperties cameraProperties;
Expand Down Expand Up @@ -185,29 +187,80 @@ public void close() {
}
}

public static class VideoCaptureSettings {
@NonNull public final ResolutionPreset resolutionPreset;
public final boolean enableAudio;
@Nullable public final Integer fps;
@Nullable public final Integer videoBitrate;
@Nullable public final Integer audioBitrate;

public VideoCaptureSettings(
@NonNull ResolutionPreset resolutionPreset,
boolean enableAudio,
@Nullable Integer fps,
@Nullable Integer videoBitrate,
@Nullable Integer audioBitrate) {
this.resolutionPreset = resolutionPreset;
this.enableAudio = enableAudio;
this.fps = fps;
this.videoBitrate = videoBitrate;
this.audioBitrate = audioBitrate;
}

public VideoCaptureSettings(@NonNull ResolutionPreset resolutionPreset, boolean enableAudio) {
this(resolutionPreset, enableAudio, null, null, null);
}
}

public Camera(
final Activity activity,
final SurfaceTextureEntry flutterTexture,
final CameraFeatureFactory cameraFeatureFactory,
final DartMessenger dartMessenger,
final CameraProperties cameraProperties,
final ResolutionPreset resolutionPreset,
final boolean enableAudio) {
final VideoCaptureSettings videoCaptureSettings) {

if (activity == null) {
throw new IllegalStateException("No activity available!");
}
this.activity = activity;
this.enableAudio = enableAudio;
this.flutterTexture = flutterTexture;
this.dartMessenger = dartMessenger;
this.applicationContext = activity.getApplicationContext();
this.cameraProperties = cameraProperties;
this.cameraFeatureFactory = cameraFeatureFactory;
this.resolutionPreset = resolutionPreset;
this.videoCaptureSettings = videoCaptureSettings;
this.cameraFeatures =
CameraFeatures.init(
cameraFeatureFactory, cameraProperties, activity, dartMessenger, resolutionPreset);
cameraFeatureFactory,
cameraProperties,
activity,
dartMessenger,
videoCaptureSettings.resolutionPreset);

Integer recordingFps = null;

if (videoCaptureSettings.fps != null && videoCaptureSettings.fps.intValue() > 0) {
recordingFps = videoCaptureSettings.fps;
} else {

if (SdkCapabilityChecker.supportsEncoderProfiles()) {
EncoderProfiles encoderProfiles = getRecordingProfile();
if (encoderProfiles != null && encoderProfiles.getVideoProfiles().size() > 0) {
recordingFps = encoderProfiles.getVideoProfiles().get(0).getFrameRate();
}
} else {
CamcorderProfile camcorderProfile = getRecordingProfileLegacy();
recordingFps = null != camcorderProfile ? camcorderProfile.videoFrameRate : null;
}
}

if (recordingFps != null && recordingFps.intValue() > 0) {

final FpsRangeFeature fpsRange = new FpsRangeFeature(cameraProperties);
fpsRange.setValue(new Range<Integer>(recordingFps, recordingFps));
this.cameraFeatures.setFpsRange(fpsRange);
}

// Create capture callback.
captureTimeouts = new CaptureTimeoutsWrapper(3000, 3000);
Expand Down Expand Up @@ -257,14 +310,28 @@ private void prepareMediaRecorder(String outputFilePath) throws IOException {
// 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
if (SdkCapabilityChecker.supportsEncoderProfiles() && getRecordingProfile() != null) {
mediaRecorderBuilder = new MediaRecorderBuilder(getRecordingProfile(), outputFilePath);
mediaRecorderBuilder =
new MediaRecorderBuilder(
getRecordingProfile(),
new MediaRecorderBuilder.RecordingParameters(
outputFilePath,
videoCaptureSettings.fps,
videoCaptureSettings.videoBitrate,
videoCaptureSettings.audioBitrate));
} else {
mediaRecorderBuilder = new MediaRecorderBuilder(getRecordingProfileLegacy(), outputFilePath);
mediaRecorderBuilder =
new MediaRecorderBuilder(
getRecordingProfileLegacy(),
new MediaRecorderBuilder.RecordingParameters(
outputFilePath,
videoCaptureSettings.fps,
videoCaptureSettings.videoBitrate,
videoCaptureSettings.audioBitrate));
}

mediaRecorder =
mediaRecorderBuilder
.setEnableAudio(enableAudio)
.setEnableAudio(videoCaptureSettings.enableAudio)
.setMediaOrientation(
lockedOrientation == null
? getDeviceOrientationManager().getVideoOrientation()
Expand Down Expand Up @@ -1314,7 +1381,11 @@ public void setDescriptionWhileRecording(
cameraProperties = properties;
cameraFeatures =
CameraFeatures.init(
cameraFeatureFactory, cameraProperties, activity, dartMessenger, resolutionPreset);
cameraFeatureFactory,
cameraProperties,
activity,
dartMessenger,
videoCaptureSettings.resolutionPreset);
cameraFeatures.setAutoFocus(
cameraFeatureFactory.createAutoFocusFeature(cameraProperties, true));
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,9 @@ private void instantiateCamera(MethodCall call, Result result) throws CameraAcce
String cameraName = call.argument("cameraName");
String preset = call.argument("resolutionPreset");
boolean enableAudio = call.argument("enableAudio");
Integer fps = call.argument("fps");
Integer videoBitrate = call.argument("videoBitrate");
Integer audioBitrate = call.argument("audioBitrate");

TextureRegistry.SurfaceTextureEntry flutterSurfaceTexture =
textureRegistry.createSurfaceTexture();
Expand All @@ -405,8 +408,8 @@ private void instantiateCamera(MethodCall call, Result result) throws CameraAcce
new CameraFeatureFactoryImpl(),
dartMessenger,
cameraProperties,
resolutionPreset,
enableAudio);
new Camera.VideoCaptureSettings(
resolutionPreset, enableAudio, fps, videoBitrate, audioBitrate));

Map<String, Object> reply = new HashMap<>();
reply.put("cameraId", flutterSurfaceTexture.id());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import android.media.EncoderProfiles;
import android.media.MediaRecorder;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.flutter.plugins.camera.SdkCapabilityChecker;
import java.io.IOException;

Expand All @@ -19,42 +20,64 @@ MediaRecorder makeMediaRecorder() {
}
}

private final String outputFilePath;
public static class RecordingParameters {
@NonNull public final String outputFilePath;
@Nullable public final Integer fps;
@Nullable public final Integer videoBitrate;
@Nullable public final Integer audioBitrate;

public RecordingParameters(@NonNull String outputFilePath) {
this(outputFilePath, null, null, null);
}

public RecordingParameters(
@NonNull String outputFilePath,
@Nullable Integer fps,
@Nullable Integer videoBitrate,
@Nullable Integer audioBitrate) {
this.outputFilePath = outputFilePath;
this.fps = fps;
this.videoBitrate = videoBitrate;
this.audioBitrate = audioBitrate;
}
}

private final CamcorderProfile camcorderProfile;
private final EncoderProfiles encoderProfiles;
private final MediaRecorderFactory recorderFactory;
@NonNull private final RecordingParameters parameters;

private boolean enableAudio;
private int mediaOrientation;

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

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

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

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

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

if (SdkCapabilityChecker.supportsEncoderProfiles() && encoderProfiles != null) {
mediaRecorder.setOutputFormat(encoderProfiles.getRecommendedFileFormat());

EncoderProfiles.VideoProfile videoProfile = encoderProfiles.getVideoProfiles().get(0);
EncoderProfiles.AudioProfile audioProfile = encoderProfiles.getAudioProfiles().get(0);

mediaRecorder.setOutputFormat(encoderProfiles.getRecommendedFileFormat());
if (enableAudio) {
EncoderProfiles.AudioProfile audioProfile = encoderProfiles.getAudioProfiles().get(0);

mediaRecorder.setAudioEncoder(audioProfile.getCodec());
mediaRecorder.setAudioEncodingBitRate(audioProfile.getBitrate());
mediaRecorder.setAudioEncodingBitRate(
(parameters.audioBitrate != null && parameters.audioBitrate.intValue() > 0)
? parameters.audioBitrate
: audioProfile.getBitrate());
mediaRecorder.setAudioSamplingRate(audioProfile.getSampleRate());
}

mediaRecorder.setVideoEncoder(videoProfile.getCodec());
mediaRecorder.setVideoEncodingBitRate(videoProfile.getBitrate());
mediaRecorder.setVideoFrameRate(videoProfile.getFrameRate());

int videoBitrate =
(parameters.videoBitrate != null && parameters.videoBitrate.intValue() > 0)
? parameters.videoBitrate
: videoProfile.getBitrate();

mediaRecorder.setVideoEncodingBitRate(videoBitrate);

int fps =
(parameters.fps != null && parameters.fps.intValue() > 0)
? parameters.fps
: videoProfile.getFrameRate();

mediaRecorder.setVideoFrameRate(fps);

mediaRecorder.setVideoSize(videoProfile.getWidth(), videoProfile.getHeight());
} else if (camcorderProfile != null) {
mediaRecorder.setOutputFormat(camcorderProfile.fileFormat);
if (enableAudio) {
mediaRecorder.setAudioEncoder(camcorderProfile.audioCodec);
mediaRecorder.setAudioEncodingBitRate(camcorderProfile.audioBitRate);
mediaRecorder.setAudioEncodingBitRate(
(parameters.audioBitrate != null && parameters.audioBitrate.intValue() > 0)
? parameters.audioBitrate
: camcorderProfile.audioBitRate);
mediaRecorder.setAudioSamplingRate(camcorderProfile.audioSampleRate);
}
mediaRecorder.setVideoEncoder(camcorderProfile.videoCodec);
mediaRecorder.setVideoEncodingBitRate(camcorderProfile.videoBitRate);
mediaRecorder.setVideoFrameRate(camcorderProfile.videoFrameRate);
mediaRecorder.setVideoEncodingBitRate(
(parameters.videoBitrate != null && parameters.videoBitrate.intValue() > 0)
? parameters.videoBitrate
: camcorderProfile.videoBitRate);
mediaRecorder.setVideoFrameRate(
(parameters.fps != null && parameters.fps.intValue() > 0)
? parameters.fps
: camcorderProfile.videoFrameRate);
mediaRecorder.setVideoSize(
camcorderProfile.videoFrameWidth, camcorderProfile.videoFrameHeight);
}

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

mediaRecorder.prepare();
Expand Down
Loading

0 comments on commit 92b29a2

Please sign in to comment.