-
Notifications
You must be signed in to change notification settings - Fork 3k
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
[camera_avfoundation] enable more than 30 fps #7394
Conversation
75250e0
to
3f0e949
Compare
@misos1 Could you split this into two different PRs? Having non-trivial changes to two unrelated plugins in the same PR makes review much more difficult. |
|
||
* Adds possibility to use any supported FPS and fixes crash when using unsupported FPS. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
*Adds ability to...
double bestFrameRate = [self frameRateForFormat:bestFormat closestTo:targetFrameRate]; | ||
double minDistance = fabs(bestFrameRate - targetFrameRate); | ||
int bestSubTypeScore = 1; | ||
for (AVCaptureDeviceFormat *format in _captureDevice.formats) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you also move this to a helper function?
@@ -211,20 +211,50 @@ - (instancetype)initWithMediaSettings:(FCPPlatformMediaSettings *)mediaSettings | |||
[_motionManager startAccelerometerUpdates]; | |||
|
|||
if (_mediaSettings.framesPerSecond) { | |||
[_mediaSettingsAVWrapper beginConfigurationForSession:_videoCaptureSession]; | |||
|
|||
// Possible values for presets are hard-coded in FLT interface having |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is all this code moving out of the locked section; is that safe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did not see anything saying that the order of beginConfiguration
and lockForConfiguration
is important or that setting the session preset must be under that lock. There is example where it is used in that order: https://developer.apple.com/documentation/avfoundation/avcapturedevice/1389221-activeformat?language=objc
I moved setCaptureSessionPreset
outside of lock because it can itself call lockForConfiguration
and unlockForConfiguration
and I did not see anything about whether that lock is recursive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems lockForConfiguration
is actually recursive from my observation (I could not get mentioned exceptions until now) as locking twice and unlocking once does not cause exceptions but unlocking twice yes. So I can revert this change (?).
Also it seems not all commitConfiguration
calls go through _mediaSettingsAVWrapper
.
[_captureDevice unlockForConfiguration]; | ||
return nil; | ||
// find the format which frame rate ranges are closest to the wanted frame rate | ||
CMVideoDimensions targetRes = self.videoDimensionsForFormat(_captureDevice.activeFormat); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You've added several calls to self
in this init method. The fact that there was already such a call is a problem, and that should not be made worse by adding more.
If there are general, stateless utilities that you want to call from init, they should be turned into freestanding static functions.
[_videoCaptureSession commitConfiguration]; | ||
[_captureDevice unlockForConfiguration]; | ||
return nil; | ||
// find the format which frame rate ranges are closest to the wanted frame rate |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comments need to follow standard sentence rules. https://google.github.io/styleguide/objcguide.html#comments
[_captureDevice unlockForConfiguration]; | ||
return nil; | ||
// find the format which frame rate ranges are closest to the wanted frame rate | ||
CMVideoDimensions targetRes = self.videoDimensionsForFormat(_captureDevice.activeFormat); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please don't abbreviate variable names. https://google.github.io/styleguide/objcguide.html#naming
FourCharCode subType = CMFormatDescriptionGetMediaSubType(format.formatDescription); | ||
int subTypeScore = subType == preferredSubType ? 1 : 0; | ||
if (distance < minDistance || | ||
(distance == minDistance && subTypeScore > bestSubTypeScore)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused by this condition. bestSubTypeScore
is initialized to 1, and subTypeScore
is always either 0 or 1. How could this last piece of the condition ever be true?
I'm not clear on what this score even is; this whole algorithm needs some explanatory comment at the beginning.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if distance < minDistance
then bestSubTypeScore
can be overwritten by 0. This line explains it: subTypeScore = subType == preferredSubType ? 1 : 0
, the score of the subtype is 1 when it is the preferred subtype and 0 otherwise. This is how to choose by minimum distance and then from formats which have equal distance choose that one with preferred subtype, it gives to subtypes some order. Instead of subTypeScore > bestSubTypeScore
there could be something like bestSubType != preferredSubType && subType == preferredSubType
but that makes it less clear as it deviates from simple way how to order something by multiple properties current.A < other.A || (current.A == other.A && current.B < other.B)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but that makes it less clear
I strongly disagree that abstracting the thing that matters—whether the current type is a preferred type—behind a completely arbitrary numeric value makes this code easier to understand. The code you have there that you described as "less clear" makes sense to me when I read it, whereas I did not understand what this score was, or why something that could only have two possible values was being called a score in the first place.
|
||
* Adds possibility to use any supported FPS and fixes crash when using unsupported FPS. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is the new test that triggers the codepath that used to crash?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the real captureDevice.activeVideoMinFrameDuration
(or ...Max...) is set to value outside of what is supported by active format it immediately throws an objc exception. For tests, mocked wrappers are used so this cannot be triggered. But the test testSettings_ShouldSelectFormatWhichSupports60FPS
tries to set 60 fps which actually triggered this crash on my device for media presets other than 1280x720.
@@ -251,6 +254,55 @@ - (instancetype)initWithMediaSettings:(FCPPlatformMediaSettings *)mediaSettings | |||
return self; | |||
} | |||
|
|||
static void selectBestFormatForRequestedFrameRate( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These helpers should be defined above their call sites; I'm suprised this is even compiling.
@@ -251,6 +254,55 @@ - (instancetype)initWithMediaSettings:(FCPPlatformMediaSettings *)mediaSettings | |||
return self; | |||
} | |||
|
|||
static void selectBestFormatForRequestedFrameRate( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per the Obj-C style guide, all declarations require declaration comments explaining them.
In this case, that should include covering what "select" means exactly, given that this doesn't return anything. Presumably it's modifying one or more arguments, but I can't tell without reading the implementation.
mediaSettings.framesPerSecond = @(bestFrameRate); | ||
} | ||
|
||
static double bestFrameRateForFormat(AVCaptureDeviceFormat *format, double targetFrameRate) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This also needs a comment (e.g., explaining what "best" means).
...ages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCam.m
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor nits, but otherwise the code LGTM, thanks!
I'm not very familiar with the camera APIs, so I'll defer to @hellohuanlin to review the behavior itself.
|
||
// Finds format with same resolution as current activeFormat for which bestFrameRateForFormat | ||
// returned frame rate closest to mediaSettings.framesPerSecond. Preferred are formats with the | ||
// same subtype as current activeFormat. Sets this format as activeFormat and also updates |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about "Sets this format as the activeFormat in captureDevice and [...]"; I wasn't sure what activeFormat
was referring to without reading the implementation.
isBestSubTypePreferred = isSubTypePreferred; | ||
} | ||
} | ||
if (![bestFormat isEqual:captureDevice.activeFormat]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there evidence in docs or actual testing that this check is necessary? Standard convention for setters in Obj-C is for them to explicitly handle set-to-same-value as a no-op, so I would expect that we could just unconditionally call the setter and get exactly the same behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will need to check this, I think it changes sessionPreset
to AVCaptureSessionPresetInputPriority
even if format is the same as it was (if I remember correctly), please wait with merging until I can get into this and other comments left.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes it changes sessionPreset
so the idea is to not change it if the format does not need any change. Although maybe it actually should be changed to prevent the session from changing to an active format which no longer supports fps set by the client: "When a client sets the session preset to anything other than AVCaptureSessionPresetInputPriority, the session resumes responsibility for configuring inputs and outputs, and is free to change its inputs' activeFormat as needed."
@@ -537,23 +580,32 @@ - (BOOL)setCaptureSessionPreset:(FCPPlatformResolutionPreset)resolutionPreset | |||
return NO; | |||
} | |||
} | |||
CMVideoDimensions size = self.videoDimensionsForFormat(_captureDevice.activeFormat); | |||
_previewSize = CGSizeMake(size.width, size.height); | |||
_audioCaptureSession.sessionPreset = _videoCaptureSession.sessionPreset; | |||
return YES; | |||
} | |||
|
|||
/// Finds the highest available resolution in terms of pixel count for the given device. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: "... if same pixel count, use the subtype as the tie breaker."
Sorry, this slipped through the cracks on getting landed. (@hellohuanlin Please make sure to add the |
flutter/packages@7219431...bb5a258 2024-11-06 [email protected] [ci] Upload screenshots, logs, and Xcode test results for drive and integration_test runs (flutter/packages#7430) 2024-11-05 [email protected] Remove use_modular_headers! from Podfiles (flutter/packages#7796) 2024-11-05 [email protected] [camera_avfoundation] enable more than 30 fps (flutter/packages#7394) 2024-11-05 [email protected] Roll Flutter from 8591d0c to 29d40f7 (25 revisions) (flutter/packages#8027) 2024-11-05 [email protected] [ci] Add vector_graphics and flutter_svg to autolabeler (flutter/packages#8025) 2024-11-05 [email protected] [vector_graphics_compiler] wasm compatibility (flutter/packages#8021) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages-flutter-autoroll Please CC [email protected] on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
* main: (1187 commits) [various] Update example app minSdkVersions (flutter#8035) [go_router] Activate leak testing (flutter#7546) [in_app_purchase_storekit] Add restore purchases and receipts (flutter#7964) [interactive_media_ads] Adds remaining methods for internal wrapper of the Android native `BaseManager` (flutter#7943) [google_sign_in/google_identity_services] Clear-up documentation of callbacks in various APIs and uses of those APIs (flutter#8029) [flutter_svg] wasm compatibility (flutter#8014) Applied Gradle Plugins Declaratively for Multiple Plugin Example Apps (Part 2) (flutter#8019) Roll Flutter from 29d40f7f6826 to 73546b3b71a7 (20 revisions) (flutter#8028) [ci] Upload screenshots, logs, and Xcode test results for drive and integration_test runs (flutter#7430) Remove use_modular_headers! from Podfiles (flutter#7796) [camera_avfoundation] enable more than 30 fps (flutter#7394) Roll Flutter from 8591d0c16a6c to 29d40f7f6826 (25 revisions) (flutter#8027) [ci] Add vector_graphics and flutter_svg to autolabeler (flutter#8025) [vector_graphics_compiler] wasm compatibility (flutter#8021) [vector_graphics*] Relax dependency constraints of vector_graphics, vector_graphics_codec, vector_graphics_compiler, flutter_svg (flutter#8018) [various] Add `missing_code_block_language_in_doc_comment` lint to flutter/packages. (flutter#6473) [various] Update example apps to Kotlin 1.9.0 (flutter#7998) [go_router] add current state getter (flutter#7651) Applied Gradle Plugins Declaratively for Multiple Plugin Example Apps (flutter#7968) Roll Flutter from f86b77721524 to 8591d0c16a6c (16 revisions) (flutter#8015) ... # Conflicts: # packages/quick_actions/quick_actions/CHANGELOG.md # packages/quick_actions/quick_actions_ios/CHANGELOG.md # packages/quick_actions/quick_actions_platform_interface/CHANGELOG.md
Camera plugin was crashing when I tried to set fps to 60 on most media presets (except maybe on 1280x720, although tested device supports 60 fps for up to 1920x1440 and can do 240 fps on 1280x720) because when is
activeVideoMinFrameDuration
andactiveVideoMaxFrameDuration
set to fps outside of what active format supports it throws exception. Now it tries to find a format which supports fps closest to wanted fps and clamps it if it cannot be set to exact value to prevent crashes. It searches for formats with the exact same resolution. For example in format list it can be like "1920x1080 { 3- 30 fps}", "1920x1080 { 3- 60 fps}" and "1920x1080 { 6-120 fps}, but when settingsessionPreset
then "1920x1080 { 3- 30 fps}" is selected by default. On the tested device there are 2 "media subtypes"420f
and420v
for each format where the first is denoted as "supports wide color" in debug description and the system has tendency to choose this one. So it tries to preserve the media subtype to what is preferred by the system and this is also added tohighestResolutionFormatForCaptureDevice
(with lower priority than max resolution/fps). Also there was nestedlockForConfiguration
andunlockForConfiguration
when usingResolutionPreset.max
together with setting up fps.Pre-launch Checklist
dart format
.)[shared_preferences]
pubspec.yaml
with an appropriate new version according to the pub versioning philosophy, or this PR is exempt from version changes.CHANGELOG.md
to add a description of the change, following repository CHANGELOG style, or this PR is exempt from CHANGELOG changes.///
).If you need help, consider asking for advice on the #hackers-new channel on Discord.