489 lines · 16.5 KB · Java
RawDownload
/* * Copyright (c) 1999, 2007, 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 com.sun.media.sound; import java.io.IOException;import java.io.InputStream;import java.io.BufferedInputStream;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.applet.AudioClip;import java.lang.InterruptedException; import javax.sound.sampled.AudioSystem;import javax.sound.sampled.Mixer;import javax.sound.sampled.Clip;import javax.sound.sampled.Control;import javax.sound.sampled.AudioInputStream;import javax.sound.sampled.AudioFormat;import javax.sound.sampled.DataLine;import javax.sound.sampled.SourceDataLine;import javax.sound.sampled.LineEvent;import javax.sound.sampled.LineListener;import javax.sound.sampled.LineUnavailableException;import javax.sound.sampled.UnsupportedAudioFileException;  import javax.sound.midi.MidiSystem;import javax.sound.midi.MidiFileFormat;import javax.sound.midi.MetaMessage;import javax.sound.midi.Sequence;import javax.sound.midi.Sequencer;import javax.sound.midi.InvalidMidiDataException;import javax.sound.midi.MidiUnavailableException;import javax.sound.midi.MetaEventListener; /** * Java Sound audio clip; * * @author Arthur van Hoff, Kara Kytle, Jan Borgersen * @author Florian Bomers */ public class JavaSoundAudioClip implements AudioClip, MetaEventListener, LineListener {     private static final boolean DEBUG = false;    private static final int BUFFER_SIZE = 16384; // number of bytes written each time to the source data line     private long lastPlayCall = 0;    private static final int MINIMUM_PLAY_DELAY = 30;     private byte loadedAudio[] = null;    private int loadedAudioByteLength = 0;    private AudioFormat loadedAudioFormat = null;     private AutoClosingClip clip = null;    private boolean clipLooping = false;     private DataPusher datapusher = null;     private Sequencer sequencer = null;    private Sequence sequence = null;    private boolean sequencerloop = false;     /**     * used for determining how many samples is the     * threshhold between playing as a Clip and streaming     * from the file.     *     * $$jb: 11.07.99: the engine has a limit of 1M     * samples to play as a Clip, so compare this number     * with the number of samples in the stream.     *     */    private final static long CLIP_THRESHOLD = 1048576;    //private final static long CLIP_THRESHOLD = 1;    private final static int STREAM_BUFFER_SIZE = 1024;     public JavaSoundAudioClip(InputStream in) throws IOException {        if (DEBUG || Printer.debug)Printer.debug("JavaSoundAudioClip.<init>");         BufferedInputStream bis = new BufferedInputStream(in, STREAM_BUFFER_SIZE);        bis.mark(STREAM_BUFFER_SIZE);        boolean success = false;        try {            AudioInputStream as = AudioSystem.getAudioInputStream(bis);            // load the stream data into memory            success = loadAudioData(as);             if (success) {                success = false;                if (loadedAudioByteLength < CLIP_THRESHOLD) {                    success = createClip();                }                if (!success) {                    success = createSourceDataLine();                }            }        } catch (UnsupportedAudioFileException e) {            // not an audio file            try {                MidiFileFormat mff = MidiSystem.getMidiFileFormat(bis);                success = createSequencer(bis);            } catch (InvalidMidiDataException e1) {                success = false;            }        }        if (!success) {            throw new IOException("Unable to create AudioClip from input stream");        }    }      public synchronized void play() {        startImpl(false);    }      public synchronized void loop() {        startImpl(true);    }     private synchronized void startImpl(boolean loop) {        // hack for some applets that call the start method very rapidly...        long currentTime = System.currentTimeMillis();        long diff = currentTime - lastPlayCall;        if (diff < MINIMUM_PLAY_DELAY) {            if (DEBUG || Printer.debug) Printer.debug("JavaSoundAudioClip.startImpl(loop="+loop+"): abort - too rapdly");            return;        }        lastPlayCall = currentTime;         if (DEBUG || Printer.debug) Printer.debug("JavaSoundAudioClip.startImpl(loop="+loop+")");        try {            if (clip != null) {                if (!clip.isOpen()) {                    if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.open()");                    clip.open(loadedAudioFormat, loadedAudio, 0, loadedAudioByteLength);                } else {                    if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.flush()");                    clip.flush();                    if (loop != clipLooping) {                        // need to stop in case the looped status changed                        if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.stop()");                        clip.stop();                    }                }                clip.setFramePosition(0);                if (loop) {                    if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.loop()");                    clip.loop(Clip.LOOP_CONTINUOUSLY);                } else {                    if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.start()");                    clip.start();                }                clipLooping = loop;                if (DEBUG || Printer.debug)Printer.debug("Clip should be playing/looping");             } else if (datapusher != null ) {                datapusher.start(loop);                if (DEBUG || Printer.debug)Printer.debug("Stream should be playing/looping");             } else if (sequencer != null) {                sequencerloop = loop;                if (sequencer.isRunning()) {                    sequencer.setMicrosecondPosition(0);                }                if (!sequencer.isOpen()) {                    try {                        sequencer.open();                        sequencer.setSequence(sequence);                     } catch (InvalidMidiDataException e1) {                        if (DEBUG || Printer.err)e1.printStackTrace();                    } catch (MidiUnavailableException e2) {                        if (DEBUG || Printer.err)e2.printStackTrace();                    }                }                sequencer.addMetaEventListener(this);                try {                    sequencer.start();                } catch (Exception e) {                    if (DEBUG || Printer.err) e.printStackTrace();                }                if (DEBUG || Printer.debug)Printer.debug("Sequencer should be playing/looping");            }        } catch (Exception e) {            if (DEBUG || Printer.err)e.printStackTrace();        }    }     public synchronized void stop() {         if (DEBUG || Printer.debug)Printer.debug("JavaSoundAudioClip->stop()");        lastPlayCall = 0;         if (clip != null) {            try {                if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.flush()");                clip.flush();            } catch (Exception e1) {                if (Printer.err) e1.printStackTrace();            }            try {                if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.stop()");                clip.stop();            } catch (Exception e2) {                if (Printer.err) e2.printStackTrace();            }            if (DEBUG || Printer.debug)Printer.debug("Clip should be stopped");         } else if (datapusher != null) {            datapusher.stop();            if (DEBUG || Printer.debug)Printer.debug("Stream should be stopped");         } else if (sequencer != null) {            try {                sequencerloop = false;                sequencer.addMetaEventListener(this);                sequencer.stop();            } catch (Exception e3) {                if (Printer.err) e3.printStackTrace();            }            try {                sequencer.close();            } catch (Exception e4) {                if (Printer.err) e4.printStackTrace();            }            if (DEBUG || Printer.debug)Printer.debug("Sequencer should be stopped");        }    }     // Event handlers (for debugging)     public synchronized void update(LineEvent event) {        if (DEBUG || Printer.debug) Printer.debug("line event received: "+event);    }     // handle MIDI track end meta events for looping     public synchronized void meta( MetaMessage message ) {         if (DEBUG || Printer.debug)Printer.debug("META EVENT RECEIVED!!!!! ");         if( message.getType() == 47 ) {            if (sequencerloop){                //notifyAll();                sequencer.setMicrosecondPosition(0);                loop();            } else {                stop();            }        }    }      public String toString() {        return getClass().toString();    }      protected void finalize() {         if (clip != null) {            if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip.finalize: clip.close()");            clip.close();        }         //$$fb 2001-09-26: may improve situation related to bug #4302884        if (datapusher != null) {            datapusher.close();        }         if (sequencer != null) {            sequencer.close();        }    }     // FILE LOADING METHODS     private boolean loadAudioData(AudioInputStream as)  throws IOException, UnsupportedAudioFileException {        if (DEBUG || Printer.debug)Printer.debug("JavaSoundAudioClip->openAsClip()");         // first possibly convert this stream to PCM        as = Toolkit.getPCMConvertedAudioInputStream(as);        if (as == null) {            return false;        }         loadedAudioFormat = as.getFormat();        long frameLen = as.getFrameLength();        int frameSize = loadedAudioFormat.getFrameSize();        long byteLen = AudioSystem.NOT_SPECIFIED;        if (frameLen != AudioSystem.NOT_SPECIFIED            && frameLen > 0            && frameSize != AudioSystem.NOT_SPECIFIED            && frameSize > 0) {            byteLen = frameLen * frameSize;        }        if (byteLen != AudioSystem.NOT_SPECIFIED) {            // if the stream length is known, it can be efficiently loaded into memory            readStream(as, byteLen);        } else {            // otherwise we use a ByteArrayOutputStream to load it into memory            readStream(as);        }         // if everything went fine, we have now the audio data in        // loadedAudio, and the byte length in loadedAudioByteLength        return true;    }       private void readStream(AudioInputStream as, long byteLen) throws IOException {        // arrays "only" max. 2GB        int intLen;        if (byteLen > 2147483647) {            intLen = 2147483647;        } else {            intLen = (int) byteLen;        }        loadedAudio = new byte[intLen];        loadedAudioByteLength = 0;         // this loop may throw an IOException        while (true) {            int bytesRead = as.read(loadedAudio, loadedAudioByteLength, intLen - loadedAudioByteLength);            if (bytesRead <= 0) {                as.close();                break;            }            loadedAudioByteLength += bytesRead;        }    }     private void readStream(AudioInputStream as) throws IOException {         DirectBAOS baos = new DirectBAOS();        byte buffer[] = new byte[16384];        int bytesRead = 0;        int totalBytesRead = 0;         // this loop may throw an IOException        while( true ) {            bytesRead = as.read(buffer, 0, buffer.length);            if (bytesRead <= 0) {                as.close();                break;            }            totalBytesRead += bytesRead;            baos.write(buffer, 0, bytesRead);        }        loadedAudio = baos.getInternalBuffer();        loadedAudioByteLength = totalBytesRead;    }      // METHODS FOR CREATING THE DEVICE     private boolean createClip() {         if (DEBUG || Printer.debug)Printer.debug("JavaSoundAudioClip.createClip()");         try {            DataLine.Info info = new DataLine.Info(Clip.class, loadedAudioFormat);            if (!(AudioSystem.isLineSupported(info)) ) {                if (DEBUG || Printer.err)Printer.err("Clip not supported: "+loadedAudioFormat);                // fail silently                return false;            }            Object line = AudioSystem.getLine(info);            if (!(line instanceof AutoClosingClip)) {                if (DEBUG || Printer.err)Printer.err("Clip is not auto closing!"+clip);                // fail -> will try with SourceDataLine                return false;            }            clip = (AutoClosingClip) line;            clip.setAutoClosing(true);            if (DEBUG || Printer.debug) clip.addLineListener(this);        } catch (Exception e) {            if (DEBUG || Printer.err)e.printStackTrace();            // fail silently            return false;        }         if (clip==null) {            // fail silently            return false;        }         if (DEBUG || Printer.debug)Printer.debug("Loaded clip.");        return true;    }     private boolean createSourceDataLine() {        if (DEBUG || Printer.debug)Printer.debug("JavaSoundAudioClip.createSourceDataLine()");        try {            DataLine.Info info = new DataLine.Info(SourceDataLine.class, loadedAudioFormat);            if (!(AudioSystem.isLineSupported(info)) ) {                if (DEBUG || Printer.err)Printer.err("Line not supported: "+loadedAudioFormat);                // fail silently                return false;            }            SourceDataLine source = (SourceDataLine) AudioSystem.getLine(info);            datapusher = new DataPusher(source, loadedAudioFormat, loadedAudio, loadedAudioByteLength);        } catch (Exception e) {            if (DEBUG || Printer.err)e.printStackTrace();            // fail silently            return false;        }         if (datapusher==null) {            // fail silently            return false;        }         if (DEBUG || Printer.debug)Printer.debug("Created SourceDataLine.");        return true;    }     private boolean createSequencer(BufferedInputStream in) throws IOException {         if (DEBUG || Printer.debug)Printer.debug("JavaSoundAudioClip.createSequencer()");         // get the sequencer        try {            sequencer = MidiSystem.getSequencer( );        } catch(MidiUnavailableException me) {            if (DEBUG || Printer.err)me.printStackTrace();            return false;        }        if (sequencer==null) {            return false;        }         try {            sequence = MidiSystem.getSequence(in);            if (sequence == null) {                return false;            }        } catch (InvalidMidiDataException e) {            if (DEBUG || Printer.err)e.printStackTrace();            return false;        }         if (DEBUG || Printer.debug)Printer.debug("Created Sequencer.");        return true;    }      /*     * private inner class representing a ByteArrayOutputStream     * which allows retrieval of the internal array     */    private static class DirectBAOS extends ByteArrayOutputStream {        public DirectBAOS() {            super();        }         public byte[] getInternalBuffer() {            return buf;        }     } // class DirectBAOS }