From f8139bd4f68ab00cb7c3a61bb026b3912c08aa26 Mon Sep 17 00:00:00 2001 From: Nekojimi Date: Mon, 20 Jun 2022 00:39:56 +0100 Subject: [PATCH] WIP: initial version of multithreaded audio playback. --- .../moe/nekojimi/chords/MusicHandler.java | 1 + .../java/moe/nekojimi/chords/TrackPlayer.java | 120 ++++++++++++++++-- 2 files changed, 111 insertions(+), 10 deletions(-) diff --git a/src/main/java/moe/nekojimi/chords/MusicHandler.java b/src/main/java/moe/nekojimi/chords/MusicHandler.java index 127f6d6..738acf9 100644 --- a/src/main/java/moe/nekojimi/chords/MusicHandler.java +++ b/src/main/java/moe/nekojimi/chords/MusicHandler.java @@ -108,6 +108,7 @@ public class MusicHandler implements AudioSendHandler, Closeable arrayErr = false; byteCount = 3840; player = new TrackPlayer(currentSong); + player.start(); // System.out.println("Queue filled to " + audioBuffer.getCurrentNumberOfBytes()); return true; } catch (UnsupportedAudioFileException | IOException ex) diff --git a/src/main/java/moe/nekojimi/chords/TrackPlayer.java b/src/main/java/moe/nekojimi/chords/TrackPlayer.java index 1162136..ec9ac93 100644 --- a/src/main/java/moe/nekojimi/chords/TrackPlayer.java +++ b/src/main/java/moe/nekojimi/chords/TrackPlayer.java @@ -8,6 +8,8 @@ package moe.nekojimi.chords; import java.io.Closeable; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; @@ -19,7 +21,7 @@ import org.apache.commons.io.input.buffer.CircularByteBuffer; * * @author jimj316 */ -public class TrackPlayer implements Closeable +public class TrackPlayer extends Thread implements Closeable { private static final int DESIRED_BUFFER_SIZE = 3840 * 500; @@ -30,19 +32,73 @@ public class TrackPlayer implements Closeable private final AudioInputStream input; private boolean arrayErr = false; // supresses ArrayIndexOutOfBoundsException after the first time, to prevent spam + private boolean ended = false; + + private final Object fillBufferWait = new Object(); + private final Object bufferFilledWait = new Object(); public TrackPlayer(Song song) throws UnsupportedAudioFileException, IOException { + setName("TrackPlayer disk thread: " + song.toString()); AudioInputStream in = AudioSystem.getAudioInputStream(song.getLocation()); AudioFormat decodedFormat = AudioSendHandler.INPUT_FORMAT; input = AudioSystem.getAudioInputStream(decodedFormat, in); - fillBuffer(false); +// fillBuffer(false); } public TrackPlayer(AudioInputStream input) throws IOException { this.input = input; - fillBuffer(false); +// fillBuffer(false); + } + + public void end() + { + ended = true; + } + + @Override + public void run() + { + while (!ended) + { + int bytes; + synchronized (audioBuffer) + { + bytes = audioBuffer.getCurrentNumberOfBytes(); + } + if (bytes >= DESIRED_BUFFER_SIZE) + { + synchronized (fillBufferWait) + { + try + { + System.out.println("DISK THREAD: waiting"); + fillBufferWait.wait(); + } catch (InterruptedException ex) + { + // this is normal + } + System.out.println("DISK THREAD: kicked"); + } + } + try + { + boolean notAtEnd = fillBuffer(true); + if (!notAtEnd) + ended = true; + synchronized (bufferFilledWait) + { + System.out.println("DISK THREAD: buffer filled; kicking read thread"); + bufferFilledWait.notifyAll(); + } + } catch (IOException ex) + { + Logger.getLogger(TrackPlayer.class.getName()).log(Level.SEVERE, null, ex); + ended = true; + } + } + System.out.println("DISK THREAD ENDED"); } public boolean has(int byteCount) @@ -53,14 +109,17 @@ public class TrackPlayer implements Closeable public ByteBuffer read(int length) throws IOException { - boolean filled = fillBuffer(true); + checkBuffer(); // if (!filled) // throw new OutOfInputException(); int toRead = Math.min(length, audioBuffer.getCurrentNumberOfBytes()); // System.out.println("To read: " + toRead + " from " + audioBuffer.getCurrentNumberOfBytes()); if (toRead <= 0) + { + ended = true; throw new OutOfInputException(); + } byte[] data = new byte[toRead]; audioBuffer.read(data, 0, data.length); @@ -68,6 +127,39 @@ public class TrackPlayer implements Closeable return ByteBuffer.wrap(data); // Wrap this in a java.nio.ByteBuffer } + private boolean checkBuffer() + { + synchronized (fillBufferWait) + { + System.out.println("READ THREAD: kicking disk thread"); + fillBufferWait.notifyAll(); // kick the disk thread to fill the buffer if needed + } + int bytes; + synchronized (audioBuffer) + { + bytes = audioBuffer.getCurrentNumberOfBytes(); + } + if (bytes == 0) + { + synchronized (bufferFilledWait) + { + try + { + System.out.println("READ THREAD: waiting for disk thread"); + bufferFilledWait.wait(); // wait for disk thread to fill the buffer + } catch (InterruptedException ex) + { + } + System.out.println("READ THREAD: kicked"); + } + } + synchronized (audioBuffer) + { + bytes = audioBuffer.getCurrentNumberOfBytes(); + } + return bytes > 0; + } + /** * * @param canSkip @@ -92,7 +184,7 @@ public class TrackPlayer implements Closeable /** * - * @return true if any data was read; false otherwise + * @return true if there is still data to be read */ private boolean readData() throws IOException { @@ -102,23 +194,31 @@ public class TrackPlayer implements Closeable { // if (din.available() == 0) // return false; - int bytesToRead = DESIRED_BUFFER_SIZE - audioBuffer.getCurrentNumberOfBytes(); - int space = audioBuffer.getSpace(); + int bytesToRead; + int space; + synchronized (audioBuffer) + { + bytesToRead = DESIRED_BUFFER_SIZE - audioBuffer.getCurrentNumberOfBytes(); + space = audioBuffer.getSpace(); + } if (input.available() > 0 && input.available() < bytesToRead) bytesToRead = input.available(); if (bytesToRead > space) bytesToRead = space; if (bytesToRead == 0) - return false; + return true; byte[] bytes = new byte[bytesToRead]; // byte[] bytes = din.readNBytes(bytesToRead); int read = input.read(bytes); -// System.out.println("Wanted: " + byteCount + " Space:" + space + " Available: " + din.available() + " To read: " + bytesToRead + " Read: " + read); + System.out.println(" Space:" + space + " Available: " + input.available() + " To read: " + bytesToRead + " Read: " + read); if (read < 0) return false; // queue.add(bytes); - audioBuffer.add(bytes, 0, read); + synchronized (audioBuffer) + { + audioBuffer.add(bytes, 0, read); + } // System.out.println("SAMPLES player buff: " + Util.printSamples(audioBuffer)); } catch (ArrayIndexOutOfBoundsException ex) {