1 /*
   2  * Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.media.sound;
  27 
  28 import java.util.Arrays;
  29 
  30 import javax.sound.sampled.AudioFormat;
  31 import javax.sound.sampled.AudioInputStream;
  32 import javax.sound.sampled.SourceDataLine;
  33 
  34 /**
  35  * Class to write an AudioInputStream to a SourceDataLine.
  36  * Was previously an inner class in various classes like JavaSoundAudioClip
  37  * and sun.audio.AudioDevice.
  38  * It auto-opens and closes the SourceDataLine.
  39  *
  40  * @author Kara Kytle
  41  * @author Florian Bomers
  42  */
  43 
  44 public final class DataPusher implements Runnable {
  45 
  46     private static final int AUTO_CLOSE_TIME = 5000;
  47     private static final boolean DEBUG = false;
  48 
  49     private final SourceDataLine source;
  50     private final AudioFormat format;
  51 
  52     // stream as source data
  53     private final AudioInputStream ais;
  54 
  55     // byte array as source data
  56     private final byte[] audioData;
  57     private final int audioDataByteLength;
  58     private int pos;
  59     private int newPos = -1;
  60     private boolean looping;
  61 
  62     private Thread pushThread = null;
  63     private int wantedState;
  64     private int threadState;
  65 
  66     private final int STATE_NONE = 0;
  67     private final int STATE_PLAYING = 1;
  68     private final int STATE_WAITING = 2;
  69     private final int STATE_STOPPING = 3;
  70     private final int STATE_STOPPED = 4;
  71     private final int BUFFER_SIZE = 16384;
  72 
  73     public DataPusher(SourceDataLine sourceLine, AudioFormat format, byte[] audioData, int byteLength) {
  74         this(sourceLine, format, null, audioData, byteLength);
  75     }
  76 
  77     public DataPusher(SourceDataLine sourceLine, AudioInputStream ais) {
  78         this(sourceLine, ais.getFormat(), ais, null, 0);
  79     }
  80 
  81     private DataPusher(final SourceDataLine source, final AudioFormat format,
  82                        final AudioInputStream ais, final byte[] audioData,
  83                        final int audioDataByteLength) {
  84         this.source = source;
  85         this.format = format;
  86         this.ais = ais;
  87         this.audioDataByteLength = audioDataByteLength;
  88         this.audioData = audioData == null ? null : Arrays.copyOf(audioData,
  89                                                                   audioData.length);
  90     }
  91 
  92     public synchronized void start() {
  93         start(false);
  94     }
  95 
  96     public synchronized void start(boolean loop) {
  97         if (DEBUG || Printer.debug) Printer.debug("> DataPusher.start(loop="+loop+")");
  98         try {
  99             if (threadState == STATE_STOPPING) {
 100                 // wait that the thread has finished stopping
 101                 if (DEBUG || Printer.trace)Printer.trace("DataPusher.start(): calling stop()");
 102                 stop();
 103             }
 104             looping = loop;
 105             newPos = 0;
 106             wantedState = STATE_PLAYING;
 107             if (!source.isOpen()) {
 108                 if (DEBUG || Printer.trace)Printer.trace("DataPusher: source.open()");
 109                 source.open(format);
 110             }
 111             if (DEBUG || Printer.trace)Printer.trace("DataPusher: source.flush()");
 112             source.flush();
 113             if (DEBUG || Printer.trace)Printer.trace("DataPusher: source.start()");
 114             source.start();
 115             if (pushThread == null) {
 116                 if (DEBUG || Printer.debug) Printer.debug("DataPusher.start(): Starting push");
 117                 pushThread = JSSecurityManager.createThread(this,
 118                                                             null,   // name
 119                                                             false,  // daemon
 120                                                             -1,    // priority
 121                                                             true); // doStart
 122             }
 123             notifyAll();
 124         } catch (Exception e) {
 125             if (DEBUG || Printer.err) e.printStackTrace();
 126         }
 127         if (DEBUG || Printer.debug) Printer.debug("< DataPusher.start(loop="+loop+")");
 128     }
 129 
 130     public synchronized void stop() {
 131         if (DEBUG || Printer.debug) Printer.debug("> DataPusher.stop()");
 132         if (threadState == STATE_STOPPING
 133             || threadState == STATE_STOPPED
 134             || pushThread == null) {
 135             if (DEBUG || Printer.debug) Printer.debug("DataPusher.stop(): nothing to do");
 136             return;
 137         }
 138         if (DEBUG || Printer.debug) Printer.debug("DataPusher.stop(): Stopping push");
 139 
 140         wantedState = STATE_WAITING;
 141         if (source != null) {
 142             if (DEBUG || Printer.trace)Printer.trace("DataPusher: source.flush()");
 143             source.flush();
 144         }
 145         notifyAll();
 146         int maxWaitCount = 50; // 5 seconds
 147         while ((maxWaitCount-- >= 0) && (threadState == STATE_PLAYING)) {
 148             try {
 149                 wait(100);
 150             } catch (InterruptedException e) {  }
 151         }
 152         if (DEBUG || Printer.debug) Printer.debug("< DataPusher.stop()");
 153     }
 154 
 155     synchronized void close() {
 156         if (source != null) {
 157                 if (DEBUG || Printer.trace)Printer.trace("DataPusher.close(): source.close()");
 158                 source.close();
 159         }
 160     }
 161 
 162     /**
 163      * Write data to the source data line.
 164      */
 165     @Override
 166     public void run() {
 167         byte[] buffer = null;
 168         boolean useStream = (ais != null);
 169         if (useStream) {
 170             buffer = new byte[BUFFER_SIZE];
 171         } else {
 172             buffer = audioData;
 173         }
 174         while (wantedState != STATE_STOPPING) {
 175             //try {
 176                 if (wantedState == STATE_WAITING) {
 177                     // wait for 5 seconds - maybe the clip is to be played again
 178                     if (DEBUG || Printer.debug)Printer.debug("DataPusher.run(): waiting 5 seconds");
 179                     try {
 180                         synchronized(this) {
 181                                 threadState = STATE_WAITING;
 182                                 wantedState = STATE_STOPPING;
 183                                 wait(AUTO_CLOSE_TIME);
 184                         }
 185                     } catch (InterruptedException ie) {}
 186                     if (DEBUG || Printer.debug)Printer.debug("DataPusher.run(): waiting finished");
 187                     continue;
 188                 }
 189                 if (newPos >= 0) {
 190                         pos = newPos;
 191                         newPos = -1;
 192                 }
 193                 threadState = STATE_PLAYING;
 194                 int toWrite = BUFFER_SIZE;
 195                 if (useStream) {
 196                     try {
 197                         pos = 0; // always write from beginning of buffer
 198                         // don't use read(byte[]), because some streams
 199                         // may not override that method
 200                         toWrite = ais.read(buffer, 0, buffer.length);
 201                     } catch (java.io.IOException ioe) {
 202                         // end of stream
 203                         toWrite = -1;
 204                     }
 205                 } else {
 206                     if (toWrite > audioDataByteLength - pos) {
 207                         toWrite = audioDataByteLength - pos;
 208                     }
 209                     if (toWrite == 0) {
 210                         toWrite = -1; // end of "stream"
 211                     }
 212                 }
 213                 if (toWrite < 0) {
 214                     if (DEBUG || Printer.debug) Printer.debug("DataPusher.run(): Found end of stream");
 215                         if (!useStream && looping) {
 216                             if (DEBUG || Printer.debug)Printer.debug("DataPusher.run(): setting pos back to 0");
 217                             pos = 0;
 218                             continue;
 219                         }
 220                     if (DEBUG || Printer.debug)Printer.debug("DataPusher.run(): calling drain()");
 221                     wantedState = STATE_WAITING;
 222                     source.drain();
 223                     continue;
 224                 }
 225                 if (DEBUG || Printer.debug) Printer.debug("> DataPusher.run(): Writing " + toWrite + " bytes");
 226                     int bytesWritten = source.write(buffer, pos, toWrite);
 227                     pos += bytesWritten;
 228                 if (DEBUG || Printer.debug) Printer.debug("< DataPusher.run(): Wrote " + bytesWritten + " bytes");
 229         }
 230         threadState = STATE_STOPPING;
 231         if (DEBUG || Printer.debug)Printer.debug("DataPusher: closing device");
 232         if (Printer.trace)Printer.trace("DataPusher: source.flush()");
 233         source.flush();
 234         if (DEBUG || Printer.trace)Printer.trace("DataPusher: source.stop()");
 235         source.stop();
 236         if (DEBUG || Printer.trace)Printer.trace("DataPusher: source.flush()");
 237         source.flush();
 238         if (DEBUG || Printer.trace)Printer.trace("DataPusher: source.close()");
 239         source.close();
 240         threadState = STATE_STOPPED;
 241         synchronized (this) {
 242                 pushThread = null;
 243                 notifyAll();
 244         }
 245         if (DEBUG || Printer.debug)Printer.debug("DataPusher:end of thread");
 246     }
 247 } // class DataPusher