Share media playback devices and await cleanup before calls
This commit is contained in:
1 parent
a893f6b4f2
commit
bebc579180
3 files changed
+21
-3
No files matched your search
@@ -207,7 +207,7 @@ object CallController : TelecomCallManager.Listener {
|
||||
val radioStopped = RadioAudioEngine.instance().stopForCall()
|
||||
StreamController.stopForCall()
|
||||
StreamPlayerEngine.stopForCall()
|
||||
withContext(Dispatchers.IO) { radioStopped.get() }
|
||||
withContext(Dispatchers.IO) { radioStopped.get(); MediaAudioController.awaitIdle() }
|
||||
val current = _state.value
|
||||
if (current?.callId != id || !current.ongoing) {
|
||||
LogManager.addLog("INFO", "Call", "media released after call cancellation id=$id")
|
||||
|
||||
@@ -12,6 +12,7 @@ internal class StreamAudioOutput(
|
||||
private val log: (String) -> Unit = {}
|
||||
) {
|
||||
internal interface Sink {
|
||||
val routeChanging: Boolean get() = false
|
||||
val bufferFrames: Int
|
||||
val threshold: Int
|
||||
val head: Long
|
||||
@@ -23,6 +24,8 @@ internal class StreamAudioOutput(
|
||||
}
|
||||
|
||||
private class AndroidSink : Sink {
|
||||
override val routeChanging: Boolean get() = MediaAudioController.state.value.switching
|
||||
private var route: AutoCloseable? = null
|
||||
private val track = AudioTrack.Builder()
|
||||
.setAudioAttributes(AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA)
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_MOVIE).build())
|
||||
@@ -35,6 +38,7 @@ internal class StreamAudioOutput(
|
||||
check(track.state == AudioTrack.STATE_INITIALIZED) { "AudioTrack initialization failed" }
|
||||
// API 26..30 не умеет уменьшать порог отдельно от аппаратного буфера.
|
||||
if (Build.VERSION.SDK_INT >= 31) track.setStartThresholdInFrames(1)
|
||||
route = MediaAudioController.attachOutput(track)
|
||||
} catch (e: Exception) { track.release(); throw e }
|
||||
}
|
||||
override val bufferFrames: Int get() = track.bufferSizeInFrames
|
||||
@@ -46,7 +50,7 @@ internal class StreamAudioOutput(
|
||||
override fun write(pcm: ShortArray, offset: Int, count: Int) = track.write(pcm, offset, count, AudioTrack.WRITE_NON_BLOCKING)
|
||||
override fun pause() = track.pause()
|
||||
override fun play() = track.play()
|
||||
override fun close() = track.release()
|
||||
override fun close() { route?.close(); route = null; track.release() }
|
||||
}
|
||||
|
||||
private var sink: Sink? = null
|
||||
@@ -132,7 +136,7 @@ internal class StreamAudioOutput(
|
||||
poll(now)
|
||||
// До достаточного заполнения ожидание сети не является зависанием устройства.
|
||||
val expectedProgress = written + padded - deviceHead >= s.threshold || blocked
|
||||
if (!expectedProgress) stalledSince = -1
|
||||
if (!expectedProgress || s.routeChanging) stalledSince = -1
|
||||
else if (stalledSince < 0) stalledSince = now
|
||||
else check(now - stalledSince < 2_000_000_000) {
|
||||
"AudioTrack made no playback progress for ${(now - stalledSince) / 1_000_000}ms; $description"
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.util.ArrayDeque
|
||||
|
||||
class StreamAudioOutputTest {
|
||||
private class Device(override val threshold: Int = 1) : StreamAudioOutput.Sink {
|
||||
override var routeChanging = false
|
||||
override val bufferFrames = maxOf(960, threshold)
|
||||
override var head = 0L
|
||||
override val description: String get() = "fake threshold=$threshold"
|
||||
@@ -84,6 +85,19 @@ class StreamAudioOutputTest {
|
||||
assertTrue(p.output.drained)
|
||||
}
|
||||
|
||||
@Test fun routeSwitchPreservesPlayerAndClockUntilOutputResumes() {
|
||||
val p = Player(); p.input.add(ShortArray(960) { 7 }); p.step(0)
|
||||
p.device.routeChanging = true
|
||||
var videoSteps = 0
|
||||
for (ms in 5L..30000L step 5) { p.step(ms); videoSteps++ }
|
||||
assertEquals(6000, videoSteps)
|
||||
assertFalse(p.device.closed); assertEquals(0L, p.output.played)
|
||||
assertEquals(List(960) { 7.toShort() }, p.device.samples)
|
||||
p.device.routeChanging = false
|
||||
p.device.head = 480; p.step(30005)
|
||||
assertTrue(p.output.drained)
|
||||
}
|
||||
|
||||
@Test fun intentionalPausePreservesPartialPcmAndSuspendsWatchdog() {
|
||||
val p = Player(); val pcm = ShortArray(960) { it.toShort() }; p.input.add(pcm); p.device.limit = 120
|
||||
p.step(0); p.step(5, true); p.step(10000, true)
|
||||
|
||||
Reference in new issue
Block a user