react-native-vision-camera icon indicating copy to clipboard operation
react-native-vision-camera copied to clipboard

💭 Enabling `video` slows down photo capture on Android

Open jkaufman opened this issue 1 year ago • 29 comments

Question

Photo capture performance drops significantly and results in a preview layer freeze when the video prop is set to true.

Condition Format Filter Photo Video Duration[^1]
0 undefined true false 373.5ms
1 [{ photoResolution: { width: 480, height: 320 } }] true false 389.6ms
2 undefined true true 779.7ms
3 [{ photoResolution: { width: 480, height: 320 } }] true true 823.8ms

[^1]: Averaged across 10 sequential captures.

What I tried

Test app: rnvc3-test.zip

VisionCamera Version

3.8.2

Additional information

jkaufman avatar Jan 25 '24 19:01 jkaufman

Can you give a bit more insights, did you test this on Android or iOS? Which Android phone did you test this on? Is this also reproduceable on iOS?

mrousavy avatar Jan 26 '24 08:01 mrousavy

Ah, I have an idea! - can you try something please?

https://github.com/mrousavy/react-native-vision-camera/blob/7e2889cf84c0346fda8589ae69d56706bad85efd/package/android/src/main/java/com/mrousavy/camera/core/CameraSession.kt#L579

After this line, add:

captureSession.stopRepeating()

This will break the preview, but I can fix that later if this works.

Try to see if that makes things faster :)

mrousavy avatar Jan 26 '24 09:01 mrousavy

Confirmed. That brings Condition 2 in the table down to 218.5ms. All testing performed on a Pixel 3a.

jkaufman avatar Jan 26 '24 20:01 jkaufman

Perfect, then I know how to speed that up! I'll leave this open for now until I implement it.

mrousavy avatar Jan 29 '24 10:01 mrousavy

Heyo, I just tested this on my end on my Huawei P10:

Without the stopRepeating() before capture, so just VisionCamera main codebase as it is today:

14:09:55.089 CameraSession            I  CAPTURE() took 265ms!
14:09:57.138 CameraSession            I  CAPTURE() took 232ms!
14:09:59.106 CameraSession            I  CAPTURE() took 249ms!
14:10:01.044 CameraSession            I  CAPTURE() took 241ms!
14:10:02.768 CameraSession            I  CAPTURE() took 241ms!
14:10:04.496 CameraSession            I  CAPTURE() took 225ms!
14:10:06.293 CameraSession            I  CAPTURE() took 263ms!
14:10:12.641 CameraSession            I  CAPTURE() took 254ms!

With the stopRepeating() added before the capture, so with the patch I suggested:

14:09:08.248 CameraSession            I  CAPTURE() took 260ms!
14:09:11.473 CameraSession            I  CAPTURE() took 246ms!
14:09:13.598 CameraSession            I  CAPTURE() took 250ms!
14:09:15.459 CameraSession            I  CAPTURE() took 233ms!
14:09:17.609 CameraSession            I  CAPTURE() took 254ms!
14:09:19.516 CameraSession            I  CAPTURE() took 234ms!
14:09:21.597 CameraSession            I  CAPTURE() took 246ms!

There doesn't seem to be a difference here at all on my end.

Can you absolutely confirm that this changes the capture speed for you?

mrousavy avatar Feb 06 '24 13:02 mrousavy

Created a PR for this based off of current main, test if you can: https://github.com/mrousavy/react-native-vision-camera/pull/2517

mrousavy avatar Feb 06 '24 13:02 mrousavy

Thanks. I don't have the Pixel 3a, right now, but will test once I do.

jkaufman avatar Feb 12 '24 18:02 jkaufman

Still working to reproduce. I encounter an Expo plugin error attempting to install from the test branch.

jkaufman avatar Feb 15 '24 22:02 jkaufman

app.plugin.js appears to point to a missing lib directory

jkaufman avatar Feb 15 '24 22:02 jkaufman

Run yarn.

mrousavy avatar Feb 16 '24 08:02 mrousavy

Hey - did you try the PR? Would be great if you could check if that makes any difference in 3.9.0.

mrousavy avatar Feb 19 '24 12:02 mrousavy

Yes, thanks for checking in. I encountered some unrelated difficulties running locally, but got it working just now. Looks good to me.

Condition Format Filter Photo Video Duration
0 undefined true false 247.2ms
1 [{ photoResolution: { width: 480, height: 320 } }] true false 170.8ms
2 undefined true true 264.4ms
3 [{ photoResolution: { width: 480, height: 320 } }] true true 227.6ms

jkaufman avatar Feb 19 '24 17:02 jkaufman

Condition 0 and 1 is on main, and Condition 2 and 3 is with that PR I assume?

I updated the PR a few hours ago, did you pull latest off that branch ?

mrousavy avatar Feb 19 '24 18:02 mrousavy

Those are all measurements from your fix branch. Compare it to the table in my original report to contrast it with main. I pulled it just before commenting. On main, capture duration was slower with video enabled. This table shows that your PR mitigates the slowdown.

jkaufman avatar Feb 19 '24 19:02 jkaufman

Ah I see - but I landed a bunch of improvements in main since you created that PR, any chance you could benchmark that again on main? Just so we have reference values if the PR really improves things.

mrousavy avatar Feb 19 '24 19:02 mrousavy

I'm having difficulty building it, again. I've set the branch in my package.json, run yarn and yarn build in the /package directory for the library in node_modules. Still getting an error that the plugin cannot be resolved.

Edit: Simple error. I was referencing the project root rather than the /package directory.

jkaufman avatar Feb 19 '24 19:02 jkaufman

3.9.0 produces very different and counterintuitive behavior.

With no parameters passed to takePhoto:

Condition Format Filter Photo Video Duration
0 undefined true false 2100ms
1 skipped
2 undefined true true TIME OUT
3 skipped

With {qualityPrioritization: 'speed'} passed to takePhoto:

Condition Format Filter Photo Video Duration
0 undefined true false 1100ms
1 skipped
2 undefined true true 300ms
3 skipped

jkaufman avatar Mar 01 '24 00:03 jkaufman

Yea, I didn't know there were so many different vendors/manufacturers not properly implementing Camera HAL for AE/AF/AWB. On all my test devices it worked perfectly fine. I guess this needs more workarounds I am just not sure what kinds of workarounds.

mrousavy avatar Mar 01 '24 07:03 mrousavy

Could performance on Pixel devices be a clue? I'd expect them to be a reference implementation of the Camera HAL. Perhaps it makes sense to target them, first.

jkaufman avatar Mar 01 '24 17:03 jkaufman

Hey! I just released a new V4 beta (v4.0.0-beta.7) where I fixed a bunch of issues! 💪🚀 Can you test that and see if that fixes the issue for you? 😅

yarn add react-native-vision-camera@beta

You might need to increase the compileSdk to the newest Android SDK (34) if you are behind.

mrousavy avatar Mar 19 '24 10:03 mrousavy

Results for 4.0.0-beta.8:

Condition Format Filter Photo Video Duration
0 undefined true false 921ms
1 skipped
2 undefined true true 697ms
3 skipped

To recap, capture with only photo enabled is slower in beta-8 than capture with video also enabled. Both conditions are slower than either of the same conditions in 3.8.2.

jkaufman avatar Mar 19 '24 19:03 jkaufman

Results for 4.0.0-beta.12:

Condition Format Filter Photo Video Duration
0 undefined true false 535ms
1 skipped
2 undefined true true 729ms
3 skipped

jkaufman avatar Apr 11 '24 22:04 jkaufman

Yea I didn't change anything about that yet. But thanks for re-testing.

mrousavy avatar Apr 12 '24 09:04 mrousavy

wait nvm it looks like the values flipped? maybe this is a bit more random than I thought? In V4 we always to the auto-exposure precapture routine (AE + AF + AWB) so this might just take some unavoidable time? takeSnapshot() is for insanely fast capture

mrousavy avatar Apr 12 '24 09:04 mrousavy

Yes, it flipped, and there was a lot of variation in timings, although video true was consistently slower. I also saw strings of 900ms captures with the device face up on the table and no light reaching the back camera.

Mind sharing what to expect from takeSnapshot of the preview layer? Are AE / AF / AWB disabled, or is it polling and periodically updating the preview layer, accordingly?

jkaufman avatar Apr 12 '24 19:04 jkaufman

It's just reading the latest pixels from the preview or video stream. No AE/AF/AWB at all.

mrousavy avatar Apr 16 '24 10:04 mrousavy

After this line, add:

captureSession.stopRepeating()

This will break the preview, but I can fix that later if this works.

Just curious, what kind of fix do you have in mind to keep Preview smooth despite stopRepeating?

A stopRepeating followed by a startRepeating has chance of recreating the capture session and may cause some freeze time (or temporary black screen) in Preview (depends on camera HAL implementation, so may vary across devices).

By the way, one possible root cause for this bug may actually be session reconfiguration. If you change a session parameter while submitting a non-repeating capture request for taking still picture, the camera may need to reconfigure the capture session to accommodate that (again, depends on HAL implementation so will vary across devices). And the tricky part is - if you are setting a session parameter on repeating request and not setting it to non-repeating request, default value from template will be used when unset and that may differ from your repeating request.

For example, imagine you have 60 FPS set to repeating request for video capture and not setting the FPS in non-repeating request which is using TEMPLATE_STILL_REQUEST, the FPS will change to 30 FPS since that's usually the default value for this template. Since 30 and 60 FPS usually require different sensors, the session may be reconfigured in some devices which may be the reason of the latency.

One way to debug this could be by logging all the capture request keys and values you are submitting to the camera framework and check if any different value is being sent when the bug is happening.

You can also try using the camera watch command for this. For example, with your device connected via adb, run a command like adb shell cmd media.camera watch start -m android.control.captureIntent,android.flash.mode,3a && adb shell cmd media.camera watch live on a terminal with the app turned on and then capture an image and see the differences in the capture tags (or just copy-paste here). (note that this command is monitoring only the 3a-related tags mainly, but you can add other tags as well if the library is using them)

Oh also, as far as I remember, this library is migrating to CameraX as well, right? Does the problem occur with CameraX as well?

Zeronfinity avatar Apr 20 '24 14:04 Zeronfinity

Hey @Zeronfinity - that's really interesting, I also didn't know about the Camera Debugging section.

Seems like you really know your sh*t about cameras, are you using VisionCamera or did you just chime in out of interest? :)

mrousavy avatar Apr 29 '24 10:04 mrousavy

And yea, react-native-vision-camera 4.x.x is using CameraX now - I'm sure you are aware of all the quirks Camera2 had, and how the entire CameraX team is dedicated to fix or workaround these device-specific quirks/bugs.

There's no point in using Camera2, I don't stand a chance against hundreds of device-specific bugs that I need to workaround myself. Better off to leave the Google engineers work on this for CameraX, especially now that CameraX also has stream-sharing and HDR features.

Only thing that I noticed was that the Frame Processors (CameraX ImageAnalysis) is much slower than my Camera2 ImageReader implementation. I thought about creating a custom output that streams HardwareBuffers using OpenGL textures to skip the ImageReader step, that way I could also do the RGB conversion straight on the GPU.

mrousavy avatar Apr 29 '24 10:04 mrousavy