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