mrousavy / react-native-vision-camera

📸 A powerful, high-performance React Native Camera library.
https://react-native-vision-camera.com
MIT License
7.58k stars 1.1k forks source link

🐛 Memory Leak on camera.takePhoto after 6 six shots. #2866

Open mdottavi opened 6 months ago

mdottavi commented 6 months ago

What's happening?

I integrated latest RNVC 4.0.3 in my project.

After taking 6 photos correctly (I checked files are saved in cache folder of the device) the takePhoto method get systematically stuck at the seventh execution (await props.camera.current.takePhoto does never end).

If I switch to another View and get back to the Camera View it resets and I can still get 6 goot photos before stucking again.

I tried to give some time from one shot and another but it does not change the issue. Fully reproducable Code is provided.

Devices: Motorola G84 5G

The error from LogCat at seventh shot is: ImageReader-4080x3072f22m9-8855-21 waitForFreeSlotThenRelock TIMED_OUT dequeuedCount=0, acquiredCount=3, mMaxAcquiredBufferCount=9, mMaxDequeuedBufferCount=8 ImageReader-4080x3072f22m9-8855-21 dequeueBuffer: No free buffer is found! getBufferLockedCommon: Stream 3: Can't dequeue next output buffer: Connection timed out (-110)

From the traces it look some buffer is not flushed after image acquisition. Do I need to flush/empty some data ?

Reproduceable Code

import React, { useRef, useState, useCallback, useEffect } from 'react';
import Ripple from 'react-native-material-ripple';
import { View } from 'react-native';
import { Camera, useCameraDevice, useCameraPermission}  from 'react-native-vision-camera';

const CameraStyles = {
    containerPortrait: {flex: 1,backgroundColor: '#000000' },
    cameraWrapperPortrait: {flex: 1,justifyContent: 'center',alignItems: 'center',overflow: 'hidden' },
    cameraPortrait: { flex: 1, aspectRatio: 3/4 },
    buttonBarPortrait: { height: 90, padding: 10 },
    buttonsPortrait: { flexDirection: 'row',flex: 1, justifyContent: 'space-between', alignItems: 'center' },
    captureButton: { width: 40, height: 40, borderRadius: 20, backgroundColor: 'rgba(140, 140, 140, 0.3)', borderColor: 'white', borderWidth: 5}
};

function _TakePictureButton ( props){
    console.log("TakePictureButton Rendering...");
    const takePhoto = useCallback(async () => {
        console.log("takePhoto: started");
        try {
          if (props.camera.current == null) throw new Error('Camera ref is null!')

          console.log('Taking photo...')
          const photo = await props.camera.current.takePhoto({
            qualityPrioritization: 'quality',
            flash: props.flash,
            enableShutterSound: true,
          })
          console.log('Photo Taken!')
          props.onClick(photo, 'photo')
        } catch (e) {
          console.error('Failed to take photo!', e)
        }
      }, [props.camera, props.flash, props.flash]);

    return (
        <Ripple style={CameraStyles.captureButton} 
            rippleColor='rgb(256, 256, 256)' 
            rippleOpacity={1} 
            rippleDuration={400} 
            rippleCentered={true}
            rippleSize={78} 
            onPress={takePhoto}
            >
        </Ripple>
    );
}
const TakePictureButton = React.memo(_TakePictureButton);

function App(props) {
    console.log("Rendering ...");

    const camera = useRef(null);
    const [orientation, setOrientation] = useState('Portrait');
    const { hasPermission, requestPermission } = useCameraPermission();

    useEffect(() => {
        async function getPermission() {
            const newCameraPermission = await requestPermission();
            console.log("getPermission returned: " + newCameraPermission);
            if (!newCameraPermission) {
                logger.error("getPermission returned: " + newCameraPermission);
            }
        }
        if (!hasPermission) {
            getPermission();
        }
    }, []);

    const [flash, setFlash] = useState('off');
    const device = useCameraDevice('back');

    // Camera callbacks
    const onError = useCallback((error) => {
        logger.error(error);
    }, []);

    const onInitialized = useCallback(() => {
        console.log('Camera initialized!');
    }, []);

    const onMediaCaptured = useCallback(
        async (media, type) => {
            try {
                console.log(`onMediaCaptured: Image captured. ${type} src=${media.path}`);
            } catch (err) {
                    logger.error("EXCEPTION: takePicture:", err);
            }            
        });

    if (device != null ) {
        console.log(`Rendering TakePicture with camera device: "${device.name}" photo Flash: ${flash}`);
    } else {
        console.log('Rendering TakePicture without active camera');
    }

    return (
        <View style={CameraStyles.containerPortrait}>
            {device != null && (<>
                <View style={CameraStyles.cameraWrapperPortrait}>
                        <Camera
                            style={CameraStyles.cameraPortrait}
                            device={device}
                            isActive={true}
                            ref={camera}
                            onInitialized={onInitialized}
                            onError={onError}
                            onStarted={() => 'Camera started!'}
                            onStopped={() => 'Camera stopped!'}
                            orientation={orientation}
                            lowLightBoost={false}
                            enableZoomGesture={false}
                            exposure={0}
                            photo={true}
                            video={false}
                            audio={false}
                            torch={flash}
                        />
                </View>
                <View style={CameraStyles.buttonBarPortrait}>
                    <View style={CameraStyles.buttonsPortrait}>
                        <TakePictureButton
                            camera={camera}
                            onClick={onMediaCaptured}
                            flash={false}
                        />
                    </View>
                </View>
            </>
            )}

        </View>
    );
}
export default App;

Relevant log output

Log from the app:
==============================================
 LOG  Running "appname" with {"rootTag":61}
 LOG  Rendering ...
 LOG  Rendering TakePicture with camera device: "0 (BACK) androidx.camera.camera2" photo Flash: off
 LOG  TakePictureButton Rendering...
 LOG  Rendering ...
 LOG  Rendering TakePicture with camera device: "0 (BACK) androidx.camera.camera2" photo Flash: off
 LOG  TakePictureButton Rendering...
 LOG  Rendering ...
 LOG  Rendering TakePicture with camera device: "0 (BACK) androidx.camera.camera2" photo Flash: off
 LOG  Camera initialized!
 LOG  takePhoto: started
 LOG  Taking photo...
 LOG  Photo Taken!
 LOG  onMediaCaptured: Image captured. photo src=/data/user/0/com.appname/cache/mrousavy-6048323629045401133.jpg
 LOG  takePhoto: started
 LOG  Taking photo...
 LOG  Photo Taken!
 LOG  onMediaCaptured: Image captured. photo src=/data/user/0/com.appname/cache/mrousavy-5487193471359461827.jpg
 LOG  takePhoto: started
 LOG  Taking photo...
 LOG  Photo Taken!
 LOG  onMediaCaptured: Image captured. photo src=/data/user/0/com.appname/cache/mrousavy-1734632536699059492.jpg
 LOG  takePhoto: started
 LOG  Taking photo...
 LOG  Photo Taken!
 LOG  onMediaCaptured: Image captured. photo src=/data/user/0/com.appname/cache/mrousavy-8567952871727161939.jpg
 LOG  takePhoto: started
 LOG  Taking photo...
 LOG  Photo Taken!
 LOG  onMediaCaptured: Image captured. photo src=/data/user/0/com.appname/cache/mrousavy-3879283333994109308.jpg
 LOG  takePhoto: started
 LOG  Taking photo...
 LOG  Photo Taken!
 LOG  onMediaCaptured: Image captured. photo src=/data/user/0/com.appname/cache/mrousavy-1440988497951052873.jpg
 LOG  takePhoto: started
 LOG  Taking photo...
 LOG  takePhoto: started
 LOG  Taking photo...

LOGCAT OUTPUT: when the problem happens.
========================================================

2024-05-09 15:04:40.033  8855-10023 BufferQueueProducer     com.appname                  D  [ImageReader-4080x3072f22m9-8855-21](id:22970000002d,api:4,p:1476,c:8855) waitForFreeSlotThenRelock TIMED_OUT dequeuedCount=0, acquiredCount=3, mMaxAcquiredBufferCount=9, mMaxDequeuedBufferCount=8
2024-05-09 15:04:40.033  8855-10023 BufferQueueProducer     com.appname                  D  [ImageReader-4080x3072f22m9-8855-21](id:22970000002d,api:4,p:1476,c:8855) dequeueBuffer: No free buffer is found!
2024-05-09 15:04:40.034  1228-1477  SurfaceFlinger          surfaceflinger                       E  Permission Denial: can't access SurfaceFlinger pid=8855, uid=10521
2024-05-09 15:04:40.034  1476-19614 Camera3-OutputStream    cameraserver                         E  getBufferLockedCommon: Stream 3: Can't dequeue next output buffer: Connection timed out (-110)
2024-05-09 15:04:40.275  8855-19610 CameraView              com.appname                  I  invokeOnAverageFpsChanged(0.0)
2024-05-09 15:04:40.818  2684-2684  StatusBarIconController com.android.systemui                 D  ignoring old pipeline callback because the new wifi icon is enabled
2024-05-09 15:04:41.035  8855-8870  BufferQueueProducer     com.appname                  D  [ImageReader-4080x3072f22m9-8855-21](id:22970000002d,api:4,p:1476,c:8855) waitForFreeSlotThenRelock TIMED_OUT dequeuedCount=0, acquiredCount=3, mMaxAcquiredBufferCount=9, mMaxDequeuedBufferCount=8
2024-05-09 15:04:41.035  8855-8870  BufferQueueProducer     com.appname                  D  [ImageReader-4080x3072f22m9-8855-21](id:22970000002d,api:4,p:1476,c:8855) dequeueBuffer: No free buffer is found!
2024-05-09 15:04:41.036  1228-1477  SurfaceFlinger          surfaceflinger                       E  Permission Denial: can't access SurfaceFlinger pid=8855, uid=10521
2024-05-09 15:04:41.037  1476-19614 Camera3-OutputStream    cameraserver                         E  getBufferLockedCommon: Stream 3: Can't dequeue next output buffer: Connection timed out (-110)

Camera Device

{
  "formats": [],
  "sensorOrientation": "landscape-left",
  "hardwareLevel": "full",
  "maxZoom": 8,
  "minZoom": 1,
  "maxExposure": 24,
  "supportsLowLightBoost": false,
  "neutralZoom": 1,
  "physicalDevices": [
    "wide-angle-camera"
  ],
  "supportsFocus": true,
  "supportsRawCapture": false,
  "isMultiCam": false,
  "minFocusDistance": 10,
  "minExposure": -24,
  "name": "0 (BACK) androidx.camera.camera2",
  "hasFlash": true,
  "hasTorch": true,
  "position": "back",
  "id": "0"
}

Device

Motorola G84 5G

VisionCamera Version

4.0.3

Can you reproduce this issue in the VisionCamera Example app?

I didn't try (⚠️ your issue might get ignored & closed if you don't try this)

Additional information

mdottavi commented 6 months ago

Motorola G84 5G (Android 14) Same thing happens (6 photos good) seventh stucks also on Android OnePlus Nord 3 (Android 14)

mdottavi commented 6 months ago

The issue does not show up in the CameraExample as it is.... BUT just removing the format={format} parameter passed to the Camera Component it happens exactly the same.

mrousavy commented 6 months ago

hm interesting.

mrousavy commented 6 months ago

Can you test again with VisionCamera 4.0.5?