From 40508c2ac236a0769196cf9ca73e517006aa8cdf Mon Sep 17 00:00:00 2001 From: Iwan Timmer Date: Mon, 9 Mar 2015 12:16:57 +0100 Subject: [PATCH] Move opus decoding code --- jni/nv_opus_dec/nv_opus_dec_jni.c | 12 ++--- src/com/limelight/Limelight.java | 4 +- .../limelight/binding/PlatformBinding.java | 8 +-- .../audio/AlsaAudioDecoderRenderer.java | 53 +++++++++++++++++++ .../binding/audio/AlsaAudioRenderer.java | 43 --------------- .../audio/FakeAudioDecoderRenderer.java | 35 ++++++++++++ .../binding/audio/FakeAudioRenderer.java | 41 -------------- .../limelight/binding/audio/OpusDecoder.java | 14 +++++ 8 files changed, 114 insertions(+), 96 deletions(-) create mode 100644 src/com/limelight/binding/audio/AlsaAudioDecoderRenderer.java delete mode 100644 src/com/limelight/binding/audio/AlsaAudioRenderer.java create mode 100644 src/com/limelight/binding/audio/FakeAudioDecoderRenderer.java delete mode 100644 src/com/limelight/binding/audio/FakeAudioRenderer.java create mode 100644 src/com/limelight/binding/audio/OpusDecoder.java diff --git a/jni/nv_opus_dec/nv_opus_dec_jni.c b/jni/nv_opus_dec/nv_opus_dec_jni.c index 047b1b1..6e88493 100644 --- a/jni/nv_opus_dec/nv_opus_dec_jni.c +++ b/jni/nv_opus_dec/nv_opus_dec_jni.c @@ -8,32 +8,32 @@ // This function must be called before // any other decoding functions JNIEXPORT jint JNICALL -Java_com_limelight_nvstream_av_audio_OpusDecoder_init(JNIEnv *env, jobject this) { +Java_com_limelight_binding_audio_OpusDecoder_init(JNIEnv *env, jobject this) { return nv_opus_init(); } // This function must be called after // decoding is finished JNIEXPORT void JNICALL -Java_com_limelight_nvstream_av_audio_OpusDecoder_destroy(JNIEnv *env, jobject this) { +Java_com_limelight_binding_audio_OpusDecoder_destroy(JNIEnv *env, jobject this) { nv_opus_destroy(); } // The Opus stream is stereo JNIEXPORT jint JNICALL -Java_com_limelight_nvstream_av_audio_OpusDecoder_getChannelCount(JNIEnv *env, jobject this) { +Java_com_limelight_binding_audio_OpusDecoder_getChannelCount(JNIEnv *env, jobject this) { return nv_opus_get_channel_count(); } // This number assumes 2 channels at 48 KHz JNIEXPORT jint JNICALL -Java_com_limelight_nvstream_av_audio_OpusDecoder_getMaxOutputShorts(JNIEnv *env, jobject this) { +Java_com_limelight_binding_audio_OpusDecoder_getMaxOutputShorts(JNIEnv *env, jobject this) { return nv_opus_get_max_out_shorts(); } // The Opus stream is 48 KHz JNIEXPORT jint JNICALL -Java_com_limelight_nvstream_av_audio_OpusDecoder_getSampleRate(JNIEnv *env, jobject this) { +Java_com_limelight_binding_audio_OpusDecoder_getSampleRate(JNIEnv *env, jobject this) { return nv_opus_get_sample_rate(); } @@ -42,7 +42,7 @@ Java_com_limelight_nvstream_av_audio_OpusDecoder_getSampleRate(JNIEnv *env, jobj // a packet loss must call this function with NULL indata and 0 inlen // returns the number of decoded samples JNIEXPORT jint JNICALL -Java_com_limelight_nvstream_av_audio_OpusDecoder_decode( +Java_com_limelight_binding_audio_OpusDecoder_decode( JNIEnv *env, jobject this, // JNI parameters jbyteArray indata, jint inoff, jint inlen, // Input parameters jbyteArray outpcmdata) // Output parameter diff --git a/src/com/limelight/Limelight.java b/src/com/limelight/Limelight.java index 0f8a50d..dc8d371 100644 --- a/src/com/limelight/Limelight.java +++ b/src/com/limelight/Limelight.java @@ -1,7 +1,7 @@ package com.limelight; import com.limelight.binding.PlatformBinding; -import com.limelight.binding.audio.FakeAudioRenderer; +import com.limelight.binding.audio.FakeAudioDecoderRenderer; import com.limelight.binding.video.FakeVideoRenderer; import com.limelight.input.EvdevLoader; import com.limelight.input.GamepadMapper; @@ -115,7 +115,7 @@ public class Limelight implements NvConnectionListener { conn = new NvConnection(host.getHostAddress(), getUniqueId(), this, streamConfig, PlatformBinding.getCryptoProvider()); conn.start(PlatformBinding.getDeviceName(), null, VideoDecoderRenderer.FLAG_PREFER_QUALITY, - new FakeAudioRenderer(), + new FakeAudioDecoderRenderer(), new FakeVideoRenderer(videoFile)); } diff --git a/src/com/limelight/binding/PlatformBinding.java b/src/com/limelight/binding/PlatformBinding.java index 4112aff..7f6153c 100644 --- a/src/com/limelight/binding/PlatformBinding.java +++ b/src/com/limelight/binding/PlatformBinding.java @@ -5,14 +5,14 @@ import java.net.UnknownHostException; import java.io.File; import java.io.IOException; -import com.limelight.binding.audio.AlsaAudioRenderer; +import com.limelight.binding.audio.AlsaAudioDecoderRenderer; import com.limelight.binding.video.ImxDecoder; import com.limelight.binding.video.ImxDecoderRenderer; import com.limelight.binding.crypto.PcCryptoProvider; import com.limelight.binding.video.OmxDecoder; import com.limelight.binding.video.OmxDecoderRenderer; import com.limelight.binding.video.AbstractVideoRenderer; -import com.limelight.nvstream.av.audio.AudioRenderer; +import com.limelight.nvstream.av.audio.AudioDecoderRenderer; import com.limelight.nvstream.av.video.VideoDecoderRenderer; import com.limelight.nvstream.http.LimelightCryptoProvider; import com.limelight.LimeLog; @@ -55,7 +55,7 @@ public class PlatformBinding { * Gets an instance of an audio decoder/renderer. * @return an audio decoder and renderer */ - public static AudioRenderer getAudioRenderer(String device) { + public static AudioDecoderRenderer getAudioRenderer(String device) { //Try to load local libopus try { Runtime.getRuntime().load(new File(".").getCanonicalPath()+File.separator+"libopus.so"); @@ -66,7 +66,7 @@ public class PlatformBinding { //Use system opus library } - return new AlsaAudioRenderer(device); + return new AlsaAudioDecoderRenderer(device); } /** diff --git a/src/com/limelight/binding/audio/AlsaAudioDecoderRenderer.java b/src/com/limelight/binding/audio/AlsaAudioDecoderRenderer.java new file mode 100644 index 0000000..50aafe5 --- /dev/null +++ b/src/com/limelight/binding/audio/AlsaAudioDecoderRenderer.java @@ -0,0 +1,53 @@ +package com.limelight.binding.audio; + +import com.limelight.LimeLog; +import com.limelight.nvstream.av.audio.AudioDecoderRenderer; + +/** + * Audio renderer implementation + * @author Iwan Timmer + */ +public class AlsaAudioDecoderRenderer implements AudioDecoderRenderer { + + private String device; + private byte[] decodedData; + + public AlsaAudioDecoderRenderer(String device) { + this.device = device; + this.decodedData = new byte[OpusDecoder.getMaxOutputShorts()*2]; + + int err; + + err = OpusDecoder.init(); + if (err != 0) { + throw new IllegalStateException("Opus decoder failed to initialize"); + } + } + + @Override + public boolean streamInitialize() { + return AlsaAudio.init(OpusDecoder.getChannelCount(), OpusDecoder.getSampleRate(), device) == 0; + } + + @Override + public void playAudio(byte[] bytes, int offset, int length) { + int decodeLen = OpusDecoder.decode(bytes, offset, length, decodedData); + + if (decodeLen > 0) { + //Value of decode is frames (shorts) decoded per channel + decodeLen *= 2*OpusDecoder.getChannelCount(); + int rc = AlsaAudio.play(decodedData, 0, decodeLen); + + if (rc<0) + LimeLog.warning("Alsa error from writei: "+rc); + else if (rc!=length/4) + LimeLog.warning("Alsa short write, write "+rc+" frames"); + } + } + + @Override + public void streamClosing() { + AlsaAudio.close(); + } + +} diff --git a/src/com/limelight/binding/audio/AlsaAudioRenderer.java b/src/com/limelight/binding/audio/AlsaAudioRenderer.java deleted file mode 100644 index bbbd0e6..0000000 --- a/src/com/limelight/binding/audio/AlsaAudioRenderer.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.limelight.binding.audio; - -import com.limelight.LimeLog; -import com.limelight.nvstream.av.audio.AudioRenderer; - -/** - * Audio renderer implementation - * @author Iwan Timmer - */ -public class AlsaAudioRenderer implements AudioRenderer { - - private String device; - - public AlsaAudioRenderer(String device) { - this.device = device; - } - - @Override - public boolean streamInitialized(int channelCount, int sampleRate) { - return AlsaAudio.init(channelCount, sampleRate, device) == 0; - } - - @Override - public void playDecodedAudio(byte[] bytes, int offset, int length) { - int rc = AlsaAudio.play(bytes, offset, length); - - if (rc<0) - LimeLog.warning("Alsa error from writei: "+rc); - else if (rc!=length/4) - LimeLog.warning("Alsa short write, write "+rc+" frames"); - } - - @Override - public void streamClosing() { - AlsaAudio.close(); - } - - @Override - public int getCapabilities() { - return 0; - } - -} diff --git a/src/com/limelight/binding/audio/FakeAudioDecoderRenderer.java b/src/com/limelight/binding/audio/FakeAudioDecoderRenderer.java new file mode 100644 index 0000000..abbeb1a --- /dev/null +++ b/src/com/limelight/binding/audio/FakeAudioDecoderRenderer.java @@ -0,0 +1,35 @@ +package com.limelight.binding.audio; + +import com.limelight.nvstream.av.audio.AudioDecoderRenderer; + +/** + * Fake implementation for audio renderer. + * @author Iwan Timmer + */ +public class FakeAudioDecoderRenderer implements AudioDecoderRenderer { + + private int dataSize; + private long last; + + @Override + public boolean streamInitialize() { + System.out.println("Fake audio output"); + return true; + } + + @Override + public void playAudio(byte[] audioData, int offset, int length) { + if (System.currentTimeMillis()>last+2000) { + int bitrate = (dataSize/2)/1024; + System.out.println("Audio " + bitrate + "kB/s"); + dataSize = 0; + last = System.currentTimeMillis(); + } + dataSize += length; + } + + @Override + public void streamClosing() { + } + +} \ No newline at end of file diff --git a/src/com/limelight/binding/audio/FakeAudioRenderer.java b/src/com/limelight/binding/audio/FakeAudioRenderer.java deleted file mode 100644 index fb26ae2..0000000 --- a/src/com/limelight/binding/audio/FakeAudioRenderer.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.limelight.binding.audio; - -import com.limelight.nvstream.av.audio.AudioRenderer; - -/** - * Fake implementation for audio renderer. - * @author Iwan Timmer - */ -public class FakeAudioRenderer implements AudioRenderer { - - private int dataSize; - private long last; - - @Override - public boolean streamInitialized(int channelCount, int sampleRate) { - System.out.println("Fake " + channelCount + " channel " + sampleRate + " samplerate audio output"); - last = System.currentTimeMillis(); - return true; - } - - @Override - public void playDecodedAudio(byte[] audioData, int offset, int length) { - if (System.currentTimeMillis()>last+2000) { - int bitrate = (dataSize/2)/1024; - System.out.println("Audio " + bitrate + "kB/s"); - dataSize = 0; - last = System.currentTimeMillis(); - } - dataSize += length; - } - - @Override - public void streamClosing() { - } - - @Override - public int getCapabilities() { - return CAPABILITY_DIRECT_SUBMIT; - } - -} \ No newline at end of file diff --git a/src/com/limelight/binding/audio/OpusDecoder.java b/src/com/limelight/binding/audio/OpusDecoder.java new file mode 100644 index 0000000..4220a75 --- /dev/null +++ b/src/com/limelight/binding/audio/OpusDecoder.java @@ -0,0 +1,14 @@ +package com.limelight.binding.audio; + +public class OpusDecoder { + static { + System.loadLibrary("nv_opus_dec"); + } + + public static native int init(); + public static native void destroy(); + public static native int getChannelCount(); + public static native int getMaxOutputShorts(); + public static native int getSampleRate(); + public static native int decode(byte[] indata, int inoff, int inlen, byte[] outpcmdata); +}