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