diff --git a/jni/nv_opus_dec/nv_opus_dec.c b/jni/nv_opus_dec/nv_opus_dec.c index 6848c29..f52a07e 100644 --- a/jni/nv_opus_dec/nv_opus_dec.c +++ b/jni/nv_opus_dec/nv_opus_dec.c @@ -37,8 +37,8 @@ int nv_opus_get_channel_count(void) { } // This number assumes 2 channels at 48 KHz -int nv_opus_get_max_out_shorts(void) { - return 512*nv_opus_get_channel_count(); +int nv_opus_get_max_out_bytes(void) { + return 1024*nv_opus_get_channel_count(); } // The Opus stream is 48 KHz @@ -46,17 +46,20 @@ int nv_opus_get_sample_rate(void) { return 48000; } -// outpcmdata must be 5760*2 shorts in length +// outpcmdata must be 11520*2 bytes in length // packets must be decoded in order // a packet loss must call this function with NULL indata and 0 inlen // returns the number of decoded samples -int nv_opus_decode(unsigned char* indata, int inlen, short* outpcmdata) { +int nv_opus_decode(unsigned char* indata, int inlen, unsigned char* outpcmdata) { int err; // Decoding to 16-bit PCM with FEC off // Maximum length assuming 48KHz sample rate err = opus_decode(decoder, indata, inlen, - outpcmdata, 512, 0); + (opus_int16*) outpcmdata, 512, 0); + if (err>0) + err = err * 2; + return err; } diff --git a/jni/nv_opus_dec/nv_opus_dec.h b/jni/nv_opus_dec/nv_opus_dec.h index c5eb7f5..d96f826 100644 --- a/jni/nv_opus_dec/nv_opus_dec.h +++ b/jni/nv_opus_dec/nv_opus_dec.h @@ -1,6 +1,6 @@ int nv_opus_init(void); void nv_opus_destroy(void); int nv_opus_get_channel_count(void); -int nv_opus_get_max_out_shorts(void); +int nv_opus_get_max_out_bytes(void); int nv_opus_get_sample_rate(void); -int nv_opus_decode(unsigned char* indata, int inlen, short* outpcmdata); +int nv_opus_decode(unsigned char* indata, int inlen, unsigned char* outpcmdata); diff --git a/jni/nv_opus_dec/nv_opus_dec_jni.c b/jni/nv_opus_dec/nv_opus_dec_jni.c index 1df6988..5678a56 100644 --- a/jni/nv_opus_dec/nv_opus_dec_jni.c +++ b/jni/nv_opus_dec/nv_opus_dec_jni.c @@ -25,8 +25,8 @@ Java_com_limelight_nvstream_av_audio_OpusDecoder_getChannelCount(JNIEnv *env, jo // This number assumes 2 channels at 48 KHz JNIEXPORT jint JNICALL -Java_com_limelight_nvstream_av_audio_OpusDecoder_getMaxOutputShorts(JNIEnv *env, jobject this) { - return nv_opus_get_max_out_shorts(); +Java_com_limelight_nvstream_av_audio_OpusDecoder_getMaxOutputBytes(JNIEnv *env, jobject this) { + return nv_opus_get_max_out_bytes(); } // The Opus stream is 48 KHz @@ -43,13 +43,13 @@ JNIEXPORT jint JNICALL Java_com_limelight_nvstream_av_audio_OpusDecoder_decode( JNIEnv *env, jobject this, // JNI parameters jbyteArray indata, jint inoff, jint inlen, // Input parameters - jshortArray outpcmdata) // Output parameter + jbyteArray outpcmdata) // Output parameter { jint ret; jbyte* jni_input_data; - jshort* jni_pcm_data; + jbyte* jni_pcm_data; - jni_pcm_data = (*env)->GetShortArrayElements(env, outpcmdata, 0); + jni_pcm_data = (*env)->GetByteArrayElements(env, outpcmdata, 0); if (indata != NULL) { jni_input_data = (*env)->GetByteArrayElements(env, indata, 0); @@ -62,7 +62,7 @@ Java_com_limelight_nvstream_av_audio_OpusDecoder_decode( ret = nv_opus_decode(NULL, 0, jni_pcm_data); } - (*env)->ReleaseShortArrayElements(env, outpcmdata, jni_pcm_data, 0); + (*env)->ReleaseByteArrayElements(env, outpcmdata, jni_pcm_data, 0); return ret; } diff --git a/src/com/limelight/binding/audio/JavaxAudioRenderer.java b/src/com/limelight/binding/audio/JavaxAudioRenderer.java index 4978fd0..7027767 100644 --- a/src/com/limelight/binding/audio/JavaxAudioRenderer.java +++ b/src/com/limelight/binding/audio/JavaxAudioRenderer.java @@ -6,8 +6,10 @@ 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.ByteBufferDescriptor; import com.limelight.nvstream.av.audio.AudioRenderer; +import java.nio.ByteOrder; +import java.util.LinkedList; /** * Audio renderer implementation @@ -16,7 +18,7 @@ import com.limelight.nvstream.av.audio.AudioRenderer; public class JavaxAudioRenderer implements AudioRenderer { private SourceDataLine soundLine; - private SoundBuffer soundBuffer; + private LinkedList soundBuffer; private byte[] lineBuffer; private int channelCount; private int sampleRate; @@ -33,16 +35,25 @@ public class JavaxAudioRenderer implements AudioRenderer { * @param length the length of data to be rendered */ @Override - public void playDecodedAudio(short[] pcmData, int offset, int length) { + public void playDecodedAudio(byte[] pcmData, int offset, int length) { if (soundLine != null) { // Queue the decoded samples into the staging sound buffer - soundBuffer.queue(new ShortBufferDescriptor(pcmData, offset, length)); + if (soundBuffer.size() > STAGING_BUFFERS) { + soundBuffer.removeFirst(); + } + + soundBuffer.addLast(new ByteBufferDescriptor(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()) { + int size = 0; + for (ByteBufferDescriptor desc : soundBuffer) { + size += desc.length; + } + + if (available < size) { System.out.println("buffer too full, buffer size: " + soundLine.getBufferSize()); int currentBuffer = soundLine.getBufferSize(); soundLine.close(); @@ -60,11 +71,16 @@ public class JavaxAudioRenderer implements AudioRenderer { // 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); + + while (available > 0 && !soundBuffer.isEmpty()) { + ByteBufferDescriptor buff = soundBuffer.peek(); + if (buff.length > available) { + break; } + + available -= soundLine.write(buff.data, buff.offset, buff.length); + + soundBuffer.remove(); } } } @@ -80,7 +96,7 @@ public class JavaxAudioRenderer implements AudioRenderer { } private void createSoundLine(int bufferSize) { - AudioFormat audioFormat = new AudioFormat(sampleRate, 16, channelCount, true, true); + AudioFormat audioFormat = new AudioFormat(sampleRate, 16, channelCount, true, ByteOrder.nativeOrder()==ByteOrder.BIG_ENDIAN); DataLine.Info info; @@ -103,7 +119,7 @@ public class JavaxAudioRenderer implements AudioRenderer { soundLine.start(); lineBuffer = new byte[soundLine.getBufferSize()]; - soundBuffer = new SoundBuffer(STAGING_BUFFERS); + soundBuffer = new LinkedList(); } catch (LineUnavailableException e) { soundLine = null; } diff --git a/src/com/limelight/binding/audio/SoundBuffer.java b/src/com/limelight/binding/audio/SoundBuffer.java deleted file mode 100644 index 8859bad..0000000 --- a/src/com/limelight/binding/audio/SoundBuffer.java +++ /dev/null @@ -1,61 +0,0 @@ -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 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; - } -}