summary refs log tree commit diff stats
path: root/src/main/kotlin/space/autistic/radio/opus
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/kotlin/space/autistic/radio/opus')
-rw-r--r--src/main/kotlin/space/autistic/radio/opus/OpusDecoder.kt77
-rw-r--r--src/main/kotlin/space/autistic/radio/opus/OpusFactory.kt26
2 files changed, 103 insertions, 0 deletions
diff --git a/src/main/kotlin/space/autistic/radio/opus/OpusDecoder.kt b/src/main/kotlin/space/autistic/radio/opus/OpusDecoder.kt
new file mode 100644
index 0000000..56fce2b
--- /dev/null
+++ b/src/main/kotlin/space/autistic/radio/opus/OpusDecoder.kt
@@ -0,0 +1,77 @@
+package space.autistic.radio.opus
+
+import com.dylibso.chicory.runtime.ByteBufferMemory
+import space.autistic.radio.reflection.getBuffer
+import java.nio.ByteOrder
+
+class OpusDecoder(sampleRate: Int, private val channels: Int) {
+    private val instance = OpusFactory()
+
+    init {
+        instance.export("_initialize").apply()
+    }
+
+    private val errorPtr = instance.export("malloc").apply(4)[0]
+
+    init {
+        if (errorPtr == 0L) {
+            throw IllegalStateException()
+        }
+        instance.memory().writeI32(errorPtr.toInt(), 0)
+    }
+
+    private val decoder =
+        instance.export("opus_decoder_create").apply(sampleRate.toLong(), channels.toLong(), errorPtr)[0]
+
+    init {
+        val error = instance.memory().readI32(errorPtr.toInt())
+        if (error < 0) {
+            throw IllegalStateException(
+                instance.memory().readCString(instance.export("opus_strerror").apply(error)[0].toInt())
+            )
+        }
+    }
+
+    private val opusDecodeFloat = instance.export("opus_decode_float")
+
+    private val outBuf = instance.export("malloc").apply((4 * MAX_FRAME_SIZE * channels).toLong())[0]
+
+    init {
+        if (outBuf == 0L) {
+            throw IllegalStateException()
+        }
+    }
+
+    private val cbits = instance.export("malloc").apply(MAX_PACKET_SIZE.toLong())[0]
+
+    init {
+        if (cbits == 0L) {
+            throw IllegalStateException()
+        }
+    }
+
+    private val memory = instance.memory() as ByteBufferMemory
+
+    fun decode(packet: ByteArray): FloatArray {
+        if (packet.size > MAX_PACKET_SIZE) {
+            throw IllegalArgumentException("packet too big")
+        }
+        memory.getBuffer().put(cbits.toInt(), packet)
+        val decoded =
+            opusDecodeFloat.apply(decoder, cbits, packet.size.toLong(), outBuf, MAX_FRAME_SIZE.toLong(), 0L)[0]
+        if (decoded < 0L) {
+            throw IllegalStateException(
+                instance.memory().readCString(instance.export("opus_strerror").apply(decoded)[0].toInt())
+            )
+        }
+        val out = FloatArray(decoded.toInt())
+        memory.getBuffer().slice(outBuf.toInt(), outBuf.toInt() + 4 * channels * decoded.toInt())
+            .order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer().get(out)
+        return out
+    }
+
+    companion object {
+        const val MAX_FRAME_SIZE = 6 * 960
+        const val MAX_PACKET_SIZE = 3 * 1276
+    }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/space/autistic/radio/opus/OpusFactory.kt b/src/main/kotlin/space/autistic/radio/opus/OpusFactory.kt
new file mode 100644
index 0000000..70e0c3c
--- /dev/null
+++ b/src/main/kotlin/space/autistic/radio/opus/OpusFactory.kt
@@ -0,0 +1,26 @@
+package space.autistic.radio.opus
+
+import com.dylibso.chicory.experimental.aot.AotMachineFactory
+import com.dylibso.chicory.runtime.ImportValues
+import com.dylibso.chicory.runtime.Instance
+import com.dylibso.chicory.wasm.Parser
+import net.fabricmc.loader.api.FabricLoader
+import java.io.InputStream
+
+object OpusFactory : () -> Instance {
+	private val defaultImports = ImportValues.builder().build()
+	private val module = Parser.parse(getModuleInputStream())
+	private val instanceBuilder =
+		Instance.builder(module)
+			.withMachineFactory(AotMachineFactory(module))
+			.withImportValues(defaultImports)
+
+	override fun invoke(): Instance = instanceBuilder.build()
+
+	private fun getModuleInputStream(): InputStream {
+		return FabricLoader.getInstance().getModContainer("pirate-radio").flatMap { it.findPath("opus.wasm") }
+			.map<InputStream?> { it.toFile().inputStream() }.orElseGet {
+				this.javaClass.getResourceAsStream("/opus.wasm")
+			}
+	}
+}
\ No newline at end of file