Monday, 31 August 2020

Android App Crashes on Physical Device But Not Emulator: "Parcel: dup() failed in Parcel::read [...] error: Too many open files"

I'm trying to turn an old phone into a networked security camera, since crime has increased dramatically in my area during all these riots (and I don't want to rely on someone else's app to control access to my private moments).

I'm chunking-and-sending, so the camera records video for a number of seconds, stops, encodes the binary of the captured file into Base64, then shoots it off to a home server via POST request, all in an endless loop. The server unwraps + decodes + saves it as the original binary "MP4" onto its own disk (TODO: fun post-processing for motion detection).

Using a variety of virtual devices at (and around) my target phone's OS version & screen size, this all works for extended periods of time. I've used 60-second chunks for 15+ minutes, plus 6-second chunks for over an hour. I consistently receive the goofy virtual room videos the emulator produces onto my server.

But on the Samsung Galaxy S5 running Android 6.0.1 that dreams of becoming a security camera, it usually takes 2 or 3 videos being sent before the app crashes... except when your resolution is set too high, then you run into a different symptom.

Symptom #0: Crashing at the End of a Chunk

E/Parcel: dup() failed in Parcel::read, i is 1, fds[i] is -1, fd_count is 2, error: Too many open files

E/Surface: dequeueBuffer: IGraphicBufferProducer::requestBuffer failed: -22

W/Adreno-EGLSUB: DequeueBuffer:721: dequeue native buffer fail: Invalid argument, buffer=0x0, handle=0x0

W/Adreno-EGL: <qeglDrvAPI_eglSwapBuffers:3800>: EGL_BAD_SURFACE

Followed immediately by this second error:

E/CameraDeviceGLThread-1: Received exception on GL render thread:

java.lang.IllegalStateException: swapBuffers: EGL error: 0x300d

Then finally, once the chunk time is up and the camera goes to record again, the final error occurs to crash the entire app:

I/CameraDeviceState: Legacy camera service transitioning to state ERROR

E/AndroidRuntime: FATAL EXCEPTION: CameraThread

Process: com.example.roselawncam, PID: 14639

android.hardware.camera2.CameraAccessException: The camera device has encountered a serious error

Symptom #1: Crashing in the Middle of a Chunk Because You Went Too 🌈High-Res🌈 For Your Own Good

These warnings make it clear that resource strain causes this symptom. They occur as the app crashes, with higher resolutions causing sooner crashes. I clocked these bad boys at:

  • 1920x1080 (30 FPS): 5 seconds
  • 1280x720 (30 FPS): 9 seconds
  • 800x480 (30 FPS): 15 seconds

Times were similar for both the front and back cameras. At lower resolutions, you start running into Symptom #0 unless you bump your chunk time way up. Anyway:

W/Adreno-GSL: <gsl_ldd_control:475>: ioctl fd 28 code 0xc01c0915 (IOCTL_KGSL_MAP_USER_MEM) failed: errno 12 Out of memory

W/Adreno-EGLSUB: SyncBackBuffer:3130: failed to map the memory for fd=281 offs=0

E/Adreno-EGLSUB: SyncBackBuffer:3131: SyncBackBuffer: FATAL ERROR : (null)

A/Adreno-GSL: Exiting the process com.example.roselawncam from function SyncBackBuffer and line 3131

A/libc: Fatal signal 6 (SIGABRT), code -6 in tid 19618 (CameraDeviceGLT)

And finally, the pertinent Kotlin pieces:

private fun createRecorder(surface: Surface) = MediaRecorder().apply {
        setAudioSource(MediaRecorder.AudioSource.MIC)
        setVideoSource(MediaRecorder.VideoSource.SURFACE)
        setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
        setOutputFile(outputFile.absolutePath)
        setVideoEncodingBitRate(RECORDER_VIDEO_BITRATE)
        if (args_fps > 0) setVideoFrameRate(args_fps)
        setVideoSize(args_width, args_height)
        setVideoEncoder(MediaRecorder.VideoEncoder.H264)
        setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
        setInputSurface(surface)
}

private fun recordIt(cameraManager: CameraManager, cameraThread: HandlerThread) {
    val cameraHandler = Handler(cameraThread.looper)
    val stateCallback: CameraDevice.StateCallback = object: CameraDevice.StateCallback() {
        override fun onOpened(camera: CameraDevice) {
            camera.createCaptureSession(
                listOf<Surface>(recorderSurface),
                object : CameraCaptureSession.StateCallback() {
                    override fun onConfigured(session: CameraCaptureSession) {
                        val recTimeSeconds = findViewById<TextView>(R.id.recTimeSeconds)
                        val chunkTimeMilliseconds = recTimeSeconds.text.toString().toLong() * 1000

                        // Boolean "stopREC" (e.g. "Stop Recording") is false at this point
                        while (!stopREC) {
                            // // // This loop should run forever, but crashes after a few times // // //
                            val recorder: MediaRecorder by lazy { createRecorder(recorderSurface) }
                            val recordRequest: CaptureRequest by lazy {
                                session.device.createCaptureRequest(CameraDevice.TEMPLATE_RECORD).apply {
                                    addTarget(recorderSurface)
                                    set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, Range(args_fps, args_fps))
                                }.build()
                            }
                            session.setRepeatingRequest(recordRequest, null, cameraHandler)
                            recorder.apply { prepare(); start() }
                            Thread.sleep(chunkTimeMilliseconds)
                            recorder.apply { stop(); release() }
    
                            // Send the video file across the network in JSON via POST request:
                            val params = HashMap<String, String>()
                            params["videodata"] = convertToBase64(outputFile)
                            val jsonObject = JSONObject(params as Map<*, *>)
                            val request = JsonObjectRequest(Request.Method.POST, url, jsonObject, null, null)
                            queue.add(request)
                            // // // End of loop that should've ran forever, but crashes occasionally instead  // // //
                        }
                        camera.close()
                    }
                    override fun onConfigureFailed(session: CameraCaptureSession) {}
                }, 
                cameraHandler
            )
        }
        override fun onDisconnected(camera: CameraDevice) { recorder.stop(); recorder.release() }
        override fun onError(camera: CameraDevice, error:Int) { camera.close() }
    }
    cameraManager.openCamera(args_cameraId, stateCallback, cameraHandler)
}


from Android App Crashes on Physical Device But Not Emulator: "Parcel: dup() failed in Parcel::read [...] error: Too many open files"

No comments:

Post a Comment