changeset 67:856e11044f24

2008-08-11 Omair Majid <omajid@redhat.com> * src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java: now notifies any linelisteners of OPEN event. write() now tests the arguments passed to it * src/native/org_classpath_icedtea_pulseaudio/PulseAudioSourceDataLine.c: enabled a little debug output * unittests/org/classpath/icedtea/pulseaudio/PulseSourceDataLineTest.java: added tests to confirm write's argument checking. also added tests to check that listeners are notified of OPEN and CLOSE LineEvents
author Omair Majid <omajid@redhat.com>
date Tue, 12 Aug 2008 15:17:14 -0400
parents 8f4e01b67c92
children 9a72b909f18a
files src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java src/native/org_classpath_icedtea_pulseaudio_PulseAudioSourceDataLine.c unittests/org/classpath/icedtea/pulseaudio/PulseSourceDataLineTest.java
diffstat 3 files changed, 158 insertions(+), 16 deletions(-) [+]
line wrap: on
line diff
--- a/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java	Tue Aug 12 11:28:22 2008 -0400
+++ b/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java	Tue Aug 12 15:17:14 2008 -0400
@@ -73,7 +73,7 @@
 	private AudioFormat currentFormat = null;
 	private AudioFormat defaultFormat = null;
 
-	private List<LineListener> listeners;
+	private List<LineListener> lineListeners;
 
 	private Control[] controls = null;
 	private PulseAudioStreamMuteControl muteControl;
@@ -123,7 +123,7 @@
 
 	public PulseAudioSourceDataLine(EventLoop eventLoop) {
 		this.eventLoop = eventLoop;
-		this.listeners = new ArrayList<LineListener>();
+		this.lineListeners = new ArrayList<LineListener>();
 		this.volume = PulseAudioVolumeControl.MAX_VOLUME;
 
 		/*
@@ -349,6 +349,24 @@
 			throw new IllegalArgumentException("Invalid format");
 		}
 
+		StreamListener openCloseListener = new StreamListener() {
+
+			@Override
+			public void update(StreamEvent e) {
+				if (e.getType() == StreamEvent.Type.READY) {
+					fireLineEvent(new LineEvent(PulseAudioSourceDataLine.this,
+							LineEvent.Type.OPEN, AudioSystem.NOT_SPECIFIED));
+				} else if (e.getType() == StreamEvent.Type.TERMINATED
+						|| e.getType() == StreamEvent.Type.FAILED) {
+					fireLineEvent((new LineEvent(PulseAudioSourceDataLine.this,
+							LineEvent.Type.CLOSE, AudioSystem.NOT_SPECIFIED)));
+				}
+			}
+
+		};
+		
+		addStreamListener(openCloseListener);
+
 		final Semaphore semaphore = new Semaphore(0);
 
 		synchronized (eventLoop.threadLock) {
@@ -374,6 +392,7 @@
 			// throw new LineUnavailableException("unable to prepare
 			// stream");
 		}
+
 		System.out.println(this.getClass().getName() + "stream is ready");
 
 		controls = new Control[2];
@@ -402,7 +421,18 @@
 	@Override
 	public int write(byte[] data, int offset, int length) {
 
-		// TODO add check that the data is an integral number of frames
+		int frameSize = currentFormat.getFrameSize();
+		if (length % frameSize != 0) {
+			throw new IllegalArgumentException(
+					"amount of data to write does not represent an integral number of frames");
+		}
+		if (length < 0) {
+			throw new IllegalArgumentException("length is negative");
+		}
+
+		if (length + offset > data.length) {
+			throw new ArrayIndexOutOfBoundsException(length + offset);
+		}
 
 		int position = offset;
 		int remainingLength = length;
@@ -462,11 +492,11 @@
 	}
 
 	public void addLineListener(LineListener listener) {
-		this.listeners.add(listener);
+		this.lineListeners.add(listener);
 	}
 
 	public void removeLineListener(LineListener listener) {
-		this.listeners.remove(listener);
+		this.lineListeners.remove(listener);
 	}
 
 	private void addStreamListener(StreamListener listener) {
@@ -490,10 +520,6 @@
 			native_close();
 		}
 
-		for (LineListener l : this.listeners) {
-			l.update(new LineEvent(this, LineEvent.Type.CLOSE, 0));
-		}
-
 	}
 
 	public int getBufferSize() {
@@ -563,7 +589,7 @@
 		} else {
 			return new Control[] {};
 		}
-		
+
 	}
 
 	public javax.sound.sampled.Line.Info getLineInfo() {
@@ -622,16 +648,16 @@
 		synchronized (eventLoop.threadLock) {
 			switch (status) {
 			case 0:
-				fireEvent(new StreamEvent(StreamEvent.Type.UNCONNECTED));
+				fireStreamEvent(new StreamEvent(StreamEvent.Type.UNCONNECTED));
 				break;
 			case 1:
-				fireEvent(new StreamEvent(StreamEvent.Type.CREATING));
+				fireStreamEvent(new StreamEvent(StreamEvent.Type.CREATING));
 				break;
 			case 2:
-				fireEvent(new StreamEvent(StreamEvent.Type.READY));
+				fireStreamEvent(new StreamEvent(StreamEvent.Type.READY));
 				break;
 			case 3:
-				fireEvent(new StreamEvent(StreamEvent.Type.FAILED));
+				fireStreamEvent(new StreamEvent(StreamEvent.Type.FAILED));
 				break;
 			case 4:
 				break;
@@ -641,8 +667,13 @@
 		}
 	}
 
-	private void fireEvent(StreamEvent e) {
+	private void fireLineEvent(LineEvent e) {
+		for (LineListener lineListener : lineListeners) {
+			lineListener.update(e);
+		}
+	}
 
+	private void fireStreamEvent(StreamEvent e) {
 		for (StreamListener streamListener : streamListeners) {
 			streamListener.update(e);
 		}
--- a/src/native/org_classpath_icedtea_pulseaudio_PulseAudioSourceDataLine.c	Tue Aug 12 11:28:22 2008 -0400
+++ b/src/native/org_classpath_icedtea_pulseaudio_PulseAudioSourceDataLine.c	Tue Aug 12 15:17:14 2008 -0400
@@ -75,7 +75,7 @@
 
 	jobject obj = java_context->obj;
 
-	//	printf("stream state changed to %d\n", pa_stream_get_state(stream));
+	printf("----> Stream state changed to %d\n", pa_stream_get_state(stream));
 
 	/* Call the 'update' method in java
 	 * to handle all java-side events
--- a/unittests/org/classpath/icedtea/pulseaudio/PulseSourceDataLineTest.java	Tue Aug 12 11:28:22 2008 -0400
+++ b/unittests/org/classpath/icedtea/pulseaudio/PulseSourceDataLineTest.java	Tue Aug 12 15:17:14 2008 -0400
@@ -48,6 +48,8 @@
 import javax.sound.sampled.DataLine;
 import javax.sound.sampled.FloatControl;
 import javax.sound.sampled.Line;
+import javax.sound.sampled.LineEvent;
+import javax.sound.sampled.LineListener;
 import javax.sound.sampled.LineUnavailableException;
 import javax.sound.sampled.Mixer;
 import javax.sound.sampled.SourceDataLine;
@@ -63,6 +65,8 @@
 public class PulseSourceDataLineTest {
 	Mixer mixer;
 
+	private int listenerCalled = 0;
+
 	public static junit.framework.Test suite() {
 		return new JUnit4TestAdapter(PulseSourceDataLineTest.class);
 	}
@@ -105,6 +109,51 @@
 
 	}
 
+	@Test(expected = IllegalArgumentException.class)
+	public void testPlayLessThanFrameSize() throws LineUnavailableException,
+			UnsupportedAudioFileException, IOException {
+		File soundFile = new File("testsounds/startup.wav");
+		AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+		AudioFormat audioFormat = audioInputStream.getFormat();
+		// the audio file must have an even number of channels
+		Assert.assertTrue(audioFormat.getChannels() % 2 == 0);
+		SourceDataLine line;
+		line = (SourceDataLine) mixer.getLine(new DataLine.Info(
+				SourceDataLine.class, audioFormat));
+
+		byte[] data = new byte[1];
+		data[0] = (byte) 'a';
+
+		line.open();
+		line.write(data, 0, 1);
+
+		line.close();
+
+	}
+
+	@Test
+	public void testOpenFormat() throws LineUnavailableException,
+			UnsupportedAudioFileException, IOException {
+		/*
+		 * This test makes sure that the default format of a line using open()
+		 * is the same format that was passed to the mixer's getLine() function
+		 * to get the line in the first place
+		 */
+
+		File soundFile = new File("testsounds/startup.wav");
+		AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+		AudioFormat audioFormat = audioInputStream.getFormat();
+
+		SourceDataLine line;
+		line = (SourceDataLine) mixer.getLine(new DataLine.Info(
+				SourceDataLine.class, audioFormat));
+		Assert.assertNotNull(line);
+		line.open();
+		Assert.assertTrue(line.getFormat().matches(audioFormat));
+	}
+
 	@Test
 	public void testFindLineWithFormat() throws LineUnavailableException {
 		System.out
@@ -199,6 +248,68 @@
 		sourceLine.close();
 	}
 
+	@Test
+	public void testOpenEvent() throws LineUnavailableException {
+
+		listenerCalled = 0;
+		LineListener openListener = new LineListener() {
+			public void update(LineEvent event) {
+				Assert.assertTrue(event.getType() == LineEvent.Type.OPEN);
+				PulseSourceDataLineTest.this.listenerCalled++;
+			}
+		};
+
+		SourceDataLine line = (SourceDataLine) mixer.getLine(new Line.Info(
+				SourceDataLine.class));
+		line.addLineListener(openListener);
+		line.open();
+		line.removeLineListener(openListener);
+		line.close();
+		Assert.assertEquals(1, listenerCalled);
+		listenerCalled = 0;
+	}
+
+	@Test
+	public void testCloseEvent() throws LineUnavailableException {
+		listenerCalled = 0;
+		LineListener closeListener = new LineListener() {
+			public void update(LineEvent event) {
+				Assert.assertTrue(event.getType() == LineEvent.Type.CLOSE);
+				PulseSourceDataLineTest.this.listenerCalled++;
+			}
+		};
+
+		SourceDataLine line = (SourceDataLine) mixer.getLine(new Line.Info(
+				SourceDataLine.class));
+		line.open();
+		line.addLineListener(closeListener);
+		line.close();
+		line.removeLineListener(closeListener);
+		Assert.assertEquals(1, listenerCalled);
+		listenerCalled = 0;
+	}
+
+	@Test
+	public void testCloseEventWrongListener() throws LineUnavailableException {
+		listenerCalled = 0;
+		LineListener closeListener = new LineListener() {
+			public void update(LineEvent event) {
+				PulseSourceDataLineTest.this.listenerCalled++;
+			}
+		};
+
+		SourceDataLine line = (SourceDataLine) mixer.getLine(new Line.Info(
+				SourceDataLine.class));
+
+		line.open();
+		line.addLineListener(closeListener);
+		line.removeLineListener(closeListener);
+		line.close();
+		Assert.assertEquals(0, listenerCalled);
+		listenerCalled = 0;
+
+	}
+
 	@After
 	public void tearDown() throws Exception {