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.*;
  31 
  32 /**
  33  * Class to write an AudioInputStream to a SourceDataLine.
  34  * Was previously an inner class in various classes like JavaSoundAudioClip
  35  * and sun.audio.AudioDevice.
  36  * It auto-opens and closes the SourceDataLine.
  37  *
  38  * @author Kara Kytle
  39  * @author Florian Bomers
  40  */
  41 
  42 public final class DataPusher implements Runnable {
  43 
  44     private static final int AUTO_CLOSE_TIME = 5000;
  45     private static final boolean DEBUG = false;
  46 
  47     private final SourceDataLine source;
  48     private final AudioFormat format;
  49 
  50     // stream as source data
  51     private final AudioInputStream ais;
  52 
  53     // byte array as source data
  54     private final byte[] audioData;
  55     private final int audioDataByteLength;
  56     private int pos;
  57     private int newPos = -1;
  58     private boolean looping;
  59 
  60     private Thread pushThread = null;
  61     private int wantedState;
  62     private int threadState;
  63 
  64     private final int STATE_NONE = 0;
  65     private final int STATE_PLAYING = 1;
  66     private final int STATE_WAITING = 2;
  67     private final int STATE_STOPPING = 3;
  68     private final int STATE_STOPPED = 4;
  69     private final int BUFFER_SIZE = 16384;
  70 
  71     public DataPusher(SourceDataLine sourceLine, AudioFormat format, byte[] audioData, int byteLength) {
  72         this(sourceLine, format, null, audioData, byteLength);
  73     }
  74 
  75     public DataPusher(SourceDataLine sourceLine, AudioInputStream ais) {
  76         this(sourceLine, ais.getFormat(), ais, null, 0);
  77     }
  78 
  79     private DataPusher(final SourceDataLine source, final AudioFormat format,
  80                        final AudioInputStream ais, final byte[] audioData,
  81                        final int audioDataByteLength) {
  82         this.source = source;
  83         this.format = format;
  84         this.ais = ais;
  85         this.audioDataByteLength = audioDataByteLength;
  86         this.audioData = audioData == null ? null : Arrays.copyOf(audioData,
  87                                                                   audioData.length);
  88     }
  89 
  90     public synchronized void start() {
  91         start(false);
  92     }
  93 
  94     public synchronized void start(boolean loop) {
  95         if (DEBUG || Printer.debug) Printer.debug("> DataPusher.start(loop="+loop+")");
  96         try {
  97             if (threadState == STATE_STOPPING) {
  98                 // wait that the thread has finished stopping
  99                 if (DEBUG || Printer.trace)Printer.trace("DataPusher.start(): calling stop()");
 100                 stop();
 101             }
 102             looping = loop;
 103             newPos = 0;
 104             wantedState = STATE_PLAYING;
 105             if (!source.isOpen()) {
 106                 if (DEBUG || Printer.trace)Printer.trace("DataPusher: source.open()");
 107                 source.open(format);
 108             }
 109             if (DEBUG || Printer.trace)Printer.trace("DataPusher: source.flush()");
 110             source.flush();
 111             if (DEBUG || Printer.trace)Printer.trace("DataPusher: source.start()");
 112             source.start();
 113             if (pushThread == null) {
 114                 if (DEBUG || Printer.debug) Printer.debug("DataPusher.start(): Starting push");
 115                 pushThread = JSSecurityManager.createThread(this,
 116                                                             null,   // name
 117                                                             false,  // daemon
 118                                                             -1,    // priority
 119                                                             true); // doStart
 120             }
 121             notifyAll();
 122         } catch (Exception e) {
 123             if (DEBUG || Printer.err) e.printStackTrace();
 124         }
 125         if (DEBUG || Printer.debug) Printer.debug("< DataPusher.start(loop="+loop+")");
 126     }
 127 
 128 
 129     public synchronized void stop() {
 130         if (DEBUG || Printer.debug) Printer.debug("> DataPusher.stop()");
 131         if (threadState == STATE_STOPPING
 132             || threadState == STATE_STOPPED
 133             || pushThread == null) {
 134             if (DEBUG || Printer.debug) Printer.debug("DataPusher.stop(): nothing to do");
 135             return;
 136         }
 137         if (DEBUG || Printer.debug) Printer.debug("DataPusher.stop(): Stopping push");
 138 
 139         wantedState = STATE_WAITING;
 140         if (source != null) {
 141             if (DEBUG || Printer.trace)Printer.trace("DataPusher: source.flush()");
 142             source.flush();
 143         }
 144         notifyAll();
 145         int maxWaitCount = 50; // 5 seconds
 146         while ((maxWaitCount-- >= 0) && (threadState == STATE_PLAYING)) {
 147             try {
 148                 wait(100);
 149             } catch (InterruptedException e) {  }
 150         }
 151         if (DEBUG || Printer.debug) Printer.debug("< DataPusher.stop()");
 152     }
 153 
 154     synchronized void close() {
 155         if (source != null) {
 156                 if (DEBUG || Printer.trace)Printer.trace("DataPusher.close(): source.close()");
 157                 source.close();
 158         }
 159     }
 160 
 161     /**
 162      * Write data to the source data line.
 163      */
 164     public void run() {
 165         byte[] buffer = null;
 166         boolean useStream = (ais != null);
 167         if (useStream) {
 168             buffer = new byte[BUFFER_SIZE];
 169         } else {
 170             buffer = audioData;
 171         }
 172         while (wantedState != STATE_STOPPING) {
 173             //try {
 174                 if (wantedState == STATE_WAITING) {
 175                     // wait for 5 seconds - maybe the clip is to be played again
 176                     if (DEBUG || Printer.debug)Printer.debug("DataPusher.run(): waiting 5 seconds");
 177                     try {
 178                         synchronized(this) {
 179                                 threadState = STATE_WAITING;
 180                                 wantedState = STATE_STOPPING;
 181                                 wait(AUTO_CLOSE_TIME);
 182                         }
 183                     } catch (InterruptedException ie) {}
 184                     if (DEBUG || Printer.debug)Printer.debug("DataPusher.run(): waiting finished");
 185                     continue;
 186                 }
 187                 if (newPos >= 0) {
 188                         pos = newPos;
 189                         newPos = -1;
 190                 }
 191                 threadState = STATE_PLAYING;
 192                 int toWrite = BUFFER_SIZE;
 193                 if (useStream) {
 194                     try {
 195                         pos = 0; // always write from beginning of buffer
 196                         // don't use read(byte[]), because some streams
 197                         // may not override that method
 198                         toWrite = ais.read(buffer, 0, buffer.length);
 199                     } catch (java.io.IOException ioe) {
 200                         // end of stream
 201                         toWrite = -1;
 202                     }
 203                 } else {
 204                     if (toWrite > audioDataByteLength - pos) {
 205                         toWrite = audioDataByteLength - pos;
 206                     }
 207                     if (toWrite == 0) {
 208                         toWrite = -1; // end of "stream"
 209                     }
 210                 }
 211                 if (toWrite < 0) {
 212                     if (DEBUG || Printer.debug) Printer.debug("DataPusher.run(): Found end of stream");
 213                         if (!useStream && looping) {
 214                             if (DEBUG || Printer.debug)Printer.debug("DataPusher.run(): setting pos back to 0");
 215                             pos = 0;
 216                             continue;
 217                         }
 218                     if (DEBUG || Printer.debug)Printer.debug("DataPusher.run(): calling drain()");
 219                     wantedState = STATE_WAITING;
 220                     source.drain();
 221                     continue;
 222                 }
 223                 if (DEBUG || Printer.debug) Printer.debug("> DataPusher.run(): Writing " + toWrite + " bytes");
 224                     int bytesWritten = source.write(buffer, pos, toWrite);
 225                     pos += bytesWritten;
 226                 if (DEBUG || Printer.debug) Printer.debug("< DataPusher.run(): Wrote " + bytesWritten + " bytes");
 227         }
 228         threadState = STATE_STOPPING;
 229         if (DEBUG || Printer.debug)Printer.debug("DataPusher: closing device");
 230         if (Printer.trace)Printer.trace("DataPusher: source.flush()");
 231         source.flush();
 232         if (DEBUG || Printer.trace)Printer.trace("DataPusher: source.stop()");
 233         source.stop();
 234         if (DEBUG || Printer.trace)Printer.trace("DataPusher: source.flush()");
 235         source.flush();
 236         if (DEBUG || Printer.trace)Printer.trace("DataPusher: source.close()");
 237         source.close();
 238         threadState = STATE_STOPPED;
 239         synchronized (this) {
 240                 pushThread = null;
 241                 notifyAll();
 242         }
 243         if (DEBUG || Printer.debug)Printer.debug("DataPusher:end of thread");
 244     }
 245 
 246 } // class DataPusher