View Javadoc
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 }