changeset 90:0ab3515d5074

2008-08-19 Omair Majid <omajid@redhat.com> * src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java (connectLine): Added support for buffer attributes. * src/java/org/classpath/icedtea/pulseaudio/Stream.java: (native_pa_stream_connect_playback): Likeswise. (native_pa_stream_get_sample_spec): New function. (native_pa_stream_get_buffer_attr): Likewise. (native_pa_stream_set_buffer_attr): Likewise. (Stream): Uses SampleSpecification objects now. (connectForPlayback): Added support for buffer attributes. (getBufferAttributes): New function. (setBufferAtrributes): Likewise. * src/native/org_classpath_icedtea_pulseaudio_Stream.c: (getStringFromFormat): New function. (getFormatFromString): New function. (Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1new): Uses getFormatFromString now. (Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1connect_1playback): New function. (Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1get_1sample_1spec): Likewise. (Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1get_1buffer_1attr): Likewise. (Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1set_1buffer_1attr): Likewise. * unittests/org/classpath/icedtea/pulseaudio/PulseSourceDataLineTest.java (testVolumeChanging): New function.
author Omair Majid <omajid@redhat.com>
date Tue, 19 Aug 2008 17:04:11 -0400
parents 52b513a7e5e9
children 675a5044aecb
files src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java src/java/org/classpath/icedtea/pulseaudio/Stream.java src/native/org_classpath_icedtea_pulseaudio_Stream.c unittests/org/classpath/icedtea/pulseaudio/PulseSourceDataLineTest.java
diffstat 4 files changed, 239 insertions(+), 66 deletions(-) [+]
line wrap: on
line diff
--- a/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java	Tue Aug 19 11:35:27 2008 -0400
+++ b/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java	Tue Aug 19 17:04:11 2008 -0400
@@ -46,11 +46,10 @@
 import javax.sound.sampled.LineListener;
 import javax.sound.sampled.LineUnavailableException;
 import javax.sound.sampled.SourceDataLine;
-import javax.sound.sampled.AudioFormat.Encoding;
 import javax.sound.sampled.Control.Type;
 
-public class PulseAudioSourceDataLine extends PulseAudioDataLine implements SourceDataLine {
-
+public class PulseAudioSourceDataLine extends PulseAudioDataLine implements
+		SourceDataLine {
 
 	private Control[] controls = null;
 	private PulseAudioStreamMuteControl muteControl;
@@ -60,8 +59,6 @@
 
 	private long currentFramePosition = 0;
 
-	
-
 	public PulseAudioSourceDataLine(EventLoop eventLoop, AudioFormat[] formats,
 			AudioFormat defaultFormat) {
 
@@ -93,7 +90,7 @@
 
 	public void open(AudioFormat format, int bufferSize)
 			throws LineUnavailableException {
-	
+
 		super.open(format, bufferSize);
 		controls = new Control[2];
 		volumeControl = new PulseAudioStreamVolumeControl(this);
@@ -102,12 +99,16 @@
 		controls[1] = muteControl;
 
 	}
-	
+
 	protected void connectLine() {
-		stream.connectForPlayback(null);
+		StreamBufferAttributes bufferAttributes = new StreamBufferAttributes(
+				StreamBufferAttributes.SANE_DEFAULT, StreamBufferAttributes.SANE_DEFAULT,
+				StreamBufferAttributes.SANE_DEFAULT, StreamBufferAttributes.SANE_DEFAULT,
+				StreamBufferAttributes.SANE_DEFAULT);
+		
+		stream.connectForPlayback(null,bufferAttributes);
 	}
 
-
 	@Override
 	public int write(byte[] data, int offset, int length) {
 
@@ -160,14 +161,12 @@
 		return sizeWritten;
 	}
 
-
 	public int available() {
 		synchronized (eventLoop.threadLock) {
 			return stream.getWritableSize();
 		}
 	};
 
-
 	public int getBufferSize() {
 		// FIXME!
 		return 10000;
@@ -290,7 +289,6 @@
 
 	}
 
-
 	protected EventLoop getEventLoop() {
 		return this.eventLoop;
 	}
--- a/src/java/org/classpath/icedtea/pulseaudio/Stream.java	Tue Aug 19 11:35:27 2008 -0400
+++ b/src/java/org/classpath/icedtea/pulseaudio/Stream.java	Tue Aug 19 17:04:11 2008 -0400
@@ -86,7 +86,9 @@
 	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,
+			int bufferMaxLength, int bufferTargetLength,
+			int bufferPreBuffering, int bufferMinimumRequest,
+			int bufferFragmentSize, int flags, long volumePointer,
 			long sync_streamPointer);
 
 	private native int native_pa_stream_connect_record(String device,
@@ -139,17 +141,20 @@
 
 	/*
 	 * 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.
+	 * the latest raw timing data structure.
 	 */
 
+	native StreamSampleSpecification native_pa_stream_get_sample_spec();
+
+	/*
+	 * const pa_channel_map * pa_stream_get_channel_map (pa_stream *s) Return a
+	 * pointer to the stream's channel map. const
+	 * 
+	 */
+	native StreamBufferAttributes native_pa_stream_get_buffer_attr();
+
+	native long native_pa_stream_set_buffer_attr(StreamBufferAttributes info);
+
 	private native long native_pa_stream_update_sample_rate(int rate);
 
 	/*
@@ -182,8 +187,11 @@
 
 		this.format = format;
 
-		native_pa_stream_new(contextPointer, name, format.toString(),
+		StreamSampleSpecification spec = new StreamSampleSpecification(format,
 				sampleRate, channels);
+
+		native_pa_stream_new(contextPointer, name, spec.getFormat().toString(),
+				spec.getRate(), spec.getChannels());
 	}
 
 	public void addStateListener(StateListener listener) {
@@ -330,10 +338,14 @@
 	 * 
 	 * @param deviceName
 	 */
-	public void connectForPlayback(String deviceName) {
+	public void connectForPlayback(String deviceName,
+			StreamBufferAttributes bufferAttributes) {
 
-		int returnValue = native_pa_stream_connect_playback(deviceName, 0, 0,
-				0, 0);
+		int returnValue = native_pa_stream_connect_playback(deviceName,
+				bufferAttributes.getMaxLength(), bufferAttributes
+						.getTargetLength(), bufferAttributes.getPreBuffering(),
+				bufferAttributes.getMinimumRequest(), bufferAttributes
+						.getFragmentSize(), 0, 0, 0);
 		assert (returnValue == 0);
 	}
 
@@ -575,8 +587,6 @@
 		return native_pa_stream_get_time();
 	}
 
-
-	
 	/**
 	 * @returns the total stream latency in microseconds
 	 */
@@ -599,18 +609,13 @@
 	 * 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.
-	 * 
-	 */
+	public StreamBufferAttributes getBufferAttributes() {
+		return native_pa_stream_get_buffer_attr();
+	}
 
-	/*
-	 * 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.
-	 * 
-	 */
+	public Operation setBufferAtrributes(StreamBufferAttributes attr) {
+		return new Operation(native_pa_stream_set_buffer_attr(attr));
+	}
 
 	/**
 	 * Change the stream sampling rate during playback.
--- a/src/native/org_classpath_icedtea_pulseaudio_Stream.c	Tue Aug 19 11:35:27 2008 -0400
+++ b/src/native/org_classpath_icedtea_pulseaudio_Stream.c	Tue Aug 19 17:04:11 2008 -0400
@@ -11,6 +11,56 @@
 
 extern JNIEnv* pulse_thread_env;
 
+const char* getStringFromFormat(pa_sample_format_t format) {
+
+	const char* value;
+
+	if (format == PA_SAMPLE_U8) {
+		value = "PA_SAMPLE_U8";
+	} else if (format == PA_SAMPLE_ALAW) {
+		value = "PA_SAMPLE_ALAW";
+	} else if (format == PA_SAMPLE_ULAW) {
+		value = "PA_SAMPLE_ULAW";
+	} else if (format == PA_SAMPLE_S16BE) {
+		value = "PA_SAMPLE_S16BE";
+	} else if (format == PA_SAMPLE_S16LE) {
+		value = "PA_SAMPLE_S16LE";
+	} else if (format == PA_SAMPLE_S32BE) {
+		value = "PA_SAMPLE_S32BE";
+	} else if (format == PA_SAMPLE_S32LE) {
+		value = "PA_SAMPLE_S32LE";
+	} else {
+		value = "PA_SAMPLE_INVALID";
+	}
+
+	return value;
+}
+
+pa_sample_format_t getFormatFromString(const char* encoding) {
+
+	pa_sample_format_t format;
+
+	if (strcmp(encoding, "PA_SAMPLE_U8") == 0) {
+		format = PA_SAMPLE_U8;
+	} else if (strcmp(encoding, "PA_SAMPLE_ALAW") == 0) {
+		format = PA_SAMPLE_ALAW;
+	} else if (strcmp(encoding, "PA_SAMPLE_ULAW;") == 0) {
+		format = PA_SAMPLE_ULAW;
+	} else if (strcmp(encoding, "PA_SAMPLE_S16BE") == 0) {
+		format = PA_SAMPLE_S16BE;
+	} else if (strcmp(encoding, "PA_SAMPLE_S16LE") == 0) {
+		format = PA_SAMPLE_S16LE;
+	} else if (strcmp(encoding, "PA_SAMPLE_S32BE") == 0) {
+		format = PA_SAMPLE_S32BE;
+	} else if (strcmp(encoding, "PA_SAMPLE_S32LE") == 0) {
+		format = PA_SAMPLE_S32LE;
+	} else {
+		format = PA_SAMPLE_INVALID;
+	}
+
+	return format;
+}
+
 static void stream_state_callback(pa_stream* stream, void *userdata) {
 	printf("stream_state_callback called\n");
 
@@ -168,11 +218,14 @@
 /*
  * Class:     org_classpath_icedtea_pulseaudio_Stream
  * Method:    native_pa_stream_new
- * Signature: (Ljava/lang/String;)V
+ * Signature: (JLjava/lang/String;Ljava/lang/String;IIIIIII)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) {
+(JNIEnv* env, jobject obj, jlong contextPointer, jstring nameString,
+		jstring encodingString, jint sampleRate, jint channels) {
+
 	printf("creating a new PulseAudio stream\n");
+
 	java_context* j_context = malloc(sizeof(java_context));
 	assert(j_context);
 	j_context->env = env;
@@ -180,6 +233,7 @@
 
 	pa_context* context = convertJavaLongToPointer(contextPointer);
 	assert(context);
+
 	const char* name = NULL;
 	if (nameString) {
 		name = (*env)->GetStringUTFChars(env,nameString, NULL);
@@ -197,30 +251,7 @@
 
 	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);
-		free(j_context);
-		(*env)->DeleteGlobalRef(env, obj);
-		return;
-	}
-
+	sample_spec.format = getFormatFromString(encoding);
 	sample_spec.rate = sampleRate;
 	sample_spec.channels = channels;
 
@@ -342,8 +373,20 @@
  * 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) {
+(JNIEnv* env, jobject obj, jstring device, jint bufferMaxLength,
+		jint bufferTargetLength, jint bufferPreBuffering,
+		jint bufferMinimumRequest, jint bufferFragmentSize, jint flags,
+		jlong volumePointer, jlong sync_streamPointer) {
 	pa_stream* stream = (pa_stream*) getJavaPointer(env, obj, "streamPointer");
+
+	pa_buffer_attr buffer_attributes;
+
+	buffer_attributes.maxlength = bufferMaxLength;
+	buffer_attributes.tlength = bufferTargetLength;
+	buffer_attributes.prebuf = bufferPreBuffering;
+	buffer_attributes.minreq = bufferMinimumRequest;
+	buffer_attributes.fragsize = bufferFragmentSize;
+
 	const char* dev = NULL;
 	if (device != NULL) {
 		dev = (*env)->GetStringUTFChars(env, device, NULL);
@@ -600,6 +643,92 @@
 
 /*
  * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_get_sample_spec
+ * Signature: ()Lorg/classpath/icedtea/pulseaudio/StreamSampleSpecification;
+ */
+JNIEXPORT jobject JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1get_1sample_1spec
+(JNIEnv* env, jobject obj) {
+	pa_stream* stream = (pa_stream*)getJavaPointer(env, obj, "streamPointer");
+	const pa_sample_spec* sample_spec = pa_stream_get_sample_spec(stream);
+
+	char* name = "Lorg/classpath/icedtea/pulseaudio/StreamSampleSpecification;";
+	jclass cls = (*env)->FindClass(env, name);
+	jmethodID constructor_mid = (*env)->GetMethodID(env, cls, "<init>", "V");
+
+	const char* formatString = getStringFromFormat(sample_spec->format);
+	int rate = sample_spec->rate;
+	int channels = sample_spec->channels;
+
+	jstring format = (*env)->NewStringUTF(env, formatString);
+
+	jobject return_object = (*env)->NewObject(env, cls, constructor_mid, format, rate, channels);
+
+	return return_object;
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_get_buffer_attr
+ * Signature: ()Lorg/classpath/icedtea/pulseaudio/StreamBufferAttributes;
+ */
+JNIEXPORT jobject JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1get_1buffer_1attr
+(JNIEnv* env, jobject obj) {
+	pa_stream* stream = (pa_stream*)getJavaPointer(env, obj, "streamPointer");
+	assert(stream);
+	const pa_buffer_attr* buffer = pa_stream_get_buffer_attr(stream);
+
+	const char* class_name = "Lorg/classpath/icedtea/pulseaudio/StreamBufferAttributes;";
+	jclass cls = (*env)->FindClass(env, class_name);
+	jmethodID constructor_mid = (*env)->GetMethodID(env, cls, "<init>", "V");
+
+	jint maxLength = buffer->maxlength;
+	jint targetLength = buffer->tlength;
+	jint preBuffering = buffer->prebuf;
+	jint minimumRequest = buffer->minreq;
+	jint fragmentSize = buffer->fragsize;
+
+	jobject return_object = (*env)->NewObject(env, cls, constructor_mid, maxLength, targetLength,
+			preBuffering, minimumRequest, fragmentSize);
+
+	return return_object;
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_set_buffer_attr
+ * Signature: (Lorg/classpath/icedtea/pulseaudio/Stream/BufferAttributes;)J
+ */
+JNIEXPORT jlong JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1set_1buffer_1attr
+(JNIEnv* env, jobject obj, jobject bufferAttributeObject) {
+
+	pa_stream* stream = (pa_stream*)getJavaPointer(env, obj, "streamPointer");
+	assert(stream);
+
+	jclass cls = (*env)->GetObjectClass(env, bufferAttributeObject);
+
+	pa_buffer_attr* buffer = malloc(sizeof(pa_buffer_attr));
+
+	jmethodID getMaxLengthID = (*env)->GetMethodID(env,cls,"getMaxLength","()I");
+	buffer->maxlength = (*env)->CallIntMethod(env, bufferAttributeObject, getMaxLengthID);
+
+	jmethodID getTargetLengthID = (*env)->GetMethodID(env,cls,"getTargetLength","()I");
+	buffer->tlength = (*env)->CallIntMethod(env, bufferAttributeObject, getTargetLengthID);
+
+	jmethodID getPreBufferingID = (*env)->GetMethodID(env,cls,"getPreBuffering","()I");
+	buffer->prebuf = (*env)->CallIntMethod(env, bufferAttributeObject, getPreBufferingID);
+
+	jmethodID getMinimumRequestID = (*env)->GetMethodID(env,cls,"getMinimumRequest ","()I");
+	buffer->minreq = (*env)->CallIntMethod(env, bufferAttributeObject, getMinimumRequestID );
+
+	jmethodID getFragmentSizeID = (*env)->GetMethodID(env,cls,"getFragmentSize","()I");
+	buffer->fragsize = (*env)->CallIntMethod(env, bufferAttributeObject, getFragmentSizeID );
+
+	pa_operation* operation = pa_stream_set_buffer_attr(stream, buffer, NULL, NULL);
+	assert(operation);
+	return convertPointerToJavaLong(operation);
+}
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
  * Method:    native_pa_stream_update_sample_rate
  * Signature: (I)J
  */
--- a/unittests/org/classpath/icedtea/pulseaudio/PulseSourceDataLineTest.java	Tue Aug 19 11:35:27 2008 -0400
+++ b/unittests/org/classpath/icedtea/pulseaudio/PulseSourceDataLineTest.java	Tue Aug 19 17:04:11 2008 -0400
@@ -225,6 +225,47 @@
 	}
 
 	@Test
+	public void testVolumeChanging() throws LineUnavailableException,
+			IOException, UnsupportedAudioFileException {
+
+		Mixer selectedMixer = mixer;
+
+		selectedMixer.open();
+		SourceDataLine line = (SourceDataLine) selectedMixer
+				.getLine(new Line.Info(SourceDataLine.class));
+
+		File soundFile = new File(new java.io.File(".").getCanonicalPath()
+				+ "/testsounds/logout.wav");
+		AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+		AudioFormat audioFormat = audioInputStream.getFormat();
+
+		line.open(audioFormat);
+		line.start();
+		PulseAudioStreamVolumeControl volume = (PulseAudioStreamVolumeControl) line
+				.getControl(FloatControl.Type.VOLUME);
+		PulseAudioStreamMuteControl mute = (PulseAudioStreamMuteControl) line
+				.getControl(BooleanControl.Type.MUTE);
+
+		volume.setValue(PulseAudioStreamVolumeControl.MIN_VOLUME);
+
+		byte[] abData = new byte[1000];
+		int bytesRead = 0;
+
+		while (bytesRead >= 0) {
+			bytesRead = audioInputStream.read(abData, 0, abData.length);
+			if (bytesRead > 0) {
+				line.write(abData, 0, bytesRead);
+				volume.setValue(volume.getValue() + 100);
+			}
+		}
+
+		line.flush();
+		selectedMixer.close();
+
+	}
+
+	@Test
 	public void testFindControl() throws LineUnavailableException {
 		SourceDataLine sourceLine = (SourceDataLine) mixer
 				.getLine(new Line.Info(SourceDataLine.class));