1 /* 2 * Copyright (c) 1999, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.media.sound; 27 28 import java.applet.AudioClip; 29 import java.io.BufferedInputStream; 30 import java.io.ByteArrayOutputStream; 31 import java.io.IOException; 32 import java.io.InputStream; 33 34 import javax.sound.midi.InvalidMidiDataException; 35 import javax.sound.midi.MetaEventListener; 36 import javax.sound.midi.MetaMessage; 37 import javax.sound.midi.MidiFileFormat; 38 import javax.sound.midi.MidiSystem; 39 import javax.sound.midi.MidiUnavailableException; 40 import javax.sound.midi.Sequence; 41 import javax.sound.midi.Sequencer; 42 import javax.sound.sampled.AudioFormat; 43 import javax.sound.sampled.AudioInputStream; 44 import javax.sound.sampled.AudioSystem; 45 import javax.sound.sampled.Clip; 46 import javax.sound.sampled.DataLine; 47 import javax.sound.sampled.LineEvent; 48 import javax.sound.sampled.LineListener; 49 import javax.sound.sampled.SourceDataLine; 50 import javax.sound.sampled.UnsupportedAudioFileException; 51 52 /** 53 * Java Sound audio clip; 54 * 55 * @author Arthur van Hoff, Kara Kytle, Jan Borgersen 56 * @author Florian Bomers 57 */ 58 @SuppressWarnings("deprecation") 59 public final class JavaSoundAudioClip implements AudioClip, MetaEventListener, LineListener { 60 61 private static final boolean DEBUG = false; 62 private static final int BUFFER_SIZE = 16384; // number of bytes written each time to the source data line 63 64 private long lastPlayCall = 0; 65 private static final int MINIMUM_PLAY_DELAY = 30; 66 67 private byte loadedAudio[] = null; 68 private int loadedAudioByteLength = 0; 69 private AudioFormat loadedAudioFormat = null; 70 71 private AutoClosingClip clip = null; 72 private boolean clipLooping = false; 73 74 private DataPusher datapusher = null; 75 76 private Sequencer sequencer = null; 77 private Sequence sequence = null; 78 private boolean sequencerloop = false; 79 80 /** 81 * used for determining how many samples is the 82 * threshhold between playing as a Clip and streaming 83 * from the file. 84 * 85 * $$jb: 11.07.99: the engine has a limit of 1M 86 * samples to play as a Clip, so compare this number 87 * with the number of samples in the stream. 88 * 89 */ 90 private static final long CLIP_THRESHOLD = 1048576; 91 //private final static long CLIP_THRESHOLD = 1; 92 private static final int STREAM_BUFFER_SIZE = 1024; 93 94 public JavaSoundAudioClip(InputStream in) throws IOException { 95 if (DEBUG || Printer.debug)Printer.debug("JavaSoundAudioClip.<init>"); 96 97 BufferedInputStream bis = new BufferedInputStream(in, STREAM_BUFFER_SIZE); 98 bis.mark(STREAM_BUFFER_SIZE); 99 boolean success = false; 100 try { 101 AudioInputStream as = AudioSystem.getAudioInputStream(bis); 102 // load the stream data into memory 103 success = loadAudioData(as); 104 105 if (success) { 106 success = false; 107 if (loadedAudioByteLength < CLIP_THRESHOLD) { 108 success = createClip(); 109 } 110 if (!success) { 111 success = createSourceDataLine(); 112 } 113 } 114 } catch (UnsupportedAudioFileException e) { 115 // not an audio file 116 try { 117 MidiFileFormat mff = MidiSystem.getMidiFileFormat(bis); 118 success = createSequencer(bis); 119 } catch (InvalidMidiDataException e1) { 120 success = false; 121 } 122 } 123 if (!success) { 124 throw new IOException("Unable to create AudioClip from input stream"); 125 } 126 } 127 128 @Override 129 public synchronized void play() { 130 startImpl(false); 131 } 132 133 @Override 134 public synchronized void loop() { 135 startImpl(true); 136 } 137 138 private synchronized void startImpl(boolean loop) { 139 // hack for some applets that call the start method very rapidly... 140 long currentTime = System.currentTimeMillis(); 141 long diff = currentTime - lastPlayCall; 142 if (diff < MINIMUM_PLAY_DELAY) { 143 if (DEBUG || Printer.debug) Printer.debug("JavaSoundAudioClip.startImpl(loop="+loop+"): abort - too rapdly"); 144 return; 145 } 146 lastPlayCall = currentTime; 147 148 if (DEBUG || Printer.debug) Printer.debug("JavaSoundAudioClip.startImpl(loop="+loop+")"); 149 try { 150 if (clip != null) { 151 if (!clip.isOpen()) { 152 if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.open()"); 153 clip.open(loadedAudioFormat, loadedAudio, 0, loadedAudioByteLength); 154 } else { 155 if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.flush()"); 156 clip.flush(); 157 if (loop != clipLooping) { 158 // need to stop in case the looped status changed 159 if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.stop()"); 160 clip.stop(); 161 } 162 } 163 clip.setFramePosition(0); 164 if (loop) { 165 if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.loop()"); 166 clip.loop(Clip.LOOP_CONTINUOUSLY); 167 } else { 168 if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.start()"); 169 clip.start(); 170 } 171 clipLooping = loop; 172 if (DEBUG || Printer.debug)Printer.debug("Clip should be playing/looping"); 173 174 } else if (datapusher != null ) { 175 datapusher.start(loop); 176 if (DEBUG || Printer.debug)Printer.debug("Stream should be playing/looping"); 177 178 } else if (sequencer != null) { 179 sequencerloop = loop; 180 if (sequencer.isRunning()) { 181 sequencer.setMicrosecondPosition(0); 182 } 183 if (!sequencer.isOpen()) { 184 try { 185 sequencer.open(); 186 sequencer.setSequence(sequence); 187 188 } catch (InvalidMidiDataException e1) { 189 if (DEBUG || Printer.err)e1.printStackTrace(); 190 } catch (MidiUnavailableException e2) { 191 if (DEBUG || Printer.err)e2.printStackTrace(); 192 } 193 } 194 sequencer.addMetaEventListener(this); 195 try { 196 sequencer.start(); 197 } catch (Exception e) { 198 if (DEBUG || Printer.err) e.printStackTrace(); 199 } 200 if (DEBUG || Printer.debug)Printer.debug("Sequencer should be playing/looping"); 201 } 202 } catch (Exception e) { 203 if (DEBUG || Printer.err)e.printStackTrace(); 204 } 205 } 206 207 @Override 208 public synchronized void stop() { 209 210 if (DEBUG || Printer.debug)Printer.debug("JavaSoundAudioClip->stop()"); 211 lastPlayCall = 0; 212 213 if (clip != null) { 214 try { 215 if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.flush()"); 216 clip.flush(); 217 } catch (Exception e1) { 218 if (Printer.err) e1.printStackTrace(); 219 } 220 try { 221 if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.stop()"); 222 clip.stop(); 223 } catch (Exception e2) { 224 if (Printer.err) e2.printStackTrace(); 225 } 226 if (DEBUG || Printer.debug)Printer.debug("Clip should be stopped"); 227 228 } else if (datapusher != null) { 229 datapusher.stop(); 230 if (DEBUG || Printer.debug)Printer.debug("Stream should be stopped"); 231 232 } else if (sequencer != null) { 233 try { 234 sequencerloop = false; 235 sequencer.removeMetaEventListener(this); 236 sequencer.stop(); 237 } catch (Exception e3) { 238 if (Printer.err) e3.printStackTrace(); 239 } 240 try { 241 sequencer.close(); 242 } catch (Exception e4) { 243 if (Printer.err) e4.printStackTrace(); 244 } 245 if (DEBUG || Printer.debug)Printer.debug("Sequencer should be stopped"); 246 } 247 } 248 249 // Event handlers (for debugging) 250 251 @Override 252 public synchronized void update(LineEvent event) { 253 if (DEBUG || Printer.debug) Printer.debug("line event received: "+event); 254 } 255 256 // handle MIDI track end meta events for looping 257 258 @Override 259 public synchronized void meta(MetaMessage message) { 260 261 if (DEBUG || Printer.debug)Printer.debug("META EVENT RECEIVED!!!!! "); 262 263 if( message.getType() == 47 ) { 264 if (sequencerloop){ 265 //notifyAll(); 266 sequencer.setMicrosecondPosition(0); 267 loop(); 268 } else { 269 stop(); 270 } 271 } 272 } 273 274 @Override 275 public String toString() { 276 return getClass().toString(); 277 } 278 279 @Override 280 protected void finalize() { 281 282 if (clip != null) { 283 if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip.finalize: clip.close()"); 284 clip.close(); 285 } 286 287 //$$fb 2001-09-26: may improve situation related to bug #4302884 288 if (datapusher != null) { 289 datapusher.close(); 290 } 291 292 if (sequencer != null) { 293 sequencer.close(); 294 } 295 } 296 297 // FILE LOADING METHODS 298 299 private boolean loadAudioData(AudioInputStream as) throws IOException, UnsupportedAudioFileException { 300 if (DEBUG || Printer.debug)Printer.debug("JavaSoundAudioClip->openAsClip()"); 301 302 // first possibly convert this stream to PCM 303 as = Toolkit.getPCMConvertedAudioInputStream(as); 304 if (as == null) { 305 return false; 306 } 307 308 loadedAudioFormat = as.getFormat(); 309 long frameLen = as.getFrameLength(); 310 int frameSize = loadedAudioFormat.getFrameSize(); 311 long byteLen = AudioSystem.NOT_SPECIFIED; 312 if (frameLen != AudioSystem.NOT_SPECIFIED 313 && frameLen > 0 314 && frameSize != AudioSystem.NOT_SPECIFIED 315 && frameSize > 0) { 316 byteLen = frameLen * frameSize; 317 } 318 if (byteLen != AudioSystem.NOT_SPECIFIED) { 319 // if the stream length is known, it can be efficiently loaded into memory 320 readStream(as, byteLen); 321 } else { 322 // otherwise we use a ByteArrayOutputStream to load it into memory 323 readStream(as); 324 } 325 326 // if everything went fine, we have now the audio data in 327 // loadedAudio, and the byte length in loadedAudioByteLength 328 return true; 329 } 330 331 private void readStream(AudioInputStream as, long byteLen) throws IOException { 332 // arrays "only" max. 2GB 333 int intLen; 334 if (byteLen > 2147483647) { 335 intLen = 2147483647; 336 } else { 337 intLen = (int) byteLen; 338 } 339 loadedAudio = new byte[intLen]; 340 loadedAudioByteLength = 0; 341 342 // this loop may throw an IOException 343 while (true) { 344 int bytesRead = as.read(loadedAudio, loadedAudioByteLength, intLen - loadedAudioByteLength); 345 if (bytesRead <= 0) { 346 as.close(); 347 break; 348 } 349 loadedAudioByteLength += bytesRead; 350 } 351 } 352 353 private void readStream(AudioInputStream as) throws IOException { 354 355 DirectBAOS baos = new DirectBAOS(); 356 byte buffer[] = new byte[16384]; 357 int bytesRead = 0; 358 int totalBytesRead = 0; 359 360 // this loop may throw an IOException 361 while( true ) { 362 bytesRead = as.read(buffer, 0, buffer.length); 363 if (bytesRead <= 0) { 364 as.close(); 365 break; 366 } 367 totalBytesRead += bytesRead; 368 baos.write(buffer, 0, bytesRead); 369 } 370 loadedAudio = baos.getInternalBuffer(); 371 loadedAudioByteLength = totalBytesRead; 372 } 373 374 // METHODS FOR CREATING THE DEVICE 375 376 private boolean createClip() { 377 378 if (DEBUG || Printer.debug)Printer.debug("JavaSoundAudioClip.createClip()"); 379 380 try { 381 DataLine.Info info = new DataLine.Info(Clip.class, loadedAudioFormat); 382 if (!(AudioSystem.isLineSupported(info)) ) { 383 if (DEBUG || Printer.err)Printer.err("Clip not supported: "+loadedAudioFormat); 384 // fail silently 385 return false; 386 } 387 Object line = AudioSystem.getLine(info); 388 if (!(line instanceof AutoClosingClip)) { 389 if (DEBUG || Printer.err)Printer.err("Clip is not auto closing!"+clip); 390 // fail -> will try with SourceDataLine 391 return false; 392 } 393 clip = (AutoClosingClip) line; 394 clip.setAutoClosing(true); 395 if (DEBUG || Printer.debug) clip.addLineListener(this); 396 } catch (Exception e) { 397 if (DEBUG || Printer.err)e.printStackTrace(); 398 // fail silently 399 return false; 400 } 401 402 if (clip==null) { 403 // fail silently 404 return false; 405 } 406 407 if (DEBUG || Printer.debug)Printer.debug("Loaded clip."); 408 return true; 409 } 410 411 private boolean createSourceDataLine() { 412 if (DEBUG || Printer.debug)Printer.debug("JavaSoundAudioClip.createSourceDataLine()"); 413 try { 414 DataLine.Info info = new DataLine.Info(SourceDataLine.class, loadedAudioFormat); 415 if (!(AudioSystem.isLineSupported(info)) ) { 416 if (DEBUG || Printer.err)Printer.err("Line not supported: "+loadedAudioFormat); 417 // fail silently 418 return false; 419 } 420 SourceDataLine source = (SourceDataLine) AudioSystem.getLine(info); 421 datapusher = new DataPusher(source, loadedAudioFormat, loadedAudio, loadedAudioByteLength); 422 } catch (Exception e) { 423 if (DEBUG || Printer.err)e.printStackTrace(); 424 // fail silently 425 return false; 426 } 427 428 if (datapusher==null) { 429 // fail silently 430 return false; 431 } 432 433 if (DEBUG || Printer.debug)Printer.debug("Created SourceDataLine."); 434 return true; 435 } 436 437 private boolean createSequencer(BufferedInputStream in) throws IOException { 438 439 if (DEBUG || Printer.debug)Printer.debug("JavaSoundAudioClip.createSequencer()"); 440 441 // get the sequencer 442 try { 443 sequencer = MidiSystem.getSequencer( ); 444 } catch(MidiUnavailableException me) { 445 if (DEBUG || Printer.err)me.printStackTrace(); 446 return false; 447 } 448 if (sequencer==null) { 449 return false; 450 } 451 452 try { 453 sequence = MidiSystem.getSequence(in); 454 if (sequence == null) { 455 return false; 456 } 457 } catch (InvalidMidiDataException e) { 458 if (DEBUG || Printer.err)e.printStackTrace(); 459 return false; 460 } 461 462 if (DEBUG || Printer.debug)Printer.debug("Created Sequencer."); 463 return true; 464 } 465 466 /* 467 * private inner class representing a ByteArrayOutputStream 468 * which allows retrieval of the internal array 469 */ 470 private static class DirectBAOS extends ByteArrayOutputStream { 471 DirectBAOS() { 472 super(); 473 } 474 475 public byte[] getInternalBuffer() { 476 return buf; 477 } 478 479 } // class DirectBAOS 480 }