Path: blob/master/src/java.desktop/share/classes/com/sun/media/sound/JavaSoundAudioClip.java
41161 views
/*1* Copyright (c) 1999, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package com.sun.media.sound;2627import java.applet.AudioClip;28import java.io.BufferedInputStream;29import java.io.ByteArrayOutputStream;30import java.io.IOException;31import java.io.InputStream;32import java.net.URL;33import java.net.URLConnection;3435import javax.sound.midi.InvalidMidiDataException;36import javax.sound.midi.MetaEventListener;37import javax.sound.midi.MetaMessage;38import javax.sound.midi.MidiFileFormat;39import javax.sound.midi.MidiSystem;40import javax.sound.midi.MidiUnavailableException;41import javax.sound.midi.Sequence;42import javax.sound.midi.Sequencer;43import javax.sound.sampled.AudioFormat;44import javax.sound.sampled.AudioInputStream;45import javax.sound.sampled.AudioSystem;46import javax.sound.sampled.Clip;47import javax.sound.sampled.DataLine;48import javax.sound.sampled.LineEvent;49import javax.sound.sampled.LineListener;50import javax.sound.sampled.SourceDataLine;51import javax.sound.sampled.UnsupportedAudioFileException;5253/**54* Java Sound audio clip;55*56* @author Arthur van Hoff, Kara Kytle, Jan Borgersen57* @author Florian Bomers58*/59@SuppressWarnings({"deprecation", "removal"})60public final class JavaSoundAudioClip implements AudioClip, MetaEventListener, LineListener {6162private long lastPlayCall = 0;63private static final int MINIMUM_PLAY_DELAY = 30;6465private byte[] loadedAudio = null;66private int loadedAudioByteLength = 0;67private AudioFormat loadedAudioFormat = null;6869private AutoClosingClip clip = null;70private boolean clipLooping = false;7172private DataPusher datapusher = null;7374private Sequencer sequencer = null;75private Sequence sequence = null;76private boolean sequencerloop = false;77private volatile boolean success;7879/**80* used for determining how many samples is the81* threshhold between playing as a Clip and streaming82* from the file.83*84* $$jb: 11.07.99: the engine has a limit of 1M85* samples to play as a Clip, so compare this number86* with the number of samples in the stream.87*88*/89private static final long CLIP_THRESHOLD = 1048576;90private static final int STREAM_BUFFER_SIZE = 1024;9192public static JavaSoundAudioClip create(final URLConnection uc) {93JavaSoundAudioClip clip = new JavaSoundAudioClip();94try {95clip.init(uc.getInputStream());96} catch (final Exception ignored) {97// AudioClip will be no-op if some exception will occurred98}99return clip;100}101102public static JavaSoundAudioClip create(final URL url) {103JavaSoundAudioClip clip = new JavaSoundAudioClip();104try {105clip.init(url.openStream());106} catch (final Exception ignored) {107// AudioClip will be no-op if some exception will occurred108}109return clip;110}111112private void init(InputStream in) throws IOException {113BufferedInputStream bis = new BufferedInputStream(in, STREAM_BUFFER_SIZE);114bis.mark(STREAM_BUFFER_SIZE);115try {116AudioInputStream as = AudioSystem.getAudioInputStream(bis);117// load the stream data into memory118success = loadAudioData(as);119120if (success) {121success = false;122if (loadedAudioByteLength < CLIP_THRESHOLD) {123success = createClip();124}125if (!success) {126success = createSourceDataLine();127}128}129} catch (UnsupportedAudioFileException e) {130// not an audio file131try {132MidiFileFormat mff = MidiSystem.getMidiFileFormat(bis);133success = createSequencer(bis);134} catch (InvalidMidiDataException e1) {135success = false;136}137}138}139140@Override141public synchronized void play() {142if (!success) {143return;144}145startImpl(false);146}147148@Override149public synchronized void loop() {150if (!success) {151return;152}153startImpl(true);154}155156private synchronized void startImpl(boolean loop) {157// hack for some applets that call the start method very rapidly...158long currentTime = System.currentTimeMillis();159long diff = currentTime - lastPlayCall;160if (diff < MINIMUM_PLAY_DELAY) {161return;162}163lastPlayCall = currentTime;164try {165if (clip != null) {166// We need to disable autoclosing mechanism otherwise the clip167// can be closed after "!clip.isOpen()" check, because of168// previous inactivity.169clip.setAutoClosing(false);170try {171if (!clip.isOpen()) {172clip.open(loadedAudioFormat, loadedAudio, 0,173loadedAudioByteLength);174} else {175clip.flush();176if (loop != clipLooping) {177// need to stop in case the looped status changed178clip.stop();179}180}181clip.setFramePosition(0);182if (loop) {183clip.loop(Clip.LOOP_CONTINUOUSLY);184} else {185clip.start();186}187clipLooping = loop;188} finally {189clip.setAutoClosing(true);190}191} else if (datapusher != null ) {192datapusher.start(loop);193194} else if (sequencer != null) {195sequencerloop = loop;196if (sequencer.isRunning()) {197sequencer.setMicrosecondPosition(0);198}199if (!sequencer.isOpen()) {200try {201sequencer.open();202sequencer.setSequence(sequence);203204} catch (InvalidMidiDataException e1) {205if (Printer.err) e1.printStackTrace();206} catch (MidiUnavailableException e2) {207if (Printer.err) e2.printStackTrace();208}209}210sequencer.addMetaEventListener(this);211try {212sequencer.start();213} catch (Exception e) {214if (Printer.err) e.printStackTrace();215}216}217} catch (Exception e) {218if (Printer.err) e.printStackTrace();219}220}221222@Override223public synchronized void stop() {224if (!success) {225return;226}227lastPlayCall = 0;228229if (clip != null) {230try {231clip.flush();232} catch (Exception e1) {233if (Printer.err) e1.printStackTrace();234}235try {236clip.stop();237} catch (Exception e2) {238if (Printer.err) e2.printStackTrace();239}240} else if (datapusher != null) {241datapusher.stop();242} else if (sequencer != null) {243try {244sequencerloop = false;245sequencer.removeMetaEventListener(this);246sequencer.stop();247} catch (Exception e3) {248if (Printer.err) e3.printStackTrace();249}250try {251sequencer.close();252} catch (Exception e4) {253if (Printer.err) e4.printStackTrace();254}255}256}257258// Event handlers (for debugging)259260@Override261public synchronized void update(LineEvent event) {262}263264// handle MIDI track end meta events for looping265266@Override267public synchronized void meta(MetaMessage message) {268if( message.getType() == 47 ) {269if (sequencerloop){270//notifyAll();271sequencer.setMicrosecondPosition(0);272loop();273} else {274stop();275}276}277}278279@Override280public String toString() {281return getClass().toString();282}283284@Override285protected void finalize() {286287if (clip != null) {288clip.close();289}290291//$$fb 2001-09-26: may improve situation related to bug #4302884292if (datapusher != null) {293datapusher.close();294}295296if (sequencer != null) {297sequencer.close();298}299}300301// FILE LOADING METHODS302303private boolean loadAudioData(AudioInputStream as) throws IOException, UnsupportedAudioFileException {304// first possibly convert this stream to PCM305as = Toolkit.getPCMConvertedAudioInputStream(as);306if (as == null) {307return false;308}309310loadedAudioFormat = as.getFormat();311long frameLen = as.getFrameLength();312int frameSize = loadedAudioFormat.getFrameSize();313long byteLen = AudioSystem.NOT_SPECIFIED;314if (frameLen != AudioSystem.NOT_SPECIFIED315&& frameLen > 0316&& frameSize != AudioSystem.NOT_SPECIFIED317&& frameSize > 0) {318byteLen = frameLen * frameSize;319}320if (byteLen != AudioSystem.NOT_SPECIFIED) {321// if the stream length is known, it can be efficiently loaded into memory322readStream(as, byteLen);323} else {324// otherwise we use a ByteArrayOutputStream to load it into memory325readStream(as);326}327328// if everything went fine, we have now the audio data in329// loadedAudio, and the byte length in loadedAudioByteLength330return true;331}332333private void readStream(AudioInputStream as, long byteLen) throws IOException {334// arrays "only" max. 2GB335int intLen;336if (byteLen > 2147483647) {337intLen = 2147483647;338} else {339intLen = (int) byteLen;340}341loadedAudio = new byte[intLen];342loadedAudioByteLength = 0;343344// this loop may throw an IOException345while (true) {346int bytesRead = as.read(loadedAudio, loadedAudioByteLength, intLen - loadedAudioByteLength);347if (bytesRead <= 0) {348as.close();349break;350}351loadedAudioByteLength += bytesRead;352}353}354355private void readStream(AudioInputStream as) throws IOException {356357DirectBAOS baos = new DirectBAOS();358int totalBytesRead;359try (as) {360totalBytesRead = (int) as.transferTo(baos);361}362loadedAudio = baos.getInternalBuffer();363loadedAudioByteLength = totalBytesRead;364}365366// METHODS FOR CREATING THE DEVICE367368private boolean createClip() {369try {370DataLine.Info info = new DataLine.Info(Clip.class, loadedAudioFormat);371if (!(AudioSystem.isLineSupported(info)) ) {372if (Printer.err) Printer.err("Clip not supported: "+loadedAudioFormat);373// fail silently374return false;375}376Object line = AudioSystem.getLine(info);377if (!(line instanceof AutoClosingClip)) {378if (Printer.err) Printer.err("Clip is not auto closing!"+clip);379// fail -> will try with SourceDataLine380return false;381}382clip = (AutoClosingClip) line;383clip.setAutoClosing(true);384} catch (Exception e) {385if (Printer.err) e.printStackTrace();386// fail silently387return false;388}389390if (clip==null) {391// fail silently392return false;393}394return true;395}396397private boolean createSourceDataLine() {398try {399DataLine.Info info = new DataLine.Info(SourceDataLine.class, loadedAudioFormat);400if (!(AudioSystem.isLineSupported(info)) ) {401if (Printer.err) Printer.err("Line not supported: "+loadedAudioFormat);402// fail silently403return false;404}405SourceDataLine source = (SourceDataLine) AudioSystem.getLine(info);406datapusher = new DataPusher(source, loadedAudioFormat, loadedAudio, loadedAudioByteLength);407} catch (Exception e) {408if (Printer.err) e.printStackTrace();409// fail silently410return false;411}412413if (datapusher==null) {414// fail silently415return false;416}417return true;418}419420private boolean createSequencer(BufferedInputStream in) throws IOException {421// get the sequencer422try {423sequencer = MidiSystem.getSequencer( );424} catch(MidiUnavailableException me) {425if (Printer.err) me.printStackTrace();426return false;427}428if (sequencer==null) {429return false;430}431432try {433sequence = MidiSystem.getSequence(in);434if (sequence == null) {435return false;436}437} catch (InvalidMidiDataException e) {438if (Printer.err) e.printStackTrace();439return false;440}441return true;442}443444/*445* private inner class representing a ByteArrayOutputStream446* which allows retrieval of the internal array447*/448private static class DirectBAOS extends ByteArrayOutputStream {449DirectBAOS() {450super();451}452453public byte[] getInternalBuffer() {454return buf;455}456457} // class DirectBAOS458}459460461