changeset 74:5f856aeca15a

2008-08-14 Omair Majid <omajid@redhat.com> * build.xml: added Stream.java to list of files to generate jni headers from * src/java/org/classpath/icedtea/pulseaudio/PulseAudioClip.java: initial implementation using the Stream class * src/java/org/classpath/icedtea/pulseaudio/Stream.java: new file * src/native/Makefile.am: added Stream.c to list of files to package * src/native/org_classpath_icedtea_pulseaudio_Stream.c * unittests/org/classpath/icedtea/pulseaudio/PulseAudioClipTest.java: added a few more tests
author Omair Majid <omajid@redhat.com>
date Thu, 14 Aug 2008 14:24:17 -0400
parents 9ed589465932
children a2034200b782
files build.xml src/java/org/classpath/icedtea/pulseaudio/PulseAudioClip.java src/java/org/classpath/icedtea/pulseaudio/Stream.java src/native/Makefile.am src/native/org_classpath_icedtea_pulseaudio_Stream.c unittests/org/classpath/icedtea/pulseaudio/PulseAudioClipTest.java
diffstat 6 files changed, 828 insertions(+), 22 deletions(-) [+]
line wrap: on
line diff
--- a/build.xml	Wed Aug 13 13:17:13 2008 -0400
+++ b/build.xml	Thu Aug 14 14:24:17 2008 -0400
@@ -39,6 +39,7 @@
 			<class name="org.classpath.icedtea.pulseaudio.Operation"/>
 			<class name="org.classpath.icedtea.pulseaudio.PulseAudioSourceDataLine"/>
 			<class name="org.classpath.icedtea.pulseaudio.PulseAudioClip"/>
+			<class name="org.classpath.icedtea.pulseaudio.Stream"/>
 			<class name="org.classpath.icedtea.pulseaudio.PulseAudioStreamVolumeControl"/>
 		</javah>
 	</target>
--- a/src/java/org/classpath/icedtea/pulseaudio/PulseAudioClip.java	Wed Aug 13 13:17:13 2008 -0400
+++ b/src/java/org/classpath/icedtea/pulseaudio/PulseAudioClip.java	Thu Aug 14 14:24:17 2008 -0400
@@ -54,6 +54,8 @@
 public class PulseAudioClip implements Clip {
 
 	private byte[] data = null;
+
+	// FIXME
 	private int bufferSize = 0;
 
 	// these are frame indices. so counted from 0
@@ -72,21 +74,20 @@
 	private List<LineListener> lineListeners = null;
 
 	private static final int DEFAULT_BUFFER_SIZE = 0;
-
-	@SuppressWarnings("unused")
-	private long streamPointer = 0;
+	public static final String DEFAULT_CLIP_NAME = "Clip";
 
-	private native void native_open();
-
-	private native void native_close();
+	private Stream stream;
 
-	private native void native_start();
-
-	private native void native_stop();
+	private Thread clipLoop = new Thread() {
+		@Override
+		public void run() {
+			while (true) {
 
-	private native long native_drain();
+			}
 
-	private native long native_flush();
+		}
+
+	};
 
 	static {
 		try {
@@ -117,20 +118,19 @@
 	@Override
 	public void close() {
 		// TODO Auto-generated method stub
-		native_close();
+		stream.drain();
+		stream.disconnect();
 		isOpen = false;
 	}
 
 	@Override
 	public void drain() {
-		// TODO Auto-generated method stub
-		native_drain();
+		stream.drain();
 	}
 
 	@Override
 	public void flush() {
-		// TODO Auto-generated method stub
-		native_flush();
+		stream.flush();
 	}
 
 	@Override
@@ -234,9 +234,13 @@
 	@Override
 	public void open(AudioFormat format, byte[] data, int offset, int bufferSize)
 			throws LineUnavailableException {
-		// TODO Auto-generated method stub
-
-		native_open();
+		
+		
+		long contextPointer = EventLoop.getEventLoop().getContextPointer();
+		int channels = 2;
+		int sampleRate = 22050;
+		stream = new Stream(contextPointer, DEFAULT_CLIP_NAME,
+				Stream.Format.PA_SAMPLE_U8, sampleRate, channels);
 
 		isOpen = true;
 	}
@@ -294,12 +298,12 @@
 
 	@Override
 	public void start() {
-		native_start();
+		stream.cork(false);
 	}
 
 	@Override
 	public void stop() {
-		native_stop();
+		stream.cork(true);
 	}
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java/org/classpath/icedtea/pulseaudio/Stream.java	Thu Aug 14 14:24:17 2008 -0400
@@ -0,0 +1,441 @@
+package org.classpath.icedtea.pulseaudio;
+
+import javax.sound.sampled.AudioFormat;
+
+/**
+ * 
+ * This class encapsulates a pa_stream object and provides easier access to the
+ * native functions
+ * 
+ */
+public class Stream {
+
+	public static enum State {
+		UNCONNECTED, CREATING, READY, FAILED, TERMINATED,
+	}
+
+	public static enum Format {
+		PA_SAMPLE_U8, PA_SAMPLE_ULAW, PA_SAMPLE_ALAW, PA_SAMPLE_S16LE, PA_SAMPLE_S16BE, PA_SAMPLE_FLOAT32LE, PA_SAMPLE_FLOAT32BE, PA_SAMPLE_S32LE, PA_SAMPLE_S32BE,
+	}
+
+	@SuppressWarnings("unused")
+	private long streamPointer;
+
+	private native void native_pa_stream_new(long contextPointer, String name,
+			String format, int sampleRate, int channels);
+
+	private native int native_pa_stream_get_state();
+
+	private native long native_pa_stream_get_context();
+
+	private native int native_pa_stream_get_index();
+
+	private native int native_pa_stream_get_device_index();
+
+	private native String native_pa_stream_get_device_name();
+
+	private native int native_pa_stream_is_suspended();
+
+	private native int native_pa_stream_connect_playback(String name,
+			long buffer_attrPointer, int flags, long volumePointer,
+			long sync_streamPointer);
+
+	private native int native_pa_stream_connect_record(String device,
+			long buffer_attrPointer, int flags);
+
+	private native int native_pa_stream_disconnect();
+
+	private native int native_pa_stream_write(byte[] data, int offset,
+			int length);
+
+	/*
+	 * private native int native_pa_stream_peek (pa_stream *p, const void
+	 * **data, size_t *nbytes) Read the next fragment from the buffer (for
+	 * recording). int pa_stream_drop (pa_stream *p) Remove the current fragment
+	 * on record streams.
+	 * 
+	 */
+
+	private native int native_pa_stream_writable_size();
+
+	private native int native_pa_stream_readable_size();
+
+	private native long native_pa_stream_drain();
+
+	/*
+	 * Drain a playback stream. pa_operation pa_stream_update_timing_info
+	 * (pa_stream *p, pa_stream_success_cb_t cb, void *userdata) Request a
+	 * timing info structure update for a stream. void
+	 * pa_stream_set_state_callback (pa_stream *s, pa_stream_notify_cb_t cb,
+	 * void *userdata) Set the callback function that is called whenever the
+	 * state of the stream changes. void pa_stream_set_write_callback (pa_stream
+	 * *p, pa_stream_request_cb_t cb, void *userdata) Set the callback function
+	 * that is called when new data may be written to the stream. void
+	 * pa_stream_set_read_callback (pa_stream *p, pa_stream_request_cb_t cb,
+	 * void *userdata) Set the callback function that is called when new data is
+	 * available from the stream. void pa_stream_set_overflow_callback
+	 * (pa_stream *p, pa_stream_notify_cb_t cb, void *userdata) Set the callback
+	 * function that is called when a buffer overflow happens. void
+	 * pa_stream_set_underflow_callback (pa_stream *p, pa_stream_notify_cb_t cb,
+	 * void *userdata) Set the callback function that is called when a buffer
+	 * underflow happens. void pa_stream_set_started_callback (pa_stream *p,
+	 * pa_stream_notify_cb_t cb, void *userdata) Set the callback function that
+	 * is called when a the server starts playback after an underrun or on
+	 * initial startup. void pa_stream_set_latency_update_callback (pa_stream
+	 * *p, pa_stream_notify_cb_t cb, void *userdata) Set the callback function
+	 * that is called whenever a latency information update happens. void
+	 * pa_stream_set_moved_callback (pa_stream *p, pa_stream_notify_cb_t cb,
+	 * void *userdata) Set the callback function that is called whenever the
+	 * stream is moved to a different sink/source. void
+	 * pa_stream_set_suspended_callback (pa_stream *p, pa_stream_notify_cb_t cb,
+	 * void *userdata) Set the callback function that is called whenever the
+	 * sink/source this stream is connected to is suspended or resumed.
+	 * 
+	 */
+
+	private native long native_pa_stream_cork(int b);
+
+	private native long native_pa_stream_flush();
+
+	/*
+	 * pa_operation pa_stream_prebuf (pa_stream *s, pa_stream_success_cb_t cb,
+	 * void *userdata) Reenable prebuffering as specified in the pa_buffer_attr
+	 * structure.
+	 */
+
+	private native long native_pa_stream_trigger();
+
+	/* returns an operationPointer */
+	private native long native_pa_stream_set_name(String name);
+
+	/*
+	 * Return the current playback/recording time private native int
+	 * native_pa_stream_get_time (pa_usec_t r_usec);
+	 */
+
+	/*
+	 * Return the total stream latency private native int
+	 * native_pa_stream_get_latency ( pa_usec_t *r_usec, int *negative);
+	 */
+
+	/*
+	 * const pa_timing_info * pa_stream_get_timing_info (pa_stream *s) Return
+	 * the latest raw timing data structure. const pa_sample_spec *
+	 * pa_stream_get_sample_spec (pa_stream *s) Return a pointer to the stream's
+	 * sample specification. const pa_channel_map * pa_stream_get_channel_map
+	 * (pa_stream *s) Return a pointer to the stream's channel map. const
+	 * pa_buffer_attr * pa_stream_get_buffer_attr (pa_stream *s) Return the
+	 * per-stream server-side buffer metrics of the stream. pa_operation *
+	 * pa_stream_set_buffer_attr (pa_stream *s, const pa_buffer_attr *attr,
+	 * pa_stream_success_cb_t cb, void *userdata) Change the buffer metrics of
+	 * the stream during playback. pa_operation * pa_stream_update_sample_rate
+	 * (pa_stream *s, uint32_t rate, pa_stream_success_cb_t cb, void *userdata)
+	 * Change the stream sampling rate during playback. pa_operation *
+	 * pa_stream_proplist_update (pa_stream *s, pa_update_mode_t mode,
+	 * pa_proplist *p, pa_stream_success_cb_t cb, void *userdata) Update the
+	 * property list of the sink input/source output of this stream, adding new
+	 * entries. pa_operation * pa_stream_proplist_remove (pa_stream *s, const
+	 * char *const keys[], pa_stream_success_cb_t cb, void *userdata) Update the
+	 * property list of the sink input/source output of this stream, remove
+	 * entries. int pa_stream_set_monitor_stream (pa_stream *s, uint32_t
+	 * sink_input_idx) For record streams connected to a monitor source: monitor
+	 * only a very specific sink input of the sink. uint32_t
+	 * pa_stream_get_monitor_stream (pa_stream *s) Return what has been set with
+	 * pa_stream_set_monitor_stream() ebfore.
+	 */
+
+	public Stream(long contextPointer, String name, Format format,
+			int sampleRate, int channels) {
+		System.out.println("format: "+ format.toString());
+		native_pa_stream_new(contextPointer, name, format.toString(),
+				sampleRate, channels);
+	}
+
+	public Stream.State getState() {
+		int state = native_pa_stream_get_state();
+		switch (state) {
+		case 0:
+			return State.UNCONNECTED;
+		case 1:
+			return State.CREATING;
+		case 2:
+			return State.READY;
+		case 3:
+			return State.FAILED;
+		case 4:
+			return State.TERMINATED;
+		default:
+			throw new IllegalStateException("invalid stream state");
+		}
+
+	}
+
+	public long getContextPointer() {
+		return native_pa_stream_get_context();
+	}
+
+	public int getSinkInputIndex() {
+		return native_pa_stream_get_index();
+	}
+
+	/**
+	 * 
+	 * @return the index of the sink or source this stream is connected to in
+	 *         the server
+	 */
+	public int getDeviceIndex() {
+		return native_pa_stream_get_device_index();
+	}
+
+	/**
+	 * 
+	 * @return the name of the sink or source this stream is connected to in the
+	 *         server
+	 */
+	public String getDeviceName() {
+		return native_pa_stream_get_device_name();
+	}
+
+	/**
+	 * if the sink or source this stream is connected to has been suspended.
+	 * 
+	 * @return
+	 */
+	public boolean isSuspended() {
+		return (native_pa_stream_is_suspended() != 0);
+	}
+
+	/**
+	 * Connect the stream to a sink
+	 * 
+	 * @param deviceName
+	 */
+	public void connectForPlayback(String deviceName) {
+
+		int returnValue = native_pa_stream_connect_playback(deviceName, 0, 0,
+				0, 0);
+	}
+
+	/**
+	 * Connect the stream to a source.
+	 * 
+	 */
+	void connectForRecording(String deviceName) {
+		int returnValue = native_pa_stream_connect_record(deviceName, 0, 0);
+	}
+
+	/**
+	 * Disconnect a stream from a source/sink.
+	 */
+	void disconnect() {
+		int returnValue = native_pa_stream_disconnect();
+	}
+
+	/**
+	 * Write data to the server
+	 * 
+	 * @param data
+	 * @param length
+	 * @return
+	 */
+	int write(byte[] data, int offset, int length) {
+		return native_pa_stream_write(data, offset, length);
+	}
+
+	/**
+	 * Read the next fragment from the buffer (for recording).
+	 * 
+	 * 
+	 * @param data
+	 */
+	public void peek(byte[] data) {
+
+	}
+
+	/**
+	 * 
+	 * Remove the current fragment on record streams.
+	 */
+	void drop() {
+
+	}
+
+	/**
+	 * Return the number of bytes that may be written using write().
+	 * 
+	 * @return
+	 */
+	public int getWritableSize() {
+		return native_pa_stream_writable_size();
+	}
+
+	/**
+	 * Return the number of bytes that may be read using peek().
+	 * 
+	 * @return
+	 */
+	public int getReableSize() {
+		return native_pa_stream_readable_size();
+	}
+
+	/**
+	 * Drain a playback stream
+	 * 
+	 * @return
+	 */
+	Operation drain() {
+		Operation drainOperation = new Operation(native_pa_stream_drain());
+		return drainOperation;
+	}
+
+	/**
+	 * this function is called whenever the state changes
+	 */
+	void stateCallback() {
+
+	}
+
+	void writeCallback() {
+
+	}
+
+	void readCallback() {
+
+	}
+
+	void overflowCallback() {
+
+	}
+
+	void underflowCallback() {
+
+	}
+
+	/**
+	 * callback function that is called when a the server starts playback after
+	 * an underrun or on initial startup
+	 */
+	void playbackStartedCallback() {
+
+	}
+
+	/**
+	 * called whenever a latency information update happens
+	 */
+	void latencyUpdateCallback() {
+
+	}
+
+	/**
+	 * whenever the stream is moved to a different sink/source
+	 */
+	void movedCallback() {
+
+	}
+
+	/**
+	 * whenever the sink/source this stream is connected to is suspended or
+	 * resumed
+	 */
+	void suspendedCallback() {
+
+	}
+
+	/**
+	 * Pause (or resume) playback of this stream temporarily.
+	 * 
+	 * @param cork
+	 * @return
+	 */
+	Operation cork(boolean cork) {
+		int yes = cork ? 1 : 0;
+		Operation corkOperation = new Operation(native_pa_stream_cork(yes));
+		return corkOperation;
+	}
+
+	/**
+	 * Flush the playback buffer of this stream.
+	 * 
+	 * @return
+	 */
+	Operation flush() {
+		Operation flushOperation = new Operation(native_pa_stream_flush());
+		return flushOperation;
+	}
+
+	/*
+	 * Operation pa_stream_prebuf (pa_stream *s, pa_stream_success_cb_t cb, void
+	 * *userdata)
+	 * 
+	 * Reenable prebuffering as specified in the pa_buffer_attr structure.
+	 */
+
+	/**
+	 * Request immediate start of playback on this stream.
+	 */
+	Operation triggerStart() {
+		Operation triggerOperation = new Operation(native_pa_stream_trigger());
+		return triggerOperation;
+	}
+
+	/**
+	 * set the stream's name
+	 * 
+	 * @param name
+	 * @return
+	 */
+	Operation setName(String name) {
+		Operation setNameOperation = new Operation(
+				native_pa_stream_set_name(name));
+		return setNameOperation;
+	}
+
+	long getTimeInMicroseconds() {
+		return -1;
+	}
+
+	// TODO: huh?
+	/**
+	 * @returns the total stream latency
+	 */
+	int getLatency() {
+		return -1;
+	}
+
+	/*
+	 * const pa_timing_info * pa_stream_get_timing_info (pa_stream *s) Return
+	 * the latest raw timing data structure.
+	 * 
+	 */
+
+	public AudioFormat getSampleSpec() {
+		return null;
+	}
+
+	/*
+	 * const pa_channel_map * pa_stream_get_channel_map (pa_stream *s) Return a
+	 * pointer to the stream's channel map.
+	 */
+
+	/*
+	 * const pa_buffer_attr * pa_stream_get_buffer_attr (pa_stream *s) Return
+	 * the per-stream server-side buffer metrics of the stream.
+	 * 
+	 */
+
+	/*
+	 * pa_operation * pa_stream_set_buffer_attr (pa_stream *s, const
+	 * pa_buffer_attr *attr, pa_stream_success_cb_t cb, void *userdata) Change
+	 * the buffer metrics of the stream during playback.
+	 * 
+	 */
+
+	/**
+	 * Change the stream sampling rate during playback.
+	 * 
+	 */
+	/*
+	 * Operation updateSampleRate(int rate) { Operation updateOp = new
+	 * Operation(native_pa_stream_) }
+	 */
+}
--- a/src/native/Makefile.am	Wed Aug 13 13:17:13 2008 -0400
+++ b/src/native/Makefile.am	Thu Aug 14 14:24:17 2008 -0400
@@ -12,7 +12,9 @@
 	org_classpath_icedtea_pulseaudio_Operation.h \
 	org_classpath_icedtea_pulseaudio_Operation.c \
 	org_classpath_icedtea_pulseaudio_PulseAudioClip.c \
-	org_classpath_icedtea_pulseaudio_PulseAudioClip.h
+	org_classpath_icedtea_pulseaudio_PulseAudioClip.h \
+	org_classpath_icedtea_pulseaudio_Stream.c \
+	org_classpath_icedtea_pulseaudio_Stream.h 
 
 AM_CFLAGS = -g -Wall -Werror $(PLATFORM_FLAGS) $(LIBPULSE_CFLAGS)
 AM_LDFLAGS = -g -Wall -Werror $(LIBPULSE_LIBS)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/native/org_classpath_icedtea_pulseaudio_Stream.c	Thu Aug 14 14:24:17 2008 -0400
@@ -0,0 +1,324 @@
+#include "org_classpath_icedtea_pulseaudio_Stream.h"
+
+#include "jni-common.h"
+#include <pulse/pulseaudio.h>
+#include <string.h>
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_new
+ * Signature: (Ljava/lang/String;)V
+ */
+JNIEXPORT void JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1new
+(JNIEnv* env, jobject obj, jlong contextPointer, jstring nameString, jstring encodingString, jint sampleRate, jint channels) {
+
+	pa_context* context = convertJavaLongToPointer(contextPointer);
+	assert(context);
+	const char* name = NULL;
+	if (nameString) {
+		name = (*env)->GetStringUTFChars(env,nameString, NULL);
+		if (name == NULL) {
+			return; // oome thrown
+		}
+	}
+
+	const char *encoding = (*env)->GetStringUTFChars(env, encodingString, NULL);
+	if( encoding == NULL) {
+		return;		//oome thrown
+	}
+
+	pa_sample_spec sample_spec;
+	
+	if (strcmp(encoding, "PA_SAMPLE_U8") == 0) {
+		sample_spec.format = PA_SAMPLE_U8;
+	} else if (strcmp(encoding, "PA_SAMPLE_ALAW") == 0) {
+		sample_spec.format = PA_SAMPLE_ALAW;
+	} else if (strcmp(encoding, "PA_SAMPLE_ULAW;") == 0) {
+		sample_spec.format = PA_SAMPLE_ULAW;
+	} else if (strcmp(encoding, "PA_SAMPLE_S16BE") == 0) {
+		sample_spec.format = PA_SAMPLE_S16BE;
+	} else if (strcmp(encoding, "PA_SAMPLE_S16LE") == 0) {
+		sample_spec.format = PA_SAMPLE_S16LE;
+	} else if (strcmp(encoding, "PA_SAMPLE_S32BE") == 0) {
+		sample_spec.format = PA_SAMPLE_S32BE;
+	} else if (strcmp(encoding, "PA_SAMPLE_S32LE") == 0) {
+		sample_spec.format = PA_SAMPLE_S32LE;
+	} else {
+		printf("error in open: encoding is : %s\n", encoding);
+		throwByName(env, "java/lang/IllegalArgumentException", "Invalid format");
+		/* clean up */
+		(*env)->ReleaseStringUTFChars(env, encodingString, encoding);
+		return;
+	}
+
+	sample_spec.rate = sampleRate;
+	sample_spec.channels = channels;
+
+	if ( !pa_sample_spec_valid(&sample_spec)) {
+		throwByName(env, "java/lang/IllegalArgumentException", "Invalid format");
+		(*env)->ReleaseStringUTFChars(env, encodingString, encoding);
+		if (name) {
+			(*env)->ReleaseStringUTFChars(env, nameString,name);
+		}
+		return;
+	}
+
+	pa_stream* stream = pa_stream_new(context, name, &sample_spec, NULL);
+	assert(stream);
+	if (name) {
+		(*env)->ReleaseStringUTFChars(env, nameString,name);
+	}
+	setJavaPointer(env, obj, "streamPointer", stream);
+
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_get_state
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1get_1state
+(JNIEnv* env, jobject obj) {
+	pa_stream* stream = (pa_stream*) getJavaPointer(env, obj, "streamPointer");
+	assert(stream);
+	return pa_stream_get_state(stream);
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_get_context
+ * Signature: ()J
+ */
+JNIEXPORT jlong JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1get_1context
+(JNIEnv* env, jobject obj) {
+	pa_stream* stream = (pa_stream*) getJavaPointer(env, obj, "streamPointer");
+	assert(stream);
+	pa_context* context = pa_stream_get_context(stream);
+	assert(context);
+	return convertPointerToJavaLong(context);
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_get_index
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1get_1index
+(JNIEnv* env, jobject obj) {
+	pa_stream* stream = (pa_stream*) getJavaPointer(env, obj, "streamPointer");
+	assert(stream);
+	return pa_stream_get_index(stream);
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_get_device_index
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1get_1device_1index
+(JNIEnv* env, jobject obj) {
+	pa_stream* stream = (pa_stream*) getJavaPointer(env, obj, "streamPointer");
+	assert(stream);
+	return pa_stream_get_device_index(stream);
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_get_device_name
+ * Signature: ()Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1get_1device_1name
+(JNIEnv* env, jobject obj) {
+	pa_stream* stream = (pa_stream*)getJavaPointer(env, obj, "streamPointer");
+	assert(stream);
+	const char* name = pa_stream_get_device_name(stream);
+	assert(name);
+	return (*env)->NewStringUTF(env, name);
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_is_suspended
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1is_1suspended
+(JNIEnv* env, jobject obj) {
+	pa_stream* stream = (pa_stream*) getJavaPointer(env, obj, "streamPointer");
+	assert(stream);
+	return pa_stream_is_suspended(stream);
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_connect_playback
+ * Signature: (Ljava/lang/String;JIJJ)I
+ */
+JNIEXPORT jint JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1connect_1playback
+(JNIEnv* env, jobject obj, jstring device, jlong pointer, jint anotherPointer, jlong yetAnotherPointer, jlong TooManyPointers) {
+	pa_stream* stream = (pa_stream*) getJavaPointer(env, obj, "streamPointer");
+	const char* dev = NULL;
+	if (device != NULL) {
+		dev = (*env)->GetStringUTFChars(env, device, NULL);
+		if (dev == NULL) {
+			return -1; // oome thrown
+		}
+	}
+	int value = pa_stream_connect_playback(stream, dev, NULL, 0, NULL, NULL);
+	if (dev != NULL) {
+		(*env)->ReleaseStringUTFChars(env, device, dev);
+		dev = NULL;
+	}
+	return value;
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_connect_record
+ * Signature: (Ljava/lang/String;JI)I
+ */
+JNIEXPORT jint JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1connect_1record
+(JNIEnv* env, jobject obj, jstring device, jlong bufferPointer, jint flags) {
+	pa_stream* stream = (pa_stream*)getJavaPointer(env, obj, "streamPointer");
+	assert(stream);
+	const char* dev = NULL;
+	if (device != NULL) {
+		dev = (*env)->GetStringUTFChars(env, device, NULL);
+		if (dev == NULL) {
+			return -1; // oome thrown
+		}
+	}
+	pa_buffer_attr* buffer = convertJavaLongToPointer(bufferPointer);
+	assert(buffer == NULL);
+	int value = pa_stream_connect_record(stream, dev, buffer, flags);
+	if (dev != NULL) {
+		(*env)->ReleaseStringUTFChars(env, device, dev);
+		dev = NULL;
+	}
+	return value;
+
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_disconnect
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1disconnect
+(JNIEnv* env, jobject obj) {
+	pa_stream* stream = (pa_stream*) getJavaPointer(env, obj, "streamPointer");
+	assert(stream);
+	return pa_stream_disconnect(stream);
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_write
+ * Signature: ([BI)I
+ */
+JNIEXPORT jint JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1write
+(JNIEnv* env, jobject obj, jbyteArray data, jint offset, jint data_length) {
+	pa_stream* stream = (pa_stream*)getJavaPointer(env, obj, "streamPointer");
+	assert(stream);
+	jbyte* data_buffer = (*env)->GetByteArrayElements(env, data, NULL);
+	if (data_buffer == NULL) {
+		return -1; // oome thrown
+	}
+	jbyte* buffer_start = data_buffer + offset;
+	int value = pa_stream_write(stream, buffer_start, data_length, NULL, 0, PA_SEEK_RELATIVE);
+	(*env)->ReleaseByteArrayElements(env, data, data_buffer, 0);
+	return value;
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_writable_size
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1writable_1size
+(JNIEnv* env, jobject obj) {
+	pa_stream* stream = (pa_stream*)getJavaPointer(env, obj, "streamPointer");
+	size_t size = pa_stream_writable_size(stream);
+	return size;
+
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_readable_size
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1readable_1size
+(JNIEnv* env, jobject obj) {
+	pa_stream* stream = (pa_stream*)getJavaPointer(env, obj, "streamPointer");
+	return pa_stream_readable_size(stream);
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_drain
+ * Signature: ()J
+ */
+JNIEXPORT jlong JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1drain
+(JNIEnv* env, jobject obj) {
+	pa_stream* stream = (pa_stream*)getJavaPointer(env, obj, "streamPointer");
+	pa_operation* operation = pa_stream_drain(stream, NULL, NULL);
+	return convertPointerToJavaLong(operation);
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_cork
+ * Signature: (I)J
+ */
+JNIEXPORT jlong JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1cork
+(JNIEnv* env, jobject obj, jint yes) {
+	pa_stream* stream = (pa_stream*)getJavaPointer(env, obj, "streamPointer");
+	pa_operation* operation = pa_stream_cork(stream, yes, NULL, NULL);
+	return convertPointerToJavaLong(operation);
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_flush
+ * Signature: ()J
+ */
+JNIEXPORT jlong JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1flush
+(JNIEnv* env, jobject obj) {
+	pa_stream* stream = (pa_stream*) getJavaPointer(env, obj, "streamPointer");
+	pa_operation* operation = pa_stream_flush(stream, NULL, NULL);
+	return convertPointerToJavaLong(operation);
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_trigger
+ * Signature: ()J
+ */
+JNIEXPORT jlong JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1trigger
+(JNIEnv* env, jobject obj) {
+	pa_stream* stream = (pa_stream*)getJavaPointer(env, obj, "streamPointer");
+	pa_operation* operation = pa_stream_trigger(stream, NULL, NULL);
+	return convertPointerToJavaLong(operation);
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_set_name
+ * Signature: (Ljava/lang/String;)J
+ */
+JNIEXPORT jlong JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1set_1name
+(JNIEnv* env, jobject obj, jstring newName) {
+	pa_stream* stream = (pa_stream*)getJavaPointer(env, obj, "streamPointer");
+	const char* name;
+	name = (*env)->GetStringUTFChars(env, newName, NULL);
+	if (name == NULL) {
+		return 0; // OutOfMemoryError already thrown
+	}
+
+	pa_operation* operation = pa_stream_set_name(stream, name, NULL, NULL);
+	assert(operation);
+	(*env)->ReleaseStringUTFChars(env, newName, name);
+
+	return convertPointerToJavaLong(operation);
+}
+
--- a/unittests/org/classpath/icedtea/pulseaudio/PulseAudioClipTest.java	Wed Aug 13 13:17:13 2008 -0400
+++ b/unittests/org/classpath/icedtea/pulseaudio/PulseAudioClipTest.java	Thu Aug 14 14:24:17 2008 -0400
@@ -37,11 +37,16 @@
 
 package org.classpath.icedtea.pulseaudio;
 
+import java.io.File;
+import java.io.IOException;
+
+import javax.sound.sampled.AudioInputStream;
 import javax.sound.sampled.AudioSystem;
 import javax.sound.sampled.Clip;
 import javax.sound.sampled.Line;
 import javax.sound.sampled.LineUnavailableException;
 import javax.sound.sampled.Mixer;
+import javax.sound.sampled.UnsupportedAudioFileException;
 
 import junit.framework.JUnit4TestAdapter;
 
@@ -79,6 +84,35 @@
 		Assert.assertNotNull(clip);
 	}
 
+	
+	@Test (expected=IllegalArgumentException.class)
+	public void testClipOpen() throws LineUnavailableException {
+		Clip clip = (Clip) mixer.getLine(new Line.Info(Clip.class));
+		clip.open();
+	}
+	
+	@Test
+	public void testClipOpens() throws LineUnavailableException, UnsupportedAudioFileException, IOException {
+		Clip clip = (Clip) mixer.getLine(new Line.Info(Clip.class));
+		File soundFile = new File("testsounds/startup.wav");
+		AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+		clip.open(audioInputStream);
+	}
+	
+	@Test
+	public void testPlayClip() throws LineUnavailableException, IOException, UnsupportedAudioFileException {
+		Clip clip = (Clip) mixer.getLine(new Line.Info(Clip.class));
+		File soundFile = new File("testsounds/startup.wav");
+		AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+		clip.open(audioInputStream);
+		clip.loop(1);
+		
+		
+		
+	}
+	
 	@After
 	public void tearDown() {