486 lines
16 KiB
Java
486 lines
16 KiB
Java
/*
|
|
* 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 com.sun.media.sound;
|
|
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.BufferedInputStream;
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.applet.AudioClip;
|
|
|
|
import javax.sound.sampled.AudioSystem;
|
|
import javax.sound.sampled.Clip;
|
|
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.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 final 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) {
|
|
// We need to disable autoclosing mechanism otherwise the clip
|
|
// can be closed after "!clip.isOpen()" check, because of
|
|
// previous inactivity.
|
|
clip.setAutoClosing(false);
|
|
try {
|
|
if (!clip.isOpen()) {
|
|
clip.open(loadedAudioFormat, loadedAudio, 0,
|
|
loadedAudioByteLength);
|
|
} else {
|
|
clip.flush();
|
|
if (loop != clipLooping) {
|
|
// need to stop in case the looped status changed
|
|
clip.stop();
|
|
}
|
|
}
|
|
clip.setFramePosition(0);
|
|
if (loop) {
|
|
clip.loop(Clip.LOOP_CONTINUOUSLY);
|
|
} else {
|
|
clip.start();
|
|
}
|
|
clipLooping = loop;
|
|
} finally {
|
|
clip.setAutoClosing(true);
|
|
}
|
|
} 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 {
|
|
DirectBAOS() {
|
|
super();
|
|
}
|
|
|
|
public byte[] getInternalBuffer() {
|
|
return buf;
|
|
}
|
|
|
|
} // class DirectBAOS
|
|
|
|
}
|