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 }