diff --git a/build.xml b/build.xml
index 417bc60..5e02527 100644
--- a/build.xml
+++ b/build.xml
@@ -9,6 +9,7 @@
+
@@ -38,6 +39,7 @@
+
@@ -81,6 +83,7 @@
+
diff --git a/jni/nv_alsa/build.sh b/jni/nv_alsa/build.sh
new file mode 100644
index 0000000..39f7ce3
--- /dev/null
+++ b/jni/nv_alsa/build.sh
@@ -0,0 +1,4 @@
+rm *.o libnv_alsa.so
+gcc -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -I /opt/vc/include -I/opt/vc/include/interface/vcos/pthreads -I/opt/vc/include/interface/vmcs_host/linux -I ./inc -fPIC -L. -c *.c
+gcc -shared -Wl,-soname,libnv_alsa.so -Wl,--no-undefined -o libnv_alsa.so *.o -L. -lasound
+rm *.o
diff --git a/jni/nv_alsa/nv_alsa.c b/jni/nv_alsa/nv_alsa.c
new file mode 100644
index 0000000..996dffd
--- /dev/null
+++ b/jni/nv_alsa/nv_alsa.c
@@ -0,0 +1,54 @@
+/* Use the newer ALSA API */
+#define ALSA_PCM_NEW_HW_PARAMS_API
+
+/* All of the ALSA library API is defined
+ * in this header */
+#include
+
+snd_pcm_t *handle;
+
+int nv_alsa_init(unsigned int channelCount, unsigned int sampleRate) {
+ int rc;
+ snd_pcm_hw_params_t *params;
+ int dir;
+
+ /* Open PCM device for playback. */
+ if ((rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0)) != 0)
+ return rc;
+
+ snd_pcm_hw_params_alloca(¶ms);
+ snd_pcm_hw_params_any(handle, params);
+
+ snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
+ snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);
+ snd_pcm_hw_params_set_channels(handle, params, channelCount);
+ snd_pcm_hw_params_set_rate_near(handle, params, &sampleRate, &dir);
+
+ snd_pcm_uframes_t frames = 32;
+ snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir);
+
+ if ((rc = snd_pcm_hw_params(handle, params)) != 0)
+ return rc;
+}
+
+int nv_alsa_play(const unsigned char* indata, int data_len) {
+ int frames = data_len/4; /* 2 bytes/sample, 2 channels */
+ int rc = snd_pcm_writei(handle, indata, frames);
+ if (rc == -EPIPE) {
+ /* EPIPE means underrun */
+ fprintf(stderr, "underrun occurred\n");
+ snd_pcm_prepare(handle);
+ } else if (rc < 0) {
+ fprintf(stderr,
+ "error from writei: %s\n",
+ snd_strerror(rc));
+ } else if (rc != (int) frames) {
+ fprintf(stderr,
+ "short write, write %d frames\n", rc);
+ }
+}
+
+int nv_alsa_close(void) {
+ snd_pcm_drain(handle);
+ snd_pcm_close(handle);
+}
\ No newline at end of file
diff --git a/jni/nv_alsa/nv_alsa.h b/jni/nv_alsa/nv_alsa.h
new file mode 100644
index 0000000..be3c34e
--- /dev/null
+++ b/jni/nv_alsa/nv_alsa.h
@@ -0,0 +1,5 @@
+#include
+
+int nv_alsa_init(unsigned int channelCount, unsigned int sampleRate);
+int nv_alsa_play(unsigned char* indata, int inlen);
+void nv_alsa_close(void);
diff --git a/jni/nv_alsa/nv_alsa_jni.c b/jni/nv_alsa/nv_alsa_jni.c
new file mode 100644
index 0000000..17b7c06
--- /dev/null
+++ b/jni/nv_alsa/nv_alsa_jni.c
@@ -0,0 +1,36 @@
+#include "nv_alsa.h"
+
+#include
+#include
+
+// This function must be called before
+// any other decoding functions
+JNIEXPORT jint JNICALL
+Java_com_limelight_binding_audio_AlsaAudio_init(JNIEnv *env, jobject this, jint channelCount, jint sampleRate)
+{
+ return nv_alsa_init(channelCount, sampleRate);
+}
+
+JNIEXPORT void JNICALL
+Java_com_limelight_binding_audio_AlsaAudio_close(JNIEnv *env, jobject this)
+{
+ nv_alsa_close();
+}
+
+JNIEXPORT jint JNICALL
+Java_com_limelight_binding_audio_AlsaAudio_play(
+ JNIEnv *env, jobject this, // JNI parameters
+ jbyteArray indata, jint inoff, jint inlen)
+{
+ jint ret;
+ jbyte* jni_input_data;
+
+ jni_input_data = (*env)->GetByteArrayElements(env, indata, 0);
+
+ ret = nv_alsa_play(&jni_input_data[inoff], inlen);
+
+ // The input data isn't changed so it can be safely aborted
+ (*env)->ReleaseByteArrayElements(env, indata, jni_input_data, JNI_ABORT);
+
+ return ret;
+}
\ No newline at end of file
diff --git a/src/com/limelight/binding/PlatformBinding.java b/src/com/limelight/binding/PlatformBinding.java
index 3f22d70..c197ec1 100644
--- a/src/com/limelight/binding/PlatformBinding.java
+++ b/src/com/limelight/binding/PlatformBinding.java
@@ -3,7 +3,7 @@ package com.limelight.binding;
import java.net.InetAddress;
import java.net.UnknownHostException;
-import com.limelight.binding.audio.JavaxAudioRenderer;
+import com.limelight.binding.audio.AlsaAudioRenderer;
import com.limelight.binding.video.OmxDecoderRenderer;
import com.limelight.nvstream.av.audio.AudioRenderer;
import com.limelight.nvstream.av.video.VideoDecoderRenderer;
@@ -40,6 +40,6 @@ public class PlatformBinding {
* @return an audio decoder and renderer
*/
public static AudioRenderer getAudioRenderer() {
- return new JavaxAudioRenderer();
+ return new AlsaAudioRenderer();
}
}
diff --git a/src/com/limelight/binding/audio/AlsaAudio.java b/src/com/limelight/binding/audio/AlsaAudio.java
new file mode 100644
index 0000000..3f50713
--- /dev/null
+++ b/src/com/limelight/binding/audio/AlsaAudio.java
@@ -0,0 +1,17 @@
+package com.limelight.binding.audio;
+
+/**
+ * JNI Alsa bindings
+ * @author Iwan Timmer
+ */
+public class AlsaAudio {
+ static {
+ System.loadLibrary("nv_alsa");
+ }
+
+ public static native int init(int channelCount, int sampleRate);
+
+ public static native void close();
+
+ public static native int play(byte[] indata, int inoff, int inlen);
+}
diff --git a/src/com/limelight/binding/audio/AlsaAudioRenderer.java b/src/com/limelight/binding/audio/AlsaAudioRenderer.java
new file mode 100644
index 0000000..452e041
--- /dev/null
+++ b/src/com/limelight/binding/audio/AlsaAudioRenderer.java
@@ -0,0 +1,26 @@
+package com.limelight.binding.audio;
+
+import com.limelight.nvstream.av.audio.AudioRenderer;
+
+/**
+ * Audio renderer implementation
+ * @author Iwan Timmer
+ */
+public class AlsaAudioRenderer implements AudioRenderer {
+
+ @Override
+ public void streamInitialized(int channelCount, int sampleRate) {
+ AlsaAudio.init(channelCount, sampleRate);
+ }
+
+ @Override
+ public void playDecodedAudio(byte[] bytes, int offset, int length) {
+ AlsaAudio.play(bytes, offset, length);
+ }
+
+ @Override
+ public void streamClosing() {
+ AlsaAudio.close();
+ }
+
+}
diff --git a/src/com/limelight/binding/audio/JavaxAudioRenderer.java b/src/com/limelight/binding/audio/JavaxAudioRenderer.java
deleted file mode 100644
index 91ab0d0..0000000
--- a/src/com/limelight/binding/audio/JavaxAudioRenderer.java
+++ /dev/null
@@ -1,87 +0,0 @@
-package com.limelight.binding.audio;
-
-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.audio.AudioRenderer;
-import java.nio.ByteOrder;
-
-/**
- * Audio renderer implementation
- * @author Cameron Gutman
- * Iwan Timmer
- */
-public class JavaxAudioRenderer implements AudioRenderer {
-
- private SourceDataLine soundLine;
- private int channelCount;
- private int sampleRate;
-
- public static final int DEFAULT_BUFFER_SIZE = 4096;
-
- /**
- * Takes some audio data and writes it out to the renderer.
- * @param pcmData the array that contains the audio data
- * @param offset the offset at which the data starts in the array
- * @param length the length of data to be rendered
- */
- @Override
- public void playDecodedAudio(byte[] pcmData, int offset, int length) {
- soundLine.write(pcmData, offset, length);
- }
-
- /**
- * Callback for when the stream session is closing and the audio renderer should stop.
- */
- @Override
- public void streamClosing() {
- if (soundLine != null) {
- soundLine.close();
- }
- }
-
- private void createSoundLine(int bufferSize) {
- AudioFormat audioFormat = new AudioFormat(sampleRate, 16, channelCount, true, ByteOrder.nativeOrder()==ByteOrder.BIG_ENDIAN);
-
- 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);
-
- if (bufferSize == DEFAULT_BUFFER_SIZE) {
- soundLine.open(audioFormat);
- }
- else {
- soundLine.open(audioFormat, bufferSize);
- }
-
- soundLine.start();
- } catch (LineUnavailableException e) {
- soundLine = null;
- }
- }
-
- /**
- * The callback for the audio stream being initialized and starting to receive.
- * @param channelCount the number of channels in the audio
- * @param sampleRate the sample rate for the audio.
- */
- @Override
- public void streamInitialized(int channelCount, int sampleRate) {
- this.channelCount = channelCount;
- this.sampleRate = sampleRate;
-
- createSoundLine(DEFAULT_BUFFER_SIZE);
- }
-
-}