Skip to content

Commit

Permalink
Adds video stabilization to camera
Browse files Browse the repository at this point in the history
- Adds support for video stabilization to camera:

    Adds getVideoStabilizationSupportedModes() and
    setVideoStabilizationMode() methods to CameraController.

- Adds support for video stabilization to camera_avfoundation

- Adds support for video stabilization to camera_android_camerax

- Adds support for video stabilization to camera_platform_interface:

    - Adds getVideoStabilizationSupportedModes() and
    setVideoStabilizationMode() methods to CameraPlatform.

    - Adds VideoStabilizationMode enum to represent an
    abstraction of the available video stabilization modes,
    meant for Android and iOS, mapped as follows:

      /// Video stabilization is disabled.
      off,

      /// Standard video stabilization is enabled.
      /// Maps to CONTROL_VIDEO_STABILIZATION_MODE_ON on Android
      /// and to AVCaptureVideoStabilizationModeStandard on iOS.
      standard,

      /// Cinematic video stabilization is enabled.
      /// Maps to CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION on Android
      /// and to AVCaptureVideoStabilizationModeCinematic on iOS.
      cinematic,

      /// Extended cinematic video stabilization is enabled.
      /// The same as cinematic on Android, so maps to CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION.
      /// Maps to AVCaptureVideoStabilizationModeCinematicExtended on iOS.
      cinematicExtended,
  • Loading branch information
ruicraveiro committed Jul 12, 2024
1 parent 2677981 commit 9d75928
Show file tree
Hide file tree
Showing 55 changed files with 1,591 additions and 30 deletions.
1 change: 1 addition & 0 deletions packages/camera/camera/AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,4 @@ Aleksandr Yurkovskiy <sanekyy@gmail.com>
Anton Borries <mail@antonborri.es>
Alex Li <google@alexv525.com>
Rahul Raj <64.rahulraj@gmail.com>
Rui Craveiro <ruicraveiro@squarealfa.com>
4 changes: 4 additions & 0 deletions packages/camera/camera/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.12.0

* Adds support for video stabilization.

## 0.11.0+1

* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2.
Expand Down
1 change: 1 addition & 0 deletions packages/camera/camera/lib/camera.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export 'package:camera_platform_interface/camera_platform_interface.dart'
FocusMode,
ImageFormatGroup,
ResolutionPreset,
VideoStabilizationMode,
XFile;

export 'src/camera_controller.dart';
Expand Down
43 changes: 43 additions & 0 deletions packages/camera/camera/lib/src/camera_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class CameraValue {
this.recordingOrientation,
this.isPreviewPaused = false,
this.previewPauseOrientation,
this.videoStabilizationMode = VideoStabilizationMode.off,
}) : _isRecordingPaused = isRecordingPaused;

/// Creates a new camera controller state for an uninitialized controller.
Expand All @@ -72,6 +73,7 @@ class CameraValue {
deviceOrientation: DeviceOrientation.portraitUp,
isPreviewPaused: false,
description: description,
videoStabilizationMode: VideoStabilizationMode.off,
);

/// True after [CameraController.initialize] has completed successfully.
Expand Down Expand Up @@ -148,6 +150,9 @@ class CameraValue {
/// The properties of the camera device controlled by this controller.
final CameraDescription description;

/// The video stabilization mode in
final VideoStabilizationMode videoStabilizationMode;

/// Creates a modified copy of the object.
///
/// Explicitly specified fields get the specified value, all other fields get
Expand All @@ -171,6 +176,7 @@ class CameraValue {
bool? isPreviewPaused,
CameraDescription? description,
Optional<DeviceOrientation>? previewPauseOrientation,
VideoStabilizationMode? videoStabilizationMode,
}) {
return CameraValue(
isInitialized: isInitialized ?? this.isInitialized,
Expand Down Expand Up @@ -198,6 +204,8 @@ class CameraValue {
previewPauseOrientation: previewPauseOrientation == null
? this.previewPauseOrientation
: previewPauseOrientation.orNull,
videoStabilizationMode:
videoStabilizationMode ?? this.videoStabilizationMode,
);
}

Expand All @@ -219,6 +227,7 @@ class CameraValue {
'recordingOrientation: $recordingOrientation, '
'isPreviewPaused: $isPreviewPaused, '
'previewPausedOrientation: $previewPauseOrientation, '
'videoStabilizationMode: $videoStabilizationMode, '
'description: $description)';
}
}
Expand Down Expand Up @@ -689,6 +698,40 @@ class CameraController extends ValueNotifier<CameraValue> {
}
}

/// Set the video stabilization mode for the selected camera.
///
/// On Android (when using camera_android_camerax) and on iOS
/// the supplied [mode] value should be a mode in the list returned
/// by [getVideoStabilizationSupportedModes].
///
/// Throws a [CameraException] when a not supported video stabilization
/// mode is supplied.
Future<void> setVideoStabilizationMode(VideoStabilizationMode mode) async {
_throwIfNotInitialized('setVideoStabilizationMode');
try {
await CameraPlatform.instance.setVideoStabilizationMode(_cameraId, mode);
value = value.copyWith(videoStabilizationMode: mode);
} on PlatformException catch (e) {
throw CameraException(e.code, e.message);
}
}

/// Gets a list of video stabilization modes that are supported for the selected camera.
///
/// Will return the list of supported video stabilization modes
/// on Android (when using camera_android_camerax package) and
/// on iOS. Will return an empty list on all other platforms.
Future<Iterable<VideoStabilizationMode>>
getVideoStabilizationSupportedModes() {
_throwIfNotInitialized('isVideoStabilizationModeSupported');
try {
return CameraPlatform.instance
.getVideoStabilizationSupportedModes(_cameraId);
} on PlatformException catch (e) {
throw CameraException(e.code, e.message);
}
}

/// Sets the flash mode for taking pictures.
Future<void> setFlashMode(FlashMode mode) async {
try {
Expand Down
8 changes: 4 additions & 4 deletions packages/camera/camera/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: A Flutter plugin for controlling the camera. Supports previewing
Dart.
repository: https://github.com/flutter/packages/tree/main/packages/camera/camera
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
version: 0.11.0+1
version: 0.12.0

environment:
sdk: ^3.2.3
Expand All @@ -21,9 +21,9 @@ flutter:
default_package: camera_web

dependencies:
camera_android_camerax: ^0.6.5
camera_avfoundation: ^0.9.15
camera_platform_interface: ^2.6.0
camera_android_camerax: ^0.7.0
camera_avfoundation: ^0.10.0
camera_platform_interface: ^2.9.0
camera_web: ^0.3.3
flutter:
sdk: flutter
Expand Down
7 changes: 7 additions & 0 deletions packages/camera/camera/test/camera_preview_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,13 @@ class FakeController extends ValueNotifier<CameraValue>

@override
CameraDescription get description => value.description;

@override
Future<void> setVideoStabilizationMode(VideoStabilizationMode mode) async {}

@override
Future<Iterable<VideoStabilizationMode>>
getVideoStabilizationSupportedModes() async => <VideoStabilizationMode>[];
}

void main() {
Expand Down
142 changes: 142 additions & 0 deletions packages/camera/camera/test/camera_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1190,6 +1190,131 @@ void main() {
.called(4);
});

test('getVideoStabilizationSupportedModes() returns empty list', () async {
// arrange
final CameraController cameraController = CameraController(
const CameraDescription(
name: 'cam',
lensDirection: CameraLensDirection.back,
sensorOrientation: 90),
ResolutionPreset.max);

await cameraController.initialize();
when(CameraPlatform.instance
.getVideoStabilizationSupportedModes(mockInitializeCamera))
.thenAnswer((_) async => <VideoStabilizationMode>[]);

// act
final Iterable<VideoStabilizationMode> modes =
await cameraController.getVideoStabilizationSupportedModes();

// assert
expect(modes, <VideoStabilizationMode>[]);
});

test('getVideoStabilizationSupportedModes() returns off', () async {
// arrange
final CameraController cameraController = CameraController(
const CameraDescription(
name: 'cam',
lensDirection: CameraLensDirection.back,
sensorOrientation: 90),
ResolutionPreset.max);

await cameraController.initialize();
when(CameraPlatform.instance
.getVideoStabilizationSupportedModes(mockInitializeCamera))
.thenAnswer((_) async =>
<VideoStabilizationMode>[VideoStabilizationMode.off]);

// act
final Iterable<VideoStabilizationMode> modes =
await cameraController.getVideoStabilizationSupportedModes();

// assert
expect(modes, <VideoStabilizationMode>[VideoStabilizationMode.off]);
});

test('getVideoStabilizationSupportedModes() returns all modes', () async {
// arrange
final CameraController cameraController = CameraController(
const CameraDescription(
name: 'cam',
lensDirection: CameraLensDirection.back,
sensorOrientation: 90),
ResolutionPreset.max);

await cameraController.initialize();
when(CameraPlatform.instance
.getVideoStabilizationSupportedModes(mockInitializeCamera))
.thenAnswer((_) async => <VideoStabilizationMode>[
VideoStabilizationMode.off,
VideoStabilizationMode.standard,
VideoStabilizationMode.cinematic,
VideoStabilizationMode.cinematicExtended,
]);

// act
final Iterable<VideoStabilizationMode> modes =
await cameraController.getVideoStabilizationSupportedModes();

// assert
expect(modes, <VideoStabilizationMode>[
VideoStabilizationMode.off,
VideoStabilizationMode.standard,
VideoStabilizationMode.cinematic,
VideoStabilizationMode.cinematicExtended,
]);
});

test('setVideoStabilizationMode() calls $CameraPlatform', () async {
final CameraController cameraController = CameraController(
const CameraDescription(
name: 'cam',
lensDirection: CameraLensDirection.back,
sensorOrientation: 90),
ResolutionPreset.max);
await cameraController.initialize();

await cameraController
.setVideoStabilizationMode(VideoStabilizationMode.off);

verify(CameraPlatform.instance.setVideoStabilizationMode(
cameraController.cameraId, VideoStabilizationMode.off))
.called(1);
});

test(
'setVideoStabilizationMode() throws $CameraException on $PlatformException',
() async {
final CameraController cameraController = CameraController(
const CameraDescription(
name: 'cam',
lensDirection: CameraLensDirection.back,
sensorOrientation: 90),
ResolutionPreset.max);
await cameraController.initialize();

when(CameraPlatform.instance.setVideoStabilizationMode(
cameraController.cameraId,
VideoStabilizationMode.cinematicExtended))
.thenThrow(
PlatformException(
code: 'TEST_ERROR',
message: 'This is a test error message',
),
);

expect(
cameraController.setVideoStabilizationMode(
VideoStabilizationMode.cinematicExtended),
throwsA(isA<CameraException>().having(
(CameraException error) => error.description,
'TEST_ERROR',
'This is a test error message',
)));
});

test('pausePreview() calls $CameraPlatform', () async {
final CameraController cameraController = CameraController(
const CameraDescription(
Expand Down Expand Up @@ -1602,6 +1727,23 @@ class MockCameraPlatform extends Mock
Invocation.method(#setExposureOffset, <Object?>[cameraId, offset]),
returnValue: Future<double>.value(1.0),
) as Future<double>;

@override
Future<Iterable<VideoStabilizationMode>> getVideoStabilizationSupportedModes(
int cameraId) {
return super.noSuchMethod(
Invocation.method(
#getVideoStabilizationSupportedModes, <Object?>[cameraId]),
returnValue: Future<Iterable<VideoStabilizationMode>>.value(
<VideoStabilizationMode>[]),
) as Future<Iterable<VideoStabilizationMode>>;
}

@override
Future<void> setVideoStabilizationMode(
int cameraId, VideoStabilizationMode mode) async =>
super.noSuchMethod(Invocation.method(
#setVideoStabilizationMode, <Object?>[cameraId, mode]));
}

class MockCameraDescription extends CameraDescription {
Expand Down
8 changes: 7 additions & 1 deletion packages/camera/camera/test/camera_value_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ void main() {
focusPointSupported: true,
previewPauseOrientation: DeviceOrientation.portraitUp,
description: FakeController.fakeDescription,
videoStabilizationMode: VideoStabilizationMode.cinematic,
);

expect(cameraValue, isA<CameraValue>());
Expand All @@ -47,6 +48,8 @@ void main() {
expect(cameraValue.recordingOrientation, DeviceOrientation.portraitUp);
expect(cameraValue.isPreviewPaused, false);
expect(cameraValue.previewPauseOrientation, DeviceOrientation.portraitUp);
expect(
cameraValue.videoStabilizationMode, VideoStabilizationMode.cinematic);
});

test('Can be created as uninitialized', () {
Expand All @@ -70,6 +73,7 @@ void main() {
expect(cameraValue.recordingOrientation, null);
expect(cameraValue.isPreviewPaused, isFalse);
expect(cameraValue.previewPauseOrientation, null);
expect(cameraValue.videoStabilizationMode, VideoStabilizationMode.off);
});

test('Can be copied with isInitialized', () {
Expand All @@ -94,6 +98,7 @@ void main() {
expect(cameraValue.recordingOrientation, null);
expect(cameraValue.isPreviewPaused, isFalse);
expect(cameraValue.previewPauseOrientation, null);
expect(cameraValue.videoStabilizationMode, VideoStabilizationMode.off);
});

test('Has aspectRatio after setting size', () {
Expand Down Expand Up @@ -144,10 +149,11 @@ void main() {
isPreviewPaused: true,
previewPauseOrientation: DeviceOrientation.portraitUp,
description: FakeController.fakeDescription,
videoStabilizationMode: VideoStabilizationMode.cinematicExtended,
);

expect(cameraValue.toString(),
'CameraValue(isRecordingVideo: false, isInitialized: false, errorDescription: null, previewSize: Size(10.0, 10.0), isStreamingImages: false, flashMode: FlashMode.auto, exposureMode: ExposureMode.auto, focusMode: FocusMode.auto, exposurePointSupported: true, focusPointSupported: true, deviceOrientation: DeviceOrientation.portraitUp, lockedCaptureOrientation: DeviceOrientation.portraitUp, recordingOrientation: DeviceOrientation.portraitUp, isPreviewPaused: true, previewPausedOrientation: DeviceOrientation.portraitUp, description: CameraDescription(, CameraLensDirection.back, 0))');
'CameraValue(isRecordingVideo: false, isInitialized: false, errorDescription: null, previewSize: Size(10.0, 10.0), isStreamingImages: false, flashMode: FlashMode.auto, exposureMode: ExposureMode.auto, focusMode: FocusMode.auto, exposurePointSupported: true, focusPointSupported: true, deviceOrientation: DeviceOrientation.portraitUp, lockedCaptureOrientation: DeviceOrientation.portraitUp, recordingOrientation: DeviceOrientation.portraitUp, isPreviewPaused: true, previewPausedOrientation: DeviceOrientation.portraitUp, videoStabilizationMode: VideoStabilizationMode.cinematicExtended, description: CameraDescription(, CameraLensDirection.back, 0))');
});
});
}
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+9

* Updates camera_platform_interface lib to 2.9.0.

## 0.10.9+8

* Removes unused code related to `maxVideoDuration`.
Expand Down
4 changes: 2 additions & 2 deletions packages/camera/camera_android/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ description: Android implementation of the camera plugin.
repository: https://github.com/flutter/packages/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.9+8
version: 0.10.9+9

environment:
sdk: ^3.4.0
Expand All @@ -19,7 +19,7 @@ flutter:
dartPluginClass: AndroidCamera

dependencies:
camera_platform_interface: ^2.6.0
camera_platform_interface: ^2.9.0
flutter:
sdk: flutter
flutter_plugin_android_lifecycle: ^2.0.2
Expand Down
1 change: 1 addition & 0 deletions packages/camera/camera_android_camerax/AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
# Name/Organization <email address>

Google Inc.
Rui Craveiro <ruicraveiro@squarealfa.com>
4 changes: 4 additions & 0 deletions packages/camera/camera_android_camerax/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.7.0

* Adds video stabilization.

## 0.6.7+1

* Updates README to remove references to `maxVideoDuration`, as it was never
Expand Down
Loading

0 comments on commit 9d75928

Please sign in to comment.