diff --git a/src/com/limelight/Limelight.java b/src/com/limelight/Limelight.java index 069d6d9..903daa1 100644 --- a/src/com/limelight/Limelight.java +++ b/src/com/limelight/Limelight.java @@ -1,14 +1,12 @@ package com.limelight; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; + import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.UIManager; +import com.limelight.binding.LibraryHelper; import com.limelight.binding.PlatformBinding; import com.limelight.gui.MainFrame; import com.limelight.gui.StreamFrame; @@ -34,53 +32,6 @@ public class Limelight implements NvConnectionListener { this.host = host; } - private static void extractNativeLibrary(String libraryName, String targetDirectory) throws IOException { - 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(targetDirectory+File.separatorChar+libraryName); - - // this will only delete it if it exists, and then create a new file - destination.delete(); - destination.createNewFile(); - - //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(); - } - } - } - - private static void prepareNativeLibraries() throws IOException { - if (!System.getProperty("os.name").contains("Windows")) { - // Nothing to do for platforms other than Windows - return; - } - - // We need to extract nv_avc_dec's runtime dependencies manually - // because the current JRE extracts them with different file names - // so they don't load properly. - String nativeLibDir = "."; - extractNativeLibrary("avfilter-3.dll", nativeLibDir); - extractNativeLibrary("avformat-55.dll", nativeLibDir); - extractNativeLibrary("avutil-52.dll", nativeLibDir); - extractNativeLibrary("postproc-52.dll", nativeLibDir); - extractNativeLibrary("pthreadVC2.dll", nativeLibDir); - extractNativeLibrary("swresample-0.dll", nativeLibDir); - extractNativeLibrary("swscale-2.dll", nativeLibDir); - extractNativeLibrary("avcodec-55.dll", nativeLibDir); - } - private void startUp() { streamFrame = new StreamFrame(); @@ -150,7 +101,7 @@ public class Limelight implements NvConnectionListener { } try { - prepareNativeLibraries(); + LibraryHelper.prepareNativeLibraries(); } catch (IOException e) { // This is expected to fail when not in a JAR } diff --git a/src/com/limelight/binding/LibraryHelper.java b/src/com/limelight/binding/LibraryHelper.java new file mode 100644 index 0000000..bc3f31a --- /dev/null +++ b/src/com/limelight/binding/LibraryHelper.java @@ -0,0 +1,85 @@ +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 avcDependencies = new HashSet(); + private static final boolean needsDependencyExtraction; + private static final String libraryExtractionFolder; + + 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 (needsDependencyExtraction && avcDependencies.contains(libraryName)) { + System.load(libraryExtractionFolder + File.separatorChar + System.mapLibraryName(libraryName)); + } + else { + System.loadLibrary(libraryName); + } + } + + public static void prepareNativeLibraries() throws IOException { + if (!needsDependencyExtraction) { + return; + } + + for (String dependency : avcDependencies) { + extractNativeLibrary(dependency); + } + } + + 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(); + + //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(); + } + } + } +} diff --git a/src/com/limelight/binding/video/SwingCpuDecoderRenderer.java b/src/com/limelight/binding/video/SwingCpuDecoderRenderer.java index 965dff7..f000f0e 100644 --- a/src/com/limelight/binding/video/SwingCpuDecoderRenderer.java +++ b/src/com/limelight/binding/video/SwingCpuDecoderRenderer.java @@ -37,15 +37,7 @@ public class SwingCpuDecoderRenderer implements VideoDecoderRenderer { // 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); diff --git a/src/com/limelight/nvstream/av/video/cpu/AvcDecoder.java b/src/com/limelight/nvstream/av/video/cpu/AvcDecoder.java new file mode 100644 index 0000000..c6ed5a8 --- /dev/null +++ b/src/com/limelight/nvstream/av/video/cpu/AvcDecoder.java @@ -0,0 +1,50 @@ +package com.limelight.nvstream.av.video.cpu; + +import com.limelight.binding.LibraryHelper; + +public class AvcDecoder { + static { + LibraryHelper.loadNativeLibrary("avutil-52"); + if (System.getProperty("os.name").contains("Windows")) { + LibraryHelper.loadNativeLibrary("postproc-52"); + LibraryHelper.loadNativeLibrary("pthreadVC2"); + } + LibraryHelper.loadNativeLibrary("swresample-0"); + LibraryHelper.loadNativeLibrary("swscale-2"); + LibraryHelper.loadNativeLibrary("avcodec-55"); + LibraryHelper.loadNativeLibrary("avformat-55"); + LibraryHelper.loadNativeLibrary("avfilter-3"); + + LibraryHelper.loadNativeLibrary("nv_avc_dec"); + } + + /** Disables the deblocking filter at the cost of image quality */ + public static final int DISABLE_LOOP_FILTER = 0x1; + /** Uses the low latency decode flag (disables multithreading) */ + public static final int LOW_LATENCY_DECODE = 0x2; + /** Threads process each slice, rather than each frame */ + public static final int SLICE_THREADING = 0x4; + /** Uses nonstandard speedup tricks */ + public static final int FAST_DECODE = 0x8; + /** Uses bilinear filtering instead of bicubic */ + public static final int BILINEAR_FILTERING = 0x10; + /** Uses a faster bilinear filtering with lower image quality */ + public static final int FAST_BILINEAR_FILTERING = 0x20; + /** Disables color conversion (output is NV21) */ + public static final int NO_COLOR_CONVERSION = 0x40; + + public static native int init(int width, int height, int perflvl, int threadcount); + public static native void destroy(); + + // Rendering API when NO_COLOR_CONVERSION == 0 + public static native boolean setRenderTarget(Object androidSurface); + public static native boolean getRgbFrameInt(int[] rgbFrame, int bufferSize); + public static native boolean getRgbFrame(byte[] rgbFrame, int bufferSize); + public static native boolean redraw(); + + // Rendering API when NO_COLOR_CONVERSION == 1 + public static native boolean getRawFrame(byte[] yuvFrame, int bufferSize); + + public static native int getInputPaddingSize(); + public static native int decode(byte[] indata, int inoff, int inlen); +}