Skip to content

[camera] add video stabilization #7108

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 55 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
9bdf58e
Adds video stabilization to camera
ruicraveiro Jul 16, 2024
39e2adf
do not merge! dependency overrides
ruicraveiro Jul 16, 2024
edf33b0
Update camera_info.dart
ruicraveiro Jul 16, 2024
741e032
Moved getAvCaptureVideoStabilizationMode from FLTCam to CameraProperties
ruicraveiro Jul 16, 2024
738ba2d
Merge branch 'main' into vs_squashed
ruicraveiro Jul 17, 2024
b404388
Merge branch 'main' into vs_squashed
ruicraveiro Jul 18, 2024
c900fa4
Merge branch 'main' into vs_squashed
ruicraveiro Jul 18, 2024
e1f7cb8
Merge branch 'main' into vs_squashed
ruicraveiro Jul 19, 2024
d220078
Refactored Camera2CameraInfoHostApiImpl with single @OptIn annotation
ruicraveiro Jul 22, 2024
887c565
Moved Android video stabilization mapping to Camera2CameraInfo
ruicraveiro Jul 22, 2024
8d986ed
fixed android camerax tests
ruicraveiro Jul 22, 2024
8040dc6
Merge branch 'main' into vs_squashed
ruicraveiro Jul 22, 2024
dba6738
Merge branch 'main' into vs_squashed
ruicraveiro Jul 23, 2024
f027dfc
Merge branch 'main' into vs_squashed
ruicraveiro Jul 24, 2024
b85a0be
Merge branch 'main' into vs_squashed
ruicraveiro Jul 24, 2024
291a740
Merge branch 'main' into vs_squashed
ruicraveiro Jul 25, 2024
1d06d3d
Merge branch 'main' into vs_squashed
ruicraveiro Jul 27, 2024
680097f
Better naming for local variable in Camera2CameraInfoHostApi.getAvail…
ruicraveiro Jul 29, 2024
a8e6544
video stabilization mapping back to android_camera_camerax
ruicraveiro Jul 29, 2024
754c577
Merge branch 'main' into vs_squashed
ruicraveiro Jul 30, 2024
55a4da8
Merge branch 'main' into vs_squashed
ruicraveiro Jul 30, 2024
06719a5
Merge branch 'main' into vs_squashed
ruicraveiro Aug 1, 2024
15d918c
Merge branch 'main' into vs_squashed
ruicraveiro Aug 2, 2024
69eb909
Merge branch 'vs_squashed' of github.com:ruicraveiro/packages into vs…
ruicraveiro Aug 5, 2024
672d834
Merge branch 'main' into vs_squashed
ruicraveiro Aug 5, 2024
1cef9b3
Merge branch 'main' into vs_squashed
ruicraveiro Aug 6, 2024
e6b483f
Update pubspec.yaml
ruicraveiro Aug 6, 2024
45fa745
Update packages/camera/camera_android_camerax/lib/src/android_camera_…
ruicraveiro Aug 6, 2024
ddc370c
Merge branch 'main' into vs_squashed
ruicraveiro Aug 7, 2024
1e4885c
Merge branch 'main' into vs_squashed
ruicraveiro Aug 17, 2024
183088d
Merge branch 'main' into vs_squashed
ruicraveiro Aug 21, 2024
c333768
video stabilizaton methods throw unimplemented
ruicraveiro Aug 21, 2024
94651bc
Using enhanced enum features with VideoStabilizationMode
ruicraveiro Aug 21, 2024
1ec3f54
Removed $ in name of test
ruicraveiro Aug 21, 2024
a9366c2
Merge branch 'main' into vs_squashed
ruicraveiro Aug 23, 2024
81afd32
Merge branch 'main' into vs_squashed
ruicraveiro Aug 26, 2024
99e604f
Merge branch 'main' into vs_squashed
ruicraveiro Aug 27, 2024
a816c5b
Merge branch 'main' into vs_squashed
ruicraveiro Aug 27, 2024
94fd363
Merge branch 'main' into vs_squashed
ruicraveiro Aug 28, 2024
76db0eb
Merge branch 'main' into vs_squashed
ruicraveiro Aug 29, 2024
3c4252b
Merge branch 'main' into vs_squashed
ruicraveiro Aug 30, 2024
c99c727
Added message when throwing UnimplementedError
ruicraveiro Aug 30, 2024
a77bea1
minor improvements to CameraController method comments
ruicraveiro Aug 30, 2024
5bb8379
Restructured video stabilization levels
ruicraveiro Sep 11, 2024
4f8b587
Merge branch 'main' into vs_squashed
ruicraveiro Sep 12, 2024
4fe99bb
Adjusted camera/camera unit tests for new VideoStabilizationMode enum…
ruicraveiro Sep 12, 2024
bf6c7d9
Merge branch 'main' into vs_squashed
ruicraveiro Sep 13, 2024
39bf897
Removed platform-specific comments
ruicraveiro Sep 13, 2024
539c65e
Merge remote-tracking branch 'official/main' into vs_squashed2
ruicraveiro Mar 11, 2025
f60a5ff
renamed getVideoStabilizationSupportedModes to getSupportedVideoStabi…
ruicraveiro Mar 12, 2025
6689162
invalid video stabilization modes throw ArgumentError
ruicraveiro Mar 12, 2025
068141d
fixed ios nits
ruicraveiro Mar 12, 2025
75382f6
Video stabilization fallback
ruicraveiro Mar 12, 2025
0317cce
removed vestigial comments
ruicraveiro Mar 13, 2025
d71672f
Merge branch 'main' into vs_squashed
ruicraveiro Mar 14, 2025
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
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 <[email protected]>
Anton Borries <[email protected]>
Alex Li <[email protected]>
Rahul Raj <[email protected]>
Rui Craveiro <[email protected]>
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.1

* Adds API support query for image streaming.
Expand Down
5 changes: 5 additions & 0 deletions packages/camera/camera/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,8 @@ dev_dependencies:

flutter:
uses-material-design: true

# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE.
# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins
dependency_overrides:
{camera: {path: ../../../camera/camera}, camera_android_camerax: {path: ../../../camera/camera_android_camerax}, camera_avfoundation: {path: ../../../camera/camera_avfoundation}, camera_platform_interface: {path: ../../../camera/camera_platform_interface}, camera_web: {path: ../../../camera/camera_web}}
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
83 changes: 83 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 @@ -687,6 +696,80 @@ class CameraController extends ValueNotifier<CameraValue> {
}
}

/// Set the video stabilization mode for the selected camera.
///
/// On Android (when using camera_android_camerax) and on iOS
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment should not be calling out specific platforms; the restriction should have the same behavior on all platforms.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're right and the comment is only creating confusion. Will remove it.

/// the supplied [mode] value should be a mode in the list returned
/// by [getSupportedVideoStabilizationModes].
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only true for allowFallback=false.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will also remove it, especially since after that comment, I document both allowFallback cases.

///
/// When [allowFallback] is true (default) and when
/// the camera supports any video stabilization other than
/// [VideoStabilizationMode.off], then the camera will
/// be set to the best video stabilization mode up to, and including, [mode].
///
/// When either [allowFallback] is false or the only
/// supported video stabilization mode is [VideoStabilizationMode.off],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes it sound like getSupportedVideoStabilizationModes can return off, which I believe we agreed should not happen. If it can't, then we can avoid confusion by saying "... or getSupportedVideoStabilizationModes() returns an empty list".

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Three separate cases:

  • When the device has no camera stabilization modes. I floated the idea that we could return off, but I agreed with you not to.

  • When the camera does support some video stabilization mode other than off. Off was always part of the available options, so much so that is one of the options in both tables in my latest specification proposal, in the comment I made on the 20th December. Furthermore, it is one of the options returned by both Android and iOS.

  • When the only option is 'off'. This is the scenario represented by the third column in those specification tables. Basically what it means is that even if allowFallback is true, if the only available mode is off and something else is specified, then it is an error.

Here's the table again:

requested mode no supported mode ([]) only off supported max level1 supported max level2 supported max level3 supported
off throw ArgumentError() off off off off
level1 throw ArgumentError() throw ArgumentError() level1 level1 level1
level2 throw ArgumentError() throw ArgumentError() level1 level2 level2
level3 throw ArgumentError() throw ArgumentError() level1 level2 level 3

Let me know if you still have a different understanding. For me it is very clear that if level1, 2 or 3 are available, there is no reason to exclude off. It is also very clear, and I agreed with you, that we shouldn't introduce off artificially if the device doesn't return any available mode. What is really hard to define is the behaviour of when off is the only mode, even more so because I think that this is a theoretical discussion as I don't expect the case of where the platform returns only off to ever happen. None of the devices I tested showed that behaviour, as they would either report no supported stabilization mode or at least 2 supported modes, one of them always being off.

So, maybe, because it isn't really something expected to happen, I can simplify the comment and ignore that case. Let me know what you think.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Three separate cases:

  • When the device has no camera stabilization modes. [...]
    [...]
  • When the only option is 'off'. [...]

I'm not following. How are these separate cases? What is the conceptual difference here?

/// and if [mode] is not one of the supported modes,
/// then it throws an [ArgumentError].
Future<void> setVideoStabilizationMode(
VideoStabilizationMode mode, {
bool allowFallback = true,
}) async {
_throwIfNotInitialized('setVideoStabilizationMode');
try {
final VideoStabilizationMode requestMode =
allowFallback ? await _getVideoStabilizationFallbackMode(mode) : mode;

await CameraPlatform.instance
.setVideoStabilizationMode(_cameraId, requestMode);
value = value.copyWith(videoStabilizationMode: mode);
} on PlatformException catch (e) {
throw CameraException(e.code, e.message);
}
}

Future<VideoStabilizationMode> _getVideoStabilizationFallbackMode(
VideoStabilizationMode mode) async {
final Iterable<VideoStabilizationMode> supportedModes = await CameraPlatform
.instance
.getSupportedVideoStabilizationModes(_cameraId);

// if there are no supported modes or if the only supported mode is Off
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: capitalize as a sentence.

// and something else is requested, then we throw an ArgumentError.
if (supportedModes.isEmpty ||
(mode != VideoStabilizationMode.off &&
supportedModes.every((VideoStabilizationMode sm) =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: mode

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are you referring to? The comment ... and something else ... (where it would be some other mode)? Is it the 'sm' variable, which stands for 'supportedMode'? Or something else?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, the variable sm.

sm == VideoStabilizationMode.off))) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be impossible?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This goes back to my previous comment. As I understand it, no it shouldn't be impossible, but I never expect it to actually happen.

throw ArgumentError('Unavailable video stabilization mode.', 'mode');
}

VideoStabilizationMode requestMode = VideoStabilizationMode.off;
for (final VideoStabilizationMode supportedMode in supportedModes) {
if (supportedMode.index <= mode.index &&
supportedMode.index >= requestMode.index) {
requestMode = supportedMode;
}
}

return requestMode;
}

/// 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. Throws an [UnimplementedError] on all other platforms.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should not throw on other platforms, it should return an empty list. See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#api-support-queries

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I'll make that change.

Future<Iterable<VideoStabilizationMode>>
getSupportedVideoStabilizationModes() {
_throwIfNotInitialized('isVideoStabilizationModeSupported');
try {
return CameraPlatform.instance
.getSupportedVideoStabilizationModes(_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
13 changes: 9 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.1
version: 0.12.0

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

dependencies:
camera_android_camerax: ^0.6.13
camera_avfoundation: ^0.9.18
camera_platform_interface: ^2.9.0
camera_android_camerax: ^0.7.0
camera_avfoundation: ^0.10.0
camera_platform_interface: ^2.10.0
camera_web: ^0.3.3
flutter:
sdk: flutter
Expand All @@ -38,3 +38,8 @@ dev_dependencies:

topics:
- camera

# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE.
# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins
dependency_overrides:
{camera_android_camerax: {path: ../../camera/camera_android_camerax}, camera_avfoundation: {path: ../../camera/camera_avfoundation}, camera_platform_interface: {path: ../../camera/camera_platform_interface}, camera_web: {path: ../../camera/camera_web}}
10 changes: 10 additions & 0 deletions packages/camera/camera/test/camera_preview_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,16 @@ class FakeController extends ValueNotifier<CameraValue>
@override
CameraDescription get description => value.description;

@override
Future<void> setVideoStabilizationMode(
VideoStabilizationMode mode, {
bool allowFallback = true,
}) async {}

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

@override
bool supportsImageStreaming() => true;
}
Expand Down
Loading