1 /* 2 * Copyright (c) 1999, 2013, 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.io.IOException; 29 import java.io.InputStream; 30 import java.io.BufferedInputStream; 31 import java.io.ByteArrayOutputStream; 32 import java.applet.AudioClip; 33 34 import javax.sound.sampled.AudioSystem; 35 import javax.sound.sampled.Clip; 36 import javax.sound.sampled.AudioInputStream; 37 import javax.sound.sampled.AudioFormat; 38 import javax.sound.sampled.DataLine; 39 import javax.sound.sampled.SourceDataLine; 40 import javax.sound.sampled.LineEvent; 41 import javax.sound.sampled.LineListener; 42 import javax.sound.sampled.UnsupportedAudioFileException; 43 44 import javax.sound.midi.MidiSystem; 45 import javax.sound.midi.MidiFileFormat; 46 import javax.sound.midi.MetaMessage; 47 import javax.sound.midi.Sequence; 48 import javax.sound.midi.Sequencer; 49 import javax.sound.midi.InvalidMidiDataException; 50 import javax.sound.midi.MidiUnavailableException; 51 import javax.sound.midi.MetaEventListener; 52 53 /** 54 * Java Sound audio clip; 55 * 56 * @author Arthur van Hoff, Kara Kytle, Jan Borgersen 57 * @author Florian Bomers 58 */ 59 60 public final class JavaSoundAudioClip implements AudioClip, MetaEventListener, LineListener { 61 62 private static final boolean DEBUG = false; 63 private static final int BUFFER_SIZE = 16384; // number of bytes written each time to the source data line 64 65 private long lastPlayCall = 0; 66 private static final int MINIMUM_PLAY_DELAY = 30; 67 68 private byte loadedAudio[] = null; 69 private int loadedAudioByteLength = 0; 70 private AudioFormat loadedAudioFormat = null; 71 72 private AutoClosingClip clip = null; 73 private boolean clipLooping = false; 74 75 private DataPusher datapusher = null; 76 77 private Sequencer sequencer = null; 78 private Sequence sequence = null; 79 private boolean sequencerloop = false; 80 81 /** 82 * used for determining how many samples is the 83 * threshhold between playing as a Clip and streaming 84 * from the file. 85 * 86 * $$jb: 11.07.99: the engine has a limit of 1M 87 * samples to play as a Clip, so compare this number 88 * with the number of samples in the stream. 89 * 90 */ 91 private final static long CLIP_THRESHOLD = 1048576; 92 //private final static long CLIP_THRESHOLD = 1; 93 private final static int STREAM_BUFFER_SIZE = 1024; 94 95 public JavaSoundAudioClip(InputStream in) throws IOException { 96 if (DEBUG || Printer.debug)Printer.debug("JavaSoundAudioClip.<init>"); 97 98 BufferedInputStream bis = new BufferedInputStream(in, STREAM_BUFFER_SIZE); 99 bis.mark(STREAM_BUFFER_SIZE); 100 boolean success = false; 101 try { 102 AudioInputStream as = AudioSystem.getAudioInputStream(bis); 103 // load the stream data into memory 104 success = loadAudioData(as); 105 106 if (success) { 107 success = false; 108 if (loadedAudioByteLength < CLIP_THRESHOLD) { 109 success = createClip(); 110 } 111 if (!success) { 112 success = createSourceDataLine(); 113 } 114 } 115 } catch (UnsupportedAudioFileException e) { 116 // not an audio file 117 try { 118 MidiFileFormat mff = MidiSystem.getMidiFileFormat(bis); 119 success = createSequencer(bis); 120 } catch (InvalidMidiDataException e1) { 121 success = false; 122 } 123 } 124 if (!success) { 125 throw new IOException("Unable to create AudioClip from input stream"); 126 } 127 } 128 129 130 public synchronized void play() { 131 startImpl(false); 132 } 133 134 135 public synchronized void loop() { 136 startImpl(true); 137 } 138 139 private synchronized void startImpl(boolean loop) { 140 // hack for some applets that call the start method very rapidly... 141 long currentTime = System.currentTimeMillis(); 142 long diff = currentTime - lastPlayCall; 143 if (diff < MINIMUM_PLAY_DELAY) { 144 if (DEBUG || Printer.debug) Printer.debug("JavaSoundAudioClip.startImpl(loop="+loop+"): abort - too rapdly"); 145 return; 146 } 147 lastPlayCall = currentTime; 148 149 if (DEBUG || Printer.debug) Printer.debug("JavaSoundAudioClip.startImpl(loop="+loop+")"); 150 try { 151 if (clip != null) { 152 if (!clip.isOpen()) { 153 if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.open()"); 154 clip.open(loadedAudioFormat, loadedAudio, 0, loadedAudioByteLength); 155 } else { 156 if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.flush()"); 157 clip.flush(); 158 if (loop != clipLooping) { 159 // need to stop in case the looped status changed 160 if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.stop()"); 161 clip.stop(); 162 } 163 } 164 clip.setFramePosition(0); 165 if (loop) { 166 if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.loop()"); 167 clip.loop(Clip.LOOP_CONTINUOUSLY); 168 } else { 169 if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.start()"); 170 clip.start(); 171 } 172 clipLooping = loop; 173 if (DEBUG || Printer.debug)Printer.debug("Clip should be playing/looping"); 174 175 } else if (datapusher != null ) { 176 datapusher.start(loop); 177 if (DEBUG || Printer.debug)Printer.debug("Stream should be playing/looping"); 178 179 } else if (sequencer != null) { 180 sequencerloop = loop; 181 if (sequencer.isRunning()) { 182 sequencer.setMicrosecondPosition(0); 183 } 184 if (!sequencer.isOpen()) { 185 try { 186 sequencer.open(); 187 sequencer.setSequence(sequence); 188 189 } catch (InvalidMidiDataException e1) { 190 if (DEBUG || Printer.err)e1.printStackTrace(); 191 } catch (MidiUnavailableException e2) { 192 if (DEBUG || Printer.err)e2.printStackTrace(); 193 } 194 } 195 sequencer.addMetaEventListener(this); 196 try { 197 sequencer.start(); 198 } catch (Exception e) { 199 if (DEBUG || Printer.err) e.printStackTrace(); 200 } 201 if (DEBUG || Printer.debug)Printer.debug("Sequencer should be playing/looping"); 202 } 203 } catch (Exception e) { 204 if (DEBUG || Printer.err)e.printStackTrace(); 205 } 206 } 207 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.addMetaEventListener(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 public synchronized void update(LineEvent event) { 252 if (DEBUG || Printer.debug) Printer.debug("line event received: "+event); 253 } 254 255 // handle MIDI track end meta events for looping 256 257 public synchronized void meta( MetaMessage message ) { 258 259 if (DEBUG || Printer.debug)Printer.debug("META EVENT RECEIVED!!!!! "); 260 261 if( message.getType() == 47 ) { 262 if (sequencerloop){ 263 //notifyAll(); 264 sequencer.setMicrosecondPosition(0); 265 loop(); 266 } else { 267 stop(); 268 } 269 } 270 } 271 272 273 public String toString() { 274 return getClass().toString(); 275 } 276 277 278 protected void finalize() { 279 280 if (clip != null) { 281 if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip.finalize: clip.close()"); 282 clip.close(); 283 } 284 285 //$$fb 2001-09-26: may improve situation related to bug #4302884 286 if (datapusher != null) { 287 datapusher.close(); 288 } 289 290 if (sequencer != null) { 291 sequencer.close(); 292 } 293 } 294 295 // FILE LOADING METHODS 296 297 private boolean loadAudioData(AudioInputStream as) throws IOException, UnsupportedAudioFileException { 298 if (DEBUG || Printer.debug)Printer.debug("JavaSoundAudioClip->openAsClip()"); 299 300 // first possibly convert this stream to PCM 301 as = Toolkit.getPCMConvertedAudioInputStream(as); 302 if (as == null) { 303 return false; 304 } 305 306 loadedAudioFormat = as.getFormat(); 307 long frameLen = as.getFrameLength(); 308 int frameSize = loadedAudioFormat.getFrameSize(); 309 long byteLen = AudioSystem.NOT_SPECIFIED; 310 if (frameLen != AudioSystem.NOT_SPECIFIED 311 && frameLen > 0 312 && frameSize != AudioSystem.NOT_SPECIFIED 313 && frameSize > 0) { 314 byteLen = frameLen * frameSize; 315 } 316 if (byteLen != AudioSystem.NOT_SPECIFIED) { 317 // if the stream length is known, it can be efficiently loaded into memory 318 readStream(as, byteLen); 319 } else { 320 // otherwise we use a ByteArrayOutputStream to load it into memory 321 readStream(as); 322 } 323 324 // if everything went fine, we have now the audio data in 325 // loadedAudio, and the byte length in loadedAudioByteLength 326 return true; 327 } 328 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 375 // METHODS FOR CREATING THE DEVICE 376 377 private boolean createClip() { 378 379 if (DEBUG || Printer.debug)Printer.debug("JavaSoundAudioClip.createClip()"); 380 381 try { 382 DataLine.Info info = new DataLine.Info(Clip.class, loadedAudioFormat); 383 if (!(AudioSystem.isLineSupported(info)) ) { 384 if (DEBUG || Printer.err)Printer.err("Clip not supported: "+loadedAudioFormat); 385 // fail silently 386 return false; 387 } 388 Object line = AudioSystem.getLine(info); 389 if (!(line instanceof AutoClosingClip)) { 390 if (DEBUG || Printer.err)Printer.err("Clip is not auto closing!"+clip); 391 // fail -> will try with SourceDataLine 392 return false; 393 } 394 clip = (AutoClosingClip) line; 395 clip.setAutoClosing(true); 396 if (DEBUG || Printer.debug) clip.addLineListener(this); 397 } catch (Exception e) { 398 if (DEBUG || Printer.err)e.printStackTrace(); 399 // fail silently 400 return false; 401 } 402 403 if (clip==null) { 404 // fail silently 405 return false; 406 } 407 408 if (DEBUG || Printer.debug)Printer.debug("Loaded clip."); 409 return true; 410 } 411 412 private boolean createSourceDataLine() { 413 if (DEBUG || Printer.debug)Printer.debug("JavaSoundAudioClip.createSourceDataLine()"); 414 try { 415 DataLine.Info info = new DataLine.Info(SourceDataLine.class, loadedAudioFormat); 416 if (!(AudioSystem.isLineSupported(info)) ) { 417 if (DEBUG || Printer.err)Printer.err("Line not supported: "+loadedAudioFormat); 418 // fail silently 419 return false; 420 } 421 SourceDataLine source = (SourceDataLine) AudioSystem.getLine(info); 422 datapusher = new DataPusher(source, loadedAudioFormat, loadedAudio, loadedAudioByteLength); 423 } catch (Exception e) { 424 if (DEBUG || Printer.err)e.printStackTrace(); 425 // fail silently 426 return false; 427 } 428 429 if (datapusher==null) { 430 // fail silently 431 return false; 432 } 433 434 if (DEBUG || Printer.debug)Printer.debug("Created SourceDataLine."); 435 return true; 436 } 437 438 private boolean createSequencer(BufferedInputStream in) throws IOException { 439 440 if (DEBUG || Printer.debug)Printer.debug("JavaSoundAudioClip.createSequencer()"); 441 442 // get the sequencer 443 try { 444 sequencer = MidiSystem.getSequencer( ); 445 } catch(MidiUnavailableException me) { 446 if (DEBUG || Printer.err)me.printStackTrace(); 447 return false; 448 } 449 if (sequencer==null) { 450 return false; 451 } 452 453 try { 454 sequence = MidiSystem.getSequence(in); 455 if (sequence == null) { 456 return false; 457 } 458 } catch (InvalidMidiDataException e) { 459 if (DEBUG || Printer.err)e.printStackTrace(); 460 return false; 461 } 462 463 if (DEBUG || Printer.debug)Printer.debug("Created Sequencer."); 464 return true; 465 } 466 467 468 /* 469 * private inner class representing a ByteArrayOutputStream 470 * which allows retrieval of the internal array 471 */ 472 private static class DirectBAOS extends ByteArrayOutputStream { 473 DirectBAOS() { 474 super(); 475 } 476 477 public byte[] getInternalBuffer() { 478 return buf; 479 } 480 481 } // class DirectBAOS 482 483 }