Adopt passive stream capture ownership before starting SDK resources
This commit is contained in:
1 parent
100d208a9a
commit
fd1e3a7f77
1 file changed
+33
-15
@@ -8,6 +8,7 @@ import android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession
|
||||
import android.hardware.camera2.CameraCharacteristics
|
||||
import android.hardware.camera2.CameraDevice
|
||||
import android.hardware.camera2.CameraManager
|
||||
import android.hardware.camera2.CameraAccessException
|
||||
import android.hardware.camera2.CaptureRequest
|
||||
import android.hardware.camera2.CaptureResult
|
||||
import android.hardware.camera2.TotalCaptureResult
|
||||
@@ -18,26 +19,33 @@ import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.HandlerThread
|
||||
import android.os.Looper
|
||||
import android.os.SystemClock
|
||||
import android.util.Range
|
||||
import android.view.OrientationEventListener
|
||||
import android.view.Surface
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
/** Camera/codec принадлежат HandlerThread; микрофон и origin сохраняются при смене видео.
|
||||
* Превью — второй выход Camera2. Поколения видео и camera session отсеивают старые callbacks. */
|
||||
/** Пассивный владелец: сервис сохраняет его до start на main. Camera/codec принадлежат HandlerThread;
|
||||
* микрофон и origin сохраняются при смене видео. Поколения отсеивают старые callbacks.
|
||||
* stop=true подтверждает все SDK/native DSP ресурсы; false сохраняет engine для повторного stop.
|
||||
*/
|
||||
class StreamCaptureEngine(
|
||||
private val context: Context, private val id: Long, private val group: Long,
|
||||
private val initialConfig: StreamCaptureConfig, private val microphone: Boolean, private val preview: StreamPreview,
|
||||
initialAudioCompressor: RadioCompressorSettings,
|
||||
private val ready: (StreamCaptureConfig, Boolean) -> Unit, private val failure: (String) -> Unit
|
||||
) {
|
||||
private val thread = HandlerThread("stream-camera").apply { start() }
|
||||
private val handler = Handler(thread.looper)
|
||||
companion object { private val retained = ConcurrentHashMap.newKeySet<StreamCaptureEngine>() }
|
||||
private val thread = HandlerThread("stream-camera")
|
||||
private lateinit var handler: Handler
|
||||
private var started = false
|
||||
private val manager = context.getSystemService(CameraManager::class.java)
|
||||
private var camera: CameraDevice? = null
|
||||
private var session: CameraCaptureSession? = null
|
||||
private var encoder: MediaCodec? = null
|
||||
private var encoderResources: VideoCodecResources? = null
|
||||
private val encoder: MediaCodec? get() = encoderResources?.codec
|
||||
private var input: Surface? = null
|
||||
private var activeConfig: StreamCaptureConfig? = null
|
||||
private var previewTarget: StreamPreviewTarget? = null
|
||||
@@ -49,12 +57,19 @@ class StreamCaptureEngine(
|
||||
@Volatile private var audioMuted = false
|
||||
@Volatile var audioCompressorSettings = initialAudioCompressor
|
||||
@Volatile private var stopped = false
|
||||
private var stopping = false
|
||||
@Volatile private var stopping = false
|
||||
private var terminalFailure = false
|
||||
private val stopCallbacks = mutableListOf<() -> Unit>()
|
||||
private val closingCameras = linkedSetOf<CameraDevice>()
|
||||
private val closedCameras = linkedSetOf<CameraDevice>()
|
||||
private val ownedSessions = linkedMapOf<CameraCaptureSession, CameraDevice>()
|
||||
private val closingSessions = linkedSetOf<CameraCaptureSession>()
|
||||
private var cleanupQueued = false
|
||||
private var cleanupDue = 0L
|
||||
private var cleanupDelay = 250L
|
||||
private val cleanupTask = Runnable { synchronized(this) { cleanupQueued = false }; cleanupResources() }
|
||||
private var opening = false
|
||||
private var openingGeneration = 0L
|
||||
private var releaseDone: ((Boolean) -> Unit)? = null
|
||||
private var releaseDone: (() -> Unit)? = null
|
||||
private var generation = 0L
|
||||
private var videoRevision = 0L
|
||||
private var videoRotation = 0
|
||||
@@ -97,14 +112,17 @@ class StreamCaptureEngine(
|
||||
|
||||
private fun observeOrientation(enabled: Boolean) {
|
||||
if (orientationEnabled == enabled) return
|
||||
orientationEnabled = enabled; orientation.resetPending()
|
||||
if (!enabled) orientationListener.disable()
|
||||
else if (orientationListener.canDetectOrientation()) orientationListener.enable()
|
||||
orientation.resetPending()
|
||||
if (!enabled) { orientationListener.disable(); orientationEnabled = false }
|
||||
else if (orientationListener.canDetectOrientation()) { orientationEnabled = true; orientationListener.enable() }
|
||||
else LogManager.addLog("WARN", "video", "stream orientation sensor unavailable id=$id; using ${orientation.degrees}")
|
||||
LogManager.videoTrace("stream orientation monitoring id=$id enabled=$enabled")
|
||||
}
|
||||
|
||||
fun start() {
|
||||
check(Looper.myLooper() == Looper.getMainLooper() && !stopping && !stopped) { "Capture start requires main and a live owner" }
|
||||
if (started) { LogManager.addLog("WARN", "stream", "duplicate capture start id=$id"); return }
|
||||
started = true; thread.start(); handler = Handler(thread.looper)
|
||||
preview.bind { target ->
|
||||
if (!handler.post {
|
||||
if (target?.released == true) { LogManager.addLog("WARN", "video", "cancelled preview command id=$id"); return@post }
|
||||
@@ -121,7 +139,7 @@ class StreamCaptureEngine(
|
||||
target?.release()
|
||||
}
|
||||
}
|
||||
handler.post {
|
||||
check(handler.post {
|
||||
if (stopping || stopped) return@post
|
||||
try {
|
||||
val mode = initialConfig.mode
|
||||
@@ -133,7 +151,7 @@ class StreamCaptureEngine(
|
||||
if (microphone) startAudio()
|
||||
configure(initialConfig)
|
||||
} catch (e: Exception) { failed("setup: ${e.message}") }
|
||||
}
|
||||
}) { LogManager.addLog("ERROR", "stream", "capture startup post rejected id=$id; partial owner retained"); "Capture start rejected" }
|
||||
}
|
||||
|
||||
fun change(config: StreamCaptureConfig) {
|
||||
@@ -155,7 +173,7 @@ class StreamCaptureEngine(
|
||||
}
|
||||
waitingFirst = true; reverting = false; switchStartedNs = System.nanoTime()
|
||||
LogManager.addLog("INFO", "video", "switch capture id=$id camera=${config.mode.camera} ${config.mode.label} orientation=${config.orientation}")
|
||||
releaseVideo { closed -> if (closed) configure(config) else failed("camera close timed out during switch") }
|
||||
releaseVideo { configure(config) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +208,7 @@ class StreamCaptureEngine(
|
||||
setInteger(MediaFormat.KEY_FRAME_RATE, mode.fps); setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1)
|
||||
if (Build.VERSION.SDK_INT >= 29) setInteger(MediaFormat.KEY_MAX_B_FRAMES, 0)
|
||||
}
|
||||
encoder = MediaCodec.createByCodecName(mode.codec)
|
||||
encoderResources = VideoCodecResources(MediaCodec.createByCodecName(mode.codec), mode.codec)
|
||||
encoder!!.also { codec ->
|
||||
codec.setCallback(object : MediaCodec.Callback() {
|
||||
override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {}
|
||||
|
||||
Reference in new issue
Block a user