Match stream audio queue to device buffer and document playback ownership
This commit is contained in:
1 parent
0fdc6e435b
commit
ded0d401c7
3 files changed
+24
-2
No files matched your search
@@ -12,6 +12,7 @@ internal class StreamAudioOutput(
|
||||
private val log: (String) -> Unit = {}
|
||||
) {
|
||||
internal interface Sink {
|
||||
val bufferFrames: Int
|
||||
val threshold: Int
|
||||
val head: Long
|
||||
val description: String
|
||||
@@ -36,6 +37,7 @@ internal class StreamAudioOutput(
|
||||
if (Build.VERSION.SDK_INT >= 31) track.setStartThresholdInFrames(1)
|
||||
} catch (e: Exception) { track.release(); throw e }
|
||||
}
|
||||
override val bufferFrames: Int get() = track.bufferSizeInFrames
|
||||
override val threshold: Int get() = if (Build.VERSION.SDK_INT >= 31)
|
||||
minOf(track.startThresholdInFrames, track.bufferSizeInFrames) else track.bufferSizeInFrames
|
||||
override val head: Long get() = track.playbackHeadPosition.toLong() and 0xffffffffL
|
||||
@@ -79,12 +81,13 @@ internal class StreamAudioOutput(
|
||||
}
|
||||
previousHead = raw
|
||||
val head = headEpoch + raw
|
||||
val first = deviceHead == 0L && head > 0
|
||||
if (head > deviceHead) {
|
||||
if (deviceHead == 0L) log("audio started delayMs=${(now - openedAt) / 1_000_000} $description")
|
||||
stalledSince = -1
|
||||
}
|
||||
deviceHead = head
|
||||
played = head.coerceAtMost(written)
|
||||
if (first) log("audio started delayMs=${(now - openedAt) / 1_000_000} $description")
|
||||
}
|
||||
|
||||
/** Ровно одна неблокирующая запись за итерацию; ожидание устройства не задерживает video pump. */
|
||||
@@ -96,7 +99,8 @@ internal class StreamAudioOutput(
|
||||
log("audio ${if (hold) "paused" else "resumed"} $description")
|
||||
}
|
||||
if (hold) return
|
||||
if (pending == null && written - played < maxOf(960, sink?.threshold ?: 1)) {
|
||||
// Размер устройства уже учитывает его период обслуживания; 20ms могут быть меньше одного периода.
|
||||
if (pending == null && written - played < (sink?.bufferFrames ?: 1)) {
|
||||
val pcm = pull()
|
||||
if (pcm != null) {
|
||||
check(pcm.isNotEmpty() && pcm.size <= 960 && pcm.size % 2 == 0) { "Invalid stream PCM size=${pcm.size}" }
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.util.ArrayDeque
|
||||
|
||||
class StreamAudioOutputTest {
|
||||
private class Device(override val threshold: Int = 1) : StreamAudioOutput.Sink {
|
||||
override val bufferFrames = maxOf(960, threshold)
|
||||
override var head = 0L
|
||||
override val description: String get() = "fake threshold=$threshold"
|
||||
val samples = ArrayList<Short>()
|
||||
@@ -59,6 +60,14 @@ class StreamAudioOutputTest {
|
||||
assertEquals(100L, p.output.written); assertEquals(0L, p.output.played)
|
||||
}
|
||||
|
||||
@Test fun feedsHardwareBufferEvenWhenStartThresholdIsSmall() {
|
||||
val p = Player(); repeat(4) { p.input.add(ShortArray(960)) }
|
||||
p.step(0); p.step(5); p.step(10)
|
||||
assertEquals(960L, p.output.written); assertEquals(2, p.pulls)
|
||||
p.device.head = 480; p.step(15)
|
||||
assertEquals(1440L, p.output.written); assertEquals(3, p.pulls)
|
||||
}
|
||||
|
||||
@Test fun clockDoesNotAdvanceWithWallTimeAndStalledDeviceFails() {
|
||||
val p = Player(); p.input.add(ShortArray(960)); p.step(0); p.step(1999)
|
||||
assertEquals(0L, p.output.played)
|
||||
|
||||
Reference in new issue
Block a user