diff --git a/jni/nv_alsa/nv_alsa.c b/jni/nv_alsa/nv_alsa.c index 3ac8cba7..016be47c 100644 --- a/jni/nv_alsa/nv_alsa.c +++ b/jni/nv_alsa/nv_alsa.c @@ -2,29 +2,60 @@ * in this header */ #include +#define CHECK_RETURN(f) if ((rc = f) != 0) return rc; + snd_pcm_t *handle; +unsigned int channels; int nv_alsa_init(unsigned int channelCount, unsigned int sampleRate, unsigned char* device) { int rc; - + snd_pcm_hw_params_t *hw_params; + snd_pcm_sw_params_t *sw_params; + snd_pcm_uframes_t period_size = 240 * channelCount * 2; + snd_pcm_uframes_t buffer_size = 12 * period_size; + + channels = channelCount; + /* Open PCM device for playback. */ - if ((rc = snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, 0)) != 0) - return rc; - - if ((rc = snd_pcm_set_params(handle, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED, channelCount, sampleRate, 1, 50000)) != 0) //50ms latency - return rc; + CHECK_RETURN(snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) + + /* Set hardware parameters */ + CHECK_RETURN(snd_pcm_hw_params_malloc(&hw_params)); + CHECK_RETURN(snd_pcm_hw_params_any(handle, hw_params)); + CHECK_RETURN(snd_pcm_hw_params_set_access(handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)); + CHECK_RETURN(snd_pcm_hw_params_set_format(handle, hw_params, SND_PCM_FORMAT_S16_LE)); + CHECK_RETURN(snd_pcm_hw_params_set_rate_near(handle, hw_params, &sampleRate, NULL)); + CHECK_RETURN(snd_pcm_hw_params_set_channels(handle, hw_params, channelCount)); + CHECK_RETURN(snd_pcm_hw_params_set_buffer_size_near(handle, hw_params, &buffer_size)); + CHECK_RETURN(snd_pcm_hw_params_set_period_size_near(handle, hw_params, &period_size, NULL)); + CHECK_RETURN(snd_pcm_hw_params(handle, hw_params)); + snd_pcm_hw_params_free(hw_params); + + /* Set software parameters */ + CHECK_RETURN(snd_pcm_sw_params_malloc(&sw_params)); + CHECK_RETURN(snd_pcm_sw_params_current(handle, sw_params)); + CHECK_RETURN(snd_pcm_sw_params_set_start_threshold(handle, sw_params, buffer_size - period_size)); + CHECK_RETURN(snd_pcm_sw_params_set_avail_min(handle, sw_params, period_size)); + CHECK_RETURN(snd_pcm_sw_params(handle, sw_params)); + snd_pcm_sw_params_free(sw_params); + + CHECK_RETURN(snd_pcm_prepare(handle)); + + return 0; } int nv_alsa_play(const unsigned char* indata, int data_len) { - int frames = data_len/4; /* 2 bytes/sample, 2 channels */ + int frames = data_len / (2 * channels); /* 2 bytes/sample */ int rc = snd_pcm_writei(handle, indata, frames); if (rc == -EPIPE) - snd_pcm_prepare(handle); + snd_pcm_recover(handle, rc, 1); return rc; } int nv_alsa_close(void) { - snd_pcm_drain(handle); - snd_pcm_close(handle); + if (handle != NULL) { + snd_pcm_drain(handle); + snd_pcm_close(handle); + } } diff --git a/jni/nv_opus_dec/nv_opus_dec.c b/jni/nv_opus_dec/nv_opus_dec.c index f594d69d..7a88f1fc 100644 --- a/jni/nv_opus_dec/nv_opus_dec.c +++ b/jni/nv_opus_dec/nv_opus_dec.c @@ -6,22 +6,11 @@ OpusDecoder* decoder; -#ifdef _WIN32 -#pragma comment(lib, "opus.lib") -#pragma comment(lib, "celt.lib") -#pragma comment(lib, "silk_common.lib") -#pragma comment(lib, "silk_float.lib") -#endif - - // This function must be called before // any other decoding functions -int nv_opus_init(void) { +int nv_opus_init(unsigned int channelcount, unsigned int samplerate) { int err; - decoder = opus_decoder_create( - nv_opus_get_sample_rate(), - nv_opus_get_channel_count(), - &err); + decoder = opus_decoder_create(samplerate, channelcount, &err); return err; } @@ -33,32 +22,16 @@ void nv_opus_destroy(void) { } } -// The Opus stream is stereo -int nv_opus_get_channel_count(void) { - return 2; -} - -// This number assumes 2 channels with 16-bit samples at 48 KHz with 2.5 ms frames -int nv_opus_get_max_out_shorts(void) { - return 240*nv_opus_get_channel_count(); -} - -// The Opus stream is 48 KHz -int nv_opus_get_sample_rate(void) { - return 48000; -} - // outpcmdata must be 5760*2 shorts 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, int framesize, short* 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); + err = opus_decode(decoder, indata, inlen, outpcmdata, framesize, 0); return err; } diff --git a/jni/nv_opus_dec/nv_opus_dec.h b/jni/nv_opus_dec/nv_opus_dec.h index c5eb7f55..975bb03a 100644 --- a/jni/nv_opus_dec/nv_opus_dec.h +++ b/jni/nv_opus_dec/nv_opus_dec.h @@ -1,6 +1,3 @@ -int nv_opus_init(void); +int nv_opus_init(unsigned int channelcount, unsigned int samplerate); void nv_opus_destroy(void); -int nv_opus_get_channel_count(void); -int nv_opus_get_max_out_shorts(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, int framelen, short* outpcmdata); diff --git a/jni/nv_opus_dec/nv_opus_dec_jni.c b/jni/nv_opus_dec/nv_opus_dec_jni.c index 6e884931..5a9bbad6 100644 --- a/jni/nv_opus_dec/nv_opus_dec_jni.c +++ b/jni/nv_opus_dec/nv_opus_dec_jni.c @@ -8,8 +8,8 @@ // This function must be called before // any other decoding functions JNIEXPORT jint JNICALL -Java_com_limelight_binding_audio_OpusDecoder_init(JNIEnv *env, jobject this) { - return nv_opus_init(); +Java_com_limelight_binding_audio_OpusDecoder_init(JNIEnv *env, jobject this, jint channelcount, jint samplerate) { + return nv_opus_init(channelcount, samplerate); } // This function must be called after @@ -19,24 +19,6 @@ 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_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_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_binding_audio_OpusDecoder_getSampleRate(JNIEnv *env, jobject this) { - return nv_opus_get_sample_rate(); -} - // outpcmdata must be 5760*2 shorts in length // packets must be decoded in order // a packet loss must call this function with NULL indata and 0 inlen @@ -44,7 +26,7 @@ Java_com_limelight_binding_audio_OpusDecoder_getSampleRate(JNIEnv *env, jobject JNIEXPORT jint JNICALL Java_com_limelight_binding_audio_OpusDecoder_decode( JNIEnv *env, jobject this, // JNI parameters - jbyteArray indata, jint inoff, jint inlen, // Input parameters + jbyteArray indata, jint inoff, jint inlen, jint framesize, // Input parameters jbyteArray outpcmdata) // Output parameter { jint ret; @@ -55,13 +37,12 @@ Java_com_limelight_binding_audio_OpusDecoder_decode( if (indata != NULL) { jni_input_data = (*env)->GetByteArrayElements(env, indata, 0); - ret = nv_opus_decode(&jni_input_data[inoff], inlen, (jshort*) jni_pcm_data); + ret = nv_opus_decode(&jni_input_data[inoff], inlen, framesize, (jshort*) jni_pcm_data); // The input data isn't changed so it can be safely aborted (*env)->ReleaseByteArrayElements(env, indata, jni_input_data, JNI_ABORT); - } - else { - ret = nv_opus_decode(NULL, 0, (jshort*) jni_pcm_data); + } else { + ret = nv_opus_decode(NULL, 0, framesize, (jshort*) jni_pcm_data); } (*env)->ReleaseByteArrayElements(env, outpcmdata, jni_pcm_data, 0); diff --git a/src/com/limelight/binding/audio/AlsaAudioDecoderRenderer.java b/src/com/limelight/binding/audio/AlsaAudioDecoderRenderer.java index 50aafe53..40c8f6d4 100644 --- a/src/com/limelight/binding/audio/AlsaAudioDecoderRenderer.java +++ b/src/com/limelight/binding/audio/AlsaAudioDecoderRenderer.java @@ -9,39 +9,40 @@ */ public class AlsaAudioDecoderRenderer implements AudioDecoderRenderer { + private final static int CHANNEL_COUNT = 2; + private final static int SAMPLE_RATE = 48000; + + /* Number of 16 bits frames */ + private final static int FRAME_SIZE = 240; + 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"); - } + this.decodedData = new byte[FRAME_SIZE * CHANNEL_COUNT * 2]; } @Override public boolean streamInitialize() { - return AlsaAudio.init(OpusDecoder.getChannelCount(), OpusDecoder.getSampleRate(), device) == 0; + return OpusDecoder.init(CHANNEL_COUNT, SAMPLE_RATE) == 0 && AlsaAudio.init(CHANNEL_COUNT, SAMPLE_RATE, device) == 0; } @Override public void playAudio(byte[] bytes, int offset, int length) { - int decodeLen = OpusDecoder.decode(bytes, offset, length, decodedData); + int decodeLen = OpusDecoder.decode(bytes, offset, length, FRAME_SIZE, decodedData); if (decodeLen > 0) { //Value of decode is frames (shorts) decoded per channel - decodeLen *= 2*OpusDecoder.getChannelCount(); + decodeLen *= CHANNEL_COUNT * 2; int rc = AlsaAudio.play(decodedData, 0, decodeLen); if (rc<0) LimeLog.warning("Alsa error from writei: "+rc); - else if (rc!=length/4) + else if (rc!=decodeLen/4) LimeLog.warning("Alsa short write, write "+rc+" frames"); + } else { + LimeLog.warning("Opus error from decode: "+decodeLen); } } diff --git a/src/com/limelight/binding/audio/OpusDecoder.java b/src/com/limelight/binding/audio/OpusDecoder.java index 4220a759..f72e5364 100644 --- a/src/com/limelight/binding/audio/OpusDecoder.java +++ b/src/com/limelight/binding/audio/OpusDecoder.java @@ -5,10 +5,7 @@ public class OpusDecoder { System.loadLibrary("nv_opus_dec"); } - public static native int init(); + public static native int init(int channelCoumt, int sampleRate); 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); + public static native int decode(byte[] indata, int inoff, int inlen, int frameSize, byte[] outpcmdata); }