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 public final class JavaSoundAudioClip implements AudioClip, MetaEventListener, LineListener { 59 60 private static final boolean DEBUG = false; 61 private static final int BUFFER_SIZE = 16384; // number of bytes written each time to the source data line 62 63 private long lastPlayCall = 0; 64 private static final int MINIMUM_PLAY_DELAY = 30; 65 66 private byte loadedAudio[] = null; 67 private int loadedAudioByteLength = 0; 68 private AudioFormat loadedAudioFormat = null; 69 70 private AutoClosingClip clip = null; 71 private boolean clipLooping = false; 72 73 private DataPusher datapusher = null; 74 75 private Sequencer sequencer = null; 76 private Sequence sequence = null; 77 private boolean sequencerloop = false; 78 79 /** 80 * used for determining how many samples is the 81 * threshhold between playing as a Clip and streaming 82 * from the file. 83 * 84 * $$jb: 11.07.99: the engine has a limit of 1M 85 * samples to play as a Clip, so compare this number 86 * with the number of samples in the stream. 87 * 88 */ 89 private static final long CLIP_THRESHOLD = 1048576; 90 //private final static long CLIP_THRESHOLD = 1; 91 private static final int STREAM_BUFFER_SIZE = 1024; 92 93 public JavaSoundAudioClip(InputStream in) throws IOException { 94 if (DEBUG || Printer.debug)Printer.debug("JavaSoundAudioClip.<init>"); 95 96 BufferedInputStream bis = new BufferedInputStream(in, STREAM_BUFFER_SIZE); 97 bis.mark(STREAM_BUFFER_SIZE); 98 boolean success = false; 99 try { 100 AudioInputStream as = AudioSystem.getAudioInputStream(bis); 101 // load the stream data into memory 102 success = loadAudioData(as); 103 104 if (success) { 105 success = false; 106 if (loadedAudioByteLength < CLIP_THRESHOLD) { 107 success = createClip(); 108 } 109 if (!success) { 110 success = createSourceDataLine(); 111 } 112 } 113 } catch (UnsupportedAudioFileException e) { 114 // not an audio file 115 try { 116 MidiFileFormat mff = MidiSystem.getMidiFileFormat(bis); 117 success = createSequencer(bis); 118 } catch (InvalidMidiDataException e1) { 119 success = false; 120 } 121 } 122 if (!success) { 123 throw new IOException("Unable to create AudioClip from input stream"); 124 } 125 } 126 127 @Override 128 public synchronized void play() { 129 startImpl(false); 130 } 131 132 @Override 133 public synchronized void loop() { 134 startImpl(true); 135 } 136 137 private synchronized void startImpl(boolean loop) { 138 // hack for some applets that call the start method very rapidly... 139 long currentTime = System.currentTimeMillis(); 140 long diff = currentTime - lastPlayCall; 141 if (diff < MINIMUM_PLAY_DELAY) { 142 if (DEBUG || Printer.debug) Printer.debug("JavaSoundAudioClip.startImpl(loop="+loop+"): abort - too rapdly"); 143 return; 144 } 145 lastPlayCall = currentTime; 146 147 if (DEBUG || Printer.debug) Printer.debug("JavaSoundAudioClip.startImpl(loop="+loop+")"); 148 try { 149 if (clip != null) { 150 if (!clip.isOpen()) { 151 if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.open()"); 152 clip.open(loadedAudioFormat, loadedAudio, 0, loadedAudioByteLength); 153 } else { 154 if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.flush()"); 155 clip.flush(); 156 if (loop != clipLooping) { 157 // need to stop in case the looped status changed 158 if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.stop()"); 159 clip.stop(); 160 } 161 } 162 clip.setFramePosition(0); 163 if (loop) { 164 if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.loop()"); 165 clip.loop(Clip.LOOP_CONTINUOUSLY); 166 } else { 167 if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.start()"); 168 clip.start(); 169 } 170 clipLooping = loop; 171 if (DEBUG || Printer.debug)Printer.debug("Clip should be playing/looping"); 172 173 } else if (datapusher != null ) { 174 datapusher.start(loop); 175 if (DEBUG || Printer.debug)Printer.debug("Stream should be playing/looping"); 176 177 } else if (sequencer != null) { 178 sequencerloop = loop; 179 if (sequencer.isRunning()) { 180 sequencer.setMicrosecondPosition(0); 181 } 182 if (!sequencer.isOpen()) { 183 try { 184 sequencer.open(); 185 sequencer.setSequence(sequence); 186 187 } catch (InvalidMidiDataException e1) { 188 if (DEBUG || Printer.err)e1.printStackTrace(); 189 } catch (MidiUnavailableException e2) { 190 if (DEBUG || Printer.err)e2.printStackTrace(); 191 } 192 } 193 sequencer.addMetaEventListener(this); 194 try { 195 sequencer.start(); 196 } catch (Exception e) { 197 if (DEBUG || Printer.err) e.printStackTrace(); 198 } 199 if (DEBUG || Printer.debug)Printer.debug("Sequencer should be playing/looping"); 200 } 201 } catch (Exception e) { 202 if (DEBUG || Printer.err)e.printStackTrace(); 203 } 204 } 205 206 @Override 207 public synchronized void stop() { 208 209 if (DEBUG || Printer.debug)Printer.debug("JavaSoundAudioClip->stop()"); 210 lastPlayCall = 0; 211 212 if (clip != null) { 213 try { 214 if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.flush()"); 215 clip.flush(); 216 } catch (Exception e1) { 217 if (Printer.err) e1.printStackTrace(); 218 } 219 try { 220 if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.stop()"); 221 clip.stop(); 222 } catch (Exception e2) { 223 if (Printer.err) e2.printStackTrace(); 224 } 225 if (DEBUG || Printer.debug)Printer.debug("Clip should be stopped"); 226 227 } else if (datapusher != null) { 228 datapusher.stop(); 229 if (DEBUG || Printer.debug)Printer.debug("Stream should be stopped"); 230 231 } else if (sequencer != null) { 232 try { 233 sequencerloop = false; 234 sequencer.removeMetaEventListener(this); 235 sequencer.stop(); 236 } catch (Exception e3) { 237 if (Printer.err) e3.printStackTrace(); 238 } 239 try { 240 sequencer.close(); 241 } catch (Exception e4) { 242 if (Printer.err) e4.printStackTrace(); 243 } 244 if (DEBUG || Printer.debug)Printer.debug("Sequencer should be stopped"); 245 } 246 } 247 248 // Event handlers (for debugging) 249 250 @Override 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 @Override 258 public synchronized void meta(MetaMessage message) { 259 260 if (DEBUG || Printer.debug)Printer.debug("META EVENT RECEIVED!!!!! "); 261 262 if( message.getType() == 47 ) { 263 if (sequencerloop){ 264 //notifyAll(); 265 sequencer.setMicrosecondPosition(0); 266 loop(); 267 } else { 268 stop(); 269 } 270 } 271 } 272 273 @Override 274 public String toString() { 275 return getClass().toString(); 276 } 277 278 @Override 279 protected void finalize() { 280 281 if (clip != null) { 282 if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip.finalize: clip.close()"); 283 clip.close(); 284 } 285 286 //$$fb 2001-09-26: may improve situation related to bug #4302884 287 if (datapusher != null) { 288 datapusher.close(); 289 } 290 291 if (sequencer != null) { 292 sequencer.close(); 293 } 294 } 295 296 // FILE LOADING METHODS 297 298 private boolean loadAudioData(AudioInputStream as) throws IOException, UnsupportedAudioFileException { 299 if (DEBUG || Printer.debug)Printer.debug("JavaSoundAudioClip->openAsClip()"); 300 301 // first possibly convert this stream to PCM 302 as = Toolkit.getPCMConvertedAudioInputStream(as); 303 if (as == null) { 304 return false; 305 } 306 307 loadedAudioFormat = as.getFormat(); 308 long frameLen = as.getFrameLength(); 309 int frameSize = loadedAudioFormat.getFrameSize(); 310 long byteLen = AudioSystem.NOT_SPECIFIED; 311 if (frameLen != AudioSystem.NOT_SPECIFIED 312 && frameLen > 0 313 && frameSize != AudioSystem.NOT_SPECIFIED 314 && frameSize > 0) { 315 byteLen = frameLen * frameSize; 316 } 317 if (byteLen != AudioSystem.NOT_SPECIFIED) { 318 // if the stream length is known, it can be efficiently loaded into memory 319 readStream(as, byteLen); 320 } else { 321 // otherwise we use a ByteArrayOutputStream to load it into memory 322 readStream(as); 323 } 324 325 // if everything went fine, we have now the audio data in 326 // loadedAudio, and the byte length in loadedAudioByteLength 327 return true; 328 } 329 330 private void readStream(AudioInputStream as, long byteLen) throws IOException { 331 // arrays "only" max. 2GB 332 int intLen; 333 if (byteLen > 2147483647) { 334 intLen = 2147483647; 335 } else { 336 intLen = (int) byteLen; 337 } 338 loadedAudio = new byte[intLen]; 339 loadedAudioByteLength = 0; 340 341 // this loop may throw an IOException 342 while (true) { 343 int bytesRead = as.read(loadedAudio, loadedAudioByteLength, intLen - loadedAudioByteLength); 344 if (bytesRead <= 0) { 345 as.close(); 346 break; 347 } 348 loadedAudioByteLength += bytesRead; 349 } 350 } 351 352 private void readStream(AudioInputStream as) throws IOException { 353 354 DirectBAOS baos = new DirectBAOS(); 355 byte buffer[] = new byte[16384]; 356 int bytesRead = 0; 357 int totalBytesRead = 0; 358 359 // this loop may throw an IOException 360 while( true ) { 361 bytesRead = as.read(buffer, 0, buffer.length); 362 if (bytesRead <= 0) { 363 as.close(); 364 break; 365 } 366 totalBytesRead += bytesRead; 367 baos.write(buffer, 0, bytesRead); 368 } 369 loadedAudio = baos.getInternalBuffer(); 370 loadedAudioByteLength = totalBytesRead; 371 } 372 373 // METHODS FOR CREATING THE DEVICE 374 375 private boolean createClip() { 376 377 if (DEBUG || Printer.debug)Printer.debug("JavaSoundAudioClip.createClip()"); 378 379 try { 380 DataLine.Info info = new DataLine.Info(Clip.class, loadedAudioFormat); 381 if (!(AudioSystem.isLineSupported(info)) ) { 382 if (DEBUG || Printer.err)Printer.err("Clip not supported: "+loadedAudioFormat); 383 // fail silently 384 return false; 385 } 386 Object line = AudioSystem.getLine(info); 387 if (!(line instanceof AutoClosingClip)) { 388 if (DEBUG || Printer.err)Printer.err("Clip is not auto closing!"+clip); 389 // fail -> will try with SourceDataLine 390 return false; 391 } 392 clip = (AutoClosingClip) line; 393 clip.setAutoClosing(true); 394 if (DEBUG || Printer.debug) clip.addLineListener(this); 395 } catch (Exception e) { 396 if (DEBUG || Printer.err)e.printStackTrace(); 397 // fail silently 398 return false; 399 } 400 401 if (clip==null) { 402 // fail silently 403 return false; 404 } 405 406 if (DEBUG || Printer.debug)Printer.debug("Loaded clip."); 407 return true; 408 } 409 410 private boolean createSourceDataLine() { 411 if (DEBUG || Printer.debug)Printer.debug("JavaSoundAudioClip.createSourceDataLine()"); 412 try { 413 DataLine.Info info = new DataLine.Info(SourceDataLine.class, loadedAudioFormat); 414 if (!(AudioSystem.isLineSupported(info)) ) { 415 if (DEBUG || Printer.err)Printer.err("Line not supported: "+loadedAudioFormat); 416 // fail silently 417 return false; 418 } 419 SourceDataLine source = (SourceDataLine) AudioSystem.getLine(info); 420 datapusher = new DataPusher(source, loadedAudioFormat, loadedAudio, loadedAudioByteLength); 421 } catch (Exception e) { 422 if (DEBUG || Printer.err)e.printStackTrace(); 423 // fail silently 424 return false; 425 } 426 427 if (datapusher==null) { 428 // fail silently 429 return false; 430 } 431 432 if (DEBUG || Printer.debug)Printer.debug("Created SourceDataLine."); 433 return true; 434 } 435 436 private boolean createSequencer(BufferedInputStream in) throws IOException { 437 438 if (DEBUG || Printer.debug)Printer.debug("JavaSoundAudioClip.createSequencer()"); 439 440 // get the sequencer 441 try { 442 sequencer = MidiSystem.getSequencer( ); 443 } catch(MidiUnavailableException me) { 444 if (DEBUG || Printer.err)me.printStackTrace(); 445 return false; 446 } 447 if (sequencer==null) { 448 return false; 449 } 450 451 try { 452 sequence = MidiSystem.getSequence(in); 453 if (sequence == null) { 454 return false; 455 } 456 } catch (InvalidMidiDataException e) { 457 if (DEBUG || Printer.err)e.printStackTrace(); 458 return false; 459 } 460 461 if (DEBUG || Printer.debug)Printer.debug("Created Sequencer."); 462 return true; 463 } 464 465 /* 466 * private inner class representing a ByteArrayOutputStream 467 * which allows retrieval of the internal array 468 */ 469 private static class DirectBAOS extends ByteArrayOutputStream { 470 DirectBAOS() { 471 super(); 472 } 473 474 public byte[] getInternalBuffer() { 475 return buf; 476 } 477 478 } // class DirectBAOS 479 }