1 /*
   2  * Copyright (c) 2002, 2014, 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 Thread thread;
1009         private byte[] audioData = null;
1010         private int frameSize;         // size of one frame in bytes
1011         private int m_lengthInFrames;
1012         private int loopCount;
1013         private int clipBytePosition;   // index in the audioData array at current playback
1014         private int newFramePosition;   // set in setFramePosition()
1015         private int loopStartFrame;
1016         private 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 
1038             byte[] newData = new byte[bufferSize];
1039             System.arraycopy(data, offset, newData, 0, bufferSize);
1040             open(format, newData, bufferSize / format.getFrameSize());
1041         }
1042 
1043         // this method does not copy the data array
1044         private void open(AudioFormat format, byte[] data, int frameLength)
1045             throws LineUnavailableException {
1046 
1047             // $$fb part of fix for 4679187: Clip.open() throws unexpected Exceptions
1048             Toolkit.isFullySpecifiedAudioFormat(format);
1049 
1050             synchronized (mixer) {
1051                 if (Printer.trace) Printer.trace("> DirectClip.open(format, data, frameLength)");
1052                 if (Printer.debug) Printer.debug("   data="+((data==null)?"null":""+data.length+" bytes"));
1053                 if (Printer.debug) Printer.debug("   frameLength="+frameLength);
1054 
1055                 if (isOpen()) {
1056                     throw new IllegalStateException("Clip is already open with format " + getFormat() +
1057                                                     " and frame lengh of " + getFrameLength());
1058                 } else {
1059                     // if the line is not currently open, try to open it with this format and buffer size
1060                     this.audioData = data;
1061                     this.frameSize = format.getFrameSize();
1062                     this.m_lengthInFrames = frameLength;
1063                     // initialize loop selection with full range
1064                     bytePosition = 0;
1065                     clipBytePosition = 0;
1066                     newFramePosition = -1; // means: do not set to a new readFramePos
1067                     loopStartFrame = 0;
1068                     loopEndFrame = frameLength - 1;
1069                     loopCount = 0; // means: play the clip irrespective of loop points from beginning to end
1070 
1071                     try {
1072                         // use DirectDL's open method to open it
1073                         open(format, (int) Toolkit.millis2bytes(format, CLIP_BUFFER_TIME)); // one second buffer
1074                     } catch (LineUnavailableException lue) {
1075                         audioData = null;
1076                         throw lue;
1077                     } catch (IllegalArgumentException iae) {
1078                         audioData = null;
1079                         throw iae;
1080                     }
1081 
1082                     // if we got this far, we can instanciate the thread
1083                     int priority = Thread.NORM_PRIORITY
1084                         + (Thread.MAX_PRIORITY - Thread.NORM_PRIORITY) / 3;
1085                     thread = JSSecurityManager.createThread(this,
1086                                                             "Direct Clip", // name
1087                                                             true,     // daemon
1088                                                             priority, // priority
1089                                                             false);  // doStart
1090                     // cannot start in createThread, because the thread
1091                     // uses the "thread" variable as indicator if it should
1092                     // continue to run
1093                     thread.start();
1094                 }
1095             }
1096             if (isAutoClosing()) {
1097                 getEventDispatcher().autoClosingClipOpened(this);
1098             }
1099             if (Printer.trace) Printer.trace("< DirectClip.open completed");
1100         }
1101 
1102         @Override
1103         public void open(AudioInputStream stream) throws LineUnavailableException, IOException {
1104 
1105             // $$fb part of fix for 4679187: Clip.open() throws unexpected Exceptions
1106             Toolkit.isFullySpecifiedAudioFormat(format);
1107 
1108             synchronized (mixer) {
1109                 if (Printer.trace) Printer.trace("> DirectClip.open(stream)");
1110                 byte[] streamData = null;
1111 
1112                 if (isOpen()) {
1113                     throw new IllegalStateException("Clip is already open with format " + getFormat() +
1114                                                     " and frame lengh of " + getFrameLength());
1115                 }
1116                 int lengthInFrames = (int)stream.getFrameLength();
1117                 if (Printer.debug) Printer.debug("DirectClip: open(AIS): lengthInFrames: " + lengthInFrames);
1118 
1119                 int bytesRead = 0;
1120                 if (lengthInFrames != AudioSystem.NOT_SPECIFIED) {
1121                     // read the data from the stream into an array in one fell swoop.
1122                     int arraysize = lengthInFrames * stream.getFormat().getFrameSize();
1123                     streamData = new byte[arraysize];
1124 
1125                     int bytesRemaining = arraysize;
1126                     int thisRead = 0;
1127                     while (bytesRemaining > 0 && thisRead >= 0) {
1128                         thisRead = stream.read(streamData, bytesRead, bytesRemaining);
1129                         if (thisRead > 0) {
1130                             bytesRead += thisRead;
1131                             bytesRemaining -= thisRead;
1132                         }
1133                         else if (thisRead == 0) {
1134                             Thread.yield();
1135                         }
1136                     }
1137                 } else {
1138                     // read data from the stream until we reach the end of the stream
1139                     // we use a slightly modified version of ByteArrayOutputStream
1140                     // to get direct access to the byte array (we don't want a new array
1141                     // to be allocated)
1142                     int MAX_READ_LIMIT = 16384;
1143                     DirectBAOS dbaos  = new DirectBAOS();
1144                     byte tmp[] = new byte[MAX_READ_LIMIT];
1145                     int thisRead = 0;
1146                     while (thisRead >= 0) {
1147                         thisRead = stream.read(tmp, 0, tmp.length);
1148                         if (thisRead > 0) {
1149                             dbaos.write(tmp, 0, thisRead);
1150                             bytesRead += thisRead;
1151                         }
1152                         else if (thisRead == 0) {
1153                             Thread.yield();
1154                         }
1155                     } // while
1156                     streamData = dbaos.getInternalBuffer();
1157                 }
1158                 lengthInFrames = bytesRead / stream.getFormat().getFrameSize();
1159 
1160                 if (Printer.debug) Printer.debug("Read to end of stream. lengthInFrames: " + lengthInFrames);
1161 
1162                 // now try to open the device
1163                 open(stream.getFormat(), streamData, lengthInFrames);
1164 
1165                 if (Printer.trace) Printer.trace("< DirectClip.open(stream) succeeded");
1166             } // synchronized
1167         }
1168 
1169         @Override
1170         public int getFrameLength() {
1171             return m_lengthInFrames;
1172         }
1173 
1174         @Override
1175         public long getMicrosecondLength() {
1176             return Toolkit.frames2micros(getFormat(), getFrameLength());
1177         }
1178 
1179         @Override
1180         public void setFramePosition(int frames) {
1181             if (Printer.trace) Printer.trace("> DirectClip: setFramePosition: " + frames);
1182 
1183             if (frames < 0) {
1184                 frames = 0;
1185             }
1186             else if (frames >= getFrameLength()) {
1187                 frames = getFrameLength();
1188             }
1189             if (doIO) {
1190                 newFramePosition = frames;
1191             } else {
1192                 clipBytePosition = frames * frameSize;
1193                 newFramePosition = -1;
1194             }
1195             // fix for failing test050
1196             // $$fb although getFramePosition should return the number of rendered
1197             // frames, it is intuitive that setFramePosition will modify that
1198             // value.
1199             bytePosition = frames * frameSize;
1200 
1201             // cease currently playing buffer
1202             flush();
1203 
1204             // set new native position (if necessary)
1205             // this must come after the flush!
1206             synchronized (lockNative) {
1207                 nSetBytePosition(id, isSource, frames * frameSize);
1208             }
1209 
1210             if (Printer.debug) Printer.debug("  DirectClip.setFramePosition: "
1211                                              +" doIO="+doIO
1212                                              +" newFramePosition="+newFramePosition
1213                                              +" clipBytePosition="+clipBytePosition
1214                                              +" bytePosition="+bytePosition
1215                                              +" getLongFramePosition()="+getLongFramePosition());
1216             if (Printer.trace) Printer.trace("< DirectClip: setFramePosition");
1217         }
1218 
1219         // replacement for getFramePosition (see AbstractDataLine)
1220         @Override
1221         public long getLongFramePosition() {
1222             /* $$fb
1223              * this would be intuitive, but the definition of getFramePosition
1224              * is the number of frames rendered since opening the device...
1225              * That also means that setFramePosition() means something very
1226              * different from getFramePosition() for Clip.
1227              */
1228             // take into account the case that a new position was set...
1229             //if (!doIO && newFramePosition >= 0) {
1230             //return newFramePosition;
1231             //}
1232             return super.getLongFramePosition();
1233         }
1234 
1235         @Override
1236         public synchronized void setMicrosecondPosition(long microseconds) {
1237             if (Printer.trace) Printer.trace("> DirectClip: setMicrosecondPosition: " + microseconds);
1238 
1239             long frames = Toolkit.micros2frames(getFormat(), microseconds);
1240             setFramePosition((int) frames);
1241 
1242             if (Printer.trace) Printer.trace("< DirectClip: setMicrosecondPosition succeeded");
1243         }
1244 
1245         @Override
1246         public void setLoopPoints(int start, int end) {
1247             if (Printer.trace) Printer.trace("> DirectClip: setLoopPoints: start: " + start + " end: " + end);
1248 
1249             if (start < 0 || start >= getFrameLength()) {
1250                 throw new IllegalArgumentException("illegal value for start: "+start);
1251             }
1252             if (end >= getFrameLength()) {
1253                 throw new IllegalArgumentException("illegal value for end: "+end);
1254             }
1255 
1256             if (end == -1) {
1257                 end = getFrameLength() - 1;
1258                 if (end < 0) {
1259                     end = 0;
1260                 }
1261             }
1262 
1263             // if the end position is less than the start position, throw IllegalArgumentException
1264             if (end < start) {
1265                 throw new IllegalArgumentException("End position " + end + "  preceeds start position " + start);
1266             }
1267 
1268             // slight race condition with the run() method, but not a big problem
1269             loopStartFrame = start;
1270             loopEndFrame = end;
1271 
1272             if (Printer.trace) Printer.trace("  loopStart: " + loopStartFrame + " loopEnd: " + loopEndFrame);
1273             if (Printer.trace) Printer.trace("< DirectClip: setLoopPoints completed");
1274         }
1275 
1276         @Override
1277         public void loop(int count) {
1278             // note: when count reaches 0, it means that the entire clip
1279             // will be played, i.e. it will play past the loop end point
1280             loopCount = count;
1281             start();
1282         }
1283 
1284         @Override
1285         void implOpen(AudioFormat format, int bufferSize) throws LineUnavailableException {
1286             // only if audioData wasn't set in a calling open(format, byte[], frameSize)
1287             // this call is allowed.
1288             if (audioData == null) {
1289                 throw new IllegalArgumentException("illegal call to open() in interface Clip");
1290             }
1291             super.implOpen(format, bufferSize);
1292         }
1293 
1294         @Override
1295         void implClose() {
1296             if (Printer.trace) Printer.trace(">> DirectClip: implClose()");
1297 
1298             // dispose of thread
1299             Thread oldThread = thread;
1300             thread = null;
1301             doIO = false;
1302             if (oldThread != null) {
1303                 // wake up the thread if it's in wait()
1304                 synchronized(lock) {
1305                     lock.notifyAll();
1306                 }
1307                 // wait for the thread to terminate itself,
1308                 // but max. 2 seconds. Must not be synchronized!
1309                 try {
1310                     oldThread.join(2000);
1311                 } catch (InterruptedException ie) {}
1312             }
1313             super.implClose();
1314             // remove audioData reference and hand it over to gc
1315             audioData = null;
1316             newFramePosition = -1;
1317 
1318             // remove this instance from the list of auto closing clips
1319             getEventDispatcher().autoClosingClipClosed(this);
1320 
1321             if (Printer.trace) Printer.trace("<< DirectClip: implClose() succeeded");
1322         }
1323 
1324         @Override
1325         void implStart() {
1326             if (Printer.trace) Printer.trace("> DirectClip: implStart()");
1327             super.implStart();
1328             if (Printer.trace) Printer.trace("< DirectClip: implStart() succeeded");
1329         }
1330 
1331         @Override
1332         void implStop() {
1333             if (Printer.trace) Printer.trace(">> DirectClip: implStop()");
1334 
1335             super.implStop();
1336             // reset loopCount field so that playback will be normal with
1337             // next call to start()
1338             loopCount = 0;
1339 
1340             if (Printer.trace) Printer.trace("<< DirectClip: implStop() succeeded");
1341         }
1342 
1343         // main playback loop
1344         @Override
1345         public void run() {
1346             if (Printer.trace) Printer.trace(">>> DirectClip: run() threadID="+Thread.currentThread().getId());
1347             while (thread != null) {
1348                 // doIO is volatile, but we could check it, then get
1349                 // pre-empted while another thread changes doIO and notifies,
1350                 // before we wait (so we sleep in wait forever).
1351                 synchronized(lock) {
1352                     if (!doIO) {
1353                         try {
1354                             lock.wait();
1355                         } catch(InterruptedException ie) {}
1356                     }
1357                 }
1358                 while (doIO) {
1359                     if (newFramePosition >= 0) {
1360                         clipBytePosition = newFramePosition * frameSize;
1361                         newFramePosition = -1;
1362                     }
1363                     int endFrame = getFrameLength() - 1;
1364                     if (loopCount > 0 || loopCount == LOOP_CONTINUOUSLY) {
1365                         endFrame = loopEndFrame;
1366                     }
1367                     long framePos = (clipBytePosition / frameSize);
1368                     int toWriteFrames = (int) (endFrame - framePos + 1);
1369                     int toWriteBytes = toWriteFrames * frameSize;
1370                     if (toWriteBytes > getBufferSize()) {
1371                         toWriteBytes = Toolkit.align(getBufferSize(), frameSize);
1372                     }
1373                     int written = write(audioData, clipBytePosition, toWriteBytes); // increases bytePosition
1374                     clipBytePosition += written;
1375                     // make sure nobody called setFramePosition, or stop() during the write() call
1376                     if (doIO && newFramePosition < 0 && written >= 0) {
1377                         framePos = clipBytePosition / frameSize;
1378                         // since endFrame is the last frame to be played,
1379                         // framePos is after endFrame when all frames, including framePos,
1380                         // are played.
1381                         if (framePos > endFrame) {
1382                             // at end of playback. If looping is on, loop back to the beginning.
1383                             if (loopCount > 0 || loopCount == LOOP_CONTINUOUSLY) {
1384                                 if (loopCount != LOOP_CONTINUOUSLY) {
1385                                     loopCount--;
1386                                 }
1387                                 newFramePosition = loopStartFrame;
1388                             } else {
1389                                 // no looping, stop playback
1390                                 if (Printer.debug) Printer.debug("stop clip in run() loop:");
1391                                 if (Printer.debug) Printer.debug("  doIO="+doIO+" written="+written+" clipBytePosition="+clipBytePosition);
1392                                 if (Printer.debug) Printer.debug("  framePos="+framePos+" endFrame="+endFrame);
1393                                 drain();
1394                                 stop();
1395                             }
1396                         }
1397                     }
1398                 }
1399             }
1400             if (Printer.trace) Printer.trace("<<< DirectClip: run() threadID="+Thread.currentThread().getId());
1401         }
1402 
1403         // AUTO CLOSING CLIP SUPPORT
1404 
1405         /* $$mp 2003-10-01
1406            The following two methods are common between this class and
1407            MixerClip. They should be moved to a base class, together
1408            with the instance variable 'autoclosing'. */
1409 
1410         @Override
1411         public boolean isAutoClosing() {
1412             return autoclosing;
1413         }
1414 
1415         @Override
1416         public void setAutoClosing(boolean value) {
1417             if (value != autoclosing) {
1418                 if (isOpen()) {
1419                     if (value) {
1420                         getEventDispatcher().autoClosingClipOpened(this);
1421                     } else {
1422                         getEventDispatcher().autoClosingClipClosed(this);
1423                     }
1424                 }
1425                 autoclosing = value;
1426             }
1427         }
1428 
1429         @Override
1430         protected boolean requiresServicing() {
1431             // no need for servicing for Clips
1432             return false;
1433         }
1434 
1435     } // DirectClip
1436 
1437     /*
1438      * private inner class representing a ByteArrayOutputStream
1439      * which allows retrieval of the internal array
1440      */
1441     private static class DirectBAOS extends ByteArrayOutputStream {
1442         DirectBAOS() {
1443             super();
1444         }
1445 
1446         public byte[] getInternalBuffer() {
1447             return buf;
1448         }
1449 
1450     } // class DirectBAOS
1451 
1452     @SuppressWarnings("rawtypes")
1453     private static native void nGetFormats(int mixerIndex, int deviceID,
1454                                            boolean isSource, Vector formats);
1455 
1456     private static native long nOpen(int mixerIndex, int deviceID, boolean isSource,
1457                                      int encoding,
1458                                      float sampleRate,
1459                                      int sampleSizeInBits,
1460                                      int frameSize,
1461                                      int channels,
1462                                      boolean signed,
1463                                      boolean bigEndian,
1464                                      int bufferSize) throws LineUnavailableException;
1465     private static native void nStart(long id, boolean isSource);
1466     private static native void nStop(long id, boolean isSource);
1467     private static native void nClose(long id, boolean isSource);
1468     private static native int nWrite(long id, byte[] b, int off, int len, int conversionSize,
1469                                      float volLeft, float volRight);
1470     private static native int nRead(long id, byte[] b, int off, int len, int conversionSize);
1471     private static native int nGetBufferSize(long id, boolean isSource);
1472     private static native boolean nIsStillDraining(long id, boolean isSource);
1473     private static native void nFlush(long id, boolean isSource);
1474     private static native int nAvailable(long id, boolean isSource);
1475     // javaPos is number of bytes read/written in Java layer
1476     private static native long nGetBytePosition(long id, boolean isSource, long javaPos);
1477     private static native void nSetBytePosition(long id, boolean isSource, long pos);
1478 
1479     // returns if the native implementation needs regular calls to nService()
1480     private static native boolean nRequiresServicing(long id, boolean isSource);
1481     // called in irregular intervals
1482     private static native void nService(long id, boolean isSource);
1483 }