Android 应用程序在物理设备而非模拟器上崩溃:“Parcel: dup() 在 Parcel::read [...] 中失败:错误:打开的文件太多”

arm*_*ani 5 android video-capture kotlin

我正在尝试将旧手机变成联网的安全摄像头,因为在所有这些骚乱期间,我所在地区的犯罪率急剧增加(而且我不想依赖其他人的应用程序来控制对我私人时刻的访问)。

我正在分块发送,所以摄像机会记录视频几秒钟,然后停止,将捕获的文件的二进制文件编码为 Base64,然后通过 POST 请求将其发送到家庭服务器,所有这些都是无限循环的。服务器解包 + 解码 + 将其作为原始二进制“MP4”保存到自己的磁盘上(TODO:运动检测的有趣后处理)。

在(和周围)我的目标手机的操作系统版本和屏幕尺寸上使用各种虚拟设备,这一切都可以长时间工作。我已经使用 60 秒的块超过 15 分钟,加上 6 秒的块超过一个小时。我一直收到模拟器在我的服务器上生成的愚蠢的虚拟房间视频。

但是在运行 Android 6.0.1 并梦想成为安全摄像头的三星 Galaxy S5 上,通常需要发送 2 到 3 个视频,然后应用程序才会崩溃……除非您将分辨率设置得太高,然后您会遇到不同的症状。

症状 #0:在块的末尾崩溃

E/Parcel:dup() 在 Parcel::read 中失败,i 为 1,fds[i] 为 -1,fd_count 为 2,错误:打开的文件太多

E/Surface:dequeueBuffer:IGraphicBufferProducer::requestBuffer 失败:-22

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

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

紧随其后的是第二个错误:

E/CameraDeviceGLThread-1:在 GL 渲染线程上收到异常:

java.lang.IllegalStateException: swapBuffers: EGL error: 0x300d
Run Code Online (Sandbox Code Playgroud)

最后,一旦块时间到了,相机再次开始录制,就会发生最终错误,使整个应用程序崩溃:

I/CameraDeviceState:传统相机服务转换到状态 ERROR

E/AndroidRuntime:致命异常:CameraThread

Process: com.example.roselawncam, PID: 14639
Run Code Online (Sandbox Code Playgroud)

android.hardware.camera2.CameraAccessException: 相机设备遇到严重错误

症状#1:在块中间崩溃,因为您为了自己的利益而使用太高分辨率

这些警告清楚地表明资源紧张会导致此症状。它们发生在应用程序崩溃时,更高的分辨率会导致更快的崩溃。我给这些坏男孩计时:

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

前置和后置摄像头的时间相似。在较低的分辨率下,除非您增加块时间,否则您会开始遇到症状 #0。反正:

W/Adreno-GSL:<gsl_ldd_control:475>:ioctl fd 28 代码 0xc01c0915 (IOCTL_KGSL_MAP_USER_MEM) 失败:errno 12内存不足

W/Adreno-EGLSUB: SyncBackBuffer:3130:无法为 fd=281 offs=0 映射内存

E/Adreno-EGLSUB:SyncBackBuffer:3131:SyncBackBuffer:致命错误:(空)

A/Adreno-GSL:从函数 SyncBackBuffer 和第 3131 行退出进程 com.example.roselawncam

A/libc:致命信号 6 (SIGABRT),tid 19618 中的代码 -6 (CameraDeviceGLT)

最后,相关的 Kotlin 片段:

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)
}
Run Code Online (Sandbox Code Playgroud)

rap*_*bgr 0

我有一些建议:

  • 在需要的地方强制使用同步函数,file lock以便线程按顺序执行该代码块,file stream以有组织的方式打开和关闭,例如,访问文件。这样就可以避免out of memory错误file already in use

  • 是否可以不将文件编码为Base 64?字符串太大,检查是否避免出现任何错误。

以及制作自己的应用程序的好倡议。