5

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)
}
armani
  • 93
  • 1
  • 10
  • 23
  • Wow, cool stuff that you're doing. I understand you don't want to use someone else's app for your private feed. But there is also a FOSS project on GitHub just for this: https://github.com/guardianproject/haven (I remembered it from this HN post: https://news.ycombinator.com/item?id=22396841). Even if you don't want to use directly, you could still check it out, see if that one does work on your S5, and if so, find out what makes the difference. Good luck! – francis duvivier Aug 30 '20 at 22:46
  • `Too many open files error` can come on a resource crunch an in your case,, that could be teh number of threads open at the same time due to multiple simultanous AsyncTasks being called. You can limit this by using a thread pool mechanism insteadof using the raw AsyncTasks. – Sisir Sep 01 '20 at 06:44
  • Quick and Simple answer: memory leaks, or too many threads being open without being closed. Make a thread pool for Async calls. – JustAFellowCoder Sep 04 '20 at 20:38
  • I have this error when using Camera in background mode on Samsung S5 only – user924 Jan 28 '21 at 21:42
  • Did you solve it? – user924 Jan 28 '21 at 21:42
  • I also have similar errors: `E/Adreno-EGLSUB: : SyncBackBuffer: FATAL ERROR : (null)` and `A/Adreno-GSL: Exiting the process * from function SyncBackBuffer and line 3131` – user924 Jan 28 '21 at 21:48
  • Interesting that with Legacy Camera API it works ok in foreground, but fails after some seconds with `E/Adreno-EGLSUB` error in background, and when using Camera2 API it fails in any way (doesn't matter if foreground or background) – user924 Jan 28 '21 at 22:01
  • @user924 No I didn't solve this. Like any Android project, I had to abandon it due to the ecosystem being an absolute nightmare to code for. :( – armani Feb 07 '21 at 10:35

2 Answers2

1

Too many open files

The first error message of Symptom #0 is

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

which came from Parcel.cpp:

status_t err = NO_ERROR;
for (size_t i=0 ; i<fd_count && err==NO_ERROR ; i++) {
    fds[i] = dup(this->readFileDescriptor());
    if (fds[i] < 0) {
        err = BAD_VALUE;
        ALOGE("dup() failed in Parcel::read, i is %zu, fds[i] is %d, fd_count is %zu, error: %s",
            i, fds[i], fd_count, strerror(errno));
    }
}

It shows that the above error took place at

fds[i] = dup(this->readFileDescriptor());

I also searched about Too many open files and found a similar question. It has a detailed error log and an answer. Both indicates File Descriptor again.

The root cause might be ...

params["videodata"] = convertToBase64(outputFile)

Please check the implementation of convertToBase64().

According to this and this, File Descriptors could leak.

qtmfld
  • 2,916
  • 2
  • 21
  • 36
0

I have some suggestions:

  • Force the use of synchronized functions where you need a file lock so the threads would execute that block of code sequentially, open and close the file stream in an organized way, for instance, access to files. That would avoid out of memory and file already in use errors.

  • Is it possible not to encode the file to Base 64? The string would be too large, check if it avoid any error.

And nice initiative for doind your own app.

raphaelbgr
  • 179
  • 3
  • 10