From ac23a2bd246df0e4d236b0708c46e51deafaeda2 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Sat, 3 Feb 2024 16:15:45 +0100 Subject: [PATCH] port audio engine to kotlin --- .../org/openbase/jul/audio/AudioData.java | 37 ----- .../java/org/openbase/jul/audio/AudioData.kt | 15 ++ .../org/openbase/jul/audio/AudioDataImpl.java | 85 ---------- .../org/openbase/jul/audio/AudioDataImpl.kt | 44 +++++ .../openbase/jul/audio/AudioFileHolder.java | 33 ---- .../org/openbase/jul/audio/AudioFileHolder.kt | 11 ++ .../org/openbase/jul/audio/AudioPlayer.java | 149 ----------------- .../org/openbase/jul/audio/AudioPlayer.kt | 150 ++++++++++++++++++ .../org/openbase/jul/audio/AudioSource.java | 31 ---- .../org/openbase/jul/audio/AudioSource.kt | 7 + 10 files changed, 227 insertions(+), 335 deletions(-) delete mode 100644 module/audio/src/main/java/org/openbase/jul/audio/AudioData.java create mode 100644 module/audio/src/main/java/org/openbase/jul/audio/AudioData.kt delete mode 100644 module/audio/src/main/java/org/openbase/jul/audio/AudioDataImpl.java create mode 100644 module/audio/src/main/java/org/openbase/jul/audio/AudioDataImpl.kt delete mode 100644 module/audio/src/main/java/org/openbase/jul/audio/AudioFileHolder.java create mode 100644 module/audio/src/main/java/org/openbase/jul/audio/AudioFileHolder.kt delete mode 100644 module/audio/src/main/java/org/openbase/jul/audio/AudioPlayer.java create mode 100644 module/audio/src/main/java/org/openbase/jul/audio/AudioPlayer.kt delete mode 100644 module/audio/src/main/java/org/openbase/jul/audio/AudioSource.java create mode 100644 module/audio/src/main/java/org/openbase/jul/audio/AudioSource.kt diff --git a/module/audio/src/main/java/org/openbase/jul/audio/AudioData.java b/module/audio/src/main/java/org/openbase/jul/audio/AudioData.java deleted file mode 100644 index df0618741..000000000 --- a/module/audio/src/main/java/org/openbase/jul/audio/AudioData.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.openbase.jul.audio; - -/*- - * #%L - * JUL Audio - * %% - * Copyright (C) 2015 - 2022 openbase.org - * %% - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Lesser Public License for more details. - * - * You should have received a copy of the GNU General Lesser Public - * License along with this program. If not, see - * . - * #L% - */ -import javax.sound.sampled.AudioFormat; - -/** - * - * @author Divine Threepwood - */ -public interface AudioData extends AudioSource { - - byte[] getData(); - - AudioFormat getFormat(); - - long getDataLenght(); -} diff --git a/module/audio/src/main/java/org/openbase/jul/audio/AudioData.kt b/module/audio/src/main/java/org/openbase/jul/audio/AudioData.kt new file mode 100644 index 000000000..eb241e8fa --- /dev/null +++ b/module/audio/src/main/java/org/openbase/jul/audio/AudioData.kt @@ -0,0 +1,15 @@ +package org.openbase.jul.audio + +import javax.sound.sampled.AudioFormat + +/** + * + * @author [Divine Threepwood](mailto:divine@openbase.org) + */ +interface AudioData : AudioSource { + val data: ByteArray + + val format: AudioFormat + + val dataLength: Long +} diff --git a/module/audio/src/main/java/org/openbase/jul/audio/AudioDataImpl.java b/module/audio/src/main/java/org/openbase/jul/audio/AudioDataImpl.java deleted file mode 100644 index 494026c33..000000000 --- a/module/audio/src/main/java/org/openbase/jul/audio/AudioDataImpl.java +++ /dev/null @@ -1,85 +0,0 @@ -package org.openbase.jul.audio; - -/*- - * #%L - * JUL Audio - * %% - * Copyright (C) 2015 - 2022 openbase.org - * %% - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Lesser Public License for more details. - * - * You should have received a copy of the GNU General Lesser Public - * License along with this program. If not, see - * . - * #L% - */ - -import static org.openbase.jul.audio.AudioPlayer.BUFSIZE; -import java.io.File; -import java.io.IOException; -import javax.sound.sampled.AudioFormat; -import javax.sound.sampled.AudioInputStream; -import javax.sound.sampled.AudioSystem; -import javax.sound.sampled.UnsupportedAudioFileException; -import org.openbase.jul.exception.CouldNotPerformException; - -/** - * - * @author Divine Threepwood - */ -public class AudioDataImpl implements AudioData { - - private final byte[] data; - private final AudioFormat format; - private final long dataLenght; - - public AudioDataImpl(final byte[] data, final AudioFormat format, final long dataLenght) { - this.data = data; - this.format = format; - this.dataLenght = dataLenght; - } - - public AudioDataImpl(final File soundFile) throws CouldNotPerformException, UnsupportedAudioFileException, IOException { - if (!soundFile.exists()) { - throw new CouldNotPerformException("AudioFile is missing!"); - } - - try (AudioInputStream ais = AudioSystem.getAudioInputStream(soundFile)) { - this.dataLenght= ais.getFrameLength(); - this.format = ais.getFormat(); - this.data = new byte[(int) ais.getFrameLength() * format.getFrameSize()]; - final byte[] buf = new byte[BUFSIZE]; - for (int i = 0; i < data.length; i += BUFSIZE) { - int r = ais.read(buf, 0, BUFSIZE); - if (i + r >= data.length) { - r = data.length - i; - } - System.arraycopy(buf, 0, data, i, r); - } - } - } - - - @Override - public byte[] getData() { - return data; - } - - @Override - public AudioFormat getFormat() { - return format; - } - - @Override - public long getDataLenght() { - return dataLenght; - } -} diff --git a/module/audio/src/main/java/org/openbase/jul/audio/AudioDataImpl.kt b/module/audio/src/main/java/org/openbase/jul/audio/AudioDataImpl.kt new file mode 100644 index 000000000..de5abf499 --- /dev/null +++ b/module/audio/src/main/java/org/openbase/jul/audio/AudioDataImpl.kt @@ -0,0 +1,44 @@ +package org.openbase.jul.audio + +import org.openbase.jul.exception.CouldNotPerformException +import java.io.File +import javax.sound.sampled.AudioFormat +import javax.sound.sampled.AudioSystem + +/** + * + * @author [Divine Threepwood](mailto:divine@openbase.org) + */ +class AudioDataImpl : AudioData { + override val data: ByteArray + override val format: AudioFormat + override val dataLength: Long + + constructor(data: ByteArray, format: AudioFormat, dataLength: Long) { + this.data = data + this.format = format + this.dataLength = dataLength + } + + constructor(soundFile: File) { + if (!soundFile.exists()) { + throw CouldNotPerformException("AudioFile is missing!") + } + + AudioSystem.getAudioInputStream(soundFile).use { ais -> + this.dataLength = ais.frameLength + this.format = ais.format + this.data = ByteArray(ais.frameLength.toInt() * format.frameSize) + val buf = ByteArray(AudioPlayer.BUFSIZE) + var i = 0 + while (i < data.size) { + var r = ais.read(buf, 0, AudioPlayer.BUFSIZE) + if (i + r >= data.size) { + r = data.size - i + } + System.arraycopy(buf, 0, data, i, r) + i += AudioPlayer.BUFSIZE + } + } + } +} diff --git a/module/audio/src/main/java/org/openbase/jul/audio/AudioFileHolder.java b/module/audio/src/main/java/org/openbase/jul/audio/AudioFileHolder.java deleted file mode 100644 index ea5d1f98f..000000000 --- a/module/audio/src/main/java/org/openbase/jul/audio/AudioFileHolder.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.openbase.jul.audio; - -/*- - * #%L - * JUL Audio - * %% - * Copyright (C) 2015 - 2022 openbase.org - * %% - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Lesser Public License for more details. - * - * You should have received a copy of the GNU General Lesser Public - * License along with this program. If not, see - * . - * #L% - */ - -import java.io.File; - -/** - * - * @author Divine Threepwood - */ -public interface AudioFileHolder extends AudioSource { - File getFile(); -} diff --git a/module/audio/src/main/java/org/openbase/jul/audio/AudioFileHolder.kt b/module/audio/src/main/java/org/openbase/jul/audio/AudioFileHolder.kt new file mode 100644 index 000000000..b18605a2c --- /dev/null +++ b/module/audio/src/main/java/org/openbase/jul/audio/AudioFileHolder.kt @@ -0,0 +1,11 @@ +package org.openbase.jul.audio + +import java.io.File + +/** + * + * @author [Divine Threepwood](mailto:divine@openbase.org) + */ +interface AudioFileHolder : AudioSource { + val file: File +} diff --git a/module/audio/src/main/java/org/openbase/jul/audio/AudioPlayer.java b/module/audio/src/main/java/org/openbase/jul/audio/AudioPlayer.java deleted file mode 100644 index 24f9c4d48..000000000 --- a/module/audio/src/main/java/org/openbase/jul/audio/AudioPlayer.java +++ /dev/null @@ -1,149 +0,0 @@ -package org.openbase.jul.audio; - -/*- - * #%L - * JUL Audio - * %% - * Copyright (C) 2015 - 2022 openbase.org - * %% - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Lesser Public License for more details. - * - * You should have received a copy of the GNU General Lesser Public - * License along with this program. If not, see - * . - * #L% - */ -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import javax.sound.sampled.AudioInputStream; -import javax.sound.sampled.AudioSystem; -import javax.sound.sampled.Clip; -import javax.sound.sampled.LineEvent; -import javax.sound.sampled.LineEvent.Type; -import javax.sound.sampled.LineListener; -import javax.sound.sampled.LineUnavailableException; -import javax.sound.sampled.UnsupportedAudioFileException; -import org.openbase.jul.exception.CouldNotPerformException; -import org.openbase.jul.exception.printer.ExceptionPrinter; -import org.openbase.jul.exception.printer.LogLevel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * @author Divine Threepwood - */ -public class AudioPlayer { - - public static final int BUFSIZE = 512; - private final ExecutorService executorService; - - private static final Logger logger = LoggerFactory.getLogger(AudioPlayer.class); - - public AudioPlayer(final int soundChannels) { - this.executorService = Executors.newFixedThreadPool(soundChannels); - } - - public AudioPlayer() { - this(10); - } - - public boolean playAudio(final AudioSource source) { - return playAudio(source, false); - } - - public boolean playAudio(final AudioSource source, final boolean wait) { - try { - if (wait) { - play(source); - return true; - } - executorService.execute(() -> { - try { - play(source); - } catch (IOException | UnsupportedAudioFileException | LineUnavailableException | InterruptedException ex) { - ExceptionPrinter.printHistory(new CouldNotPerformException("Could not play clip!", ex), logger, LogLevel.WARN); - } - }); - } catch (IOException | UnsupportedAudioFileException | LineUnavailableException | InterruptedException ex) { - ExceptionPrinter.printHistory(new CouldNotPerformException("Could not play clip!", ex), logger, LogLevel.WARN); - return false; - } - return true; - } - - private static void play(final AudioSource source) throws IOException, UnsupportedAudioFileException, LineUnavailableException, InterruptedException { - if (source instanceof AudioData) { - play((AudioData) source); - } else if (source instanceof AudioFileHolder) { - play(((AudioFileHolder) source).getFile()); - } else { - logger.warn("Unkown audio source! Skip clip..."); - assert false; - } - } - - private static void play(final AudioData audioData) throws IOException, UnsupportedAudioFileException, LineUnavailableException, InterruptedException { - - AudioInputStream audioInputStream = new AudioInputStream(new ByteArrayInputStream(audioData.getData()), audioData.getFormat(), audioData.getDataLenght()); -// - play(audioInputStream); -// play(AudioSystem.getAudioInputStream(new ByteArrayInputStream(audioData.getData()))); - -// SourceDataLine line = null; -// DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat); -// -// line = (SourceDataLine) AudioSystem.getLine(info); - } - - private static void play(final File clipFile) throws IOException, UnsupportedAudioFileException, LineUnavailableException, InterruptedException { - play(AudioSystem.getAudioInputStream(clipFile)); - } - - private static void play(final AudioInputStream audioInputStream) throws IOException, UnsupportedAudioFileException, LineUnavailableException, InterruptedException { - class AudioListener implements LineListener { - - private boolean done = false; - - @Override - public synchronized void update(final LineEvent event) { - final Type eventType = event.getType(); - if (eventType == Type.STOP || eventType == Type.CLOSE) { - done = true; - notifyAll(); - } - } - - public synchronized void waitUntilDone() throws InterruptedException { - while (!done) { - wait(); - } - } - } - final AudioListener listener = new AudioListener(); - try { - final Clip clip = AudioSystem.getClip(); - clip.addLineListener(listener); - clip.open(audioInputStream); - try { - clip.start(); - listener.waitUntilDone(); - } finally { - clip.close(); - } - } finally { - audioInputStream.close(); - } - } -} diff --git a/module/audio/src/main/java/org/openbase/jul/audio/AudioPlayer.kt b/module/audio/src/main/java/org/openbase/jul/audio/AudioPlayer.kt new file mode 100644 index 000000000..3bd4b3666 --- /dev/null +++ b/module/audio/src/main/java/org/openbase/jul/audio/AudioPlayer.kt @@ -0,0 +1,150 @@ +package org.openbase.jul.audio + +import org.openbase.jul.exception.CouldNotPerformException +import org.openbase.jul.exception.printer.ExceptionPrinter +import org.openbase.jul.exception.printer.LogLevel +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.io.ByteArrayInputStream +import java.io.File +import java.io.IOException +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors +import java.util.concurrent.locks.Condition +import java.util.concurrent.locks.ReentrantLock +import javax.sound.sampled.* +import kotlin.concurrent.withLock + +/** + * + * @author [Divine Threepwood](mailto:divine@openbase.org) + */ +class AudioPlayer @JvmOverloads constructor(soundChannels: Int = 10) { + private val executorService: ExecutorService = Executors.newFixedThreadPool(soundChannels) + + @JvmOverloads + fun playAudio(source: AudioSource, wait: Boolean = false): Boolean { + try { + if (wait) { + play(source) + return true + } + executorService.execute { + try { + play(source) + } catch (ex: IOException) { + ExceptionPrinter.printHistory( + CouldNotPerformException("Could not play clip!", ex), + logger, + LogLevel.WARN + ) + } catch (ex: UnsupportedAudioFileException) { + ExceptionPrinter.printHistory( + CouldNotPerformException("Could not play clip!", ex), + logger, + LogLevel.WARN + ) + } catch (ex: LineUnavailableException) { + ExceptionPrinter.printHistory( + CouldNotPerformException("Could not play clip!", ex), + logger, + LogLevel.WARN + ) + } + } + } catch (ex: IOException) { + ExceptionPrinter.printHistory(CouldNotPerformException("Could not play clip!", ex), logger, LogLevel.WARN) + return false + } catch (ex: UnsupportedAudioFileException) { + ExceptionPrinter.printHistory(CouldNotPerformException("Could not play clip!", ex), logger, LogLevel.WARN) + return false + } catch (ex: LineUnavailableException) { + ExceptionPrinter.printHistory(CouldNotPerformException("Could not play clip!", ex), logger, LogLevel.WARN) + return false + } + return true + } + + companion object { + const val BUFSIZE: Int = 512 + private val logger: Logger = LoggerFactory.getLogger(AudioPlayer::class.java) + + val lock = ReentrantLock() + val condition: Condition = lock.newCondition() + + @Throws( + IOException::class, + UnsupportedAudioFileException::class, + LineUnavailableException::class, + InterruptedException::class + ) + private fun play(source: AudioSource) = when (source) { + is AudioData -> play(source) + is AudioFileHolder -> play(source.file) + else -> { + logger.warn("Unknown audio source! Skip clip ...") + } + } + + @Throws( + IOException::class, + UnsupportedAudioFileException::class, + LineUnavailableException::class, + InterruptedException::class + ) + private fun play(audioData: AudioData) = AudioInputStream( + ByteArrayInputStream(audioData.data), + audioData.format, + audioData.dataLength + ).also { play(it) } + + @Throws( + IOException::class, + UnsupportedAudioFileException::class, + LineUnavailableException::class, + InterruptedException::class + ) + private fun play(clipFile: File?) = play(AudioSystem.getAudioInputStream(clipFile)) + + @Throws( + IOException::class, + UnsupportedAudioFileException::class, + LineUnavailableException::class, + InterruptedException::class + ) + private fun play(audioInputStream: AudioInputStream) { + class AudioListener : LineListener { + private var done = false + + override fun update(event: LineEvent) { + lock.withLock { + val eventType = event.type + if (eventType === LineEvent.Type.STOP || eventType === LineEvent.Type.CLOSE) { + done = true + condition.signalAll() + } + } + } + + @Throws(InterruptedException::class) + fun waitUntilDone() = lock.withLock { + while (!done) { + condition.await() + } + } + } + + AudioListener().also { listener -> + audioInputStream.use { + val clip = AudioSystem.getClip() + clip.addLineListener(listener) + clip.open(audioInputStream) + clip.use { + clip.start() + listener.waitUntilDone() + } + } + } + } + } +} diff --git a/module/audio/src/main/java/org/openbase/jul/audio/AudioSource.java b/module/audio/src/main/java/org/openbase/jul/audio/AudioSource.java deleted file mode 100644 index 63962019a..000000000 --- a/module/audio/src/main/java/org/openbase/jul/audio/AudioSource.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.openbase.jul.audio; - -/*- - * #%L - * JUL Audio - * %% - * Copyright (C) 2015 - 2022 openbase.org - * %% - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Lesser Public License for more details. - * - * You should have received a copy of the GNU General Lesser Public - * License along with this program. If not, see - * . - * #L% - */ - -/** - * - * @author Divine Threepwood - */ -public interface AudioSource { - -} diff --git a/module/audio/src/main/java/org/openbase/jul/audio/AudioSource.kt b/module/audio/src/main/java/org/openbase/jul/audio/AudioSource.kt new file mode 100644 index 000000000..507110f53 --- /dev/null +++ b/module/audio/src/main/java/org/openbase/jul/audio/AudioSource.kt @@ -0,0 +1,7 @@ +package org.openbase.jul.audio + +/** + * + * @author [Divine Threepwood](mailto:divine@openbase.org) + */ +interface AudioSource