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

🐛 V3 runAsync doesn't work

Open manolo-battista opened this issue 1 year ago • 17 comments

What's happening?

On docs is described:

Screenshot 2023-09-11 alle 17 32 27

But I think it needs update, cause request frame as first parameter and func (callback) as second parameter.

Screenshot 2023-09-11 alle 17 33 10

The callback function is never called. I saw that code reach the func on runOnAsyncContext function, but I think enter always on finally cause I can't see my log ok 2.

Screenshot 2023-09-11 alle 17 38 02

Reproduceable Code

No response

Relevant log output

No response

Camera Device

No response

Device

iPhone 14 Pro

VisionCamera Version

3.0.0

Can you reproduce this issue in the VisionCamera Example app?

  • [ ] I can reproduce the issue in the VisionCamera Example app.

Additional information

manolo-battista avatar Sep 11 '23 15:09 manolo-battista

@manolo-battista did you find any solution for this ?

abdelrahman-muntaser avatar Sep 21 '23 05:09 abdelrahman-muntaser

@manolo-battista did you find any solution for this ?

no, unfortunately I still didn't find any solution. For my projects at the moment I'm using runAtTargetFps to run lower FPS rate.

const frameProcessor = useFrameProcessor((frame) => {
  'worklet'
  console.log("I'm running synchronously at 60 FPS!")
  runAtTargetFps(2, () => {
    'worklet'
    console.log("I'm running synchronously at 2 FPS!")
  })
}, [])

manolo-battista avatar Sep 21 '23 07:09 manolo-battista

Hi. Would've been great if you could've sent a PR for that to fix it in docs.

But in my tests, runAsync works - can you put a console.log in there to double check that it does not get called?

Can you reproduce it in the example app?

mrousavy avatar Sep 30 '23 10:09 mrousavy

hey @mrousavy i think we should keep this issue open, i'm facing the same issue since this function was released, can you provide a minimal example that's working for you?

rodgomesc avatar Oct 18 '23 14:10 rodgomesc

some weirdness scope things happening

UseCase1:

logcat throws JSI rethrowing JS exception: Regular javascript function cannot be shared. Try decorating the function with the 'worklet' keyword to allow the javascript function to be used as a worklet."


  const frameProcessor = useFrameProcessor(
    (frame) => {
      "worklet";
      runAsync(frame, () => {
        "worklet";
        console.log("my awesome log");
      });
    },
    []
  );
  

UseCase2:

do not throws anything onlogcat but frameContext is always undefined, the callback is not being triggered for some reason

  const myFn = (frameContext) => {
    "worklet";
    console.log("Here", frameContext);
  };

  const frameProcessor = useFrameProcessor(
    (frame) => {
      "worklet";
      runAsync(frame, myFn);
    },
    [myFn]
  );
  

rodgomesc avatar Oct 18 '23 14:10 rodgomesc

Oh! Hm that's weird, I remember this worked when I built it.

@chrfalch do you maybe have any insights?

mrousavy avatar Oct 18 '23 15:10 mrousavy

oh wait

it seems that by design the frame it's not being passed to myFn as a callback prop, this forces us to uses arrow functions to hook the frame scope, also it seems that's no guarantees that internal.decrementRefCount() is being called after my long running function finishes the execution?, which causes a java.lang.IllegalStateException: Image is already closed,

https://github.com/mrousavy/react-native-vision-camera/blob/764897dcf119e26668b9fb6c6eb78f0e2993ffd1/package/src/FrameProcessorPlugins.ts#L50-L55

rodgomesc avatar Oct 18 '23 15:10 rodgomesc

Wait what? not sure if I follow -

it seems that by design the frame it's not being passed to myFn as a callback prop

no you're right, you can just use the Frame inside your lambda directly, the lambda has no parameters.

there's no guarantees that internal.decrementRefCount() is being called after my long running function finishes the execution

Why not? It runs in finally, no?

mrousavy avatar Oct 18 '23 15:10 mrousavy

@mrousavy yep you are totally right, i was very drunk that day sorry

found a pattern that doesn't make sense for me, example app works my app doesn't work, tried different combination of libs and now i'm using same version that's on example but it always end up with

image

rodgomesc avatar Nov 09 '23 02:11 rodgomesc

i have a serie of frame processors at this moment running in sequence

something like this

const frameProcessor = (frame) => {
    'worklet';

     // processors
     const faces = faceDetectorProcessor(frame)
     const luminanceResults = luminanceDetectionProcessor(frame, faces)
     const spoofResults = antiSpoofingProcessor(frame);


    // callbacks
    handleSpoofingDetection(spoofResults)
    handleLuminanceDetection(luminanceResults)

},[handleSpoofingDetection, handleLuminanceDetection])

the problem is that at this point the frame are being deallocated before i finish my operations, so i'm thinking to implement something like this, to drop some frames while my processors still running

const frameProcessor = (frame) => {
    'worklet';

    // just drop the frame
    if(!jsiUtil.finishedOperations) return;  

    const frameCopy = jsiUtil.copyFrame(frame);

     // processors      
     const faces = faceDetectorProcessor(frameCopy)
     const luminanceResults = luminanceDetectionProcessor(frameCopy, faces)
     const spoofResults = antiSpoofingProcessor(frameCopy);

    // cleanup frame copy
    jsiUtil.ReleaseFrameCopy()    

    // callbacks
    handleSpoofingDetection(spoofResults)
    handleLuminanceDetection(luminanceResults)

},[handleSpoofingDetection, handleLuminanceDetection])

would be nice to hear your opinion on this temporary solution to make sure i'm not missing anything

some final notes:

i'm linking my lib with visionCamera.so and unwraping the frame, i've tried increment refcount before my operations and decrement on the last one, but it seems to be ignored need further investigation

rodgomesc avatar Nov 09 '23 02:11 rodgomesc

Woah, that's a weird error. Can you add console.log statements to your code (begin and end of sync calls, and begin and end of async calls) and then also to the native Frame's release method?

This is weird and you shouldn't need such workarounds - it should just work magically with ref counting.

mrousavy avatar Nov 09 '23 09:11 mrousavy

i still couldn't figure out what's happening, i'll try to upload something reproducible

rodgomesc avatar Dec 05 '23 00:12 rodgomesc

Haaa I found out, i have a monorepo with this structure

-- packages ---- playground ---- mylib

if i have reanimated in my lib i face this issue, removed reanimated from mylib and now i'm using it only in the playground, working like a charm

Edit1:

if i try a simple console.log after removing reanimated lib it works

useFrameProcessor(frame => .....

 runAsync(frame, () => {
 'worklet'
    console.log('hello from runAsync')
  })
  
},[])

if i try to use any function inside of it, it crashes exactly with same error as before

 const myFunc = () => {
    'worklet'
     console.log('hello from runAsync')
 }
 
useFrameProcessor(frame => .....

 runAsync(frame, () => {
  'worklet'
    myFunc();
  })
  
},[myFunc])
 
 

rodgomesc avatar Dec 05 '23 02:12 rodgomesc

@mrousavy fyi it's reproducible on example app now

rodgomesc avatar Dec 05 '23 18:12 rodgomesc

Wait so Reanimated makes it break? Yea it's a bit weird that Worklets and reanimated do the same thing... Maybe we can fix this compatibility for now and think about a better solution in the future.

mrousavy avatar Dec 05 '23 19:12 mrousavy

Wait so Reanimated makes it break?

that's what i thought because after removing reanimated i can do a console.log inside the runAsync, however if i call any function inside it i got the same error

rodgomesc avatar Dec 06 '23 18:12 rodgomesc

Similar issue here. Using @ismaelmoreiraa/vision-camera-ocr, any call to scanOCR using runAsync throws:

JSI rethrowing JS exception: Exception in HostFunction: java.lang.ClassNotFoundException: Didn't find class "com.facebook.jni.MapIteratorHelper" on path: DexPathList[[directory "."],nativeLibraryDirectories=[/system/lib64, /system/system_ext/lib64, /system/lib64, /system/system_ext/lib64]]

Error: Exception in HostFunction: java.lang.ClassNotFoundException: Didn't find class "com.facebook.jni.MapIteratorHelper" on path: DexPathList[[directory "."],nativeLibraryDirectories=[/system/lib64, /system/system_ext/lib64, /system/lib64, /system/system_ext/lib64]]
    at call (native)
    at scanOCR (/Users/username/dev/app/node_modules/@ismaelmoreiraa/vision-camera-ocr/src/index.tsx:8:21)
    at fn (native)
    at anonymous (/Users/username/dev/app/packages/app/src/features/foo.tsx:18:27)
    at fn (native)
    at anonymous (/Users/username/dev/app/node_modules/react-native-vision-camera/src/FrameProcessorPlugins.ts:6:9)

Running the same exact same code using runAtTargetFps works fine.

I'm not using Reanimated in my project.

gbark avatar Dec 06 '23 19:12 gbark

Btw.; here's an explanation and a fix: https://github.com/margelo/react-native-worklets-core/issues/136

mrousavy avatar Jan 11 '24 16:01 mrousavy

This is not related to VisionCamera, but rather about Reanimated.

If you do not use Reanimated, runAsync works fine. If you do use Reanimated, you need to enable processNestedWorklets in the Reanimated's babel plugin. See https://github.com/software-mansion/react-native-reanimated/issues/5576 for more info

mrousavy avatar Jan 15 '24 12:01 mrousavy

Enabling processNestedWorklets solved the error for me, but now this error pops up: Frame Processor Error: Value is undefined, expected an Object, js engine: VisionCamera

This is my code (from react-native-fast-tflite), note that the model is printed out, but then it shows the error, I'm trying to make the model not interfere with how the camera output is displayed in my app (it interferes on Android but not iOS):

  const frameProcessor = useFrameProcessor((frame) => {
    'worklet'
  
    const data = resize(frame, {
      size: {
        // center-crop
        x: (frame.width / 2) - (320 / 2),
        y: (frame.height / 2) - (320 / 2),
        width: 320,
        height: 320,
      },
      pixelFormat: 'rgb',
      dataType: 'uint8'
    })

    runAsync(frame, () => {
      'worklet'
      console.log(model)

      const output = model.runSync([data])
    
      const numDetections = output[0]
    })
  }, [model])

If I pass the frame into runAsync, I get this error: ERROR Frame Processor Error: Exception in HostFunction: no ArrayBuffer attached, js engine: VisionCamera

Not sure if I'm misunderstanding some fundamentals here.

"react-native-vision-camera": "^3.8.2",
"react-native-worklets-core": "^0.2.4",
"react-native-reanimated": "^3.6.1",

Cosmorider avatar Jan 26 '24 15:01 Cosmorider

ArrayBuffers can't be shared (right @chrfalch?), do the resize also in runAsynx.

mrousavy avatar Jan 26 '24 15:01 mrousavy

Correct.

chrfalch avatar Jan 26 '24 15:01 chrfalch

Sorry, I meant that I passed the resize into runAsync and then I got this error, if that was what you were asking for: ERROR Frame Processor Error: Exception in HostFunction: no ArrayBuffer attached, js engine: VisionCamera

Cosmorider avatar Jan 26 '24 16:01 Cosmorider

Yeah - try to move the resize call also into runAsync.

mrousavy avatar Jan 26 '24 17:01 mrousavy

I did that, and I get this error: ERROR Frame Processor Error: Exception in HostFunction: java.lang.ClassNotFoundException: Didn't find class "com.mrousavy.camera.frameprocessor.Frame" on path: DexPathList[[directory "."],nativeLibraryDirectories=[/system/lib64, /system/product/lib64, /system/lib64, /system/product/lib64]], js engine: VisionCamera

When I use this code:

  const frameProcessor = useFrameProcessor((frame) => {
    'worklet'

    runAsync(frame, () => {
      'worklet'

      const data = resize(frame, {
        size: {
          // center-crop
          x: (frame.width / 2) - (320 / 2),
          y: (frame.height / 2) - (320 / 2),
          width: 320,
          height: 320,
        },
        pixelFormat: 'rgb',
        dataType: 'uint8'
      })
    })
  }, [])

Edit: If I have both resize outside and inside runAsync, it just crashes with no symbolic stack trace:

const frameProcessor = useFrameProcessor((frame) => {
    'worklet'

    const data = resize(frame, {
      size: {
        // center-crop
        x: (frame.width / 2) - (320 / 2),
        y: (frame.height / 2) - (320 / 2),
        width: 320,
        height: 320,
      },
      pixelFormat: 'rgb',
      dataType: 'uint8'
    })

    runAsync(frame, () => {
      'worklet'

      const data = resize(frame, {
        size: {
          // center-crop
          x: (frame.width / 2) - (320 / 2),
          y: (frame.height / 2) - (320 / 2),
          width: 320,
          height: 320,
        },
        pixelFormat: 'rgb',
        dataType: 'uint8'
      })
    })
  }, [])

Cosmorider avatar Jan 26 '24 18:01 Cosmorider

I did that, and I get this error: ERROR Frame Processor Error: Exception in HostFunction: java.lang.ClassNotFoundException: Didn't find class "com.mrousavy.camera.frameprocessor.Frame" on path: DexPathList[[directory "."],nativeLibraryDirectories=[/system/lib64, /system/product/lib64, /system/lib64, /system/product/lib64]], js engine: VisionCamera

that so weird, same problem here

rodgomesc avatar Jan 27 '24 00:01 rodgomesc

From reading the error message, it looks like the reason the JNI class cannot be found in runAsync is because there is no JNI Environment set up on that specific Thread - which is weird, because we set it up here: https://github.com/margelo/react-native-worklets-core/blob/d8dae58ffac6b7050bb0b410b69acc621dcf74d8/cpp/WKTJsiWorkletContext.cpp#L147-L149

Is that not called when invoking a worklet from JS, @chrfalch ?

mrousavy avatar Jan 29 '24 10:01 mrousavy

Hey @rodgomesc and @Cosmorider - a discord user posted a SIGABRT stacktrace with symbols attached so I was able to figure out that this came from JVisionCameraScheduler - can you guys maybe try if this PR fixes the issue for you? https://github.com/mrousavy/react-native-vision-camera/pull/2457

mrousavy avatar Jan 30 '24 10:01 mrousavy

I'm not seeing the same error. I have this one when using runAsync:

Frame Processor Error: Exception in HostFunction: java.lang.ClassNotFoundException: Didn't find class "com.facebook.jni.MapIteratorHelper" on path: DexPathList[[directory "."],nativeLibraryDirectories=[/system/lib64, /system_ext/lib64, /system/lib64, /system_ext/lib64]], js engine: VisionCamera

The new PR does not fix this particular one.

MSchmidt avatar Jan 30 '24 12:01 MSchmidt

@MSchmidt can you try to add a

facebook::jni::ThreadScope scope;

right at the top of this method: https://github.com/mrousavy/react-native-vision-camera/blob/02bc8a979c192707efc5d6e1f424812e36f6369f/package/android/src/main/cpp/frameprocessor/java-bindings/JFrameProcessor.cpp#L35-L45

? Let me know if that works for you

mrousavy avatar Jan 30 '24 13:01 mrousavy