1 /* 2 * Copyright (c) 1999, 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 sun.audio; 27 28 import java.util.Hashtable; 29 import java.util.Vector; 30 import java.io.IOException; 31 import java.io.InputStream; 32 import java.io.BufferedInputStream; 33 34 import javax.sound.sampled.*; 35 import javax.sound.midi.*; 36 import com.sun.media.sound.DataPusher; 37 import com.sun.media.sound.Toolkit; 38 39 /** 40 * This class provides an interface to the Headspace Audio engine through 41 * the Java Sound API. 42 * 43 * This class emulates systems with multiple audio channels, mixing 44 * multiple streams for the workstation's single-channel device. 45 * 46 * @see AudioData 47 * @see AudioDataStream 48 * @see AudioStream 49 * @see AudioStreamSequence 50 * @see ContinuousAudioDataStream 51 * @author David Rivas 52 * @author Kara Kytle 53 * @author Jan Borgersen 54 * @author Florian Bomers 55 */ 56 57 public final class AudioDevice { 58 59 private boolean DEBUG = false /*true*/ ; 60 61 /** Hashtable of audio clips / input streams. */ 62 private Hashtable clipStreams; 63 64 private Vector infos; 65 66 /** Are we currently playing audio? */ 67 private boolean playing = false; 68 69 /** Handle to the JS audio mixer. */ 70 private Mixer mixer = null; 71 72 73 74 /** 75 * The default audio player. This audio player is initialized 76 * automatically. 77 */ 78 public static final AudioDevice device = new AudioDevice(); 79 80 /** 81 * Create an AudioDevice instance. 82 */ 83 private AudioDevice() { 84 85 clipStreams = new Hashtable(); 86 infos = new Vector(); 87 } 88 89 90 private synchronized void startSampled( AudioInputStream as, 91 InputStream in ) throws UnsupportedAudioFileException, 92 LineUnavailableException { 93 94 Info info = null; 95 DataPusher datapusher = null; 96 DataLine.Info lineinfo = null; 97 SourceDataLine sourcedataline = null; 98 99 // if ALAW or ULAW, we must convert.... 100 as = Toolkit.getPCMConvertedAudioInputStream(as); 101 102 if( as==null ) { 103 // could not convert 104 return; 105 } 106 107 lineinfo = new DataLine.Info(SourceDataLine.class, 108 as.getFormat()); 109 if( !(AudioSystem.isLineSupported(lineinfo))) { 110 return; 111 } 112 sourcedataline = (SourceDataLine)AudioSystem.getLine(lineinfo); 113 datapusher = new DataPusher(sourcedataline, as); 114 115 info = new Info( null, in, datapusher ); 116 infos.addElement( info ); 117 118 datapusher.start(); 119 } 120 121 private synchronized void startMidi( InputStream bis, 122 InputStream in ) throws InvalidMidiDataException, 123 MidiUnavailableException { 124 125 Sequencer sequencer = null; 126 Info info = null; 127 128 sequencer = MidiSystem.getSequencer( ); 129 sequencer.open(); 130 try { 131 sequencer.setSequence( bis ); 132 } catch( IOException e ) { 133 throw new InvalidMidiDataException( e.getMessage() ); 134 } 135 136 info = new Info( sequencer, in, null ); 137 138 infos.addElement( info ); 139 140 // fix for bug 4302884: Audio device is not released when AudioClip stops 141 sequencer.addMetaEventListener(info); 142 143 sequencer.start(); 144 145 } 146 147 148 149 /** 150 * Open an audio channel. 151 */ 152 public synchronized void openChannel(InputStream in) { 153 154 155 if(DEBUG) { 156 System.out.println("AudioDevice: openChannel"); 157 System.out.println("input stream =" + in); 158 } 159 160 Info info = null; 161 162 // is this already playing? if so, then just return 163 for(int i=0; i<infos.size(); i++) { 164 info = (AudioDevice.Info)infos.elementAt(i); 165 if( info.in == in ) { 166 167 return; 168 } 169 } 170 171 172 AudioInputStream as = null; 173 174 if( in instanceof AudioStream ) { 175 176 if ( ((AudioStream)in).midiformat != null ) { 177 178 // it's a midi file 179 try { 180 startMidi( ((AudioStream)in).stream, in ); 181 } catch (Exception e) { 182 return; 183 } 184 185 186 } else if( ((AudioStream)in).ais != null ) { 187 188 // it's sampled audio 189 try { 190 startSampled( ((AudioStream)in).ais, in ); 191 } catch (Exception e) { 192 return; 193 } 194 195 } 196 } else if (in instanceof AudioDataStream ) { 197 if (in instanceof ContinuousAudioDataStream) { 198 try { 199 AudioInputStream ais = new AudioInputStream(in, 200 ((AudioDataStream)in).getAudioData().format, 201 AudioSystem.NOT_SPECIFIED); 202 startSampled(ais, in ); 203 } catch (Exception e) { 204 return; 205 } 206 } 207 else { 208 try { 209 AudioInputStream ais = new AudioInputStream(in, 210 ((AudioDataStream)in).getAudioData().format, 211 ((AudioDataStream)in).getAudioData().buffer.length); 212 startSampled(ais, in ); 213 } catch (Exception e) { 214 return; 215 } 216 } 217 } else { 218 BufferedInputStream bis = new BufferedInputStream( in, 1024 ); 219 220 try { 221 222 try { 223 as = AudioSystem.getAudioInputStream(bis); 224 } catch(IOException ioe) { 225 return; 226 } 227 228 startSampled( as, in ); 229 230 } catch( UnsupportedAudioFileException e ) { 231 232 try { 233 try { 234 MidiFileFormat mff = 235 MidiSystem.getMidiFileFormat( bis ); 236 } catch(IOException ioe1) { 237 return; 238 } 239 240 startMidi( bis, in ); 241 242 243 } catch( InvalidMidiDataException e1 ) { 244 245 // $$jb:08.01.99: adding this section to make some of our other 246 // legacy classes work..... 247 // not MIDI either, special case handling for all others 248 249 AudioFormat defformat = new AudioFormat( AudioFormat.Encoding.ULAW, 250 8000, 8, 1, 1, 8000, true ); 251 try { 252 AudioInputStream defaif = new AudioInputStream( bis, 253 defformat, AudioSystem.NOT_SPECIFIED); 254 startSampled( defaif, in ); 255 } catch (UnsupportedAudioFileException es) { 256 return; 257 } catch (LineUnavailableException es2) { 258 return; 259 } 260 261 } catch( MidiUnavailableException e2 ) { 262 263 // could not open sequence 264 return; 265 } 266 267 } catch( LineUnavailableException e ) { 268 269 return; 270 } 271 } 272 273 // don't forget adjust for a new stream. 274 notify(); 275 } 276 277 278 /** 279 * Close an audio channel. 280 */ 281 public synchronized void closeChannel(InputStream in) { 282 283 if(DEBUG) { 284 System.out.println("AudioDevice.closeChannel"); 285 } 286 287 if (in == null) return; // can't go anywhere here! 288 289 Info info; 290 291 for(int i=0; i<infos.size(); i++) { 292 293 info = (AudioDevice.Info)infos.elementAt(i); 294 295 if( info.in == in ) { 296 297 if( info.sequencer != null ) { 298 299 info.sequencer.stop(); 300 //info.sequencer.close(); 301 infos.removeElement( info ); 302 303 } else if( info.datapusher != null ) { 304 305 info.datapusher.stop(); 306 infos.removeElement( info ); 307 } 308 } 309 } 310 notify(); 311 } 312 313 314 /** 315 * Open the device (done automatically) 316 */ 317 public synchronized void open() { 318 319 // $$jb: 06.24.99: This is done on a per-stream 320 // basis using the new JS API now. 321 } 322 323 324 /** 325 * Close the device (done automatically) 326 */ 327 public synchronized void close() { 328 329 // $$jb: 06.24.99: This is done on a per-stream 330 // basis using the new JS API now. 331 332 } 333 334 335 /** 336 * Play open audio stream(s) 337 */ 338 public void play() { 339 340 // $$jb: 06.24.99: Holdover from old architechture ... 341 // we now open/close the devices as needed on a per-stream 342 // basis using the JavaSound API. 343 344 if (DEBUG) { 345 System.out.println("exiting play()"); 346 } 347 } 348 349 /** 350 * Close streams 351 */ 352 public synchronized void closeStreams() { 353 354 Info info; 355 356 for(int i=0; i<infos.size(); i++) { 357 358 info = (AudioDevice.Info)infos.elementAt(i); 359 360 if( info.sequencer != null ) { 361 362 info.sequencer.stop(); 363 info.sequencer.close(); 364 infos.removeElement( info ); 365 366 } else if( info.datapusher != null ) { 367 368 info.datapusher.stop(); 369 infos.removeElement( info ); 370 } 371 } 372 373 374 if (DEBUG) { 375 System.err.println("Audio Device: Streams all closed."); 376 } 377 // Empty the hash table. 378 clipStreams = new Hashtable(); 379 infos = new Vector(); 380 } 381 382 /** 383 * Number of channels currently open. 384 */ 385 public int openChannels() { 386 return infos.size(); 387 } 388 389 /** 390 * Make the debug info print out. 391 */ 392 void setVerbose(boolean v) { 393 DEBUG = v; 394 } 395 396 397 398 399 400 401 // INFO CLASS 402 403 final class Info implements MetaEventListener { 404 405 final Sequencer sequencer; 406 final InputStream in; 407 final DataPusher datapusher; 408 409 Info( Sequencer sequencer, InputStream in, DataPusher datapusher ) { 410 411 this.sequencer = sequencer; 412 this.in = in; 413 this.datapusher = datapusher; 414 } 415 416 public void meta(MetaMessage event) { 417 if (event.getType() == 47 && sequencer != null) { 418 sequencer.close(); 419 } 420 } 421 } 422 423 424 425 }