Back to index...
/*
 * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package sun.audio;
import java.util.Hashtable;
import java.util.Vector;
import java.io.IOException;
import java.io.InputStream;
import java.io.BufferedInputStream;
import javax.sound.sampled.*;
import javax.sound.midi.*;
import com.sun.media.sound.DataPusher;
import com.sun.media.sound.Toolkit;
/**
 * This class provides an interface to the Headspace Audio engine through
 * the Java Sound API.
 *
 * This class emulates systems with multiple audio channels, mixing
 * multiple streams for the workstation's single-channel device.
 *
 * @see AudioData
 * @see AudioDataStream
 * @see AudioStream
 * @see AudioStreamSequence
 * @see ContinuousAudioDataStream
 * @author David Rivas
 * @author Kara Kytle
 * @author Jan Borgersen
 * @author Florian Bomers
 */
public final class AudioDevice {
    private boolean DEBUG = false  /*true*/ ;
    /** Hashtable of audio clips / input streams. */
    private Hashtable clipStreams;
    private Vector infos;
    /** Are we currently playing audio? */
    private boolean playing = false;
    /** Handle to the JS audio mixer. */
    private Mixer mixer = null;
    /**
     * The default audio player. This audio player is initialized
     * automatically.
     */
    public static final AudioDevice device = new AudioDevice();
    /**
     * Create an AudioDevice instance.
     */
    private AudioDevice() {
        clipStreams = new Hashtable();
        infos = new Vector();
    }
    private synchronized void startSampled( AudioInputStream as,
                                            InputStream in ) throws UnsupportedAudioFileException,
                                  LineUnavailableException {
        Info info = null;
        DataPusher datapusher = null;
        DataLine.Info lineinfo = null;
        SourceDataLine sourcedataline = null;
        // if ALAW or ULAW, we must convert....
        as = Toolkit.getPCMConvertedAudioInputStream(as);
        if( as==null ) {
            // could not convert
            return;
        }
        lineinfo = new DataLine.Info(SourceDataLine.class,
                                     as.getFormat());
        if( !(AudioSystem.isLineSupported(lineinfo))) {
            return;
        }
        sourcedataline = (SourceDataLine)AudioSystem.getLine(lineinfo);
        datapusher = new DataPusher(sourcedataline, as);
        info = new Info( null, in, datapusher );
        infos.addElement( info );
        datapusher.start();
    }
    private synchronized void startMidi( InputStream bis,
                                         InputStream in ) throws InvalidMidiDataException,
                                  MidiUnavailableException  {
        Sequencer sequencer = null;
        Info info = null;
        sequencer = MidiSystem.getSequencer( );
        sequencer.open();
        try {
            sequencer.setSequence( bis );
        } catch( IOException e ) {
            throw new InvalidMidiDataException( e.getMessage() );
        }
        info = new Info( sequencer, in, null );
        infos.addElement( info );
        // fix for bug 4302884: Audio device is not released when AudioClip stops
        sequencer.addMetaEventListener(info);
        sequencer.start();
    }
    /**
     *  Open an audio channel.
     */
    public synchronized void openChannel(InputStream in) {
        if(DEBUG) {
            System.out.println("AudioDevice: openChannel");
            System.out.println("input stream =" + in);
        }
        Info info = null;
        // is this already playing?  if so, then just return
        for(int i=0; i<infos.size(); i++) {
            info = (AudioDevice.Info)infos.elementAt(i);
            if( info.in == in ) {
                return;
            }
        }
        AudioInputStream as = null;
        if( in instanceof AudioStream ) {
            if ( ((AudioStream)in).midiformat != null ) {
                // it's a midi file
                try {
                    startMidi( ((AudioStream)in).stream, in );
                } catch (Exception e) {
                    return;
                }
            } else if( ((AudioStream)in).ais != null ) {
                // it's sampled audio
                try {
                    startSampled( ((AudioStream)in).ais, in );
                } catch (Exception e) {
                    return;
                }
            }
        } else if (in instanceof AudioDataStream ) {
            if (in instanceof ContinuousAudioDataStream) {
                try {
                    AudioInputStream ais = new AudioInputStream(in,
                                                                ((AudioDataStream)in).getAudioData().format,
                                                                AudioSystem.NOT_SPECIFIED);
                    startSampled(ais, in );
                } catch (Exception e) {
                    return;
                }
            }
            else {
                try {
                    AudioInputStream ais = new AudioInputStream(in,
                                                                ((AudioDataStream)in).getAudioData().format,
                                                                ((AudioDataStream)in).getAudioData().buffer.length);
                    startSampled(ais, in );
                } catch (Exception e) {
                    return;
                }
            }
        } else {
            BufferedInputStream bis = new BufferedInputStream( in, 1024 );
            try {
                try {
                    as = AudioSystem.getAudioInputStream(bis);
                } catch(IOException ioe) {
                    return;
                }
                startSampled( as, in );
            } catch( UnsupportedAudioFileException e ) {
                try {
                    try {
                        MidiFileFormat mff =
                            MidiSystem.getMidiFileFormat( bis );
                    } catch(IOException ioe1) {
                        return;
                    }
                    startMidi( bis, in );
                } catch( InvalidMidiDataException e1 ) {
                    // $$jb:08.01.99: adding this section to make some of our other
                    // legacy classes work.....
                    // not MIDI either, special case handling for all others
                    AudioFormat defformat = new AudioFormat( AudioFormat.Encoding.ULAW,
                                                             8000, 8, 1, 1, 8000, true );
                    try {
                        AudioInputStream defaif = new AudioInputStream( bis,
                                                                        defformat, AudioSystem.NOT_SPECIFIED);
                        startSampled( defaif, in );
                    } catch (UnsupportedAudioFileException es) {
                        return;
                    } catch (LineUnavailableException es2) {
                        return;
                    }
                } catch( MidiUnavailableException e2 ) {
                    // could not open sequence
                    return;
                }
            } catch( LineUnavailableException e ) {
                return;
            }
        }
        // don't forget adjust for a new stream.
        notify();
    }
    /**
     *  Close an audio channel.
     */
    public synchronized void closeChannel(InputStream in) {
        if(DEBUG) {
            System.out.println("AudioDevice.closeChannel");
        }
        if (in == null) return;         // can't go anywhere here!
        Info info;
        for(int i=0; i<infos.size(); i++) {
            info = (AudioDevice.Info)infos.elementAt(i);
            if( info.in == in ) {
                if( info.sequencer != null ) {
                    info.sequencer.stop();
                    //info.sequencer.close();
                    infos.removeElement( info );
                } else if( info.datapusher != null ) {
                    info.datapusher.stop();
                    infos.removeElement( info );
                }
            }
        }
        notify();
    }
    /**
     * Open the device (done automatically)
     */
    public synchronized void open() {
        // $$jb: 06.24.99: This is done on a per-stream
        // basis using the new JS API now.
    }
    /**
     * Close the device (done automatically)
     */
    public synchronized void close() {
        // $$jb: 06.24.99: This is done on a per-stream
        // basis using the new JS API now.
    }
    /**
     * Play open audio stream(s)
     */
    public void play() {
        // $$jb: 06.24.99:  Holdover from old architechture ...
        // we now open/close the devices as needed on a per-stream
        // basis using the JavaSound API.
        if (DEBUG) {
            System.out.println("exiting play()");
        }
    }
    /**
     * Close streams
     */
    public synchronized void closeStreams() {
        Info info;
        for(int i=0; i<infos.size(); i++) {
            info = (AudioDevice.Info)infos.elementAt(i);
            if( info.sequencer != null ) {
                info.sequencer.stop();
                info.sequencer.close();
                infos.removeElement( info );
            } else if( info.datapusher != null ) {
                info.datapusher.stop();
                infos.removeElement( info );
            }
        }
        if (DEBUG) {
            System.err.println("Audio Device: Streams all closed.");
        }
        // Empty the hash table.
        clipStreams = new Hashtable();
        infos = new Vector();
    }
    /**
     * Number of channels currently open.
     */
    public int openChannels() {
        return infos.size();
    }
    /**
     * Make the debug info print out.
     */
    void setVerbose(boolean v) {
        DEBUG = v;
    }
    // INFO CLASS
    final class Info implements MetaEventListener {
        final Sequencer   sequencer;
        final InputStream in;
        final DataPusher  datapusher;
        Info( Sequencer sequencer, InputStream in, DataPusher datapusher ) {
            this.sequencer  = sequencer;
            this.in         = in;
            this.datapusher = datapusher;
        }
        public void meta(MetaMessage event) {
            if (event.getType() == 47 && sequencer != null) {
                sequencer.close();
            }
        }
    }
}
Back to index...