Merge branch 'master' of github.com:limelight-stream/limelight-pc

# By Cameron Gutman (15) and others
# Via Cameron Gutman (2) and Diego Waxemberg (1)
* 'master' of github.com:limelight-stream/limelight-pc:
  Update common jar
  Fix library loading on Windows when not using a JAR
  Delete the extracted libraries when Limelight terminates
  Update common jar
  Write a better native library loader so DLLs are no longer extracted to the current directory on Windows.
  Suppress log spam from jinput on Windows 8 and higher
  Rough Draft of Icon
  Fix settings.lime being created as a directory. See GitHub issue 1 for details.
  Update limelight-common jar
  Use the stream's frame rate as the redraw rate instead of always refreshing at 30 FPS
  Don't print stack traces for InterruptExceptions. Call the NvConnectionListener class to terminate the stream without causing the unexpected termination error dialog.
  Fix resolution changing on Windows. Improve the resolution selection algorithm.
  Only use the audio buffer hack on OS X
  dynamically increase the buffer size if we build up to large of a queue
  Revert "Try to workaround Java's poor OS X mixer"
  Try to workaround Java's poor OS X mixer
  Move audio buffering responsibility onto us and simply fill whatever the runtime gives us

Conflicts:
	src/com/limelight/Limelight.java
	src/com/limelight/binding/audio/JavaxAudioRenderer.java
	src/com/limelight/gui/StreamFrame.java
This commit is contained in:
Diego Waxemberg
2013-12-29 13:24:22 -05:00
13 changed files with 363 additions and 55 deletions

View File

@@ -0,0 +1,97 @@
package com.limelight.binding;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
public class LibraryHelper {
private static final HashSet<String> avcDependencies = new HashSet<String>();
private static final boolean needsDependencyExtraction;
private static final String libraryExtractionFolder;
private static boolean librariesExtracted = false;
static {
needsDependencyExtraction = System.getProperty("os.name", "").contains("Windows");
libraryExtractionFolder = System.getProperty("java.io.tmpdir", ".");
// FFMPEG libraries
avcDependencies.add("avutil-52");
avcDependencies.add("swresample-0");
avcDependencies.add("swscale-2");
avcDependencies.add("avcodec-55");
avcDependencies.add("avformat-55");
avcDependencies.add("avfilter-3");
// The AVC JNI library itself
avcDependencies.add("nv_avc_dec");
// Additional Windows dependencies
if (System.getProperty("os.name").contains("Windows")) {
avcDependencies.add("postproc-52");
avcDependencies.add("pthreadVC2");
}
}
public static void loadNativeLibrary(String libraryName) {
if (librariesExtracted && avcDependencies.contains(libraryName)) {
System.load(libraryExtractionFolder + File.separatorChar + System.mapLibraryName(libraryName));
}
else {
System.loadLibrary(libraryName);
}
}
public static void prepareNativeLibraries() {
if (!needsDependencyExtraction) {
return;
}
try {
for (String dependency : avcDependencies) {
extractNativeLibrary(dependency);
}
} catch (IOException e) {
// This is expected if this code is not running from a JAR
return;
}
librariesExtracted = true;
}
private static void extractNativeLibrary(String libraryName) throws IOException {
// convert general library name to platform-specific name
libraryName = System.mapLibraryName(libraryName);
InputStream resource = new Object().getClass().getResourceAsStream("/binlib/"+libraryName);
if (resource == null) {
throw new FileNotFoundException("Unable to find native library in JAR: "+libraryName);
}
File destination = new File(libraryExtractionFolder+File.separatorChar+libraryName);
// this will only delete it if it exists, and then create a new file
destination.delete();
destination.createNewFile();
// schedule the temporary file to be deleted when the program exits
destination.deleteOnExit();
//this is the janky java 6 way to copy a file
FileOutputStream fos = null;
try {
fos = new FileOutputStream(destination);
int read;
byte[] readBuffer = new byte[16384];
while ((read = resource.read(readBuffer)) != -1) {
fos.write(readBuffer, 0, read);
}
} finally {
if (fos != null) {
fos.close();
}
}
}
}

View File

@@ -1,15 +1,13 @@
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;
/**
* Audio renderer implementation
@@ -18,6 +16,15 @@ 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
/**
* Takes some audio data and writes it out to the renderer.
@@ -28,13 +35,36 @@ public class JavaxAudioRenderer implements AudioRenderer {
@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);
}
}
}
}
@@ -56,15 +86,51 @@ public class JavaxAudioRenderer implements AudioRenderer {
*/
@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;
}
}

View File

@@ -40,23 +40,15 @@ public class SwingCpuDecoderRenderer implements VideoDecoderRenderer {
* @param drFlags flags for the decoder and renderer
*/
@Override
public void setup(int width, int height, Object renderTarget, int drFlags) {
this.targetFps = 30;
public void setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) {
this.targetFps = redrawRate;
this.width = width;
this.height = height;
// Single threaded low latency decode is ideal
int avcFlags = AvcDecoder.LOW_LATENCY_DECODE;
int threadCount = 1;
// Hack to work around the bad Java native library loader
// which can't resolve native library dependencies
if (System.getProperty("os.name").contains("Windows")) {
System.loadLibrary("avutil-52");
System.loadLibrary("postproc-52");
System.loadLibrary("pthreadVC2");
}
int err = AvcDecoder.init(width, height, avcFlags, threadCount);
if (err != 0) {
throw new IllegalStateException("AVC decoder initialization failure: "+err);