我正试图把一部旧手机变成一个联网的安全摄像头,因为在所有这些骚乱期间,我所在地区的犯罪率急剧上升(我不想依赖别人的应用程序来控制对我私人时刻的访问)。
我正在进行分块和发送,所以相机会记录几秒钟的视频,停止,将捕获的文件的二进制编码到Base64中,然后通过POST请求将其发送到家庭服务器,所有这些都是无休止的循环。服务器将其展开+解码+保存为原始二进制"0";MP4";到自己的磁盘上(TODO:用于运动检测的有趣的后处理)。
在我的目标手机的操作系统版本&屏幕大小,这一切都适用于长时间。我已经用了60秒的时间段15分钟以上,再加上6秒的时间长达一个多小时。我一直收到模拟器在我的服务器上制作的愚蠢的虚拟房间视频。
但在运行安卓6.0.1的三星Galaxy S5上,它梦想成为一款安全摄像头,通常需要发送2到3个视频,然后应用程序才会崩溃。。。除非你的决心设定得太高,否则你会遇到不同的症状。
症状#0:在Chunk结束时崩溃
E/Parcel:dup()在Parcel::read中失败,i为1,fds[i]为-1,fd_count为2,错误:打开的文件太多
E/Surface:dequeueBuffer:IGraphicBufferProducer::requestBuffer失败:-22
W/Adreno EGLSUB:出队缓冲区:721:出队本机缓冲区失败:无效参数,缓冲区=0x0,句柄=0x0
W/Adreno EGL:<qeglDrvAPI_eglSwapBuffers:3800>:EGL_BAD_SURFACE
紧接着是第二个错误:
E/CameraDeviceGLThread-1:在GL渲染线程上接收到异常:
java.lang.IllegalStateException: swapBuffers: EGL error: 0x300d
最后,一旦区块时间到了,相机再次进行记录,就会出现最终错误,导致整个应用程序崩溃:
I/CameraDeviceState:传统相机服务转换到状态ERROR
E/AndroidRuntime:致命异常:CameraThread
Process: com.example.roselawncam, PID: 14639
android.hardware.camera2.CameraAccessException:相机设备遇到严重错误
症状#1:因为你为了自己的利益太高而在Chunk中间崩溃
这些警告清楚地表明,资源紧张导致了这种症状。它们发生在应用程序崩溃时,更高的分辨率会导致更快的崩溃。我在记录了这些坏男孩
- 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:致命错误:(null)
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)
}
打开的文件太多
症状#0的第一条错误消息是
E/Parcel: dup() failed in Parcel::read, i is 1, fds[i] is -1, fd_count is 2, error: Too many open files
来自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));
}
}
这表明上述错误发生在
fds[i] = dup(this->readFileDescriptor());
我还搜索了Too many open files
,发现了一个类似的问题。它有一个详细的错误日志和一个答案。两者都再次指示文件描述符。
根本原因可能是
params["videodata"] = convertToBase64(outputFile)
请检查convertToBase64()
的执行情况。
根据这个和这个,文件描述符可能会泄漏。
我有一些建议:
-
在需要
file lock
的地方强制使用同步函数,以便线程顺序执行该代码块,以有组织的方式打开和关闭file stream
,例如访问文件。这将避免out of memory
和file already in use
错误。 -
是否可以不将文件编码为
Base 64
?字符串太大,请检查它是否避免任何错误。
还有一个很好的计划来开发你自己的应用程序。