/* * ----------------------------------------------------------------------- * File: $Source: /home/keith/cvsroot/projects/LanguageAids/uk/co/dabsol/stribley/sound/OutputLineController.java,v $ * Version: $Revision: 852 $ * Last Modified: $Date: 2007-06-09 16:02:23 +0700 (Sat, 09 Jun 2007) $ * ----------------------------------------------------------------------- * Copyright (C) 2004 Keith Stribley <tech@thanlwinsoft.org> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA * ----------------------------------------------------------------------- * OutputLineController.java * This uses the term output to mean Output from the program to the speakers * i.e. it controls the line used for playing sound. * Created on February 6, 2004, 12:02 PM */ package org.thanlwinsoft.languagetest.sound; import java.util.BitSet; import javax.sound.sampled.SourceDataLine; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.DataLine; import javax.sound.sampled.LineEvent; import javax.sound.sampled.LineUnavailableException; /** * * @author keith */ public class OutputLineController implements javax.sound.sampled.LineListener, java.lang.Runnable { final static int REINIT_BIT = 0; final static int OPEN_BIT = 1; final static int PLAY_BIT = 2; final static int DRAIN_BIT = 3; final static int FLUSH_BIT = 4; final static int NUM_BITS = 5; /* final static int CLOSED = 0; final static int WAITING_FOR_OPEN = 1; final static int START_ON_OPEN = 2; final static int WAITING_FOR_START = 3; final static int IS_OPEN = 4; final static int WAITING_FOR_STOP = 5; final static int PLAYING = 6; final static int OPEN_AND_START = 7; final static int OPEN = 8; final static int WAITING_FOR_CLOSE = 9; final static int CLOSE_ON_STOP = 10; final static int CLOSE = 11; final static int INIT = 12; final static int REINIT_ON_CLOSE = 13; final static int START = 14; final static int PLAY_TO_END = 15; */ private SourceDataLine outputLine = null; private BitSet targetState = null; private BitSet actualState = null; private BitSet requestedState = null; private AudioFormat format = null; private int bufferSize = -1; private boolean exit = false; private AudioPlayListener listener = null; private LineController lineController; private int idleCount = 0; //private final static int SLEEP_TIME = 10; //ms // multiple of sleep time before line times out and closes private final static int MAX_IDLE_BEFORE_CLOSE = 3000; /** Creates a new instance of OutputLineController */ public OutputLineController(LineController lineController, int bufferSize ) { this.format = null; this.bufferSize = bufferSize; this.lineController = lineController; targetState = new BitSet(NUM_BITS); actualState = new BitSet(NUM_BITS); requestedState = new BitSet(NUM_BITS); targetState.set(REINIT_BIT); } public void setFormat(AudioFormat newFormat) { if (newFormat == null || (format != null && newFormat.matches(format))) { // do nothing } else { format = newFormat; setTarget(REINIT_BIT); clearTarget(DRAIN_BIT); clearTarget(PLAY_BIT); } } /* synchronized setter * @param bit index of bit to set */ protected void setTarget(int bit) { synchronized (this) { if (!targetState.get(bit)) { targetState.set(bit); System.out.println("OutputLineController target: " + stateToString(targetState)); } } } /* synchronized clear * @param bit index of bit to clear */ protected void clearTarget(int bit) { synchronized (this) { if (targetState.get(bit)) { targetState.clear(bit); System.out.println("OutputLineController target: " + stateToString(targetState)); } } } /* synchronized setter * @param bit index of bit to set */ protected void setRequested(int bit) { synchronized (this) { requestedState.set(bit); } System.out.println("OutputLineController request: " + stateToString(requestedState)); } /* synchronized clear * @param bit index of bit to clear */ protected void clearRequested(int bit) { synchronized (this) { requestedState.clear(bit); } System.out.println("OutputLineController request: " + stateToString(requestedState)); } protected void init() { synchronized(this) { if (outputLine != null) { // close old line // double check it isn't open if (outputLine.isOpen()) outputLine.close(); outputLine.removeLineListener(this); outputLine = null; } } //outputLine = lineController.getSourceDataLine(); //if (!lineController.getPlayLineFormat().matches(format)) { DataLine.Info newInfo = new DataLine.Info(SourceDataLine.class, format); if (lineController.getPlayMixer().isLineSupported(newInfo)) { try { SourceDataLine newLine = (SourceDataLine) lineController.getPlayMixer().getLine(newInfo); // only synchronize now we have got the line (which // could block) synchronized (this) { outputLine = newLine; } } catch (LineUnavailableException e) { System.out.println(e.getMessage()); outputLine = null; } } } if (outputLine != null) { outputLine.addLineListener(this); // auto open setTarget(OPEN_BIT); System.out.println("OutputLineController inititialised"); } } public void update(javax.sound.sampled.LineEvent event) { synchronized (this) { if (event.getType() == LineEvent.Type.OPEN) { actualState.set(OPEN_BIT); } else if (event.getType() == LineEvent.Type.CLOSE) { actualState.clear(OPEN_BIT); } else if (event.getType() == LineEvent.Type.START) { actualState.set(PLAY_BIT); } else if (event.getType() == LineEvent.Type.STOP) { actualState.clear(PLAY_BIT); if (targetState.get(DRAIN_BIT)) { clearTarget(DRAIN_BIT); clearTarget(PLAY_BIT); } } } System.out.println("OutputLineController Event: " + event.getType().toString() + " Target: " + stateToString(targetState)); } /** * All calls on outputLine are done inside iternal thread * targetState is called by multiple threads, so it is always synchronized. */ public void run() { int waitCount = 0; System.out.println("OutputLineController thread started"); while (exit == false) { // instantaneous target, state BitSet iTarget; BitSet iState; synchronized (this) { iTarget = (BitSet)targetState.clone(); iState = (BitSet)actualState.clone(); if (outputLine != null) { if (outputLine.isOpen() != actualState.get(OPEN_BIT)) { // actualState.set(OPEN_BIT, outputLine.isOpen()); System.out.println("OutputLineController open mismatch" + stateToString(actualState)); } if (outputLine.isRunning() != actualState.get(PLAY_BIT)) { // actualState.set(PLAY_BIT, outputLine.isRunning()); System.out.println("OutputLineController play mismatch" + stateToString(actualState)); } } else { // output line not initialised so sleep and try again sleep(); if ((format != null) && (lineController.getSourceDataLine() != null)) //lineController.linesAreAvailable()) { init(); } continue; } } if (!requestedState.equals(iState)) // wait for action to take place { waitCount++; if (waitCount > 100) { System.out.println("Waiting for " + stateToString(iState) + " -> " + stateToString(requestedState)); waitCount = 0; // clear requested state to force retry requestedState = (BitSet)iState.clone(); } else { sleep(); } } else { waitCount = 0; if (iTarget.get(REINIT_BIT)) { if (iState.get(PLAY_BIT)) { clearRequested(PLAY_BIT); outputLine.stop(); } else if (iState.get(OPEN_BIT)) { outputLine.drain(); clearRequested(OPEN_BIT); outputLine.close(); } else { // line is now closed so init can proceed // init now in progress so clear bit to stop it being // repeated clearTarget(REINIT_BIT); if ((format != null) && (lineController.getSourceDataLine() != null)) //lineController.linesAreAvailable()) { init(); } } } else if (iTarget.get(OPEN_BIT)) // now deal with open state { if (iState.get(OPEN_BIT)) // line is already open { // open target met, so check play bits if (!iState.get(PLAY_BIT)) // is line not playing { if (iTarget.get(PLAY_BIT)) { // neet to start play idleCount = 0; setRequested(PLAY_BIT); outputLine.start(); } else if (iTarget.get(FLUSH_BIT)) { // play is stopped as required, but still need to flush clearTarget(FLUSH_BIT); outputLine.flush(); } // play not requested, so leave line open for awhile else { sleep(); idleCount++; if (idleCount > MAX_IDLE_BEFORE_CLOSE) { // if the line is not used for awhile, close // it to give other applications access System.out.println("Line idle - closing"); clearTarget(OPEN_BIT); } } } // line is playing but target is not playing else if (iState.get(PLAY_BIT) && (!iTarget.get(PLAY_BIT))) { // need to stop play clearRequested(PLAY_BIT); outputLine.stop(); } else // let it continue playing { if (!outputLine.isActive()) { idleCount++; if (idleCount > MAX_IDLE_BEFORE_CLOSE) { // if the line is not used for awhile, close // it to give other applications access System.out.println("Line idle - closing"); clearTarget(OPEN_BIT); } } else { idleCount = 0; } sleep(); } } else // need to open { idleCount = 0; // line is still open, so close it now setRequested(OPEN_BIT); try { listener.initialisationProgress(-1); outputLine.open(format, bufferSize); listener.initialisationProgress(0); } catch (LineUnavailableException lue) { System.out.println(lue.getMessage()); sleep(); } } } // to have got this far, target is for line to be closed // If it is still running then stop it first else if (iState.get(PLAY_BIT)) { clearRequested(OPEN_BIT); outputLine.stop(); } // to have got this far, line is stopped, // target is for line to be closed else if (iState.get(OPEN_BIT)) { // line is still open, so close it now clearRequested(OPEN_BIT); // always flush before close outputLine.flush(); outputLine.close(); } else { sleep(); } } } } // switch (instantaneousState) // { // case INIT: // if (format != null) init(); // break; // case REINIT_ON_CLOSE: // break; // case OPEN_AND_START: // synchronized (this) // { // targetState = START_ON_OPEN; // } // try // { // listener.initialisationProgress(-1); // outputLine.open(format, bufferSize); // listener.initialisationProgress(0); // } // catch (LineUnavailableException lue) // { // System.out.println(lue.getMessage()); // sleep(); // } // break; // case OPEN: // synchronized (this) // { // targetState = WAITING_FOR_OPEN; // } // try // { // listener.initialisationProgress(-1); // outputLine.open(format, bufferSize); // listener.initialisationProgress(0); // } // catch (LineUnavailableException lue) // { // System.out.println(lue.getMessage()); // sleep(); // } // break; // // the rest can fall through // case CLOSE: // synchronized (this) // { // targetState = WAITING_FOR_CLOSE; // } // outputLine.close(); // break; // case START: // synchronized (this) // { // targetState = WAITING_FOR_START; // } // outputLine.start(); // break; // case CLOSED: // case WAITING_FOR_OPEN: // case START_ON_OPEN: // case WAITING_FOR_START: // case IS_OPEN: // case WAITING_FOR_STOP: // case PLAYING: // case WAITING_FOR_CLOSE: // case CLOSE_ON_STOP: // sleep(); // break; // default: // System.out.println("OutputLineController Unknown state: " // + targetState); // sleep(); // } // } // System.out.println("OutputLineController thread finished"); public synchronized boolean open() { setTarget(OPEN_BIT); return actualState.get(OPEN_BIT); } public synchronized boolean close() { clearTarget(DRAIN_BIT); clearTarget(PLAY_BIT); clearTarget(OPEN_BIT); return !actualState.get(OPEN_BIT); } public synchronized boolean keepPlaying() { clearTarget(DRAIN_BIT); setTarget(PLAY_BIT); setTarget(OPEN_BIT); return actualState.get(PLAY_BIT); } public void playToEnd() { setTarget(DRAIN_BIT); setTarget(PLAY_BIT); setTarget(OPEN_BIT); } public void stop() { clearTarget(PLAY_BIT); clearTarget(DRAIN_BIT); } public void flush() { setTarget(FLUSH_BIT); } public synchronized boolean isOpen() { return (actualState.get(OPEN_BIT) && !targetState.get(REINIT_BIT)); } public void sleep() { try { Thread.sleep(10); } catch (InterruptedException e) { // just return immediately } } public synchronized int write(byte [] data, int offset, int length) { int wrote = 0; if (actualState.get(OPEN_BIT)) { // prevent blocking by checking available int lengthToWrite = outputLine.available(); if (lengthToWrite > length) lengthToWrite = length; wrote = outputLine.write(data, offset, lengthToWrite); } return wrote; } public synchronized int writeSpaceAvailable() { int writeSpace = 0; if (actualState.get(OPEN_BIT)) { writeSpace = outputLine.available(); } return writeSpace; } public synchronized int getBufferSize() { int writeSpace = 0; if (actualState.get(OPEN_BIT)) { writeSpace = outputLine.getBufferSize(); } return writeSpace; } public synchronized void setListener(AudioPlayListener listener) { this.listener = listener; } protected String stateToString(BitSet s) { StringBuffer stateString = new StringBuffer(); if (s.get(REINIT_BIT)) { stateString.append("REINIT,"); } if (s.get(OPEN_BIT)) { stateString.append("OPEN,"); } else { stateString.append("CLOSED,"); } if (s.get(PLAY_BIT)) { stateString.append("STARTED"); } else { stateString.append("STOPPED"); } if (s.get(DRAIN_BIT)) { stateString.append(",DRAIN"); } return stateString.toString(); } public synchronized boolean isActive() { System.out.println(outputLine.isActive() + " R" + outputLine.isActive() + " Req " + requestedState.get(PLAY_BIT) + " Drain" + requestedState.get(DRAIN_BIT) + " Act" + actualState.get(PLAY_BIT)); if (outputLine.isActive()) { return true; } return false; } }