changeset 95:23c52715cfb2

added start, stop and loop in Clip
author iivan@town.yyz.redhat.com
date Wed, 20 Aug 2008 14:27:45 -0400
parents c1154939ba8b
children 9a4c1d255bc6
files src/java/org/classpath/icedtea/pulseaudio/PulseAudioClip.java src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixer.java unittests/org/classpath/icedtea/pulseaudio/PulseAudioClipTest.java
diffstat 3 files changed, 215 insertions(+), 51 deletions(-) [+]
line wrap: on
line diff
--- a/src/java/org/classpath/icedtea/pulseaudio/PulseAudioClip.java	Wed Aug 20 13:34:24 2008 -0400
+++ b/src/java/org/classpath/icedtea/pulseaudio/PulseAudioClip.java	Wed Aug 20 14:27:45 2008 -0400
@@ -38,8 +38,10 @@
 package org.classpath.icedtea.pulseaudio;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.concurrent.Semaphore;
 
 import javax.sound.sampled.AudioFormat;
 import javax.sound.sampled.AudioInputStream;
@@ -51,7 +53,7 @@
 import javax.sound.sampled.LineUnavailableException;
 import javax.sound.sampled.Control.Type;
 
-public class PulseAudioClip implements Clip {
+public class PulseAudioClip extends PulseAudioDataLine implements Clip {
 
 	private byte[] data = null;
 
@@ -59,45 +61,121 @@
 	private int bufferSize = 0;
 
 	// these are frame indices. so counted from 0
-	private long currentFrame = 0;
+	private int currentFrame = 0;
 	private int frameCount = 0;
-	private long startFrame = 0;
-	private long endFrame = 0;
-	private long framesSinceOpen = 0;
+	private int startFrame = 0;
+	private int endFrame = 0;
+	private int framesSinceOpen = 0;
 
 	private AudioFormat currentFormat = null;
 	private static final AudioFormat DEFAULT_FORMAT = new AudioFormat(
 			AudioFormat.Encoding.PCM_UNSIGNED, 22050, 8, 2, 2, 22050 / 2, false);
 
-	private boolean isOpen = false;
 
-	private List<LineListener> lineListeners = null;
 
 	private static final int DEFAULT_BUFFER_SIZE = 0;
 	public static final String DEFAULT_CLIP_NAME = "Clip";
 
-	private Stream stream;
+	private Object clipLock = new Object();
+	private boolean clipThreadStarted;
+	private int loopsLeft;
+	private Semaphore clipSemaphore= new Semaphore(1);
 
-	private Thread clipLoop = new Thread() {
+	private class ClipThread extends Thread {
 		@Override
 		public void run() {
-			while (true) {
-
+			clipThreadStarted = true;
+			while(loopsLeft >= 0) {
+				writeFrames(currentFrame, endFrame + 1);
+				if(Thread.interrupted()) {
+					//Thread.currentThread().interrupt();
+					clipThreadStarted = false;
+					return;
+				}
+			
+				
+		
+			try {
+				//if loop(0) has been called from the mainThread,
+				//wait until loopsLeft has been set
+				clipSemaphore.acquire();
+				if(loopsLeft == 0){
+						System.out.println("Reading to the end of the file");
+						writeFrames( endFrame, getFrameLength());
+						return;
+				} else {
+					synchronized (clipLock) {
+						currentFrame = startFrame;
+						loopsLeft--;
+					}
+				}
+				clipSemaphore.release();
+			} catch (InterruptedException e) {
+					return;
 			}
-
+					
+					
+			}
 		}
-
-	};
+	}
+	
+	private ClipThread clipThread; 
+			
+	
+	private void writeFrames(int startingFrame, int lastFrame) {
 
-	public PulseAudioClip() {
-		lineListeners = new LinkedList<LineListener>();
+		int remainingFrames = lastFrame - startingFrame - 1;
+		while(remainingFrames > 0) {
+			synchronized (eventLoop.threadLock) {
+				int availableSize = stream.getWritableSize();
+				int framesToWrite = Math.min(remainingFrames, availableSize / getFormat().getFrameSize());
+				stream.write(data, currentFrame * getFormat().getFrameSize(), framesToWrite * getFormat().getFrameSize());
+				remainingFrames -= framesToWrite;
+				currentFrame += framesToWrite;
+				if(Thread.interrupted()) {
+					Thread.currentThread().interrupt();
+					break;
+				}
+				System.out.println("remaining frames" + remainingFrames);
+			}
+		}
 	}
 
-	@Override
-	public void addLineListener(LineListener listener) {
-		lineListeners.add(listener);
+	
+	static {
+		try {
+			String library = new java.io.File(".").getCanonicalPath()
+					+ java.io.File.separatorChar
+					+ System.mapLibraryName("pulse-java");
+			System.out.println(library);
+			System.load(library);
+		} catch (IOException e) {
+			assert ("Loading failed".endsWith("library"));
+		}
 	}
 
+	public PulseAudioClip(EventLoop eventLoop, AudioFormat[] formats, AudioFormat defaultFormat) {
+		supportedFormats = formats;
+		this.eventLoop = eventLoop;
+		this.lineListeners = new ArrayList<LineListener>();
+		this.defaultFormat = defaultFormat; 
+		this.currentFormat = defaultFormat;
+		clipThread = new ClipThread();
+
+	}
+	
+	protected void connectLine() {
+		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 available() {
 		return 0; // a clip always returns 0
@@ -105,7 +183,12 @@
 
 	@Override
 	public void close() {
-		// TODO Auto-generated method stub
+		try {
+			clipThread.join();
+		} catch (InterruptedException e) {
+			e.printStackTrace();
+		}
+		
 		stream.drain();
 		stream.disconnect();
 		isOpen = false;
@@ -113,7 +196,15 @@
 
 	@Override
 	public void drain() {
-		stream.drain();
+		Operation operation;
+
+		synchronized (eventLoop.threadLock) {
+			operation = stream.drain();
+		}
+
+		operation.waitForCompletion();
+		operation.releaseReference();
+
 	}
 
 	@Override
@@ -169,7 +260,9 @@
 
 	@Override
 	public long getLongFramePosition() {
-		return framesSinceOpen;
+		synchronized(clipLock) {
+			return framesSinceOpen;
+		}
 	}
 
 	@Override
@@ -177,12 +270,16 @@
 		if (!isOpen) {
 			return AudioSystem.NOT_SPECIFIED;
 		}
-		return frameCount / currentFormat.getFrameSize();
+		synchronized(clipLock) {
+			return frameCount / currentFormat.getFrameSize();
+		}
 	}
 
 	@Override
 	public long getMicrosecondPosition() {
-		return framesSinceOpen / currentFormat.getFrameSize();
+		synchronized(clipLock) {
+			return framesSinceOpen / currentFormat.getFrameSize();
+		}
 	}
 
 	@Override
@@ -210,8 +307,23 @@
 
 	@Override
 	public void loop(int count) {
-		// TODO Auto-generated method stub
-
+		System.out.println("Loop " + count + " called");
+		if(clipThreadStarted && count != 0) {
+			//Do nothing; behavior not specified by the Java API 
+			return;
+		}
+		synchronized(clipLock) {
+			if(currentFrame > endFrame) {
+				loopsLeft = 0;
+			} else {
+				loopsLeft = count; 
+			}
+		}
+		if (!clipThread.isAlive()) {
+			clipThread = new ClipThread();
+			clipThread.start();
+		} 
+			
 	}
 
 	@Override
@@ -223,24 +335,22 @@
 	public void open(AudioFormat format, byte[] data, int offset, int bufferSize)
 			throws LineUnavailableException {
 
-		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);
-
+		super.open(format);
+		this.data = new byte[bufferSize];
+		System.arraycopy(data, offset, this.data, 0, bufferSize);
+		frameCount =  bufferSize / format.getFrameSize();
 		isOpen = true;
 	}
 
 	@Override
 	public void open(AudioInputStream stream) throws LineUnavailableException,
 			IOException {
-		// TODO Auto-generated method stub
-
 		byte[] buffer = new byte[(int) (stream.getFrameLength() * stream
 				.getFormat().getFrameSize())];
+		stream.read(buffer, 0, buffer.length);
 
-		open(stream.getFormat(), buffer, 0, DEFAULT_BUFFER_SIZE);
+		open(stream.getFormat(), buffer, 0, buffer.length);
+	
 
 	}
 
@@ -255,8 +365,10 @@
 		if (frames > frameCount) {
 			throw new IllegalArgumentException("incorreft frame value");
 		}
-
-		currentFrame = frames;
+		
+		synchronized(clipLock) {
+			currentFrame = frames;
+		}
 
 	}
 
@@ -271,26 +383,47 @@
 					"ending point must be greater than or equal to the starting point");
 		}
 
-		startFrame = start;
-		endFrame = end;
+		synchronized(clipLock) {
+			startFrame = start;
+			endFrame = end;
+		}
 
 	}
 
 	@Override
 	public void setMicrosecondPosition(long microseconds) {
 		float frameIndex = microseconds * currentFormat.getFrameRate();
-		currentFrame = (long) frameIndex;
+		synchronized(clipLock) {
+			currentFrame = (int) frameIndex;
+		}
 
 	}
 
 	@Override
 	public void start() {
-		stream.cork(false);
+		if(!clipThread.isAlive()) {
+			synchronized(clipLock) {
+				loopsLeft = 0;
+			}
+			clipThread = new ClipThread();
+			clipThread.start();
+		}
 	}
-
-	@Override
+	
+	
 	public void stop() {
-		stream.cork(true);
+		if(clipThread.isAlive()) {
+			clipThread.interrupt();
+		}
+		try {
+			clipThread.join();
+		} catch (InterruptedException e) {
+			e.printStackTrace();
+		}
+		synchronized(clipLock) {
+			currentFrame = 0;
+			loopsLeft = 0;
+		}
 	}
 
 }
--- a/src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixer.java	Wed Aug 20 13:34:24 2008 -0400
+++ b/src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixer.java	Wed Aug 20 14:27:45 2008 -0400
@@ -49,6 +49,7 @@
 import javax.sound.sampled.AudioFormat;
 import javax.sound.sampled.AudioInputStream;
 import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.Clip;
 import javax.sound.sampled.Control;
 import javax.sound.sampled.DataLine;
 import javax.sound.sampled.Line;
@@ -318,10 +319,9 @@
 					defaultFormat);
 		}
 
-		PulseAudioClip clip = new PulseAudioClip();
-
-		if (info.matches(clip.getLineInfo())) {
-			clips.add(clip);
+		if ((info.getLineClass() == Clip.class)) {
+			Clip clip =  new PulseAudioClip(eventLoop, formats, defaultFormat);
+			clips.add((PulseAudioClip) clip);
 			return clip;
 		}
 
--- a/unittests/org/classpath/icedtea/pulseaudio/PulseAudioClipTest.java	Wed Aug 20 13:34:24 2008 -0400
+++ b/unittests/org/classpath/icedtea/pulseaudio/PulseAudioClipTest.java	Wed Aug 20 14:27:45 2008 -0400
@@ -102,16 +102,47 @@
 	}
 	
 	@Test
-	public void testPlayClip() throws LineUnavailableException, IOException, UnsupportedAudioFileException {
+	public void testLoopStopStartClip() 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);
 		
+		clip.setLoopPoints((int) (clip.getFrameLength() / 4), (int) (clip.getFrameLength() / 2));
+		clip.loop(4);
+		try {
+			Thread.sleep(2000);
+		} catch (InterruptedException e) {
+			e.printStackTrace();
+		}
+		clip.stop();
+		try {
+			Thread.sleep(2000);
+		} catch (InterruptedException e) {
+			e.printStackTrace();
+		}
+		clip.start();
+		clip.close();
+	}
+	
+	@Test
+	public void testLoop0Clip() 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.setLoopPoints((int) (clip.getFrameLength() / 4), (int) (clip.getFrameLength() / 2));
+		clip.loop(4);
+		try {
+			Thread.sleep(2000);
+		} catch (InterruptedException e) {
+			e.printStackTrace();
+		}
+		clip.loop(0);
+		clip.close();
 	}
 	
 	@Test (expected=IllegalArgumentException.class)