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