diff --git a/src/com/limelight/binding/audio/JavaxAudioRenderer.java b/src/com/limelight/binding/audio/JavaxAudioRenderer.java index e6f94f2..134b390 100644 --- a/src/com/limelight/binding/audio/JavaxAudioRenderer.java +++ b/src/com/limelight/binding/audio/JavaxAudioRenderer.java @@ -1,30 +1,36 @@ package com.limelight.binding.audio; -import java.nio.ByteBuffer; - import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.DataLine; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.SourceDataLine; +import com.limelight.nvstream.av.ShortBufferDescriptor; import com.limelight.nvstream.av.audio.AudioRenderer; -import com.limelight.nvstream.av.audio.OpusDecoder; public class JavaxAudioRenderer implements AudioRenderer { private SourceDataLine soundLine; + private SoundBuffer soundBuffer; + private byte[] lineBuffer; + + public static final int STAGING_BUFFERS = 3; // 3 complete frames of audio @Override public void playDecodedAudio(short[] pcmData, int offset, int length) { if (soundLine != null) { - byte[] pcmDataBytes = new byte[length * 2]; - ByteBuffer.wrap(pcmDataBytes).asShortBuffer().put(pcmData, offset, length); - if (soundLine.available() < length) { - soundLine.write(pcmDataBytes, 0, soundLine.available()); - } - else { - soundLine.write(pcmDataBytes, 0, pcmDataBytes.length); + // Queue the decoded samples into the staging sound buffer + soundBuffer.queue(new ShortBufferDescriptor(pcmData, offset, length)); + + // If there's space available in the sound line, pull some data out + // of the staging buffer and write it to the sound line + int available = soundLine.available(); + if (available > 0) { + int written = soundBuffer.fill(lineBuffer, 0, available); + if (written > 0) { + soundLine.write(lineBuffer, 0, written); + } } } } @@ -39,11 +45,14 @@ public class JavaxAudioRenderer implements AudioRenderer { @Override public void streamInitialized(int channelCount, int sampleRate) { AudioFormat audioFormat = new AudioFormat(sampleRate, 16, channelCount, true, true); - DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat, OpusDecoder.getMaxOutputShorts()); + DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat); try { soundLine = (SourceDataLine) AudioSystem.getLine(info); - soundLine.open(audioFormat, OpusDecoder.getMaxOutputShorts()*4*2); + soundLine.open(audioFormat); soundLine.start(); + + lineBuffer = new byte[soundLine.getBufferSize()]; + soundBuffer = new SoundBuffer(STAGING_BUFFERS); } catch (LineUnavailableException e) { soundLine = null; } diff --git a/src/com/limelight/binding/audio/SoundBuffer.java b/src/com/limelight/binding/audio/SoundBuffer.java new file mode 100644 index 0000000..9197ebb --- /dev/null +++ b/src/com/limelight/binding/audio/SoundBuffer.java @@ -0,0 +1,53 @@ +package com.limelight.binding.audio; + +import java.nio.ByteBuffer; +import java.nio.ShortBuffer; +import java.util.LinkedList; + +import com.limelight.nvstream.av.ShortBufferDescriptor; + +public class SoundBuffer { + + private LinkedList bufferList; + private int maxBuffers; + + public SoundBuffer(int maxBuffers) { + this.bufferList = new LinkedList(); + this.maxBuffers = maxBuffers; + } + + public void queue(ShortBufferDescriptor buff) { + if (bufferList.size() > maxBuffers) { + bufferList.removeFirst(); + } + + bufferList.addLast(buff); + } + + public int fill(byte[] data, int offset, int length) { + int filled = 0; + + // Convert offset and length to be relative to shorts + offset /= 2; + length /= 2; + + ShortBuffer sb = ByteBuffer.wrap(data).asShortBuffer(); + sb.position(offset); + while (length > 0 && !bufferList.isEmpty()) { + ShortBufferDescriptor buff = bufferList.getFirst(); + + if (buff.length > length) { + break; + } + + sb.put(buff.data, buff.offset, buff.length); + length -= buff.length; + filled += buff.length; + + bufferList.removeFirst(); + } + + // Return bytes instead of shorts + return filled * 2; + } +}