1 /*
   2  * Copyright (c) 2002, 2018, 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             synchronized(lock) {
 551                 doIO = true;
 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 
 561             if (Printer.trace) Printer.trace("<< DirectDL: implStart() succeeded");
 562         }
 563 
 564         @Override
 565         void implStop() {
 566             if (Printer.trace) Printer.trace(">> DirectDL: implStop()");
 567 
 568             // check for record permission
 569             if (!isSource) {
 570                 JSSecurityManager.checkRecordPermission();
 571             }
 572 
 573             if (monitoring) {
 574                 getEventDispatcher().removeLineMonitor(this);
 575                 monitoring = false;
 576             }
 577             synchronized (lockNative) {
 578                 nStop(id, isSource);
 579             }
 580             // wake up any waiting threads
 581             synchronized(lock) {
 582                 // need to set doIO to false before notifying the
 583                 // read/write thread, that's why isStartedRunning()
 584                 // cannot be used
 585                 doIO = false;
 586                 setActive(false);
 587                 setStarted(false);
 588                 lock.notifyAll();
 589             }
 590             stoppedWritten = false;
 591 
 592             if (Printer.trace) Printer.trace(" << DirectDL: implStop() succeeded");
 593         }
 594 
 595         @Override
 596         void implClose() {
 597             if (Printer.trace) Printer.trace(">> DirectDL: implClose()");
 598 
 599             // check for record permission
 600             if (!isSource) {
 601                 JSSecurityManager.checkRecordPermission();
 602             }
 603 
 604             // be sure to remove this monitor
 605             if (monitoring) {
 606                 getEventDispatcher().removeLineMonitor(this);
 607                 monitoring = false;
 608             }
 609 
 610             doIO = false;
 611             long oldID = id;
 612             id = 0;
 613             synchronized (lockNative) {
 614                 nClose(oldID, isSource);
 615             }
 616             bytePosition = 0;
 617             softwareConversionSize = 0;
 618             if (Printer.trace) Printer.trace("<< DirectDL: implClose() succeeded");
 619         }
 620 
 621         @Override
 622         public int available() {
 623             if (id == 0) {
 624                 return 0;
 625             }
 626             int a;
 627             synchronized (lockNative) {
 628                 a = nAvailable(id, isSource);
 629             }
 630             return a;
 631         }
 632 
 633         @Override
 634         public void drain() {
 635             noService = true;
 636             // additional safeguard against draining forever
 637             // this occurred on Solaris 8 x86, probably due to a bug
 638             // in the audio driver
 639             int counter = 0;
 640             long startPos = getLongFramePosition();
 641             boolean posChanged = false;
 642             while (!drained) {
 643                 synchronized (lockNative) {
 644                     if ((id == 0) || (!doIO) || !nIsStillDraining(id, isSource))
 645                         break;
 646                 }
 647                 // check every now and then for a new position
 648                 if ((counter % 5) == 4) {
 649                     long thisFramePos = getLongFramePosition();
 650                     posChanged = posChanged | (thisFramePos != startPos);
 651                     if ((counter % 50) > 45) {
 652                         // when some time elapsed, check that the frame position
 653                         // really changed
 654                         if (!posChanged) {
 655                             if (Printer.err) Printer.err("Native reports isDraining, but frame position does not increase!");
 656                             break;
 657                         }
 658                         posChanged = false;
 659                         startPos = thisFramePos;
 660                     }
 661                 }
 662                 counter++;
 663                 synchronized(lock) {
 664                     try {
 665                         lock.wait(10);
 666                     } catch (InterruptedException ie) {}
 667                 }
 668             }
 669 
 670             if (doIO && id != 0) {
 671                 drained = true;
 672             }
 673             noService = false;
 674         }
 675 
 676         @Override
 677         public void flush() {
 678             if (id != 0) {
 679                 // first stop ongoing read/write method
 680                 flushing = true;
 681                 synchronized(lock) {
 682                     lock.notifyAll();
 683                 }
 684                 synchronized (lockNative) {
 685                     if (id != 0) {
 686                         // then flush native buffers
 687                         nFlush(id, isSource);
 688                     }
 689                 }
 690                 drained = true;
 691             }
 692         }
 693 
 694         // replacement for getFramePosition (see AbstractDataLine)
 695         @Override
 696         public long getLongFramePosition() {
 697             long pos;
 698             synchronized (lockNative) {
 699                 pos = nGetBytePosition(id, isSource, bytePosition);
 700             }
 701             // hack because ALSA sometimes reports wrong framepos
 702             if (pos < 0) {
 703                 if (Printer.debug) Printer.debug("DirectLine.getLongFramePosition: Native reported pos="
 704                                                  +pos+"! is changed to 0. byteposition="+bytePosition);
 705                 pos = 0;
 706             }
 707             return (pos / getFormat().getFrameSize());
 708         }
 709 
 710         /*
 711          * write() belongs into SourceDataLine and Clip,
 712          * so define it here and make it accessible by
 713          * declaring the respective interfaces with DirectSDL and DirectClip
 714          */
 715         public int write(byte[] b, int off, int len) {
 716             flushing = false;
 717             if (len == 0) {
 718                 return 0;
 719             }
 720             if (len < 0) {
 721                 throw new IllegalArgumentException("illegal len: "+len);
 722             }
 723             if (len % getFormat().getFrameSize() != 0) {
 724                 throw new IllegalArgumentException("illegal request to write "
 725                                                    +"non-integral number of frames ("
 726                                                    +len+" bytes, "
 727                                                    +"frameSize = "+getFormat().getFrameSize()+" bytes)");
 728             }
 729             if (off < 0) {
 730                 throw new ArrayIndexOutOfBoundsException(off);
 731             }
 732             if ((long)off + (long)len > (long)b.length) {
 733                 throw new ArrayIndexOutOfBoundsException(b.length);
 734             }
 735             synchronized(lock) {
 736                 if (!isActive() && doIO) {
 737                     // this is not exactly correct... would be nicer
 738                     // if the native sub system sent a callback when IO really
 739                     // starts
 740                     setActive(true);
 741                     setStarted(true);
 742                 }
 743             }
 744             int written = 0;
 745             while (!flushing) {
 746                 int thisWritten;
 747                 synchronized (lockNative) {
 748                     thisWritten = nWrite(id, b, off, len,
 749                             softwareConversionSize,
 750                             leftGain, rightGain);
 751                     if (thisWritten < 0) {
 752                         // error in native layer
 753                         break;
 754                     }
 755                     bytePosition += thisWritten;
 756                     if (thisWritten > 0) {
 757                         drained = false;
 758                     }
 759                 }
 760                 len -= thisWritten;
 761                 written += thisWritten;
 762                 if (doIO && len > 0) {
 763                     off += thisWritten;
 764                     synchronized (lock) {
 765                         try {
 766                             lock.wait(waitTime);
 767                         } catch (InterruptedException ie) {}
 768                     }
 769                 } else {
 770                     break;
 771                 }
 772             }
 773             if (written > 0 && !doIO) {
 774                 stoppedWritten = true;
 775             }
 776             return written;
 777         }
 778 
 779         protected boolean requiresServicing() {
 780             return nRequiresServicing(id, isSource);
 781         }
 782 
 783         // called from event dispatcher for lines that need servicing
 784         @Override
 785         public void checkLine() {
 786             synchronized (lockNative) {
 787                 if (monitoring
 788                         && doIO
 789                         && id != 0
 790                         && !flushing
 791                         && !noService) {
 792                     nService(id, isSource);
 793                 }
 794             }
 795         }
 796 
 797         private void calcVolume() {
 798             if (getFormat() == null) {
 799                 return;
 800             }
 801             if (muteControl.getValue()) {
 802                 leftGain = 0.0f;
 803                 rightGain = 0.0f;
 804                 return;
 805             }
 806             float gain = gainControl.getLinearGain();
 807             if (getFormat().getChannels() == 1) {
 808                 // trivial case: only use gain
 809                 leftGain = gain;
 810                 rightGain = gain;
 811             } else {
 812                 // need to combine gain and balance
 813                 float bal = balanceControl.getValue();
 814                 if (bal < 0.0f) {
 815                     // left
 816                     leftGain = gain;
 817                     rightGain = gain * (bal + 1.0f);
 818                 } else {
 819                     leftGain = gain * (1.0f - bal);
 820                     rightGain = gain;
 821                 }
 822             }
 823         }
 824 
 825         /////////////////// CONTROLS /////////////////////////////
 826 
 827         protected final class Gain extends FloatControl {
 828 
 829             private float linearGain = 1.0f;
 830 
 831             private Gain() {
 832 
 833                 super(FloatControl.Type.MASTER_GAIN,
 834                       Toolkit.linearToDB(0.0f),
 835                       Toolkit.linearToDB(2.0f),
 836                       Math.abs(Toolkit.linearToDB(1.0f)-Toolkit.linearToDB(0.0f))/128.0f,
 837                       -1,
 838                       0.0f,
 839                       "dB", "Minimum", "", "Maximum");
 840             }
 841 
 842             @Override
 843             public void setValue(float newValue) {
 844                 // adjust value within range ?? spec says IllegalArgumentException
 845                 //newValue = Math.min(newValue, getMaximum());
 846                 //newValue = Math.max(newValue, getMinimum());
 847 
 848                 float newLinearGain = Toolkit.dBToLinear(newValue);
 849                 super.setValue(Toolkit.linearToDB(newLinearGain));
 850                 // if no exception, commit to our new gain
 851                 linearGain = newLinearGain;
 852                 calcVolume();
 853             }
 854 
 855             float getLinearGain() {
 856                 return linearGain;
 857             }
 858         } // class Gain
 859 
 860         private final class Mute extends BooleanControl {
 861 
 862             private Mute() {
 863                 super(BooleanControl.Type.MUTE, false, "True", "False");
 864             }
 865 
 866             @Override
 867             public void setValue(boolean newValue) {
 868                 super.setValue(newValue);
 869                 calcVolume();
 870             }
 871         }  // class Mute
 872 
 873         private final class Balance extends FloatControl {
 874 
 875             private Balance() {
 876                 super(FloatControl.Type.BALANCE, -1.0f, 1.0f, (1.0f / 128.0f), -1, 0.0f,
 877                       "", "Left", "Center", "Right");
 878             }
 879 
 880             @Override
 881             public void setValue(float newValue) {
 882                 setValueImpl(newValue);
 883                 panControl.setValueImpl(newValue);
 884                 calcVolume();
 885             }
 886 
 887             void setValueImpl(float newValue) {
 888                 super.setValue(newValue);
 889             }
 890 
 891         } // class Balance
 892 
 893         private final class Pan extends FloatControl {
 894 
 895             private Pan() {
 896                 super(FloatControl.Type.PAN, -1.0f, 1.0f, (1.0f / 128.0f), -1, 0.0f,
 897                       "", "Left", "Center", "Right");
 898             }
 899 
 900             @Override
 901             public void setValue(float newValue) {
 902                 setValueImpl(newValue);
 903                 balanceControl.setValueImpl(newValue);
 904                 calcVolume();
 905             }
 906             void setValueImpl(float newValue) {
 907                 super.setValue(newValue);
 908             }
 909         } // class Pan
 910     } // class DirectDL
 911 
 912     /**
 913      * Private inner class representing a SourceDataLine.
 914      */
 915     private static final class DirectSDL extends DirectDL
 916             implements SourceDataLine {
 917 
 918         private DirectSDL(DataLine.Info info,
 919                           AudioFormat format,
 920                           int bufferSize,
 921                           DirectAudioDevice mixer) {
 922             super(info, mixer, format, bufferSize, mixer.getMixerIndex(), mixer.getDeviceID(), true);
 923             if (Printer.trace) Printer.trace("DirectSDL CONSTRUCTOR: completed");
 924         }
 925 
 926     }
 927 
 928     /**
 929      * Private inner class representing a TargetDataLine.
 930      */
 931     private static final class DirectTDL extends DirectDL
 932             implements TargetDataLine {
 933 
 934         private DirectTDL(DataLine.Info info,
 935                           AudioFormat format,
 936                           int bufferSize,
 937                           DirectAudioDevice mixer) {
 938             super(info, mixer, format, bufferSize, mixer.getMixerIndex(), mixer.getDeviceID(), false);
 939             if (Printer.trace) Printer.trace("DirectTDL CONSTRUCTOR: completed");
 940         }
 941 
 942         @Override
 943         public int read(byte[] b, int off, int len) {
 944             flushing = false;
 945             if (len == 0) {
 946                 return 0;
 947             }
 948             if (len < 0) {
 949                 throw new IllegalArgumentException("illegal len: "+len);
 950             }
 951             if (len % getFormat().getFrameSize() != 0) {
 952                 throw new IllegalArgumentException("illegal request to read "
 953                                                    +"non-integral number of frames ("
 954                                                    +len+" bytes, "
 955                                                    +"frameSize = "+getFormat().getFrameSize()+" bytes)");
 956             }
 957             if (off < 0) {
 958                 throw new ArrayIndexOutOfBoundsException(off);
 959             }
 960             if ((long)off + (long)len > (long)b.length) {
 961                 throw new ArrayIndexOutOfBoundsException(b.length);
 962             }
 963             synchronized(lock) {
 964                 if (!isActive() && doIO) {
 965                     // this is not exactly correct... would be nicer
 966                     // if the native sub system sent a callback when IO really
 967                     // starts
 968                     setActive(true);
 969                     setStarted(true);
 970                 }
 971             }
 972             int read = 0;
 973             while (doIO && !flushing) {
 974                 int thisRead;
 975                 synchronized (lockNative) {
 976                     thisRead = nRead(id, b, off, len, softwareConversionSize);
 977                     if (thisRead < 0) {
 978                         // error in native layer
 979                         break;
 980                     }
 981                     bytePosition += thisRead;
 982                     if (thisRead > 0) {
 983                         drained = false;
 984                     }
 985                 }
 986                 len -= thisRead;
 987                 read += thisRead;
 988                 if (len > 0) {
 989                     off += thisRead;
 990                     synchronized(lock) {
 991                         try {
 992                             lock.wait(waitTime);
 993                         } catch (InterruptedException ie) {}
 994                     }
 995                 } else {
 996                     break;
 997                 }
 998             }
 999             if (flushing) {
1000                 read = 0;
1001             }
1002             return read;
1003         }
1004 
1005     }
1006 
1007     /**
1008      * Private inner class representing a Clip
1009      * This clip is realized in software only
1010      */
1011     private static final class DirectClip extends DirectDL
1012             implements Clip, Runnable, AutoClosingClip {
1013 
1014         private volatile Thread thread;
1015         private volatile byte[] audioData = null;
1016         private volatile int frameSize;         // size of one frame in bytes
1017         private volatile int m_lengthInFrames;
1018         private volatile int loopCount;
1019         private volatile int clipBytePosition;   // index in the audioData array at current playback
1020         private volatile int newFramePosition;   // set in setFramePosition()
1021         private volatile int loopStartFrame;
1022         private volatile int loopEndFrame;      // the last sample included in the loop
1023 
1024         // auto closing clip support
1025         private boolean autoclosing = false;
1026 
1027         private DirectClip(DataLine.Info info,
1028                            AudioFormat format,
1029                            int bufferSize,
1030                            DirectAudioDevice mixer) {
1031             super(info, mixer, format, bufferSize, mixer.getMixerIndex(), mixer.getDeviceID(), true);
1032             if (Printer.trace) Printer.trace("DirectClip CONSTRUCTOR: completed");
1033         }
1034 
1035         // CLIP METHODS
1036 
1037         @Override
1038         public void open(AudioFormat format, byte[] data, int offset, int bufferSize)
1039             throws LineUnavailableException {
1040 
1041             // $$fb part of fix for 4679187: Clip.open() throws unexpected Exceptions
1042             Toolkit.isFullySpecifiedAudioFormat(format);
1043             Toolkit.validateBuffer(format.getFrameSize(), bufferSize);
1044 
1045             byte[] newData = new byte[bufferSize];
1046             System.arraycopy(data, offset, newData, 0, bufferSize);
1047             open(format, newData, bufferSize / format.getFrameSize());
1048         }
1049 
1050         // this method does not copy the data array
1051         private void open(AudioFormat format, byte[] data, int frameLength)
1052             throws LineUnavailableException {
1053 
1054             // $$fb part of fix for 4679187: Clip.open() throws unexpected Exceptions
1055             Toolkit.isFullySpecifiedAudioFormat(format);
1056 
1057             synchronized (mixer) {
1058                 if (Printer.trace) Printer.trace("> DirectClip.open(format, data, frameLength)");
1059                 if (Printer.debug) Printer.debug("   data="+((data==null)?"null":""+data.length+" bytes"));
1060                 if (Printer.debug) Printer.debug("   frameLength="+frameLength);
1061 
1062                 if (isOpen()) {
1063                     throw new IllegalStateException("Clip is already open with format " + getFormat() +
1064                                                     " and frame lengh of " + getFrameLength());
1065                 } else {
1066                     // if the line is not currently open, try to open it with this format and buffer size
1067                     this.audioData = data;
1068                     this.frameSize = format.getFrameSize();
1069                     this.m_lengthInFrames = frameLength;
1070                     // initialize loop selection with full range
1071                     bytePosition = 0;
1072                     clipBytePosition = 0;
1073                     newFramePosition = -1; // means: do not set to a new readFramePos
1074                     loopStartFrame = 0;
1075                     loopEndFrame = frameLength - 1;
1076                     loopCount = 0; // means: play the clip irrespective of loop points from beginning to end
1077 
1078                     try {
1079                         // use DirectDL's open method to open it
1080                         open(format, (int) Toolkit.millis2bytes(format, CLIP_BUFFER_TIME)); // one second buffer
1081                     } catch (LineUnavailableException lue) {
1082                         audioData = null;
1083                         throw lue;
1084                     } catch (IllegalArgumentException iae) {
1085                         audioData = null;
1086                         throw iae;
1087                     }
1088 
1089                     // if we got this far, we can instanciate the thread
1090                     int priority = Thread.NORM_PRIORITY
1091                         + (Thread.MAX_PRIORITY - Thread.NORM_PRIORITY) / 3;
1092                     thread = JSSecurityManager.createThread(this,
1093                                                             "Direct Clip", // name
1094                                                             true,     // daemon
1095                                                             priority, // priority
1096                                                             false);  // doStart
1097                     // cannot start in createThread, because the thread
1098                     // uses the "thread" variable as indicator if it should
1099                     // continue to run
1100                     thread.start();
1101                 }
1102             }
1103             if (isAutoClosing()) {
1104                 getEventDispatcher().autoClosingClipOpened(this);
1105             }
1106             if (Printer.trace) Printer.trace("< DirectClip.open completed");
1107         }
1108 
1109         @Override
1110         public void open(AudioInputStream stream) throws LineUnavailableException, IOException {
1111 
1112             // $$fb part of fix for 4679187: Clip.open() throws unexpected Exceptions
1113             Toolkit.isFullySpecifiedAudioFormat(format);
1114 
1115             synchronized (mixer) {
1116                 if (Printer.trace) Printer.trace("> DirectClip.open(stream)");
1117                 byte[] streamData = null;
1118 
1119                 if (isOpen()) {
1120                     throw new IllegalStateException("Clip is already open with format " + getFormat() +
1121                                                     " and frame lengh of " + getFrameLength());
1122                 }
1123                 int lengthInFrames = (int)stream.getFrameLength();
1124                 if (Printer.debug) Printer.debug("DirectClip: open(AIS): lengthInFrames: " + lengthInFrames);
1125 
1126                 int bytesRead = 0;
1127                 if (lengthInFrames != AudioSystem.NOT_SPECIFIED) {
1128                     // read the data from the stream into an array in one fell swoop.
1129                     int arraysize = lengthInFrames * stream.getFormat().getFrameSize();
1130                     streamData = new byte[arraysize];
1131 
1132                     int bytesRemaining = arraysize;
1133                     int thisRead = 0;
1134                     while (bytesRemaining > 0 && thisRead >= 0) {
1135                         thisRead = stream.read(streamData, bytesRead, bytesRemaining);
1136                         if (thisRead > 0) {
1137                             bytesRead += thisRead;
1138                             bytesRemaining -= thisRead;
1139                         }
1140                         else if (thisRead == 0) {
1141                             Thread.yield();
1142                         }
1143                     }
1144                 } else {
1145                     // read data from the stream until we reach the end of the stream
1146                     // we use a slightly modified version of ByteArrayOutputStream
1147                     // to get direct access to the byte array (we don't want a new array
1148                     // to be allocated)
1149                     int MAX_READ_LIMIT = 16384;
1150                     DirectBAOS dbaos  = new DirectBAOS();
1151                     byte[] tmp = new byte[MAX_READ_LIMIT];
1152                     int thisRead = 0;
1153                     while (thisRead >= 0) {
1154                         thisRead = stream.read(tmp, 0, tmp.length);
1155                         if (thisRead > 0) {
1156                             dbaos.write(tmp, 0, thisRead);
1157                             bytesRead += thisRead;
1158                         }
1159                         else if (thisRead == 0) {
1160                             Thread.yield();
1161                         }
1162                     } // while
1163                     streamData = dbaos.getInternalBuffer();
1164                 }
1165                 lengthInFrames = bytesRead / stream.getFormat().getFrameSize();
1166 
1167                 if (Printer.debug) Printer.debug("Read to end of stream. lengthInFrames: " + lengthInFrames);
1168 
1169                 // now try to open the device
1170                 open(stream.getFormat(), streamData, lengthInFrames);
1171 
1172                 if (Printer.trace) Printer.trace("< DirectClip.open(stream) succeeded");
1173             } // synchronized
1174         }
1175 
1176         @Override
1177         public int getFrameLength() {
1178             return m_lengthInFrames;
1179         }
1180 
1181         @Override
1182         public long getMicrosecondLength() {
1183             return Toolkit.frames2micros(getFormat(), getFrameLength());
1184         }
1185 
1186         @Override
1187         public void setFramePosition(int frames) {
1188             if (Printer.trace) Printer.trace("> DirectClip: setFramePosition: " + frames);
1189 
1190             if (frames < 0) {
1191                 frames = 0;
1192             }
1193             else if (frames >= getFrameLength()) {
1194                 frames = getFrameLength();
1195             }
1196             if (doIO) {
1197                 newFramePosition = frames;
1198             } else {
1199                 clipBytePosition = frames * frameSize;
1200                 newFramePosition = -1;
1201             }
1202             // fix for failing test050
1203             // $$fb although getFramePosition should return the number of rendered
1204             // frames, it is intuitive that setFramePosition will modify that
1205             // value.
1206             bytePosition = frames * frameSize;
1207 
1208             // cease currently playing buffer
1209             flush();
1210 
1211             // set new native position (if necessary)
1212             // this must come after the flush!
1213             synchronized (lockNative) {
1214                 nSetBytePosition(id, isSource, frames * frameSize);
1215             }
1216 
1217             if (Printer.debug) Printer.debug("  DirectClip.setFramePosition: "
1218                                              +" doIO="+doIO
1219                                              +" newFramePosition="+newFramePosition
1220                                              +" clipBytePosition="+clipBytePosition
1221                                              +" bytePosition="+bytePosition
1222                                              +" getLongFramePosition()="+getLongFramePosition());
1223             if (Printer.trace) Printer.trace("< DirectClip: setFramePosition");
1224         }
1225 
1226         // replacement for getFramePosition (see AbstractDataLine)
1227         @Override
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         @Override
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         @Override
1253         public void setLoopPoints(int start, int end) {
1254             if (Printer.trace) Printer.trace("> DirectClip: setLoopPoints: start: " + start + " end: " + end);
1255 
1256             if (start < 0 || start >= getFrameLength()) {
1257                 throw new IllegalArgumentException("illegal value for start: "+start);
1258             }
1259             if (end >= getFrameLength()) {
1260                 throw new IllegalArgumentException("illegal value for end: "+end);
1261             }
1262 
1263             if (end == -1) {
1264                 end = getFrameLength() - 1;
1265                 if (end < 0) {
1266                     end = 0;
1267                 }
1268             }
1269 
1270             // if the end position is less than the start position, throw IllegalArgumentException
1271             if (end < start) {
1272                 throw new IllegalArgumentException("End position " + end + "  preceeds start position " + start);
1273             }
1274 
1275             // slight race condition with the run() method, but not a big problem
1276             loopStartFrame = start;
1277             loopEndFrame = end;
1278 
1279             if (Printer.trace) Printer.trace("  loopStart: " + loopStartFrame + " loopEnd: " + loopEndFrame);
1280             if (Printer.trace) Printer.trace("< DirectClip: setLoopPoints completed");
1281         }
1282 
1283         @Override
1284         public void loop(int count) {
1285             // note: when count reaches 0, it means that the entire clip
1286             // will be played, i.e. it will play past the loop end point
1287             loopCount = count;
1288             start();
1289         }
1290 
1291         @Override
1292         void implOpen(AudioFormat format, int bufferSize) throws LineUnavailableException {
1293             // only if audioData wasn't set in a calling open(format, byte[], frameSize)
1294             // this call is allowed.
1295             if (audioData == null) {
1296                 throw new IllegalArgumentException("illegal call to open() in interface Clip");
1297             }
1298             super.implOpen(format, bufferSize);
1299         }
1300 
1301         @Override
1302         void implClose() {
1303             if (Printer.trace) Printer.trace(">> DirectClip: implClose()");
1304 
1305             // dispose of thread
1306             Thread oldThread = thread;
1307             thread = null;
1308             doIO = false;
1309             if (oldThread != null) {
1310                 // wake up the thread if it's in wait()
1311                 synchronized(lock) {
1312                     lock.notifyAll();
1313                 }
1314                 // wait for the thread to terminate itself,
1315                 // but max. 2 seconds. Must not be synchronized!
1316                 try {
1317                     oldThread.join(2000);
1318                 } catch (InterruptedException ie) {}
1319             }
1320             super.implClose();
1321             // remove audioData reference and hand it over to gc
1322             audioData = null;
1323             newFramePosition = -1;
1324 
1325             // remove this instance from the list of auto closing clips
1326             getEventDispatcher().autoClosingClipClosed(this);
1327 
1328             if (Printer.trace) Printer.trace("<< DirectClip: implClose() succeeded");
1329         }
1330 
1331         @Override
1332         void implStart() {
1333             if (Printer.trace) Printer.trace("> DirectClip: implStart()");
1334             super.implStart();
1335             if (Printer.trace) Printer.trace("< DirectClip: implStart() succeeded");
1336         }
1337 
1338         @Override
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         // main playback loop
1351         @Override
1352         public void run() {
1353             if (Printer.trace) Printer.trace(">>> DirectClip: run() threadID="+Thread.currentThread().getId());
1354             Thread curThread = Thread.currentThread();
1355             while (thread == curThread) {
1356                 // doIO is volatile, but we could check it, then get
1357                 // pre-empted while another thread changes doIO and notifies,
1358                 // before we wait (so we sleep in wait forever).
1359                 synchronized(lock) {
1360                     while (!doIO && thread == curThread) {
1361                         try {
1362                             lock.wait();
1363                         } catch (InterruptedException ignored) {
1364                         }
1365                     }
1366                 }
1367                 while (doIO && thread == curThread) {
1368                     if (newFramePosition >= 0) {
1369                         clipBytePosition = newFramePosition * frameSize;
1370                         newFramePosition = -1;
1371                     }
1372                     int endFrame = getFrameLength() - 1;
1373                     if (loopCount > 0 || loopCount == LOOP_CONTINUOUSLY) {
1374                         endFrame = loopEndFrame;
1375                     }
1376                     long framePos = (clipBytePosition / frameSize);
1377                     int toWriteFrames = (int) (endFrame - framePos + 1);
1378                     int toWriteBytes = toWriteFrames * frameSize;
1379                     if (toWriteBytes > getBufferSize()) {
1380                         toWriteBytes = Toolkit.align(getBufferSize(), frameSize);
1381                     }
1382                     int written = write(audioData, clipBytePosition, toWriteBytes); // increases bytePosition
1383                     clipBytePosition += written;
1384                     // make sure nobody called setFramePosition, or stop() during the write() call
1385                     if (doIO && newFramePosition < 0 && written >= 0) {
1386                         framePos = clipBytePosition / frameSize;
1387                         // since endFrame is the last frame to be played,
1388                         // framePos is after endFrame when all frames, including framePos,
1389                         // are played.
1390                         if (framePos > endFrame) {
1391                             // at end of playback. If looping is on, loop back to the beginning.
1392                             if (loopCount > 0 || loopCount == LOOP_CONTINUOUSLY) {
1393                                 if (loopCount != LOOP_CONTINUOUSLY) {
1394                                     loopCount--;
1395                                 }
1396                                 newFramePosition = loopStartFrame;
1397                             } else {
1398                                 // no looping, stop playback
1399                                 if (Printer.debug) Printer.debug("stop clip in run() loop:");
1400                                 if (Printer.debug) Printer.debug("  doIO="+doIO+" written="+written+" clipBytePosition="+clipBytePosition);
1401                                 if (Printer.debug) Printer.debug("  framePos="+framePos+" endFrame="+endFrame);
1402                                 drain();
1403                                 stop();
1404                             }
1405                         }
1406                     }
1407                 }
1408             }
1409             if (Printer.trace) Printer.trace("<<< DirectClip: run() threadID="+Thread.currentThread().getId());
1410         }
1411 
1412         // AUTO CLOSING CLIP SUPPORT
1413 
1414         /* $$mp 2003-10-01
1415            The following two methods are common between this class and
1416            MixerClip. They should be moved to a base class, together
1417            with the instance variable 'autoclosing'. */
1418 
1419         @Override
1420         public boolean isAutoClosing() {
1421             return autoclosing;
1422         }
1423 
1424         @Override
1425         public void setAutoClosing(boolean value) {
1426             if (value != autoclosing) {
1427                 if (isOpen()) {
1428                     if (value) {
1429                         getEventDispatcher().autoClosingClipOpened(this);
1430                     } else {
1431                         getEventDispatcher().autoClosingClipClosed(this);
1432                     }
1433                 }
1434                 autoclosing = value;
1435             }
1436         }
1437 
1438         @Override
1439         protected boolean requiresServicing() {
1440             // no need for servicing for Clips
1441             return false;
1442         }
1443 
1444     } // DirectClip
1445 
1446     /*
1447      * private inner class representing a ByteArrayOutputStream
1448      * which allows retrieval of the internal array
1449      */
1450     private static class DirectBAOS extends ByteArrayOutputStream {
1451         DirectBAOS() {
1452             super();
1453         }
1454 
1455         public byte[] getInternalBuffer() {
1456             return buf;
1457         }
1458 
1459     } // class DirectBAOS
1460 
1461     @SuppressWarnings("rawtypes")
1462     private static native void nGetFormats(int mixerIndex, int deviceID,
1463                                            boolean isSource, Vector formats);
1464 
1465     private static native long nOpen(int mixerIndex, int deviceID, boolean isSource,
1466                                      int encoding,
1467                                      float sampleRate,
1468                                      int sampleSizeInBits,
1469                                      int frameSize,
1470                                      int channels,
1471                                      boolean signed,
1472                                      boolean bigEndian,
1473                                      int bufferSize) throws LineUnavailableException;
1474     private static native void nStart(long id, boolean isSource);
1475     private static native void nStop(long id, boolean isSource);
1476     private static native void nClose(long id, boolean isSource);
1477     private static native int nWrite(long id, byte[] b, int off, int len, int conversionSize,
1478                                      float volLeft, float volRight);
1479     private static native int nRead(long id, byte[] b, int off, int len, int conversionSize);
1480     private static native int nGetBufferSize(long id, boolean isSource);
1481     private static native boolean nIsStillDraining(long id, boolean isSource);
1482     private static native void nFlush(long id, boolean isSource);
1483     private static native int nAvailable(long id, boolean isSource);
1484     // javaPos is number of bytes read/written in Java layer
1485     private static native long nGetBytePosition(long id, boolean isSource, long javaPos);
1486     private static native void nSetBytePosition(long id, boolean isSource, long pos);
1487 
1488     // returns if the native implementation needs regular calls to nService()
1489     private static native boolean nRequiresServicing(long id, boolean isSource);
1490     // called in irregular intervals
1491     private static native void nService(long id, boolean isSource);
1492 }