-
Notifications
You must be signed in to change notification settings - Fork 9.7k
[Camera] Improved resolution presets and matched quality between Android and iOS #1403
[Camera] Improved resolution presets and matched quality between Android and iOS #1403
Conversation
EDIT: I updated clang-format and now the formating is working fine. The format test are failing but I ran flutter_plugin_tools (and it did the changes that are now failing).
|
|
||
int cameraId = Integer.parseInt(cameraName); | ||
switch (camcorderProfileIndex) { | ||
case 0: |
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.
Wouldn't it be better if the user selected veryHigh but QUALITY_2160P isn't available on the device that it uses the next best solution?
I think right now it would fall back to low quality if I read that correctly.
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.
That's what is actually happening. This bit above is just to convert the preset from a string to an int. having anything else than one of the preset value here in an error. It should never happen since on the dart side creating the CameraController
require a ResolutionPreset
object, so it's impossible for users to make a mistake here. But people updating/improving the plugin could so it's cleaner to take it into account.
The bit under is actually trying to use the requested preset and reducing if the preset is not available on the 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.
Another precision, I did the same thing as what was already on the iOS side for the second switch (which is pretty smart BTW). The switch doesn't break or return unless the profile is available, which means it will try all the presets under the requested one if it's not available.
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.
It also took me a couple read throughs and this comment chain to understand the behavior of the two case statements one after the other. Just restating in my own words to make sure I'm understanding right.
- The first case on line 437 verifies that
resolutionPreset
is a valid argument, since this is just a random string from dart and could be something we're not expecting. It checks against the expected magic string names and throws if it doesn't match any of our magic constants. It translates the expected string names into ints ordered by preset quality to use in the second case statement below. - The second case statement on line 458 deliberately uses the random ints from above and fallthroughs to get the highest available
CamcorderProfile
possible for a given string preset. It always falls through toQUALITY_LOW
and will only throw if we're in some kind of actually exceptional state if there's no camera capture at all.
This logic is solid. But I think the combination of magic strings/numbers and silent case fall through behavior made this tricky to read. I have some subjective suggestions to try and make this easier to understand for anyone else going through the code later and not seeing this comment chain:
- I think it would help to create a
ResolutionPreset
enum and extract the first case into a helper method that takes theString resolutionPreset
and returns an enum, throwing if it doesn't work. That'll help in two ways. It'll make it immediately obvious what's happening where the first case is because it will just collapse the case into the lineResolutionPreset resolutionPreset = getResolutionPresetFromString(resolutionPreset)
, and the helper method itself will have obvious intent from the name. Then in the second switch below, the case statements will all be on named enum constants instead of random numbers. (Could also create an enum class with a factory constructor, but that's probably overkill here.) Also protects against any bugs from forgetting to cover all the string cases in the second switch. - I would add a comment to the second case explaining that it's deliberately falling through on all of these cases to try resolutions in order of descending quality, just to really point out that there's not a break here like normal.
f47e1de
to
dcac87d
Compare
Hi @mklim @bparrishMines, The automated CI tests seems to be broken (the error message suggest a server issue). But the code passes all the tests when I run
I think we can proceed with the review and I'll push a changeless version later to trigger the test again. |
Hi @mklim @bparrishMines , |
Is there any ETA for this to be merged? Video quality on Android is VERY bad |
I'll drop this here while we wait for the PR review, it's a fix for the camera plugin and video plugin recording quality and rotation issues. For people interested in a fix have a look at the discussion on this issue: flutter/flutter#29951 Copy-paste, for more fixes (like issues with aspect ratio) check the previous link:
I created a branch with those 4 fixes merged together, I suggest you try this branch first and see if it works on your side: You can use it by adding this in your pubspec.yaml:
|
@quentinleguennec using your branch fixes most of my problems on Android, in particular for the video quality. |
Hi @danyf90
|
Ciao @quentinleguennec , In case you want/need to test on your side, code is available here. Thanks for your help |
Hi @danyf90 |
Thank you @quentinleguennec , There have a issue when I take and upload a photo ( portrait mode) to the server. It will be rotated 90 degree counter-clockwise (landscape mode). I tried to take photo with the default app in iPhone 6 plus and upload to server. Nothing changes abnormally. Best. |
Please merge this ! Really useful :) |
I don't know if this helps anyone, but I merged in my fork @quentinleguennec branch and master of this repo. Now I can have a better video quality and the fixes in here. |
This camera ratio is too bad!!! |
Thanks for the contribution! This PR isn't trivial to review and we've begun refactoring the camera plugin as a whole, so I'm labeling it with "backlog". We will prioritize creating a temporary hotfix with this PR according to the issue's priority. Relevant issues: |
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.
Thanks for the solid contribution and the patience while we review this. This PR is fixing a high priority issue so I've moved it out of the backlog and reviewed it. The basic approach looks good, I have some feedback before merging. If you don't have time to keep working on this PR, let us know and one of us will be happy to shepherd it forward from here. Thanks again for the fix!
Nothing in this patch looks like it would effect orientation. I suspect that the rotation bug reported in earlier comments here have to do with pre-existing issues like flutter/flutter#35334.
In general it's unfortunate that we can't really test this. I'm manually testing this to verify for now. Testability will be great to have as part of the upcoming camera refactor.
nit: This needs to have merge conflicts fixed with the latest master, pubpec.yaml, etc.
* Added 2 new quality presets (veryHigh and veryLow). | ||
* Now quality presets match on Android and iOS | ||
* Now quality presets can be used to control image capture quality. | ||
** NOTE: ** Existing presets have been updated, this will affect the quality of pictures and videos in existing apps. |
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'd prefer to keep the existing behavior the same, if possible, just to avoid having to create breaking changes. We can introduce breaking changes if needed but I don't think it's really worth it here.
It reads a little silly, but I think I'd prefer that we just add more variations of "high" to keep the existing presets the same.
- "ultra high" = 2160 (or maybe QUALITY_HIGH? discussed below)
- "very high" = 1080
- "high" = 720
- "medium" = 480
- "low" = 240
- "very low" = 176 (QCIF)
What do you think?
if (goodEnough.isEmpty()) { | ||
previewSize = sizes[0]; | ||
videoSize = sizes[0]; | ||
// The preview should never be bigger than 720p (1280 x 720) or it will mess up the recording. |
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.
Do you know how this happens or why this is? I'm wondering if this constant is true for all devices, or if less powerful ones are sensitive to even smaller max preview sizes.
// The preview should never be bigger than 720p (1280 x 720) or it will mess up the recording. | ||
private void computeBestPreviewSize(CamcorderProfile profile) { | ||
float ratio = (float) profile.videoFrameWidth / profile.videoFrameHeight; | ||
if (profile.videoFrameWidth > 1280) { |
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.
readability nit: would be nice to have 1280
extracted out into a named static constant instead of used as a magic number throughout here.
CamcorderProfile profile, | ||
String resolutionPreset) { | ||
// For still image captures, use the largest image size if resolutionPreset is veryHigh | ||
if ("veryHigh".equals(resolutionPreset)) { |
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.
Mentioned below, but I think this would be another reason to have the "veryHigh"
correspond with QUALITY_HIGH
. That's already the highest resolution available, so we wouldn't need to try to parse the argument and get the capture size again here.
|
||
int cameraId = Integer.parseInt(cameraName); | ||
switch (camcorderProfileIndex) { | ||
case 0: |
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.
It also took me a couple read throughs and this comment chain to understand the behavior of the two case statements one after the other. Just restating in my own words to make sure I'm understanding right.
- The first case on line 437 verifies that
resolutionPreset
is a valid argument, since this is just a random string from dart and could be something we're not expecting. It checks against the expected magic string names and throws if it doesn't match any of our magic constants. It translates the expected string names into ints ordered by preset quality to use in the second case statement below. - The second case statement on line 458 deliberately uses the random ints from above and fallthroughs to get the highest available
CamcorderProfile
possible for a given string preset. It always falls through toQUALITY_LOW
and will only throw if we're in some kind of actually exceptional state if there's no camera capture at all.
This logic is solid. But I think the combination of magic strings/numbers and silent case fall through behavior made this tricky to read. I have some subjective suggestions to try and make this easier to understand for anyone else going through the code later and not seeing this comment chain:
- I think it would help to create a
ResolutionPreset
enum and extract the first case into a helper method that takes theString resolutionPreset
and returns an enum, throwing if it doesn't work. That'll help in two ways. It'll make it immediately obvious what's happening where the first case is because it will just collapse the case into the lineResolutionPreset resolutionPreset = getResolutionPresetFromString(resolutionPreset)
, and the helper method itself will have obvious intent from the name. Then in the second switch below, the case statements will all be on named enum constants instead of random numbers. (Could also create an enum class with a factory constructor, but that's probably overkill here.) Also protects against any bugs from forgetting to cover all the string cases in the second switch. - I would add a comment to the second case explaining that it's deliberately falling through on all of these cases to try resolutions in order of descending quality, just to really point out that there's not a break here like normal.
int cameraId = Integer.parseInt(cameraName); | ||
switch (camcorderProfileIndex) { | ||
case 0: | ||
if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_2160P)) { |
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.
Any reason not to use QUALITY_HIGH
here? That's the "highest available" resolution, like how the fallthrough is QUALITY_LOW
.
@@ -203,7 +203,7 @@ class _CameraExampleHomeState extends State<CameraExampleHome> { | |||
if (controller != null) { | |||
await controller.dispose(); | |||
} | |||
controller = CameraController(cameraDescription, ResolutionPreset.high); | |||
controller = CameraController(cameraDescription, ResolutionPreset.medium); |
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: Would like to revert this assuming the existing preset names are preserved like I mentioned above.
@@ -216,7 +221,9 @@ - (void)stop { | |||
|
|||
- (void)captureToFile:(NSString *)path result:(FlutterResult)result { | |||
AVCapturePhotoSettings *settings = [AVCapturePhotoSettings photoSettings]; | |||
[settings setHighResolutionPhotoEnabled:YES]; | |||
if ([_resolutionPreset isEqualToString:@"veryHigh"]) { | |||
[settings setHighResolutionPhotoEnabled:YES]; |
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 this universally only needed for AVCaptureSessionPreset3840x2160
? I'm having trouble finding what the objective cutoff is for this in the developer docs. They mention CMVideoDimensions highResolutionStillImageDimensions
describing its dimensions when this flag is set, but I'm not seeing anywhere to see what the max dimensions without this flag set would be. Probably missing something obvious.
@@ -227,14 +234,25 @@ - (void)captureToFile:(NSString *)path result:(FlutterResult)result { | |||
|
|||
- (void)setCaptureSessionPreset:(NSString *)resolutionPreset { |
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.
(optional) Ah, okay. This is the exact same structure as the two new cases in Java. I think this would also be easier to read if it were split up like I mentioned above, but since this is existing code no pressure to refactor it.
This has been sitting without response for awhile now and we would like to land it, so I've continued the work started here in #1952. I'm happy to continue working on landing my patches in this PR instead if that's easier, but someone with commit rights will need to apply the new changes to this branch. I'm going to close this in favor of the updated PR, let me know if you need anything changed. Thanks again for the contribution, this is extremely helpful. |
Description
This PR is here to address issues in the quality of video recording for the camera plugin on Android, as well as discrepancies in quality level between Android and iOS. It also allows to control the still picture quality (which was so far only maximum quality). The existing resolution presets have been updated to match between Android and iOS, and 2 new presets have been added (veryHigh and veryLow).
There are no breaking changes but the quality of the presets have been adjusted which will result in different picture/video quality on apps using this plugin.
We believe this change is necessary as, for the same resolution preset, Android video quality was very poor and the quality on iOS was to high, generating 4k files that are too heavy to upload.
Related Issues
#1186
#1107
flutter/flutter#30459
flutter/flutter#29500
flutter/flutter#26959
Checklist
///
).flutter analyze
) does not report any problems on my PR.Breaking Change
But: Existing presets have been updated, this will affect the quality of pictures and videos in existing apps.