Save This Page
Home » openjdk-7 » com.sun.media » sound » [javadoc | source]
    1   /*
    2    * Copyright (c) 2002, 2011, 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.ByteArrayOutputStream;
   29   import java.io.IOException;
   30   import java.util.Vector;
   31   
   32   import javax.sound.sampled;
   33   
   34   // IDEA:
   35   // Use java.util.concurrent.Semaphore,
   36   // java.util.concurrent.locks.ReentrantLock and other new classes/methods
   37   // to improve this class's thread safety.
   38   
   39   
   40   /**
   41    * A Mixer which provides direct access to audio devices
   42    *
   43    * @author Florian Bomers
   44    */
   45   class DirectAudioDevice extends AbstractMixer {
   46   
   47       // CONSTANTS
   48       private static final int CLIP_BUFFER_TIME = 1000; // in milliseconds
   49   
   50       private static final int DEFAULT_LINE_BUFFER_TIME = 500; // in milliseconds
   51   
   52       // INSTANCE VARIABLES
   53   
   54       /** number of opened lines */
   55       private int deviceCountOpened = 0;
   56   
   57       /** number of started lines */
   58       private int deviceCountStarted = 0;
   59   
   60       // CONSTRUCTOR
   61       DirectAudioDevice(DirectAudioDeviceProvider.DirectAudioDeviceInfo portMixerInfo) {
   62           // pass in Line.Info, mixer, controls
   63           super(portMixerInfo,              // Mixer.Info
   64                 null,                       // Control[]
   65                 null,                       // Line.Info[] sourceLineInfo
   66                 null);                      // Line.Info[] targetLineInfo
   67   
   68           if (Printer.trace) Printer.trace(">> DirectAudioDevice: constructor");
   69   
   70           // source lines
   71           DirectDLI srcLineInfo = createDataLineInfo(true);
   72           if (srcLineInfo != null) {
   73               sourceLineInfo = new Line.Info[2];
   74               // SourcedataLine
   75               sourceLineInfo[0] = srcLineInfo;
   76               // Clip
   77               sourceLineInfo[1] = new DirectDLI(Clip.class, srcLineInfo.getFormats(),
   78                                                 srcLineInfo.getHardwareFormats(),
   79                                                 32, // arbitrary minimum buffer size
   80                                                 AudioSystem.NOT_SPECIFIED);
   81           } else {
   82               sourceLineInfo = new Line.Info[0];
   83           }
   84   
   85           // TargetDataLine
   86           DataLine.Info dstLineInfo = createDataLineInfo(false);
   87           if (dstLineInfo != null) {
   88               targetLineInfo = new Line.Info[1];
   89               targetLineInfo[0] = dstLineInfo;
   90           } else {
   91               targetLineInfo = new Line.Info[0];
   92           }
   93           if (Printer.trace) Printer.trace("<< DirectAudioDevice: constructor completed");
   94       }
   95   
   96       private DirectDLI createDataLineInfo(boolean isSource) {
   97           Vector formats = new Vector();
   98           AudioFormat[] hardwareFormatArray = null;
   99           AudioFormat[] formatArray = null;
  100   
  101           synchronized(formats) {
  102               nGetFormats(getMixerIndex(), getDeviceID(),
  103                           isSource /* true:SourceDataLine/Clip, false:TargetDataLine */,
  104                           formats);
  105               if (formats.size() > 0) {
  106                   int size = formats.size();
  107                   int formatArraySize = size;
  108                   hardwareFormatArray = new AudioFormat[size];
  109                   for (int i = 0; i < size; i++) {
  110                       AudioFormat format = (AudioFormat)formats.elementAt(i);
  111                       hardwareFormatArray[i] = format;
  112                       int bits = format.getSampleSizeInBits();
  113                       boolean isSigned = format.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED);
  114                       boolean isUnsigned = format.getEncoding().equals(AudioFormat.Encoding.PCM_UNSIGNED);
  115                       if ((isSigned || isUnsigned)) {
  116                           // will insert a magically converted format here
  117                           formatArraySize++;
  118                       }
  119                   }
  120                   formatArray = new AudioFormat[formatArraySize];
  121                   int formatArrayIndex = 0;
  122                   for (int i = 0; i < size; i++) {
  123                       AudioFormat format = hardwareFormatArray[i];
  124                       formatArray[formatArrayIndex++] = format;
  125                       int bits = format.getSampleSizeInBits();
  126                       boolean isSigned = format.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED);
  127                       boolean isUnsigned = format.getEncoding().equals(AudioFormat.Encoding.PCM_UNSIGNED);
  128                       // add convenience formats (automatic conversion)
  129                       if (bits == 8) {
  130                           // add the other signed'ness for 8-bit
  131                           if (isSigned) {
  132                               formatArray[formatArrayIndex++] =
  133                                   new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED,
  134                                       format.getSampleRate(), bits, format.getChannels(),
  135                                       format.getFrameSize(), format.getSampleRate(),
  136                                       format.isBigEndian());
  137                           }
  138                           else if (isUnsigned) {
  139                               formatArray[formatArrayIndex++] =
  140                                   new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
  141                                       format.getSampleRate(), bits, format.getChannels(),
  142                                       format.getFrameSize(), format.getSampleRate(),
  143                                       format.isBigEndian());
  144                           }
  145                       } else if (bits > 8 && (isSigned || isUnsigned)) {
  146                           // add the other endian'ness for more than 8-bit
  147                           formatArray[formatArrayIndex++] =
  148                               new AudioFormat(format.getEncoding(),
  149                                                 format.getSampleRate(), bits,
  150                                                 format.getChannels(),
  151                                                 format.getFrameSize(),
  152                                                 format.getSampleRate(),
  153                                                 !format.isBigEndian());
  154                       }
  155                       //System.out.println("Adding "+v.get(v.size()-1));
  156                   }
  157               }
  158           }
  159           // todo: find out more about the buffer size ?
  160           if (formatArray != null) {
  161               return new DirectDLI(isSource?SourceDataLine.class:TargetDataLine.class,
  162                                    formatArray, hardwareFormatArray,
  163                                    32, // arbitrary minimum buffer size
  164                                    AudioSystem.NOT_SPECIFIED);
  165           }
  166           return null;
  167       }
  168   
  169       // ABSTRACT MIXER: ABSTRACT METHOD IMPLEMENTATIONS
  170   
  171       public Line getLine(Line.Info info) throws LineUnavailableException {
  172           Line.Info fullInfo = getLineInfo(info);
  173           if (fullInfo == null) {
  174               throw new IllegalArgumentException("Line unsupported: " + info);
  175           }
  176           if (fullInfo instanceof DataLine.Info) {
  177   
  178               DataLine.Info dataLineInfo = (DataLine.Info)fullInfo;
  179               AudioFormat lineFormat;
  180               int lineBufferSize = AudioSystem.NOT_SPECIFIED;
  181   
  182               // if a format is specified by the info class passed in, use it.
  183               // otherwise use a format from fullInfo.
  184   
  185               AudioFormat[] supportedFormats = null;
  186   
  187               if (info instanceof DataLine.Info) {
  188                   supportedFormats = ((DataLine.Info)info).getFormats();
  189                   lineBufferSize = ((DataLine.Info)info).getMaxBufferSize();
  190               }
  191   
  192               if ((supportedFormats == null) || (supportedFormats.length == 0)) {
  193                   // use the default format
  194                   lineFormat = null;
  195               } else {
  196                   // use the last format specified in the line.info object passed
  197                   // in by the app
  198                   lineFormat = supportedFormats[supportedFormats.length-1];
  199   
  200                   // if something is not specified, use default format
  201                   if (!Toolkit.isFullySpecifiedPCMFormat(lineFormat)) {
  202                       lineFormat = null;
  203                   }
  204               }
  205   
  206               if (dataLineInfo.getLineClass().isAssignableFrom(DirectSDL.class)) {
  207                   return new DirectSDL(dataLineInfo, lineFormat, lineBufferSize, this);
  208               }
  209               if (dataLineInfo.getLineClass().isAssignableFrom(DirectClip.class)) {
  210                   return new DirectClip(dataLineInfo, lineFormat, lineBufferSize, this);
  211               }
  212               if (dataLineInfo.getLineClass().isAssignableFrom(DirectTDL.class)) {
  213                   return new DirectTDL(dataLineInfo, lineFormat, lineBufferSize, this);
  214               }
  215           }
  216           throw new IllegalArgumentException("Line unsupported: " + info);
  217       }
  218   
  219   
  220       public int getMaxLines(Line.Info info) {
  221           Line.Info fullInfo = getLineInfo(info);
  222   
  223           // if it's not supported at all, return 0.
  224           if (fullInfo == null) {
  225               return 0;
  226           }
  227   
  228           if (fullInfo instanceof DataLine.Info) {
  229               // DirectAudioDevices should mix !
  230               return getMaxSimulLines();
  231           }
  232   
  233           return 0;
  234       }
  235   
  236   
  237       protected void implOpen() throws LineUnavailableException {
  238           if (Printer.trace) Printer.trace("DirectAudioDevice: implOpen - void method");
  239       }
  240   
  241       protected void implClose() {
  242           if (Printer.trace) Printer.trace("DirectAudioDevice: implClose - void method");
  243       }
  244   
  245       protected void implStart() {
  246           if (Printer.trace) Printer.trace("DirectAudioDevice: implStart - void method");
  247       }
  248   
  249       protected void implStop() {
  250           if (Printer.trace) Printer.trace("DirectAudioDevice: implStop - void method");
  251       }
  252   
  253   
  254       // IMPLEMENTATION HELPERS
  255   
  256       int getMixerIndex() {
  257           return ((DirectAudioDeviceProvider.DirectAudioDeviceInfo) getMixerInfo()).getIndex();
  258       }
  259   
  260       int getDeviceID() {
  261           return ((DirectAudioDeviceProvider.DirectAudioDeviceInfo) getMixerInfo()).getDeviceID();
  262       }
  263   
  264       int getMaxSimulLines() {
  265           return ((DirectAudioDeviceProvider.DirectAudioDeviceInfo) getMixerInfo()).getMaxSimulLines();
  266       }
  267   
  268       private static void addFormat(Vector v, int bits, int frameSizeInBytes, int channels, float sampleRate,
  269                                     int encoding, boolean signed, boolean bigEndian) {
  270           AudioFormat.Encoding enc = null;
  271           switch (encoding) {
  272           case PCM:
  273               enc = signed?AudioFormat.Encoding.PCM_SIGNED:AudioFormat.Encoding.PCM_UNSIGNED;
  274               break;
  275           case ULAW:
  276               enc = AudioFormat.Encoding.ULAW;
  277               if (bits != 8) {
  278                   if (Printer.err) Printer.err("DirectAudioDevice.addFormat called with ULAW, but bitsPerSample="+bits);
  279                   bits = 8; frameSizeInBytes = channels;
  280               }
  281               break;
  282           case ALAW:
  283               enc = AudioFormat.Encoding.ALAW;
  284               if (bits != 8) {
  285                   if (Printer.err) Printer.err("DirectAudioDevice.addFormat called with ALAW, but bitsPerSample="+bits);
  286                   bits = 8; frameSizeInBytes = channels;
  287               }
  288               break;
  289           }
  290           if (enc==null) {
  291               if (Printer.err) Printer.err("DirectAudioDevice.addFormat called with unknown encoding: "+encoding);
  292               return;
  293           }
  294           if (frameSizeInBytes <= 0) {
  295               if (channels > 0) {
  296                   frameSizeInBytes = ((bits + 7) / 8) * channels;
  297               } else {
  298                   frameSizeInBytes = AudioSystem.NOT_SPECIFIED;
  299               }
  300           }
  301           v.add(new AudioFormat(enc, sampleRate, bits, channels, frameSizeInBytes, sampleRate, bigEndian));
  302       }
  303   
  304       protected static AudioFormat getSignOrEndianChangedFormat(AudioFormat format) {
  305           boolean isSigned = format.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED);
  306           boolean isUnsigned = format.getEncoding().equals(AudioFormat.Encoding.PCM_UNSIGNED);
  307           if (format.getSampleSizeInBits() > 8 && isSigned) {
  308               // if this is PCM_SIGNED and 16-bit or higher, then try with endian-ness magic
  309               return new AudioFormat(format.getEncoding(),
  310                                      format.getSampleRate(), format.getSampleSizeInBits(), format.getChannels(),
  311                                      format.getFrameSize(), format.getFrameRate(), !format.isBigEndian());
  312           }
  313           else if (format.getSampleSizeInBits() == 8 && (isSigned || isUnsigned)) {
  314               // if this is PCM and 8-bit, then try with signed-ness magic
  315               return new AudioFormat(isSigned?AudioFormat.Encoding.PCM_UNSIGNED:AudioFormat.Encoding.PCM_SIGNED,
  316                                      format.getSampleRate(), format.getSampleSizeInBits(), format.getChannels(),
  317                                      format.getFrameSize(), format.getFrameRate(), format.isBigEndian());
  318           }
  319           return null;
  320       }
  321   
  322   
  323   
  324   
  325       // INNER CLASSES
  326   
  327   
  328       /**
  329        * Private inner class for the DataLine.Info objects
  330        * adds a little magic for the isFormatSupported so
  331        * that the automagic conversion of endianness and sign
  332        * does not show up in the formats array.
  333        * I.e. the formats array contains only the formats
  334        * that are really supported by the hardware,
  335        * but isFormatSupported() also returns true
  336        * for formats with wrong endianness.
  337        */
  338       private static class DirectDLI extends DataLine.Info {
  339           AudioFormat[] hardwareFormats;
  340   
  341           private DirectDLI(Class clazz, AudioFormat[] formatArray,
  342                             AudioFormat[] hardwareFormatArray,
  343                             int minBuffer, int maxBuffer) {
  344               super(clazz, formatArray, minBuffer, maxBuffer);
  345               this.hardwareFormats = hardwareFormatArray;
  346           }
  347   
  348           public boolean isFormatSupportedInHardware(AudioFormat format) {
  349               if (format == null) return false;
  350               for (int i = 0; i < hardwareFormats.length; i++) {
  351                   if (format.matches(hardwareFormats[i])) {
  352                       return true;
  353                   }
  354               }
  355               return false;
  356           }
  357   
  358           /*public boolean isFormatSupported(AudioFormat format) {
  359            *   return isFormatSupportedInHardware(format)
  360            *      || isFormatSupportedInHardware(getSignOrEndianChangedFormat(format));
  361            *}
  362            */
  363   
  364            private AudioFormat[] getHardwareFormats() {
  365                return hardwareFormats;
  366            }
  367       }
  368   
  369       /**
  370        * Private inner class as base class for direct lines
  371        */
  372       private static class DirectDL extends AbstractDataLine implements EventDispatcher.LineMonitor {
  373           protected int mixerIndex;
  374           protected int deviceID;
  375           protected long id;
  376           protected int waitTime;
  377           protected volatile boolean flushing = false;
  378           protected boolean isSource;         // true for SourceDataLine, false for TargetDataLine
  379           protected volatile long bytePosition;
  380           protected volatile boolean doIO = false;     // true in between start() and stop() calls
  381           protected volatile boolean stoppedWritten = false; // true if a write occured in stopped state
  382           protected volatile boolean drained = false; // set to true when drain function returns, set to false in write()
  383           protected boolean monitoring = false;
  384   
  385           // if native needs to manually swap samples/convert sign, this
  386           // is set to the framesize
  387           protected int softwareConversionSize = 0;
  388           protected AudioFormat hardwareFormat;
  389   
  390           private Gain gainControl = new Gain();
  391           private Mute muteControl = new Mute();
  392           private Balance balanceControl = new Balance();
  393           private Pan panControl = new Pan();
  394           private float leftGain, rightGain;
  395           protected volatile boolean noService = false; // do not run the nService method
  396   
  397           // Guards all native calls.
  398           protected final Object lockNative = new Object();
  399   
  400           // CONSTRUCTOR
  401           protected DirectDL(DataLine.Info info,
  402                              DirectAudioDevice mixer,
  403                              AudioFormat format,
  404                              int bufferSize,
  405                              int mixerIndex,
  406                              int deviceID,
  407                              boolean isSource) {
  408               super(info, mixer, null, format, bufferSize);
  409               if (Printer.trace) Printer.trace("DirectDL CONSTRUCTOR: info: " + info);
  410               this.mixerIndex = mixerIndex;
  411               this.deviceID = deviceID;
  412               this.waitTime = 10; // 10 milliseconds default wait time
  413               this.isSource = isSource;
  414   
  415           }
  416   
  417   
  418           // ABSTRACT METHOD IMPLEMENTATIONS
  419   
  420           // ABSTRACT LINE / DATALINE
  421   
  422           void implOpen(AudioFormat format, int bufferSize) throws LineUnavailableException {
  423               if (Printer.trace) Printer.trace(">> DirectDL: implOpen("+format+", "+bufferSize+" bytes)");
  424   
  425               // $$fb part of fix for 4679187: Clip.open() throws unexpected Exceptions
  426               Toolkit.isFullySpecifiedAudioFormat(format);
  427   
  428               // check for record permission
  429               if (!isSource) {
  430                   JSSecurityManager.checkRecordPermission();
  431               }
  432               int encoding = PCM;
  433               if (format.getEncoding().equals(AudioFormat.Encoding.ULAW)) {
  434                   encoding = ULAW;
  435               }
  436               else if (format.getEncoding().equals(AudioFormat.Encoding.ALAW)) {
  437                   encoding = ALAW;
  438               }
  439   
  440               if (bufferSize <= AudioSystem.NOT_SPECIFIED) {
  441                   bufferSize = (int) Toolkit.millis2bytes(format, DEFAULT_LINE_BUFFER_TIME);
  442               }
  443   
  444               DirectDLI ddli = null;
  445               if (info instanceof DirectDLI) {
  446                   ddli = (DirectDLI) info;
  447               }
  448   
  449               /* set up controls */
  450               if (isSource) {
  451                   if (!format.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED)
  452                       && !format.getEncoding().equals(AudioFormat.Encoding.PCM_UNSIGNED)) {
  453                       // no controls for non-PCM formats */
  454                       controls = new Control[0];
  455                   }
  456                   else if (format.getChannels() > 2
  457                            || format.getSampleSizeInBits() > 16) {
  458                       // no support for more than 2 channels or more than 16 bits
  459                       controls = new Control[0];
  460                   } else {
  461                       if (format.getChannels() == 1) {
  462                           controls = new Control[2];
  463                       } else {
  464                           controls = new Control[4];
  465                           controls[2] = balanceControl;
  466                           /* to keep compatibility with apps that rely on
  467                            * MixerSourceLine's PanControl
  468                            */
  469                           controls[3] = panControl;
  470                       }
  471                       controls[0] = gainControl;
  472                       controls[1] = muteControl;
  473                   }
  474               }
  475               if (Printer.debug) Printer.debug("DirectAudioDevice: got "+controls.length+" controls.");
  476   
  477               hardwareFormat = format;
  478   
  479               /* some magic to account for not-supported endianness or signed-ness */
  480               softwareConversionSize = 0;
  481               if (ddli != null && !ddli.isFormatSupportedInHardware(format)) {
  482                   AudioFormat newFormat = getSignOrEndianChangedFormat(format);
  483                   if (ddli.isFormatSupportedInHardware(newFormat)) {
  484                       // apparently, the new format can be used.
  485                       hardwareFormat = newFormat;
  486                       // So do endian/sign conversion in software
  487                       softwareConversionSize = format.getFrameSize() / format.getChannels();
  488                       if (Printer.debug) {
  489                           Printer.debug("DirectAudioDevice: softwareConversionSize "
  490                                         +softwareConversionSize+":");
  491                           Printer.debug("  from "+format);
  492                           Printer.debug("  to   "+newFormat);
  493                       }
  494                   }
  495               }
  496   
  497               // align buffer to full frames
  498               bufferSize = ((int) bufferSize / format.getFrameSize()) * format.getFrameSize();
  499   
  500               id = nOpen(mixerIndex, deviceID, isSource,
  501                       encoding,
  502                       hardwareFormat.getSampleRate(),
  503                       hardwareFormat.getSampleSizeInBits(),
  504                       hardwareFormat.getFrameSize(),
  505                       hardwareFormat.getChannels(),
  506                       hardwareFormat.getEncoding().equals(
  507                           AudioFormat.Encoding.PCM_SIGNED),
  508                       hardwareFormat.isBigEndian(),
  509                       bufferSize);
  510   
  511               if (id == 0) {
  512                   // TODO: nicer error messages...
  513                   throw new LineUnavailableException(
  514                           "line with format "+format+" not supported.");
  515               }
  516   
  517               this.bufferSize = nGetBufferSize(id, isSource);
  518               if (this.bufferSize < 1) {
  519                   // this is an error!
  520                   this.bufferSize = bufferSize;
  521               }
  522               this.format = format;
  523               // wait time = 1/4 of buffer time
  524               waitTime = (int) Toolkit.bytes2millis(format, this.bufferSize) / 4;
  525               if (waitTime < 10) {
  526                   waitTime = 1;
  527               }
  528               else if (waitTime > 1000) {
  529                   // we have seen large buffer sizes!
  530                   // never wait for more than a second
  531                   waitTime = 1000;
  532               }
  533               bytePosition = 0;
  534               stoppedWritten = false;
  535               doIO = false;
  536               calcVolume();
  537   
  538               if (Printer.trace) Printer.trace("<< DirectDL: implOpen() succeeded");
  539           }
  540   
  541   
  542           void implStart() {
  543               if (Printer.trace) Printer.trace(" >> DirectDL: implStart()");
  544   
  545               // check for record permission
  546               if (!isSource) {
  547                   JSSecurityManager.checkRecordPermission();
  548               }
  549   
  550               synchronized (lockNative)
  551               {
  552                   nStart(id, isSource);
  553               }
  554               // check for monitoring/servicing
  555               monitoring = requiresServicing();
  556               if (monitoring) {
  557                   getEventDispatcher().addLineMonitor(this);
  558               }
  559   
  560               doIO = true;
  561   
  562               // need to set Active and Started
  563               // note: the current API always requires that
  564               //       Started and Active are set at the same time...
  565               if (isSource && stoppedWritten) {
  566                   setStarted(true);
  567                   setActive(true);
  568               }
  569   
  570               if (Printer.trace) Printer.trace("<< DirectDL: implStart() succeeded");
  571           }
  572   
  573           void implStop() {
  574               if (Printer.trace) Printer.trace(">> DirectDL: implStop()");
  575   
  576               // check for record permission
  577               if (!isSource) {
  578                   JSSecurityManager.checkRecordPermission();
  579               }
  580   
  581               if (monitoring) {
  582                   getEventDispatcher().removeLineMonitor(this);
  583                   monitoring = false;
  584               }
  585               synchronized (lockNative) {
  586                   nStop(id, isSource);
  587               }
  588               // wake up any waiting threads
  589               synchronized(lock) {
  590                   // need to set doIO to false before notifying the
  591                   // read/write thread, that's why isStartedRunning()
  592                   // cannot be used
  593                   doIO = false;
  594                   lock.notifyAll();
  595               }
  596               setActive(false);
  597               setStarted(false);
  598               stoppedWritten = false;
  599   
  600               if (Printer.trace) Printer.trace(" << DirectDL: implStop() succeeded");
  601           }
  602   
  603           void implClose() {
  604               if (Printer.trace) Printer.trace(">> DirectDL: implClose()");
  605   
  606               // check for record permission
  607               if (!isSource) {
  608                   JSSecurityManager.checkRecordPermission();
  609               }
  610   
  611               // be sure to remove this monitor
  612               if (monitoring) {
  613                   getEventDispatcher().removeLineMonitor(this);
  614                   monitoring = false;
  615               }
  616   
  617               doIO = false;
  618               long oldID = id;
  619               id = 0;
  620               synchronized (lockNative) {
  621                   nClose(oldID, isSource);
  622               }
  623               bytePosition = 0;
  624               softwareConversionSize = 0;
  625               if (Printer.trace) Printer.trace("<< DirectDL: implClose() succeeded");
  626           }
  627   
  628           // METHOD OVERRIDES
  629   
  630           public int available() {
  631               if (id == 0) {
  632                   return 0;
  633               }
  634               int a;
  635               synchronized (lockNative) {
  636                   a = nAvailable(id, isSource);
  637               }
  638               return a;
  639           }
  640   
  641   
  642           public void drain() {
  643               noService = true;
  644               // additional safeguard against draining forever
  645               // this occured on Solaris 8 x86, probably due to a bug
  646               // in the audio driver
  647               int counter = 0;
  648               long startPos = getLongFramePosition();
  649               boolean posChanged = false;
  650               while (!drained) {
  651                   synchronized (lockNative) {
  652                       if ((id == 0) || (!doIO) || !nIsStillDraining(id, isSource))
  653                           break;
  654                   }
  655                   // check every now and then for a new position
  656                   if ((counter % 5) == 4) {
  657                       long thisFramePos = getLongFramePosition();
  658                       posChanged = posChanged | (thisFramePos != startPos);
  659                       if ((counter % 50) > 45) {
  660                           // when some time elapsed, check that the frame position
  661                           // really changed
  662                           if (!posChanged) {
  663                               if (Printer.err) Printer.err("Native reports isDraining, but frame position does not increase!");
  664                               break;
  665                           }
  666                           posChanged = false;
  667                           startPos = thisFramePos;
  668                       }
  669                   }
  670                   counter++;
  671                   synchronized(lock) {
  672                       try {
  673                           lock.wait(10);
  674                       } catch (InterruptedException ie) {}
  675                   }
  676               }
  677   
  678               if (doIO && id != 0) {
  679                   drained = true;
  680               }
  681               noService = false;
  682           }
  683   
  684           public void flush() {
  685               if (id != 0) {
  686                   // first stop ongoing read/write method
  687                   flushing = true;
  688                   synchronized(lock) {
  689                       lock.notifyAll();
  690                   }
  691                   synchronized (lockNative) {
  692                       if (id != 0) {
  693                           // then flush native buffers
  694                           nFlush(id, isSource);
  695                       }
  696                   }
  697                   drained = true;
  698               }
  699           }
  700   
  701           // replacement for getFramePosition (see AbstractDataLine)
  702           public long getLongFramePosition() {
  703               long pos;
  704               synchronized (lockNative) {
  705                   pos = nGetBytePosition(id, isSource, bytePosition);
  706               }
  707               // hack because ALSA sometimes reports wrong framepos
  708               if (pos < 0) {
  709                   if (Printer.debug) Printer.debug("DirectLine.getLongFramePosition: Native reported pos="
  710                                                    +pos+"! is changed to 0. byteposition="+bytePosition);
  711                   pos = 0;
  712               }
  713               return (pos / getFormat().getFrameSize());
  714           }
  715   
  716   
  717           /*
  718            * write() belongs into SourceDataLine and Clip,
  719            * so define it here and make it accessible by
  720            * declaring the respective interfaces with DirectSDL and DirectClip
  721            */
  722           public int write(byte[] b, int off, int len) {
  723               flushing = false;
  724               if (len == 0) {
  725                   return 0;
  726               }
  727               if (len < 0) {
  728                   throw new IllegalArgumentException("illegal len: "+len);
  729               }
  730               if (len % getFormat().getFrameSize() != 0) {
  731                   throw new IllegalArgumentException("illegal request to write "
  732                                                      +"non-integral number of frames ("
  733                                                      +len+" bytes, "
  734                                                      +"frameSize = "+getFormat().getFrameSize()+" bytes)");
  735               }
  736               if (off < 0) {
  737                   throw new ArrayIndexOutOfBoundsException(off);
  738               }
  739               if (off + len > b.length) {
  740                   throw new ArrayIndexOutOfBoundsException(b.length);
  741               }
  742   
  743               if (!isActive() && doIO) {
  744                   // this is not exactly correct... would be nicer
  745                   // if the native sub system sent a callback when IO really starts
  746                   setActive(true);
  747                   setStarted(true);
  748               }
  749               int written = 0;
  750               while (!flushing) {
  751                   int thisWritten;
  752                   synchronized (lockNative) {
  753                       thisWritten = nWrite(id, b, off, len,
  754                               softwareConversionSize,
  755                               leftGain, rightGain);
  756                       if (thisWritten < 0) {
  757                           // error in native layer
  758                           break;
  759                       }
  760                       bytePosition += thisWritten;
  761                       if (thisWritten > 0) {
  762                           drained = false;
  763                       }
  764                   }
  765                   len -= thisWritten;
  766                   written += thisWritten;
  767                   if (doIO && len > 0) {
  768                       off += thisWritten;
  769                       synchronized (lock) {
  770                           try {
  771                               lock.wait(waitTime);
  772                           } catch (InterruptedException ie) {}
  773                       }
  774                   } else {
  775                       break;
  776                   }
  777               }
  778               if (written > 0 && !doIO) {
  779                   stoppedWritten = true;
  780               }
  781               return written;
  782           }
  783   
  784           protected boolean requiresServicing() {
  785               return nRequiresServicing(id, isSource);
  786           }
  787   
  788           // called from event dispatcher for lines that need servicing
  789           public void checkLine() {
  790               synchronized (lockNative) {
  791                   if (monitoring
  792                           && doIO
  793                           && id != 0
  794                           && !flushing
  795                           && !noService) {
  796                       nService(id, isSource);
  797                   }
  798               }
  799           }
  800   
  801           private void calcVolume() {
  802               if (getFormat() == null) {
  803                   return;
  804               }
  805               if (muteControl.getValue()) {
  806                   leftGain = 0.0f;
  807                   rightGain = 0.0f;
  808                   return;
  809               }
  810               float gain = gainControl.getLinearGain();
  811               if (getFormat().getChannels() == 1) {
  812                   // trivial case: only use gain
  813                   leftGain = gain;
  814                   rightGain = gain;
  815               } else {
  816                   // need to combine gain and balance
  817                   float bal = balanceControl.getValue();
  818                   if (bal < 0.0f) {
  819                       // left
  820                       leftGain = gain;
  821                       rightGain = gain * (bal + 1.0f);
  822                   } else {
  823                       leftGain = gain * (1.0f - bal);
  824                       rightGain = gain;
  825                   }
  826               }
  827           }
  828   
  829   
  830           /////////////////// CONTROLS /////////////////////////////
  831   
  832           protected class Gain extends FloatControl {
  833   
  834               private float linearGain = 1.0f;
  835   
  836               private Gain() {
  837   
  838                   super(FloatControl.Type.MASTER_GAIN,
  839                         Toolkit.linearToDB(0.0f),
  840                         Toolkit.linearToDB(2.0f),
  841                         Math.abs(Toolkit.linearToDB(1.0f)-Toolkit.linearToDB(0.0f))/128.0f,
  842                         -1,
  843                         0.0f,
  844                         "dB", "Minimum", "", "Maximum");
  845               }
  846   
  847               public void setValue(float newValue) {
  848                   // adjust value within range ?? spec says IllegalArgumentException
  849                   //newValue = Math.min(newValue, getMaximum());
  850                   //newValue = Math.max(newValue, getMinimum());
  851   
  852                   float newLinearGain = Toolkit.dBToLinear(newValue);
  853                   super.setValue(Toolkit.linearToDB(newLinearGain));
  854                   // if no exception, commit to our new gain
  855                   linearGain = newLinearGain;
  856                   calcVolume();
  857               }
  858   
  859               float getLinearGain() {
  860                   return linearGain;
  861               }
  862           } // class Gain
  863   
  864   
  865           private class Mute extends BooleanControl {
  866   
  867               private Mute() {
  868                   super(BooleanControl.Type.MUTE, false, "True", "False");
  869               }
  870   
  871               public void setValue(boolean newValue) {
  872                   super.setValue(newValue);
  873                   calcVolume();
  874               }
  875           }  // class Mute
  876   
  877           private class Balance extends FloatControl {
  878   
  879               private Balance() {
  880                   super(FloatControl.Type.BALANCE, -1.0f, 1.0f, (1.0f / 128.0f), -1, 0.0f,
  881                         "", "Left", "Center", "Right");
  882               }
  883   
  884               public void setValue(float newValue) {
  885                   setValueImpl(newValue);
  886                   panControl.setValueImpl(newValue);
  887                   calcVolume();
  888               }
  889   
  890               void setValueImpl(float newValue) {
  891                   super.setValue(newValue);
  892               }
  893   
  894           } // class Balance
  895   
  896           private class Pan extends FloatControl {
  897   
  898               private Pan() {
  899                   super(FloatControl.Type.PAN, -1.0f, 1.0f, (1.0f / 128.0f), -1, 0.0f,
  900                         "", "Left", "Center", "Right");
  901               }
  902   
  903               public void setValue(float newValue) {
  904                   setValueImpl(newValue);
  905                   balanceControl.setValueImpl(newValue);
  906                   calcVolume();
  907               }
  908               void setValueImpl(float newValue) {
  909                   super.setValue(newValue);
  910               }
  911           } // class Pan
  912   
  913   
  914   
  915       } // class DirectDL
  916   
  917   
  918       /**
  919        * Private inner class representing a SourceDataLine
  920        */
  921       private static class DirectSDL extends DirectDL implements SourceDataLine {
  922   
  923           // CONSTRUCTOR
  924           private DirectSDL(DataLine.Info info,
  925                             AudioFormat format,
  926                             int bufferSize,
  927                             DirectAudioDevice mixer) {
  928               super(info, mixer, format, bufferSize, mixer.getMixerIndex(), mixer.getDeviceID(), true);
  929               if (Printer.trace) Printer.trace("DirectSDL CONSTRUCTOR: completed");
  930           }
  931   
  932       }
  933   
  934       /**
  935        * Private inner class representing a TargetDataLine
  936        */
  937       private static class DirectTDL extends DirectDL implements TargetDataLine {
  938   
  939           // CONSTRUCTOR
  940           private DirectTDL(DataLine.Info info,
  941                             AudioFormat format,
  942                             int bufferSize,
  943                             DirectAudioDevice mixer) {
  944               super(info, mixer, format, bufferSize, mixer.getMixerIndex(), mixer.getDeviceID(), false);
  945               if (Printer.trace) Printer.trace("DirectTDL CONSTRUCTOR: completed");
  946           }
  947   
  948           // METHOD OVERRIDES
  949   
  950           public int read(byte[] b, int off, int len) {
  951               flushing = false;
  952               if (len == 0) {
  953                   return 0;
  954               }
  955               if (len < 0) {
  956                   throw new IllegalArgumentException("illegal len: "+len);
  957               }
  958               if (len % getFormat().getFrameSize() != 0) {
  959                   throw new IllegalArgumentException("illegal request to read "
  960                                                      +"non-integral number of frames ("
  961                                                      +len+" bytes, "
  962                                                      +"frameSize = "+getFormat().getFrameSize()+" bytes)");
  963               }
  964               if (off < 0) {
  965                   throw new ArrayIndexOutOfBoundsException(off);
  966               }
  967               if (off + len > b.length) {
  968                   throw new ArrayIndexOutOfBoundsException(b.length);
  969               }
  970               if (!isActive() && doIO) {
  971                   // this is not exactly correct... would be nicer
  972                   // if the native sub system sent a callback when IO really starts
  973                   setActive(true);
  974                   setStarted(true);
  975               }
  976               int read = 0;
  977               while (doIO && !flushing) {
  978                   int thisRead;
  979                   synchronized (lockNative) {
  980                       thisRead = nRead(id, b, off, len, softwareConversionSize);
  981                       if (thisRead < 0) {
  982                           // error in native layer
  983                           break;
  984                       }
  985                       bytePosition += thisRead;
  986                       if (thisRead > 0) {
  987                           drained = false;
  988                       }
  989                   }
  990                   len -= thisRead;
  991                   read += thisRead;
  992                   if (len > 0) {
  993                       off += thisRead;
  994                       synchronized(lock) {
  995                           try {
  996                               lock.wait(waitTime);
  997                           } catch (InterruptedException ie) {}
  998                       }
  999                   } else {
 1000                       break;
 1001                   }
 1002               }
 1003               if (flushing) {
 1004                   read = 0;
 1005               }
 1006               return read;
 1007           }
 1008   
 1009       }
 1010   
 1011       /**
 1012        * Private inner class representing a Clip
 1013        * This clip is realized in software only
 1014        */
 1015       private static class DirectClip extends DirectDL implements Clip,  Runnable, AutoClosingClip {
 1016           private Thread thread;
 1017           private byte[] audioData = null;
 1018           private int frameSize;         // size of one frame in bytes
 1019           private int m_lengthInFrames;
 1020           private int loopCount;
 1021           private int clipBytePosition;   // index in the audioData array at current playback
 1022           private int newFramePosition;   // set in setFramePosition()
 1023           private int loopStartFrame;
 1024           private int loopEndFrame;      // the last sample included in the loop
 1025   
 1026           // auto closing clip support
 1027           private boolean autoclosing = false;
 1028   
 1029           // CONSTRUCTOR
 1030           private DirectClip(DataLine.Info info,
 1031                              AudioFormat format,
 1032                              int bufferSize,
 1033                              DirectAudioDevice mixer) {
 1034               super(info, mixer, format, bufferSize, mixer.getMixerIndex(), mixer.getDeviceID(), true);
 1035               if (Printer.trace) Printer.trace("DirectClip CONSTRUCTOR: completed");
 1036           }
 1037   
 1038           // CLIP METHODS
 1039   
 1040           public void open(AudioFormat format, byte[] data, int offset, int bufferSize)
 1041               throws LineUnavailableException {
 1042   
 1043               // $$fb part of fix for 4679187: Clip.open() throws unexpected Exceptions
 1044               Toolkit.isFullySpecifiedAudioFormat(format);
 1045   
 1046               byte[] newData = new byte[bufferSize];
 1047               System.arraycopy(data, offset, newData, 0, bufferSize);
 1048               open(format, data, bufferSize / format.getFrameSize());
 1049           }
 1050   
 1051           // this method does not copy the data array
 1052           private void open(AudioFormat format, byte[] data, int frameLength)
 1053               throws LineUnavailableException {
 1054   
 1055               // $$fb part of fix for 4679187: Clip.open() throws unexpected Exceptions
 1056               Toolkit.isFullySpecifiedAudioFormat(format);
 1057   
 1058               synchronized (mixer) {
 1059                   if (Printer.trace) Printer.trace("> DirectClip.open(format, data, frameLength)");
 1060                   if (Printer.debug) Printer.debug("   data="+((data==null)?"null":""+data.length+" bytes"));
 1061                   if (Printer.debug) Printer.debug("   frameLength="+frameLength);
 1062   
 1063                   if (isOpen()) {
 1064                       throw new IllegalStateException("Clip is already open with format " + getFormat() +
 1065                                                       " and frame lengh of " + getFrameLength());
 1066                   } else {
 1067                       // if the line is not currently open, try to open it with this format and buffer size
 1068                       this.audioData = data;
 1069                       this.frameSize = format.getFrameSize();
 1070                       this.m_lengthInFrames = frameLength;
 1071                       // initialize loop selection with full range
 1072                       bytePosition = 0;
 1073                       clipBytePosition = 0;
 1074                       newFramePosition = -1; // means: do not set to a new readFramePos
 1075                       loopStartFrame = 0;
 1076                       loopEndFrame = frameLength - 1;
 1077                       loopCount = 0; // means: play the clip irrespective of loop points from beginning to end
 1078   
 1079                       try {
 1080                           // use DirectDL's open method to open it
 1081                           open(format, (int) Toolkit.millis2bytes(format, CLIP_BUFFER_TIME)); // one second buffer
 1082                       } catch (LineUnavailableException lue) {
 1083                           audioData = null;
 1084                           throw lue;
 1085                       } catch (IllegalArgumentException iae) {
 1086                           audioData = null;
 1087                           throw iae;
 1088                       }
 1089   
 1090                       // if we got this far, we can instanciate the thread
 1091                       int priority = Thread.NORM_PRIORITY
 1092                           + (Thread.MAX_PRIORITY - Thread.NORM_PRIORITY) / 3;
 1093                       thread = JSSecurityManager.createThread(this,
 1094                                                               "Direct Clip", // name
 1095                                                               true,     // daemon
 1096                                                               priority, // priority
 1097                                                               false);  // doStart
 1098                       // cannot start in createThread, because the thread
 1099                       // uses the "thread" variable as indicator if it should
 1100                       // continue to run
 1101                       thread.start();
 1102                   }
 1103               }
 1104               if (isAutoClosing()) {
 1105                   getEventDispatcher().autoClosingClipOpened(this);
 1106               }
 1107               if (Printer.trace) Printer.trace("< DirectClip.open completed");
 1108           }
 1109   
 1110   
 1111           public void open(AudioInputStream stream) throws LineUnavailableException, IOException {
 1112   
 1113               // $$fb part of fix for 4679187: Clip.open() throws unexpected Exceptions
 1114               Toolkit.isFullySpecifiedAudioFormat(format);
 1115   
 1116               synchronized (mixer) {
 1117                   if (Printer.trace) Printer.trace("> DirectClip.open(stream)");
 1118                   byte[] streamData = null;
 1119   
 1120                   if (isOpen()) {
 1121                       throw new IllegalStateException("Clip is already open with format " + getFormat() +
 1122                                                       " and frame lengh of " + getFrameLength());
 1123                   }
 1124                   int lengthInFrames = (int)stream.getFrameLength();
 1125                   if (Printer.debug) Printer.debug("DirectClip: open(AIS): lengthInFrames: " + lengthInFrames);
 1126   
 1127                   int bytesRead = 0;
 1128                   if (lengthInFrames != AudioSystem.NOT_SPECIFIED) {
 1129                       // read the data from the stream into an array in one fell swoop.
 1130                       int arraysize = lengthInFrames * stream.getFormat().getFrameSize();
 1131                       streamData = new byte[arraysize];
 1132   
 1133                       int bytesRemaining = arraysize;
 1134                       int thisRead = 0;
 1135                       while (bytesRemaining > 0 && thisRead >= 0) {
 1136                           thisRead = stream.read(streamData, bytesRead, bytesRemaining);
 1137                           if (thisRead > 0) {
 1138                               bytesRead += thisRead;
 1139                               bytesRemaining -= thisRead;
 1140                           }
 1141                           else if (thisRead == 0) {
 1142                               Thread.yield();
 1143                           }
 1144                       }
 1145                   } else {
 1146                       // read data from the stream until we reach the end of the stream
 1147                       // we use a slightly modified version of ByteArrayOutputStream
 1148                       // to get direct access to the byte array (we don't want a new array
 1149                       // to be allocated)
 1150                       int MAX_READ_LIMIT = 16384;
 1151                       DirectBAOS dbaos  = new DirectBAOS();
 1152                       byte tmp[] = new byte[MAX_READ_LIMIT];
 1153                       int thisRead = 0;
 1154                       while (thisRead >= 0) {
 1155                           thisRead = stream.read(tmp, 0, tmp.length);
 1156                           if (thisRead > 0) {
 1157                               dbaos.write(tmp, 0, thisRead);
 1158                               bytesRead += thisRead;
 1159                           }
 1160                           else if (thisRead == 0) {
 1161                               Thread.yield();
 1162                           }
 1163                       } // while
 1164                       streamData = dbaos.getInternalBuffer();
 1165                   }
 1166                   lengthInFrames = bytesRead / stream.getFormat().getFrameSize();
 1167   
 1168                   if (Printer.debug) Printer.debug("Read to end of stream. lengthInFrames: " + lengthInFrames);
 1169   
 1170                   // now try to open the device
 1171                   open(stream.getFormat(), streamData, lengthInFrames);
 1172   
 1173                   if (Printer.trace) Printer.trace("< DirectClip.open(stream) succeeded");
 1174               } // synchronized
 1175           }
 1176   
 1177   
 1178           public int getFrameLength() {
 1179               return m_lengthInFrames;
 1180           }
 1181   
 1182   
 1183           public long getMicrosecondLength() {
 1184               return Toolkit.frames2micros(getFormat(), getFrameLength());
 1185           }
 1186   
 1187   
 1188           public void setFramePosition(int frames) {
 1189               if (Printer.trace) Printer.trace("> DirectClip: setFramePosition: " + frames);
 1190   
 1191               if (frames < 0) {
 1192                   frames = 0;
 1193               }
 1194               else if (frames >= getFrameLength()) {
 1195                   frames = getFrameLength();
 1196               }
 1197               if (doIO) {
 1198                   newFramePosition = frames;
 1199               } else {
 1200                   clipBytePosition = frames * frameSize;
 1201                   newFramePosition = -1;
 1202               }
 1203               // fix for failing test050
 1204               // $$fb although getFramePosition should return the number of rendered
 1205               // frames, it is intuitive that setFramePosition will modify that
 1206               // value.
 1207               bytePosition = frames * frameSize;
 1208   
 1209               // cease currently playing buffer
 1210               flush();
 1211   
 1212               // set new native position (if necessary)
 1213               // this must come after the flush!
 1214               synchronized (lockNative) {
 1215                   nSetBytePosition(id, isSource, frames * frameSize);
 1216               }
 1217   
 1218               if (Printer.debug) Printer.debug("  DirectClip.setFramePosition: "
 1219                                                +" doIO="+doIO
 1220                                                +" newFramePosition="+newFramePosition
 1221                                                +" clipBytePosition="+clipBytePosition
 1222                                                +" bytePosition="+bytePosition
 1223                                                +" getLongFramePosition()="+getLongFramePosition());
 1224               if (Printer.trace) Printer.trace("< DirectClip: setFramePosition");
 1225           }
 1226   
 1227           // replacement for getFramePosition (see AbstractDataLine)
 1228           public long getLongFramePosition() {
 1229               /* $$fb
 1230                * this would be intuitive, but the definition of getFramePosition
 1231                * is the number of frames rendered since opening the device...
 1232                * That also means that setFramePosition() means something very
 1233                * different from getFramePosition() for Clip.
 1234                */
 1235               // take into account the case that a new position was set...
 1236               //if (!doIO && newFramePosition >= 0) {
 1237               //return newFramePosition;
 1238               //}
 1239               return super.getLongFramePosition();
 1240           }
 1241   
 1242   
 1243           public synchronized void setMicrosecondPosition(long microseconds) {
 1244               if (Printer.trace) Printer.trace("> DirectClip: setMicrosecondPosition: " + microseconds);
 1245   
 1246               long frames = Toolkit.micros2frames(getFormat(), microseconds);
 1247               setFramePosition((int) frames);
 1248   
 1249               if (Printer.trace) Printer.trace("< DirectClip: setMicrosecondPosition succeeded");
 1250           }
 1251   
 1252           public void setLoopPoints(int start, int end) {
 1253               if (Printer.trace) Printer.trace("> DirectClip: setLoopPoints: start: " + start + " end: " + end);
 1254   
 1255               if (start < 0 || start >= getFrameLength()) {
 1256                   throw new IllegalArgumentException("illegal value for start: "+start);
 1257               }
 1258               if (end >= getFrameLength()) {
 1259                   throw new IllegalArgumentException("illegal value for end: "+end);
 1260               }
 1261   
 1262               if (end == -1) {
 1263                   end = getFrameLength() - 1;
 1264                   if (end < 0) {
 1265                       end = 0;
 1266                   }
 1267               }
 1268   
 1269               // if the end position is less than the start position, throw IllegalArgumentException
 1270               if (end < start) {
 1271                   throw new IllegalArgumentException("End position " + end + "  preceeds start position " + start);
 1272               }
 1273   
 1274               // slight race condition with the run() method, but not a big problem
 1275               loopStartFrame = start;
 1276               loopEndFrame = end;
 1277   
 1278               if (Printer.trace) Printer.trace("  loopStart: " + loopStartFrame + " loopEnd: " + loopEndFrame);
 1279               if (Printer.trace) Printer.trace("< DirectClip: setLoopPoints completed");
 1280           }
 1281   
 1282   
 1283           public void loop(int count) {
 1284               // note: when count reaches 0, it means that the entire clip
 1285               // will be played, i.e. it will play past the loop end point
 1286               loopCount = count;
 1287               start();
 1288           }
 1289   
 1290           // ABSTRACT METHOD IMPLEMENTATIONS
 1291   
 1292           // ABSTRACT LINE
 1293   
 1294           void implOpen(AudioFormat format, int bufferSize) throws LineUnavailableException {
 1295               // only if audioData wasn't set in a calling open(format, byte[], frameSize)
 1296               // this call is allowed.
 1297               if (audioData == null) {
 1298                   throw new IllegalArgumentException("illegal call to open() in interface Clip");
 1299               }
 1300               super.implOpen(format, bufferSize);
 1301           }
 1302   
 1303           void implClose() {
 1304               if (Printer.trace) Printer.trace(">> DirectClip: implClose()");
 1305   
 1306               // dispose of thread
 1307               Thread oldThread = thread;
 1308               thread = null;
 1309               doIO = false;
 1310               if (oldThread != null) {
 1311                   // wake up the thread if it's in wait()
 1312                   synchronized(lock) {
 1313                       lock.notifyAll();
 1314                   }
 1315                   // wait for the thread to terminate itself,
 1316                   // but max. 2 seconds. Must not be synchronized!
 1317                   try {
 1318                       oldThread.join(2000);
 1319                   } catch (InterruptedException ie) {}
 1320               }
 1321               super.implClose();
 1322               // remove audioData reference and hand it over to gc
 1323               audioData = null;
 1324               newFramePosition = -1;
 1325   
 1326               // remove this instance from the list of auto closing clips
 1327               getEventDispatcher().autoClosingClipClosed(this);
 1328   
 1329               if (Printer.trace) Printer.trace("<< DirectClip: implClose() succeeded");
 1330           }
 1331   
 1332   
 1333           void implStart() {
 1334               if (Printer.trace) Printer.trace("> DirectClip: implStart()");
 1335               super.implStart();
 1336               if (Printer.trace) Printer.trace("< DirectClip: implStart() succeeded");
 1337           }
 1338   
 1339           void implStop() {
 1340               if (Printer.trace) Printer.trace(">> DirectClip: implStop()");
 1341   
 1342               super.implStop();
 1343               // reset loopCount field so that playback will be normal with
 1344               // next call to start()
 1345               loopCount = 0;
 1346   
 1347               if (Printer.trace) Printer.trace("<< DirectClip: implStop() succeeded");
 1348           }
 1349   
 1350   
 1351           // main playback loop
 1352           public void run() {
 1353               if (Printer.trace) Printer.trace(">>> DirectClip: run() threadID="+Thread.currentThread().getId());
 1354               while (thread != null) {
 1355                   // doIO is volatile, but we could check it, then get
 1356                   // pre-empted while another thread changes doIO and notifies,
 1357                   // before we wait (so we sleep in wait forever).
 1358                   synchronized(lock) {
 1359                       if (!doIO) {
 1360                           try {
 1361                               lock.wait();
 1362                           } catch(InterruptedException ie) {}
 1363                       }
 1364                   }
 1365                   while (doIO) {
 1366                       if (newFramePosition >= 0) {
 1367                           clipBytePosition = newFramePosition * frameSize;
 1368                           newFramePosition = -1;
 1369                       }
 1370                       int endFrame = getFrameLength() - 1;
 1371                       if (loopCount > 0 || loopCount == LOOP_CONTINUOUSLY) {
 1372                           endFrame = loopEndFrame;
 1373                       }
 1374                       long framePos = (clipBytePosition / frameSize);
 1375                       int toWriteFrames = (int) (endFrame - framePos + 1);
 1376                       int toWriteBytes = toWriteFrames * frameSize;
 1377                       if (toWriteBytes > getBufferSize()) {
 1378                           toWriteBytes = Toolkit.align(getBufferSize(), frameSize);
 1379                       }
 1380                       int written = write(audioData, (int) clipBytePosition, toWriteBytes); // increases bytePosition
 1381                       clipBytePosition += written;
 1382                       // make sure nobody called setFramePosition, or stop() during the write() call
 1383                       if (doIO && newFramePosition < 0 && written >= 0) {
 1384                           framePos = clipBytePosition / frameSize;
 1385                           // since endFrame is the last frame to be played,
 1386                           // framePos is after endFrame when all frames, including framePos,
 1387                           // are played.
 1388                           if (framePos > endFrame) {
 1389                               // at end of playback. If looping is on, loop back to the beginning.
 1390                               if (loopCount > 0 || loopCount == LOOP_CONTINUOUSLY) {
 1391                                   if (loopCount != LOOP_CONTINUOUSLY) {
 1392                                       loopCount--;
 1393                                   }
 1394                                   newFramePosition = loopStartFrame;
 1395                               } else {
 1396                                   // no looping, stop playback
 1397                                   if (Printer.debug) Printer.debug("stop clip in run() loop:");
 1398                                   if (Printer.debug) Printer.debug("  doIO="+doIO+" written="+written+" clipBytePosition="+clipBytePosition);
 1399                                   if (Printer.debug) Printer.debug("  framePos="+framePos+" endFrame="+endFrame);
 1400                                   drain();
 1401                                   stop();
 1402                               }
 1403                           }
 1404                       }
 1405                   }
 1406               }
 1407               if (Printer.trace) Printer.trace("<<< DirectClip: run() threadID="+Thread.currentThread().getId());
 1408           }
 1409   
 1410           // AUTO CLOSING CLIP SUPPORT
 1411   
 1412           /* $$mp 2003-10-01
 1413              The following two methods are common between this class and
 1414              MixerClip. They should be moved to a base class, together
 1415              with the instance variable 'autoclosing'. */
 1416   
 1417           public boolean isAutoClosing() {
 1418               return autoclosing;
 1419           }
 1420   
 1421           public void setAutoClosing(boolean value) {
 1422               if (value != autoclosing) {
 1423                   if (isOpen()) {
 1424                       if (value) {
 1425                           getEventDispatcher().autoClosingClipOpened(this);
 1426                       } else {
 1427                           getEventDispatcher().autoClosingClipClosed(this);
 1428                       }
 1429                   }
 1430                   autoclosing = value;
 1431               }
 1432           }
 1433   
 1434           protected boolean requiresServicing() {
 1435               // no need for servicing for Clips
 1436               return false;
 1437           }
 1438   
 1439       } // DirectClip
 1440   
 1441       /*
 1442        * private inner class representing a ByteArrayOutputStream
 1443        * which allows retrieval of the internal array
 1444        */
 1445       private static class DirectBAOS extends ByteArrayOutputStream {
 1446           public DirectBAOS() {
 1447               super();
 1448           }
 1449   
 1450           public byte[] getInternalBuffer() {
 1451               return buf;
 1452           }
 1453   
 1454       } // class DirectBAOS
 1455   
 1456   
 1457       private static native void nGetFormats(int mixerIndex, int deviceID,
 1458                                              boolean isSource, Vector formats);
 1459   
 1460       private static native long nOpen(int mixerIndex, int deviceID, boolean isSource,
 1461                                        int encoding,
 1462                                        float sampleRate,
 1463                                        int sampleSizeInBits,
 1464                                        int frameSize,
 1465                                        int channels,
 1466                                        boolean signed,
 1467                                        boolean bigEndian,
 1468                                        int bufferSize) throws LineUnavailableException;
 1469       private static native void nStart(long id, boolean isSource);
 1470       private static native void nStop(long id, boolean isSource);
 1471       private static native void nClose(long id, boolean isSource);
 1472       private static native int nWrite(long id, byte[] b, int off, int len, int conversionSize,
 1473                                        float volLeft, float volRight);
 1474       private static native int nRead(long id, byte[] b, int off, int len, int conversionSize);
 1475       private static native int nGetBufferSize(long id, boolean isSource);
 1476       private static native boolean nIsStillDraining(long id, boolean isSource);
 1477       private static native void nFlush(long id, boolean isSource);
 1478       private static native int nAvailable(long id, boolean isSource);
 1479       // javaPos is number of bytes read/written in Java layer
 1480       private static native long nGetBytePosition(long id, boolean isSource, long javaPos);
 1481       private static native void nSetBytePosition(long id, boolean isSource, long pos);
 1482   
 1483       // returns if the native implementation needs regular calls to nService()
 1484       private static native boolean nRequiresServicing(long id, boolean isSource);
 1485       // called in irregular intervals
 1486       private static native void nService(long id, boolean isSource);
 1487   
 1488   }

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