Skip to content
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

Merged
merged 14 commits into from
Nov 5, 2024

Conversation

misos1
Copy link
Contributor

@misos1 misos1 commented Aug 12, 2024

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 and activeVideoMaxFrameDuration 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 setting sessionPreset then "1920x1080 { 3- 30 fps}" is selected by default. On the tested device there are 2 "media subtypes" 420f and 420v 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 to highestResolutionFormatForCaptureDevice (with lower priority than max resolution/fps). Also there was nested lockForConfiguration and unlockForConfiguration when using ResolutionPreset.max together with setting up fps.

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@misos1 misos1 marked this pull request as ready for review August 14, 2024 18:46
@misos1 misos1 requested a review from hellohuanlin as a code owner August 14, 2024 18:46
@stuartmorgan
Copy link
Contributor

@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.

@misos1 misos1 changed the title [video_player_avfoundation, camera_avfoundation] enable more than 30 fps [camera_avfoundation] enable more than 30 fps Aug 21, 2024

* Adds possibility to use any supported FPS and fixes crash when using unsupported FPS.
Copy link
Contributor

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) {
Copy link
Contributor

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
Copy link
Contributor

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?

Copy link
Contributor Author

@misos1 misos1 Sep 4, 2024

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.

Copy link
Contributor Author

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);
Copy link
Contributor

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
Copy link
Contributor

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);
Copy link
Contributor

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)) {
Copy link
Contributor

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.

Copy link
Contributor Author

@misos1 misos1 Aug 30, 2024

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).

Copy link
Contributor

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.
Copy link
Contributor

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?

Copy link
Contributor Author

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(
Copy link
Contributor

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(
Copy link
Contributor

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) {
Copy link
Contributor

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).

@misos1 misos1 requested a review from stuartmorgan September 19, 2024 18:04
Copy link
Contributor

@stuartmorgan stuartmorgan left a 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
Copy link
Contributor

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]) {
Copy link
Contributor

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.

Copy link
Contributor Author

@misos1 misos1 Oct 1, 2024

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.

Copy link
Contributor Author

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.
Copy link
Contributor

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."

@stuartmorgan stuartmorgan added the autosubmit Merge PR when tree becomes green via auto submit App label Nov 5, 2024
@stuartmorgan
Copy link
Contributor

Sorry, this slipped through the cracks on getting landed.

(@hellohuanlin Please make sure to add the autosubmit label when doing the last review on a non-member PR.)

@auto-submit auto-submit bot merged commit 719bd84 into flutter:main Nov 5, 2024
76 checks passed
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Nov 6, 2024
auto-submit bot pushed a commit to flutter/flutter that referenced this pull request Nov 6, 2024
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
sinyu1012 added a commit to sinyu1012/packages that referenced this pull request Nov 8, 2024
* 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
autosubmit Merge PR when tree becomes green via auto submit App p: camera platform-ios platform-macos
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants