From eb62d05dc1b903173ae0c05b7da673c982b68c74 Mon Sep 17 00:00:00 2001 From: Caleb Hulbert Date: Wed, 5 Jul 2023 17:14:12 -0400 Subject: [PATCH] Fix/alsa wait (#1770) * fix: use `snd_pcm_drop` to stop sound immediately * fix: add and remove sample use in `wait()` previously, `wait()` was returnign immediately, but `stop()` was not stopping properly (drain, instead of drop), so it had the impression of working. * check number of frames left to play, and wait until less than one period worth of frames remaining Fixes the issue where sounds where not properly waiting to play before finishing. Would cause only a very small portion of the start of the sound to play. * lock modification to `availableSamples` --- .../korlibs/audio/sound/SoundAudioStream.kt | 1 + .../korlibs/audio/sound/backends/ALSA.kt | 32 ++++++++++++++++--- .../korlibs/audio/sound/backends/ALSAImpl.kt | 9 ++++++ .../korlibs/audio/sound/backends/ALSAImpl.kt | 11 +++++++ 4 files changed, 48 insertions(+), 5 deletions(-) diff --git a/korau/src/commonMain/kotlin/korlibs/audio/sound/SoundAudioStream.kt b/korau/src/commonMain/kotlin/korlibs/audio/sound/SoundAudioStream.kt index b7988956b7..c1a986c35b 100644 --- a/korau/src/commonMain/kotlin/korlibs/audio/sound/SoundAudioStream.kt +++ b/korau/src/commonMain/kotlin/korlibs/audio/sound/SoundAudioStream.kt @@ -88,6 +88,7 @@ class SoundAudioStream( } } catch (e: CancellationException) { // Do nothing + nas.stop() params.onCancel?.invoke() } finally { nas.wait() diff --git a/korau/src/commonMain/kotlin/korlibs/audio/sound/backends/ALSA.kt b/korau/src/commonMain/kotlin/korlibs/audio/sound/backends/ALSA.kt index 0e50f3a9a6..ef51dc881a 100644 --- a/korau/src/commonMain/kotlin/korlibs/audio/sound/backends/ALSA.kt +++ b/korau/src/commonMain/kotlin/korlibs/audio/sound/backends/ALSA.kt @@ -33,6 +33,7 @@ class ALSAPlatformAudioOutput( coroutineContext: CoroutineContext, frequency: Int, ) : PlatformAudioOutput(coroutineContext, frequency) { + override var availableSamples: Int = 0 val channels = 2 private val lock = Lock() val sdeque = AudioSamplesDeque(channels) @@ -45,7 +46,8 @@ class ALSAPlatformAudioOutput( } override suspend fun add(samples: AudioSamples, offset: Int, size: Int) { - if (!ASound2.initialized) return super.add(samples, offset, size) + if (size == 0) return + if (!ASound2.initialized) return super.add(samples, offset, samples.totalSamples) //if (!running) start() if (!running) delay(10.milliseconds) @@ -53,7 +55,10 @@ class ALSAPlatformAudioOutput( while (running && lock { sdeque.availableRead > 4 * 1024 }) { delay(10.milliseconds) } - lock { sdeque.write(samples, offset, size) } + lock { + availableSamples += samples.totalSamples + sdeque.write(samples, offset, samples.totalSamples) + } } override fun start() { @@ -70,6 +75,9 @@ class ALSAPlatformAudioOutput( } //println("START!") sdeque.clear() + lock { + availableSamples = 0 + } if (!ASound2.initialized) return @@ -127,16 +135,26 @@ class ALSAPlatformAudioOutput( buff[n * channels + ch] = (samples[ch, n] * rscale).toInt().toShort() } } + if (!running) break val result = ASound2.snd_pcm_writei(pcm, buff, frames) //println("result=$result") if (result == -ASound2.EPIPE) { ASound2.snd_pcm_prepare(pcm) + } else { + while (running && ASound2.snd_pcm_delay(pcm) > frames) { + blockingSleep(10.milliseconds) + } + } + lock { + availableSamples -= samples.totalSamples } } } finally { //println("COMPLETED: $pcm") thread = null -// stop() + lock { + availableSamples = 0 + } } }.also { it.isDaemon = true @@ -148,9 +166,11 @@ class ALSAPlatformAudioOutput( running = false if (pcm != 0L) { - ASound2.snd_pcm_drain(pcm) + ASound2.snd_pcm_drop(pcm) ASound2.snd_pcm_close(pcm) - //println("ASound2.snd_pcm_close: ${pcm}") + lock { + availableSamples = 0 + } pcm = 0L } } @@ -181,6 +201,8 @@ interface ASound2 { fun snd_pcm_writei(pcm: Long, buffer: ShortArray, size: Int): Int = ERROR fun snd_pcm_prepare(pcm: Long): Int = ERROR fun snd_pcm_drain(pcm: Long): Int = ERROR + fun snd_pcm_drop(pcm: Long): Int = ERROR + fun snd_pcm_delay(pcm: Long): Int = ERROR fun snd_pcm_close(pcm: Long): Int = ERROR companion object : ASound2 by ASoundImpl { diff --git a/korau/src/jvmMain/kotlin/korlibs/audio/sound/backends/ALSAImpl.kt b/korau/src/jvmMain/kotlin/korlibs/audio/sound/backends/ALSAImpl.kt index 5057a498e6..4974ed2395 100644 --- a/korau/src/jvmMain/kotlin/korlibs/audio/sound/backends/ALSAImpl.kt +++ b/korau/src/jvmMain/kotlin/korlibs/audio/sound/backends/ALSAImpl.kt @@ -47,6 +47,12 @@ actual object ASoundImpl : ASound2 { return tempOut.getInt(0L) } + override fun snd_pcm_delay(pcm: Long): Int { + val tempDelay = Memory(4).also { it.clear() } + A2.snd_pcm_delay(pcm.toCPointer(), tempDelay) + return tempDelay.getInt(0L) + } + override fun snd_pcm_writei(pcm: Long, buffer: ShortArray, size: Int): Int { val mem = Memory((buffer.size * 2).toLong()).also { it.clear() } for (n in 0 until buffer.size) mem.setShort((n * 2).toLong(), buffer[n]) @@ -54,6 +60,7 @@ actual object ASoundImpl : ASound2 { } override fun snd_pcm_prepare(pcm: Long): Int = A2.snd_pcm_prepare(pcm.toCPointer()) + override fun snd_pcm_drop(pcm: Long): Int = A2.snd_pcm_drop(pcm.toCPointer()) override fun snd_pcm_drain(pcm: Long): Int = A2.snd_pcm_drain(pcm.toCPointer()) override fun snd_pcm_close(pcm: Long): Int = A2.snd_pcm_close(pcm.toCPointer()) } @@ -76,6 +83,8 @@ object A2 { @JvmStatic external fun snd_pcm_writei(pcm: Pointer?, buffer: Pointer?, size: Int): Int @JvmStatic external fun snd_pcm_prepare(pcm: Pointer?): Int @JvmStatic external fun snd_pcm_drain(pcm: Pointer?): Int + @JvmStatic external fun snd_pcm_drop(pcm: Pointer?): Int + @JvmStatic external fun snd_pcm_delay(pcm: Pointer?, delay: Pointer?): Int @JvmStatic external fun snd_pcm_close(pcm: Pointer?): Int init { diff --git a/korau/src/nativeMain/kotlin/korlibs/audio/sound/backends/ALSAImpl.kt b/korau/src/nativeMain/kotlin/korlibs/audio/sound/backends/ALSAImpl.kt index 2a345cede8..adcf946d92 100644 --- a/korau/src/nativeMain/kotlin/korlibs/audio/sound/backends/ALSAImpl.kt +++ b/korau/src/nativeMain/kotlin/korlibs/audio/sound/backends/ALSAImpl.kt @@ -38,12 +38,21 @@ actual object ASoundImpl : ASound2 { } } + override fun snd_pcm_delay(params: Long): Int { + memScoped { + val out = alloc() + A2.snd_pcm_delay(params.toCPointer(), out.ptr) + return out.value.toInt() + } + } + override fun snd_pcm_writei(pcm: Long, buffer: ShortArray, size: Int): Int = buffer.usePinned { A2.snd_pcm_writei(pcm.toCPointer(), it.startAddressOf, size) } override fun snd_pcm_prepare(pcm: Long): Int = A2.snd_pcm_prepare(pcm.toCPointer()) override fun snd_pcm_drain(pcm: Long): Int = A2.snd_pcm_drain(pcm.toCPointer()) + override fun snd_pcm_drop(pcm: Long): Int = A2.snd_pcm_drop(pcm.toCPointer()) override fun snd_pcm_close(pcm: Long): Int = A2.snd_pcm_close(pcm.toCPointer()) } @@ -67,6 +76,8 @@ internal object A2 : DynamicLibrary("libasound.so.2") { val snd_pcm_writei by func<(pcm: COpaquePointer?, buffer: COpaquePointer?, size: Int) -> Int>() val snd_pcm_prepare by func<(pcm: COpaquePointer?) -> Int>() val snd_pcm_drain by func<(pcm: COpaquePointer?) -> Int>() + val snd_pcm_drop by func<(pcm: COpaquePointer?) -> Int>() + val snd_pcm_delay by func<(pcm: COpaquePointer?, delay: COpaquePointer?) -> Int>() val snd_pcm_close by func<(pcm: COpaquePointer?) -> Int>() }