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