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

Save This Page
Home » openjdk-7 » com.sun.media » sound » [javadoc | source]