Preserve capture camera and session ownership through late callbacks
This commit is contained in:
1 parent
fd1e3a7f77
commit
5e9f7e01e0
1 file changed
+43
-25
@@ -255,34 +255,48 @@ class StreamCaptureEngine(
|
||||
try {
|
||||
manager.openCamera(mode.camera, object : CameraDevice.StateCallback() {
|
||||
override fun onOpened(device: CameraDevice) {
|
||||
if (openingGeneration == token) { opening = false; camera = device }
|
||||
if (!current(token)) { device.close(); return }
|
||||
if (device in closedCameras) { LogManager.addLog("WARN", "video", "closed capture camera callback ignored id=$id token=$token"); return }
|
||||
if (openingGeneration == token) opening = false
|
||||
if (!current(token)) { closingCameras.add(device); queueCleanup(0); return }
|
||||
camera = device
|
||||
createSession(device, config, token)
|
||||
}
|
||||
override fun onClosed(device: CameraDevice) {
|
||||
if (!closedCameras.add(device)) { LogManager.videoTrace("duplicate capture camera close id=$id token=$token"); return }
|
||||
if (camera === device) camera = null
|
||||
closingCameras.remove(device)
|
||||
val pending = cameraSessions.values.filter { it.first === device }.map { it.second }
|
||||
LogManager.videoTrace("camera closed id=$id generation=$token retiringSessions=${pending.size}")
|
||||
pending.forEach { it("cameraClosed") }
|
||||
if (releaseDone != null && camera == null && !opening) finishRelease(true)
|
||||
maybeQuit()
|
||||
}
|
||||
override fun onDisconnected(device: CameraDevice) {
|
||||
if (openingGeneration == token) { opening = false; camera = device }
|
||||
device.close(); captureError(token, "camera disconnected")
|
||||
}
|
||||
override fun onError(device: CameraDevice, error: Int) {
|
||||
if (openingGeneration == token) { opening = false; camera = device }
|
||||
device.close(); captureError(token, "camera error=$error")
|
||||
ownedSessions.entries.removeAll { it.value === device }; closingSessions.removeAll { it !in ownedSessions }
|
||||
queueCleanup(0)
|
||||
}
|
||||
override fun onDisconnected(device: CameraDevice) { cameraFailure(device, token, "camera disconnected") }
|
||||
override fun onError(device: CameraDevice, error: Int) { cameraFailure(device, token, "camera error=$error") }
|
||||
}, handler)
|
||||
} catch (e: Exception) { opening = false; throw e }
|
||||
handler.postDelayed({ if (current(token) && (waitingFirst || confirming)) captureError(token, "keyframe/capture rate validation timeout") }, 8000)
|
||||
} catch (e: Exception) {
|
||||
val awaitsCallback = e is CameraAccessException && e.reason in listOf(CameraAccessException.CAMERA_IN_USE,
|
||||
CameraAccessException.CAMERA_DISABLED, CameraAccessException.CAMERA_DISCONNECTED)
|
||||
if (!awaitsCallback && openingGeneration == token) opening = false
|
||||
LogManager.addLog("ERROR", "video", "capture camera open failed id=$id token=$token awaitingCallback=$awaitsCallback: $e")
|
||||
throw e
|
||||
}
|
||||
check(handler.postDelayed({ if (current(token) && (waitingFirst || confirming))
|
||||
captureError(token, "keyframe/capture rate validation timeout") }, 8000)) { "Capture validation timer rejected" }
|
||||
} catch (e: Exception) { captureError(token, "video setup: ${e.message}") }
|
||||
}
|
||||
|
||||
private fun current(token: Long) = token == generation && !stopping && !stopped && !terminalFailure && releaseDone == null
|
||||
|
||||
private fun cameraFailure(device: CameraDevice, token: Long, detail: String) {
|
||||
if (device in closedCameras) { LogManager.addLog("WARN", "video", "closed capture camera error ignored id=$id token=$token: $detail"); return }
|
||||
if (openingGeneration == token) opening = false
|
||||
closingCameras.add(device)
|
||||
if (current(token)) { camera = device; captureError(token, detail) }
|
||||
else LogManager.addLog("WARN", "video", "stale capture camera error id=$id token=$token/$generation: $detail")
|
||||
queueCleanup(0)
|
||||
}
|
||||
|
||||
/** Меняется только набор Camera2 outputs; encoder, audio и origin не перезапускаются. */
|
||||
private fun createSession(device: CameraDevice, config: StreamCaptureConfig, token: Long, withPreview: Boolean = true) {
|
||||
val mode = config.mode
|
||||
@@ -294,7 +308,7 @@ class StreamCaptureEngine(
|
||||
if (completed) return
|
||||
completed = true; cameraSessions.remove(serial); output?.release()
|
||||
LogManager.videoTrace("camera session output released id=$id session=$serial reason=$reason remaining=${cameraSessions.size}")
|
||||
maybeQuit()
|
||||
if (releaseDone != null || stopping) queueCleanup(0)
|
||||
}
|
||||
cameraSessions[serial] = device to ::complete
|
||||
fun valid() = current(token) && serial == sessionGeneration
|
||||
@@ -305,6 +319,7 @@ class StreamCaptureEngine(
|
||||
preview.update(format, failed = true)
|
||||
createSession(device, config, token, withPreview = false)
|
||||
}
|
||||
var submitted = false
|
||||
try {
|
||||
if (output != null) {
|
||||
val characteristics = manager.getCameraCharacteristics(mode.camera)
|
||||
@@ -323,7 +338,9 @@ class StreamCaptureEngine(
|
||||
val targets = listOfNotNull(checkNotNull(input) { "Missing encoder surface" }, output?.surface)
|
||||
val callback = object : CameraCaptureSession.StateCallback() {
|
||||
override fun onConfigured(configured: CameraCaptureSession) {
|
||||
if (!valid()) { configured.close(); return }
|
||||
if (completed) { LogManager.addLog("WARN", "video", "closed capture session callback ignored id=$id serial=$serial"); return }
|
||||
ownedSessions[configured] = device
|
||||
if (!valid()) { closingSessions.add(configured); queueCleanup(0); return }
|
||||
session = configured
|
||||
try {
|
||||
val request = device.createCaptureRequest(CameraDevice.TEMPLATE_RECORD).apply {
|
||||
@@ -360,26 +377,27 @@ class StreamCaptureEngine(
|
||||
LogManager.addLog("INFO", "video", "capture started id=$id generation=$token session=$serial camera=${mode.camera} ${mode.label} " +
|
||||
"range=${mode.minimumFps}..${mode.fps} highSpeed=${mode.highSpeed} encoder=${encoder?.name} " +
|
||||
"preview=${output != null} previewSize=${format?.let { "${it.width}x${it.height}" }} origin=$originNs")
|
||||
} catch (e: Exception) { configured.close(); rejected("camera request: ${e.message}") }
|
||||
} catch (e: Exception) { closingSessions.add(configured); queueCleanup(0); rejected("camera request: ${e.message}") }
|
||||
}
|
||||
override fun onConfigureFailed(session: CameraCaptureSession) {
|
||||
complete("configureFailed"); rejected("camera session configuration")
|
||||
}
|
||||
override fun onClosed(closed: CameraCaptureSession) {
|
||||
ownedSessions.remove(closed); closingSessions.remove(closed)
|
||||
if (session === closed) session = null
|
||||
complete("sessionClosed")
|
||||
}
|
||||
}
|
||||
LogManager.videoTrace("camera session configuring id=$id generation=$token session=$serial outputs=${targets.size}")
|
||||
submitted = true
|
||||
if (mode.highSpeed) device.createConstrainedHighSpeedCaptureSession(targets, callback, handler)
|
||||
else device.createCaptureSession(targets, callback, handler)
|
||||
} catch (e: Exception) { complete("exception"); rejected("camera session: ${e.message}") }
|
||||
}
|
||||
|
||||
private fun maybeQuit() {
|
||||
if (stopped && camera == null && !opening && cameraSessions.isEmpty()) {
|
||||
LogManager.addLog("INFO", "video", "capture handler released id=$id pendingSessions=0")
|
||||
thread.quitSafely()
|
||||
} catch (e: Exception) {
|
||||
if (!submitted) { complete("exception before submit"); rejected("camera session: ${e.message}") }
|
||||
else {
|
||||
LogManager.addLog("ERROR", "video", "capture session submit failed id=$id serial=$serial; outputs retained until device closes: $e")
|
||||
captureError(token, "camera session: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,7 +408,7 @@ class StreamCaptureEngine(
|
||||
if (previous == null || reverting) { failed(detail); return }
|
||||
waitingFirst = true; reverting = true
|
||||
LogManager.addLog("WARN", "video", "restoring camera=${previous.mode.camera} ${previous.mode.label} orientation=${previous.orientation} id=$id")
|
||||
releaseVideo { closed -> if (closed) configure(previous) else failed("camera close timed out during restore") }
|
||||
releaseVideo { configure(previous) }
|
||||
}
|
||||
|
||||
private fun encoded(bytes: ByteArray, info: MediaCodec.BufferInfo, config: StreamCaptureConfig, pose: StreamPreviewCamera, token: Long) {
|
||||
|
||||
Reference in new issue
Block a user