Merge branch 'audio-fix'

This commit is contained in:
Cameron Gutman
2013-12-26 14:55:00 -05:00
2 changed files with 138 additions and 14 deletions

View File

@@ -1,30 +1,60 @@
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;
private int channelCount;
private int sampleRate;
private boolean reallocateLines;
public static final int DEFAULT_BUFFER_SIZE = 0;
public static final int STARING_BUFFER_SIZE = 4096;
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());
// Queue the decoded samples into the staging sound buffer
soundBuffer.queue(new ShortBufferDescriptor(pcmData, offset, length));
int available = soundLine.available();
if (reallocateLines) {
// Kinda jank. If the queued is larger than available, we are going to have a delay
// so we increase the buffer size
if (available < soundBuffer.size()) {
System.out.println("buffer too full, buffer size: " + soundLine.getBufferSize());
int currentBuffer = soundLine.getBufferSize();
soundLine.close();
createSoundLine(currentBuffer*2);
if (soundLine != null) {
available = soundLine.available();
System.out.println("creating new line with buffer size: " + soundLine.getBufferSize());
}
else {
available = 0;
System.out.println("failed to create sound line");
}
}
}
else {
soundLine.write(pcmDataBytes, 0, pcmDataBytes.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
if (available > 0) {
int written = soundBuffer.fill(lineBuffer, 0, available);
if (written > 0) {
soundLine.write(lineBuffer, 0, written);
}
}
}
}
@@ -35,18 +65,51 @@ public class JavaxAudioRenderer implements AudioRenderer {
soundLine.close();
}
}
@Override
public void streamInitialized(int channelCount, int sampleRate) {
private void createSoundLine(int bufferSize) {
AudioFormat audioFormat = new AudioFormat(sampleRate, 16, channelCount, true, true);
DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat, OpusDecoder.getMaxOutputShorts());
DataLine.Info info;
if (bufferSize == DEFAULT_BUFFER_SIZE) {
info = new DataLine.Info(SourceDataLine.class, audioFormat);
}
else {
info = new DataLine.Info(SourceDataLine.class, audioFormat, bufferSize);
}
try {
soundLine = (SourceDataLine) AudioSystem.getLine(info);
soundLine.open(audioFormat, OpusDecoder.getMaxOutputShorts()*4*2);
if (bufferSize == DEFAULT_BUFFER_SIZE) {
soundLine.open(audioFormat);
}
else {
soundLine.open(audioFormat, bufferSize);
}
soundLine.start();
lineBuffer = new byte[soundLine.getBufferSize()];
soundBuffer = new SoundBuffer(STAGING_BUFFERS);
} catch (LineUnavailableException e) {
soundLine = null;
}
}
@Override
public void streamInitialized(int channelCount, int sampleRate) {
this.channelCount = channelCount;
this.sampleRate = sampleRate;
// Workaround OS X's bad Java mixer
if (System.getProperty("os.name").contains("Mac OS X")) {
createSoundLine(STARING_BUFFER_SIZE);
reallocateLines = true;
}
else {
createSoundLine(DEFAULT_BUFFER_SIZE);
reallocateLines = false;
}
}
}

View File

@@ -0,0 +1,61 @@
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<ShortBufferDescriptor> bufferList;
private int maxBuffers;
public SoundBuffer(int maxBuffers) {
this.bufferList = new LinkedList<ShortBufferDescriptor>();
this.maxBuffers = maxBuffers;
}
public void queue(ShortBufferDescriptor buff) {
if (bufferList.size() > maxBuffers) {
bufferList.removeFirst();
}
bufferList.addLast(buff);
}
public int size() {
int size = 0;
for (ShortBufferDescriptor desc : bufferList) {
size += desc.length;
}
return size;
}
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;
}
}