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 }