0N/A/*
2362N/A * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
0N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
0N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
2362N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
2362N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
0N/A * or visit www.oracle.com if you need additional information or have any
0N/A * questions.
0N/A */
0N/A
0N/Apackage com.sun.media.sound;
0N/A
0N/Aimport java.io.IOException;
0N/Aimport java.io.InputStream;
0N/Aimport java.io.BufferedInputStream;
0N/Aimport java.io.ByteArrayOutputStream;
0N/Aimport java.applet.AudioClip;
0N/A
0N/Aimport javax.sound.sampled.AudioSystem;
0N/Aimport javax.sound.sampled.Clip;
0N/Aimport javax.sound.sampled.AudioInputStream;
0N/Aimport javax.sound.sampled.AudioFormat;
0N/Aimport javax.sound.sampled.DataLine;
0N/Aimport javax.sound.sampled.SourceDataLine;
0N/Aimport javax.sound.sampled.LineEvent;
0N/Aimport javax.sound.sampled.LineListener;
0N/Aimport javax.sound.sampled.UnsupportedAudioFileException;
0N/A
0N/Aimport javax.sound.midi.MidiSystem;
0N/Aimport javax.sound.midi.MidiFileFormat;
0N/Aimport javax.sound.midi.MetaMessage;
0N/Aimport javax.sound.midi.Sequence;
0N/Aimport javax.sound.midi.Sequencer;
0N/Aimport javax.sound.midi.InvalidMidiDataException;
0N/Aimport javax.sound.midi.MidiUnavailableException;
0N/Aimport javax.sound.midi.MetaEventListener;
0N/A
0N/A/**
0N/A * Java Sound audio clip;
0N/A *
0N/A * @author Arthur van Hoff, Kara Kytle, Jan Borgersen
0N/A * @author Florian Bomers
0N/A */
0N/A
0N/Apublic final class JavaSoundAudioClip implements AudioClip, MetaEventListener, LineListener {
0N/A
0N/A private static final boolean DEBUG = false;
0N/A private static final int BUFFER_SIZE = 16384; // number of bytes written each time to the source data line
0N/A
0N/A private long lastPlayCall = 0;
0N/A private static final int MINIMUM_PLAY_DELAY = 30;
0N/A
0N/A private byte loadedAudio[] = null;
0N/A private int loadedAudioByteLength = 0;
0N/A private AudioFormat loadedAudioFormat = null;
0N/A
0N/A private AutoClosingClip clip = null;
0N/A private boolean clipLooping = false;
0N/A
0N/A private DataPusher datapusher = null;
0N/A
0N/A private Sequencer sequencer = null;
0N/A private Sequence sequence = null;
0N/A private boolean sequencerloop = false;
0N/A
0N/A /**
0N/A * used for determining how many samples is the
0N/A * threshhold between playing as a Clip and streaming
0N/A * from the file.
0N/A *
0N/A * $$jb: 11.07.99: the engine has a limit of 1M
0N/A * samples to play as a Clip, so compare this number
0N/A * with the number of samples in the stream.
0N/A *
0N/A */
0N/A private final static long CLIP_THRESHOLD = 1048576;
0N/A //private final static long CLIP_THRESHOLD = 1;
0N/A private final static int STREAM_BUFFER_SIZE = 1024;
0N/A
0N/A public JavaSoundAudioClip(InputStream in) throws IOException {
0N/A if (DEBUG || Printer.debug)Printer.debug("JavaSoundAudioClip.<init>");
0N/A
0N/A BufferedInputStream bis = new BufferedInputStream(in, STREAM_BUFFER_SIZE);
0N/A bis.mark(STREAM_BUFFER_SIZE);
0N/A boolean success = false;
0N/A try {
0N/A AudioInputStream as = AudioSystem.getAudioInputStream(bis);
0N/A // load the stream data into memory
0N/A success = loadAudioData(as);
0N/A
0N/A if (success) {
0N/A success = false;
0N/A if (loadedAudioByteLength < CLIP_THRESHOLD) {
0N/A success = createClip();
0N/A }
0N/A if (!success) {
0N/A success = createSourceDataLine();
0N/A }
0N/A }
0N/A } catch (UnsupportedAudioFileException e) {
0N/A // not an audio file
0N/A try {
0N/A MidiFileFormat mff = MidiSystem.getMidiFileFormat(bis);
0N/A success = createSequencer(bis);
0N/A } catch (InvalidMidiDataException e1) {
0N/A success = false;
0N/A }
0N/A }
0N/A if (!success) {
0N/A throw new IOException("Unable to create AudioClip from input stream");
0N/A }
0N/A }
0N/A
0N/A
0N/A public synchronized void play() {
0N/A startImpl(false);
0N/A }
0N/A
0N/A
0N/A public synchronized void loop() {
0N/A startImpl(true);
0N/A }
0N/A
0N/A private synchronized void startImpl(boolean loop) {
0N/A // hack for some applets that call the start method very rapidly...
0N/A long currentTime = System.currentTimeMillis();
0N/A long diff = currentTime - lastPlayCall;
0N/A if (diff < MINIMUM_PLAY_DELAY) {
0N/A if (DEBUG || Printer.debug) Printer.debug("JavaSoundAudioClip.startImpl(loop="+loop+"): abort - too rapdly");
0N/A return;
0N/A }
0N/A lastPlayCall = currentTime;
0N/A
0N/A if (DEBUG || Printer.debug) Printer.debug("JavaSoundAudioClip.startImpl(loop="+loop+")");
0N/A try {
0N/A if (clip != null) {
0N/A if (!clip.isOpen()) {
0N/A if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.open()");
0N/A clip.open(loadedAudioFormat, loadedAudio, 0, loadedAudioByteLength);
0N/A } else {
0N/A if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.flush()");
0N/A clip.flush();
0N/A if (loop != clipLooping) {
0N/A // need to stop in case the looped status changed
0N/A if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.stop()");
0N/A clip.stop();
0N/A }
0N/A }
0N/A clip.setFramePosition(0);
0N/A if (loop) {
0N/A if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.loop()");
0N/A clip.loop(Clip.LOOP_CONTINUOUSLY);
0N/A } else {
0N/A if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.start()");
0N/A clip.start();
0N/A }
0N/A clipLooping = loop;
0N/A if (DEBUG || Printer.debug)Printer.debug("Clip should be playing/looping");
0N/A
0N/A } else if (datapusher != null ) {
0N/A datapusher.start(loop);
0N/A if (DEBUG || Printer.debug)Printer.debug("Stream should be playing/looping");
0N/A
0N/A } else if (sequencer != null) {
0N/A sequencerloop = loop;
0N/A if (sequencer.isRunning()) {
0N/A sequencer.setMicrosecondPosition(0);
0N/A }
0N/A if (!sequencer.isOpen()) {
0N/A try {
0N/A sequencer.open();
0N/A sequencer.setSequence(sequence);
0N/A
0N/A } catch (InvalidMidiDataException e1) {
0N/A if (DEBUG || Printer.err)e1.printStackTrace();
0N/A } catch (MidiUnavailableException e2) {
0N/A if (DEBUG || Printer.err)e2.printStackTrace();
0N/A }
0N/A }
0N/A sequencer.addMetaEventListener(this);
0N/A try {
0N/A sequencer.start();
0N/A } catch (Exception e) {
0N/A if (DEBUG || Printer.err) e.printStackTrace();
0N/A }
0N/A if (DEBUG || Printer.debug)Printer.debug("Sequencer should be playing/looping");
0N/A }
0N/A } catch (Exception e) {
0N/A if (DEBUG || Printer.err)e.printStackTrace();
0N/A }
0N/A }
0N/A
0N/A public synchronized void stop() {
0N/A
0N/A if (DEBUG || Printer.debug)Printer.debug("JavaSoundAudioClip->stop()");
0N/A lastPlayCall = 0;
0N/A
0N/A if (clip != null) {
0N/A try {
0N/A if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.flush()");
0N/A clip.flush();
0N/A } catch (Exception e1) {
0N/A if (Printer.err) e1.printStackTrace();
0N/A }
0N/A try {
0N/A if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.stop()");
0N/A clip.stop();
0N/A } catch (Exception e2) {
0N/A if (Printer.err) e2.printStackTrace();
0N/A }
0N/A if (DEBUG || Printer.debug)Printer.debug("Clip should be stopped");
0N/A
0N/A } else if (datapusher != null) {
0N/A datapusher.stop();
0N/A if (DEBUG || Printer.debug)Printer.debug("Stream should be stopped");
0N/A
0N/A } else if (sequencer != null) {
0N/A try {
0N/A sequencerloop = false;
0N/A sequencer.addMetaEventListener(this);
0N/A sequencer.stop();
0N/A } catch (Exception e3) {
0N/A if (Printer.err) e3.printStackTrace();
0N/A }
0N/A try {
0N/A sequencer.close();
0N/A } catch (Exception e4) {
0N/A if (Printer.err) e4.printStackTrace();
0N/A }
0N/A if (DEBUG || Printer.debug)Printer.debug("Sequencer should be stopped");
0N/A }
0N/A }
0N/A
0N/A // Event handlers (for debugging)
0N/A
0N/A public synchronized void update(LineEvent event) {
0N/A if (DEBUG || Printer.debug) Printer.debug("line event received: "+event);
0N/A }
0N/A
0N/A // handle MIDI track end meta events for looping
0N/A
0N/A public synchronized void meta( MetaMessage message ) {
0N/A
0N/A if (DEBUG || Printer.debug)Printer.debug("META EVENT RECEIVED!!!!! ");
0N/A
0N/A if( message.getType() == 47 ) {
0N/A if (sequencerloop){
0N/A //notifyAll();
0N/A sequencer.setMicrosecondPosition(0);
0N/A loop();
0N/A } else {
0N/A stop();
0N/A }
0N/A }
0N/A }
0N/A
0N/A
0N/A public String toString() {
0N/A return getClass().toString();
0N/A }
0N/A
0N/A
0N/A protected void finalize() {
0N/A
0N/A if (clip != null) {
0N/A if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip.finalize: clip.close()");
0N/A clip.close();
0N/A }
0N/A
0N/A //$$fb 2001-09-26: may improve situation related to bug #4302884
0N/A if (datapusher != null) {
0N/A datapusher.close();
0N/A }
0N/A
0N/A if (sequencer != null) {
0N/A sequencer.close();
0N/A }
0N/A }
0N/A
0N/A // FILE LOADING METHODS
0N/A
0N/A private boolean loadAudioData(AudioInputStream as) throws IOException, UnsupportedAudioFileException {
0N/A if (DEBUG || Printer.debug)Printer.debug("JavaSoundAudioClip->openAsClip()");
0N/A
0N/A // first possibly convert this stream to PCM
0N/A as = Toolkit.getPCMConvertedAudioInputStream(as);
0N/A if (as == null) {
0N/A return false;
0N/A }
0N/A
0N/A loadedAudioFormat = as.getFormat();
0N/A long frameLen = as.getFrameLength();
0N/A int frameSize = loadedAudioFormat.getFrameSize();
0N/A long byteLen = AudioSystem.NOT_SPECIFIED;
0N/A if (frameLen != AudioSystem.NOT_SPECIFIED
0N/A && frameLen > 0
0N/A && frameSize != AudioSystem.NOT_SPECIFIED
0N/A && frameSize > 0) {
0N/A byteLen = frameLen * frameSize;
0N/A }
0N/A if (byteLen != AudioSystem.NOT_SPECIFIED) {
0N/A // if the stream length is known, it can be efficiently loaded into memory
0N/A readStream(as, byteLen);
0N/A } else {
0N/A // otherwise we use a ByteArrayOutputStream to load it into memory
0N/A readStream(as);
0N/A }
0N/A
0N/A // if everything went fine, we have now the audio data in
0N/A // loadedAudio, and the byte length in loadedAudioByteLength
0N/A return true;
0N/A }
0N/A
0N/A
0N/A
0N/A private void readStream(AudioInputStream as, long byteLen) throws IOException {
0N/A // arrays "only" max. 2GB
0N/A int intLen;
0N/A if (byteLen > 2147483647) {
0N/A intLen = 2147483647;
0N/A } else {
0N/A intLen = (int) byteLen;
0N/A }
0N/A loadedAudio = new byte[intLen];
0N/A loadedAudioByteLength = 0;
0N/A
0N/A // this loop may throw an IOException
0N/A while (true) {
0N/A int bytesRead = as.read(loadedAudio, loadedAudioByteLength, intLen - loadedAudioByteLength);
0N/A if (bytesRead <= 0) {
0N/A as.close();
0N/A break;
0N/A }
0N/A loadedAudioByteLength += bytesRead;
0N/A }
0N/A }
0N/A
0N/A private void readStream(AudioInputStream as) throws IOException {
0N/A
0N/A DirectBAOS baos = new DirectBAOS();
0N/A byte buffer[] = new byte[16384];
0N/A int bytesRead = 0;
0N/A 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 {
DirectBAOS() {
super();
}
public byte[] getInternalBuffer() {
return buf;
}
} // class DirectBAOS
}