1 /*
   2  * Copyright (c) 2002, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.media.sound;
  27 
  28 import java.io.ByteArrayOutputStream;
  29 import java.io.IOException;
  30 import java.util.Vector;
  31 
  32 import javax.sound.sampled.*;
  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 final 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<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     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<AudioFormat> 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 final class DirectDLI extends DataLine.Info {
 339         final 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 final int mixerIndex;
 374         protected final int deviceID;
 375         protected long id;
 376         protected int waitTime;
 377         protected volatile boolean flushing = false;
 378         protected final 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 occurred 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 final Gain gainControl = new Gain();
 391         private final Mute muteControl = new Mute();
 392         private final Balance balanceControl = new Balance();
 393         private final 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 = ( 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 occurred 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 ((long)off + (long)len > (long)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 final 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 final 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 final 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 final 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 final class DirectSDL extends DirectDL
 922             implements SourceDataLine {
 923 
 924         // CONSTRUCTOR
 925         private DirectSDL(DataLine.Info info,
 926                           AudioFormat format,
 927                           int bufferSize,
 928                           DirectAudioDevice mixer) {
 929             super(info, mixer, format, bufferSize, mixer.getMixerIndex(), mixer.getDeviceID(), true);
 930             if (Printer.trace) Printer.trace("DirectSDL CONSTRUCTOR: completed");
 931         }
 932 
 933     }
 934 
 935     /**
 936      * Private inner class representing a TargetDataLine
 937      */
 938     private static final class DirectTDL extends DirectDL
 939             implements TargetDataLine {
 940 
 941         // CONSTRUCTOR
 942         private DirectTDL(DataLine.Info info,
 943                           AudioFormat format,
 944                           int bufferSize,
 945                           DirectAudioDevice mixer) {
 946             super(info, mixer, format, bufferSize, mixer.getMixerIndex(), mixer.getDeviceID(), false);
 947             if (Printer.trace) Printer.trace("DirectTDL CONSTRUCTOR: completed");
 948         }
 949 
 950         // METHOD OVERRIDES
 951 
 952         public int read(byte[] b, int off, int len) {
 953             flushing = false;
 954             if (len == 0) {
 955                 return 0;
 956             }
 957             if (len < 0) {
 958                 throw new IllegalArgumentException("illegal len: "+len);
 959             }
 960             if (len % getFormat().getFrameSize() != 0) {
 961                 throw new IllegalArgumentException("illegal request to read "
 962                                                    +"non-integral number of frames ("
 963                                                    +len+" bytes, "
 964                                                    +"frameSize = "+getFormat().getFrameSize()+" bytes)");
 965             }
 966             if (off < 0) {
 967                 throw new ArrayIndexOutOfBoundsException(off);
 968             }
 969             if ((long)off + (long)len > (long)b.length) {
 970                 throw new ArrayIndexOutOfBoundsException(b.length);
 971             }
 972             if (!isActive() && doIO) {
 973                 // this is not exactly correct... would be nicer
 974                 // if the native sub system sent a callback when IO really starts
 975                 setActive(true);
 976                 setStarted(true);
 977             }
 978             int read = 0;
 979             while (doIO && !flushing) {
 980                 int thisRead;
 981                 synchronized (lockNative) {
 982                     thisRead = nRead(id, b, off, len, softwareConversionSize);
 983                     if (thisRead < 0) {
 984                         // error in native layer
 985                         break;
 986                     }
 987                     bytePosition += thisRead;
 988                     if (thisRead > 0) {
 989                         drained = false;
 990                     }
 991                 }
 992                 len -= thisRead;
 993                 read += thisRead;
 994                 if (len > 0) {
 995                     off += thisRead;
 996                     synchronized(lock) {
 997                         try {
 998                             lock.wait(waitTime);
 999                         } catch (InterruptedException ie) {}
1000                     }
1001                 } else {
1002                     break;
1003                 }
1004             }
1005             if (flushing) {
1006                 read = 0;
1007             }
1008             return read;
1009         }
1010 
1011     }
1012 
1013     /**
1014      * Private inner class representing a Clip
1015      * This clip is realized in software only
1016      */
1017     private static final class DirectClip extends DirectDL
1018             implements Clip, Runnable, AutoClosingClip {
1019 
1020         private Thread thread;
1021         private byte[] audioData = null;
1022         private int frameSize;         // size of one frame in bytes
1023         private int m_lengthInFrames;
1024         private int loopCount;
1025         private int clipBytePosition;   // index in the audioData array at current playback
1026         private int newFramePosition;   // set in setFramePosition()
1027         private int loopStartFrame;
1028         private int loopEndFrame;      // the last sample included in the loop
1029 
1030         // auto closing clip support
1031         private boolean autoclosing = false;
1032 
1033         // CONSTRUCTOR
1034         private DirectClip(DataLine.Info info,
1035                            AudioFormat format,
1036                            int bufferSize,
1037                            DirectAudioDevice mixer) {
1038             super(info, mixer, format, bufferSize, mixer.getMixerIndex(), mixer.getDeviceID(), true);
1039             if (Printer.trace) Printer.trace("DirectClip CONSTRUCTOR: completed");
1040         }
1041 
1042         // CLIP METHODS
1043 
1044         public void open(AudioFormat format, byte[] data, int offset, int bufferSize)
1045             throws LineUnavailableException {
1046 
1047             // $$fb part of fix for 4679187: Clip.open() throws unexpected Exceptions
1048             Toolkit.isFullySpecifiedAudioFormat(format);
1049 
1050             byte[] newData = new byte[bufferSize];
1051             System.arraycopy(data, offset, newData, 0, bufferSize);
1052             open(format, newData, bufferSize / format.getFrameSize());
1053         }
1054 
1055         // this method does not copy the data array
1056         private void open(AudioFormat format, byte[] data, int frameLength)
1057             throws LineUnavailableException {
1058 
1059             // $$fb part of fix for 4679187: Clip.open() throws unexpected Exceptions
1060             Toolkit.isFullySpecifiedAudioFormat(format);
1061 
1062             synchronized (mixer) {
1063                 if (Printer.trace) Printer.trace("> DirectClip.open(format, data, frameLength)");
1064                 if (Printer.debug) Printer.debug("   data="+((data==null)?"null":""+data.length+" bytes"));
1065                 if (Printer.debug) Printer.debug("   frameLength="+frameLength);
1066 
1067                 if (isOpen()) {
1068                     throw new IllegalStateException("Clip is already open with format " + getFormat() +
1069                                                     " and frame lengh of " + getFrameLength());
1070                 } else {
1071                     // if the line is not currently open, try to open it with this format and buffer size
1072                     this.audioData = data;
1073                     this.frameSize = format.getFrameSize();
1074                     this.m_lengthInFrames = frameLength;
1075                     // initialize loop selection with full range
1076                     bytePosition = 0;
1077                     clipBytePosition = 0;
1078                     newFramePosition = -1; // means: do not set to a new readFramePos
1079                     loopStartFrame = 0;
1080                     loopEndFrame = frameLength - 1;
1081                     loopCount = 0; // means: play the clip irrespective of loop points from beginning to end
1082 
1083                     try {
1084                         // use DirectDL's open method to open it
1085                         open(format, (int) Toolkit.millis2bytes(format, CLIP_BUFFER_TIME)); // one second buffer
1086                     } catch (LineUnavailableException lue) {
1087                         audioData = null;
1088                         throw lue;
1089                     } catch (IllegalArgumentException iae) {
1090                         audioData = null;
1091                         throw iae;
1092                     }
1093 
1094                     // if we got this far, we can instanciate the thread
1095                     int priority = Thread.NORM_PRIORITY
1096                         + (Thread.MAX_PRIORITY - Thread.NORM_PRIORITY) / 3;
1097                     thread = JSSecurityManager.createThread(this,
1098                                                             "Direct Clip", // name
1099                                                             true,     // daemon
1100                                                             priority, // priority
1101                                                             false);  // doStart
1102                     // cannot start in createThread, because the thread
1103                     // uses the "thread" variable as indicator if it should
1104                     // continue to run
1105                     thread.start();
1106                 }
1107             }
1108             if (isAutoClosing()) {
1109                 getEventDispatcher().autoClosingClipOpened(this);
1110             }
1111             if (Printer.trace) Printer.trace("< DirectClip.open completed");
1112         }
1113 
1114 
1115         public void open(AudioInputStream stream) throws LineUnavailableException, IOException {
1116 
1117             // $$fb part of fix for 4679187: Clip.open() throws unexpected Exceptions
1118             Toolkit.isFullySpecifiedAudioFormat(format);
1119 
1120             synchronized (mixer) {
1121                 if (Printer.trace) Printer.trace("> DirectClip.open(stream)");
1122                 byte[] streamData = null;
1123 
1124                 if (isOpen()) {
1125                     throw new IllegalStateException("Clip is already open with format " + getFormat() +
1126                                                     " and frame lengh of " + getFrameLength());
1127                 }
1128                 int lengthInFrames = (int)stream.getFrameLength();
1129                 if (Printer.debug) Printer.debug("DirectClip: open(AIS): lengthInFrames: " + lengthInFrames);
1130 
1131                 int bytesRead = 0;
1132                 if (lengthInFrames != AudioSystem.NOT_SPECIFIED) {
1133                     // read the data from the stream into an array in one fell swoop.
1134                     int arraysize = lengthInFrames * stream.getFormat().getFrameSize();
1135                     streamData = new byte[arraysize];
1136 
1137                     int bytesRemaining = arraysize;
1138                     int thisRead = 0;
1139                     while (bytesRemaining > 0 && thisRead >= 0) {
1140                         thisRead = stream.read(streamData, bytesRead, bytesRemaining);
1141                         if (thisRead > 0) {
1142                             bytesRead += thisRead;
1143                             bytesRemaining -= thisRead;
1144                         }
1145                         else if (thisRead == 0) {
1146                             Thread.yield();
1147                         }
1148                     }
1149                 } else {
1150                     // read data from the stream until we reach the end of the stream
1151                     // we use a slightly modified version of ByteArrayOutputStream
1152                     // to get direct access to the byte array (we don't want a new array
1153                     // to be allocated)
1154                     int MAX_READ_LIMIT = 16384;
1155                     DirectBAOS dbaos  = new DirectBAOS();
1156                     byte tmp[] = new byte[MAX_READ_LIMIT];
1157                     int thisRead = 0;
1158                     while (thisRead >= 0) {
1159                         thisRead = stream.read(tmp, 0, tmp.length);
1160                         if (thisRead > 0) {
1161                             dbaos.write(tmp, 0, thisRead);
1162                             bytesRead += thisRead;
1163                         }
1164                         else if (thisRead == 0) {
1165                             Thread.yield();
1166                         }
1167                     } // while
1168                     streamData = dbaos.getInternalBuffer();
1169                 }
1170                 lengthInFrames = bytesRead / stream.getFormat().getFrameSize();
1171 
1172                 if (Printer.debug) Printer.debug("Read to end of stream. lengthInFrames: " + lengthInFrames);
1173 
1174                 // now try to open the device
1175                 open(stream.getFormat(), streamData, lengthInFrames);
1176 
1177                 if (Printer.trace) Printer.trace("< DirectClip.open(stream) succeeded");
1178             } // synchronized
1179         }
1180 
1181 
1182         public int getFrameLength() {
1183             return m_lengthInFrames;
1184         }
1185 
1186 
1187         public long getMicrosecondLength() {
1188             return Toolkit.frames2micros(getFormat(), getFrameLength());
1189         }
1190 
1191 
1192         public void setFramePosition(int frames) {
1193             if (Printer.trace) Printer.trace("> DirectClip: setFramePosition: " + frames);
1194 
1195             if (frames < 0) {
1196                 frames = 0;
1197             }
1198             else if (frames >= getFrameLength()) {
1199                 frames = getFrameLength();
1200             }
1201             if (doIO) {
1202                 newFramePosition = frames;
1203             } else {
1204                 clipBytePosition = frames * frameSize;
1205                 newFramePosition = -1;
1206             }
1207             // fix for failing test050
1208             // $$fb although getFramePosition should return the number of rendered
1209             // frames, it is intuitive that setFramePosition will modify that
1210             // value.
1211             bytePosition = frames * frameSize;
1212 
1213             // cease currently playing buffer
1214             flush();
1215 
1216             // set new native position (if necessary)
1217             // this must come after the flush!
1218             synchronized (lockNative) {
1219                 nSetBytePosition(id, isSource, frames * frameSize);
1220             }
1221 
1222             if (Printer.debug) Printer.debug("  DirectClip.setFramePosition: "
1223                                              +" doIO="+doIO
1224                                              +" newFramePosition="+newFramePosition
1225                                              +" clipBytePosition="+clipBytePosition
1226                                              +" bytePosition="+bytePosition
1227                                              +" getLongFramePosition()="+getLongFramePosition());
1228             if (Printer.trace) Printer.trace("< DirectClip: setFramePosition");
1229         }
1230 
1231         // replacement for getFramePosition (see AbstractDataLine)
1232         public long getLongFramePosition() {
1233             /* $$fb
1234              * this would be intuitive, but the definition of getFramePosition
1235              * is the number of frames rendered since opening the device...
1236              * That also means that setFramePosition() means something very
1237              * different from getFramePosition() for Clip.
1238              */
1239             // take into account the case that a new position was set...
1240             //if (!doIO && newFramePosition >= 0) {
1241             //return newFramePosition;
1242             //}
1243             return super.getLongFramePosition();
1244         }
1245 
1246 
1247         public synchronized void setMicrosecondPosition(long microseconds) {
1248             if (Printer.trace) Printer.trace("> DirectClip: setMicrosecondPosition: " + microseconds);
1249 
1250             long frames = Toolkit.micros2frames(getFormat(), microseconds);
1251             setFramePosition((int) frames);
1252 
1253             if (Printer.trace) Printer.trace("< DirectClip: setMicrosecondPosition succeeded");
1254         }
1255 
1256         public void setLoopPoints(int start, int end) {
1257             if (Printer.trace) Printer.trace("> DirectClip: setLoopPoints: start: " + start + " end: " + end);
1258 
1259             if (start < 0 || start >= getFrameLength()) {
1260                 throw new IllegalArgumentException("illegal value for start: "+start);
1261             }
1262             if (end >= getFrameLength()) {
1263                 throw new IllegalArgumentException("illegal value for end: "+end);
1264             }
1265 
1266             if (end == -1) {
1267                 end = getFrameLength() - 1;
1268                 if (end < 0) {
1269                     end = 0;
1270                 }
1271             }
1272 
1273             // if the end position is less than the start position, throw IllegalArgumentException
1274             if (end < start) {
1275                 throw new IllegalArgumentException("End position " + end + "  preceeds start position " + start);
1276             }
1277 
1278             // slight race condition with the run() method, but not a big problem
1279             loopStartFrame = start;
1280             loopEndFrame = end;
1281 
1282             if (Printer.trace) Printer.trace("  loopStart: " + loopStartFrame + " loopEnd: " + loopEndFrame);
1283             if (Printer.trace) Printer.trace("< DirectClip: setLoopPoints completed");
1284         }
1285 
1286 
1287         public void loop(int count) {
1288             // note: when count reaches 0, it means that the entire clip
1289             // will be played, i.e. it will play past the loop end point
1290             loopCount = count;
1291             start();
1292         }
1293 
1294         // ABSTRACT METHOD IMPLEMENTATIONS
1295 
1296         // ABSTRACT LINE
1297 
1298         void implOpen(AudioFormat format, int bufferSize) throws LineUnavailableException {
1299             // only if audioData wasn't set in a calling open(format, byte[], frameSize)
1300             // this call is allowed.
1301             if (audioData == null) {
1302                 throw new IllegalArgumentException("illegal call to open() in interface Clip");
1303             }
1304             super.implOpen(format, bufferSize);
1305         }
1306 
1307         void implClose() {
1308             if (Printer.trace) Printer.trace(">> DirectClip: implClose()");
1309 
1310             // dispose of thread
1311             Thread oldThread = thread;
1312             thread = null;
1313             doIO = false;
1314             if (oldThread != null) {
1315                 // wake up the thread if it's in wait()
1316                 synchronized(lock) {
1317                     lock.notifyAll();
1318                 }
1319                 // wait for the thread to terminate itself,
1320                 // but max. 2 seconds. Must not be synchronized!
1321                 try {
1322                     oldThread.join(2000);
1323                 } catch (InterruptedException ie) {}
1324             }
1325             super.implClose();
1326             // remove audioData reference and hand it over to gc
1327             audioData = null;
1328             newFramePosition = -1;
1329 
1330             // remove this instance from the list of auto closing clips
1331             getEventDispatcher().autoClosingClipClosed(this);
1332 
1333             if (Printer.trace) Printer.trace("<< DirectClip: implClose() succeeded");
1334         }
1335 
1336 
1337         void implStart() {
1338             if (Printer.trace) Printer.trace("> DirectClip: implStart()");
1339             super.implStart();
1340             if (Printer.trace) Printer.trace("< DirectClip: implStart() succeeded");
1341         }
1342 
1343         void implStop() {
1344             if (Printer.trace) Printer.trace(">> DirectClip: implStop()");
1345 
1346             super.implStop();
1347             // reset loopCount field so that playback will be normal with
1348             // next call to start()
1349             loopCount = 0;
1350 
1351             if (Printer.trace) Printer.trace("<< DirectClip: implStop() succeeded");
1352         }
1353 
1354 
1355         // main playback loop
1356         public void run() {
1357             if (Printer.trace) Printer.trace(">>> DirectClip: run() threadID="+Thread.currentThread().getId());
1358             while (thread != null) {
1359                 // doIO is volatile, but we could check it, then get
1360                 // pre-empted while another thread changes doIO and notifies,
1361                 // before we wait (so we sleep in wait forever).
1362                 synchronized(lock) {
1363                     if (!doIO) {
1364                         try {
1365                             lock.wait();
1366                         } catch(InterruptedException ie) {}
1367                     }
1368                 }
1369                 while (doIO) {
1370                     if (newFramePosition >= 0) {
1371                         clipBytePosition = newFramePosition * frameSize;
1372                         newFramePosition = -1;
1373                     }
1374                     int endFrame = getFrameLength() - 1;
1375                     if (loopCount > 0 || loopCount == LOOP_CONTINUOUSLY) {
1376                         endFrame = loopEndFrame;
1377                     }
1378                     long framePos = (clipBytePosition / frameSize);
1379                     int toWriteFrames = (int) (endFrame - framePos + 1);
1380                     int toWriteBytes = toWriteFrames * frameSize;
1381                     if (toWriteBytes > getBufferSize()) {
1382                         toWriteBytes = Toolkit.align(getBufferSize(), frameSize);
1383                     }
1384                     int written = write(audioData, clipBytePosition, toWriteBytes); // increases bytePosition
1385                     clipBytePosition += written;
1386                     // make sure nobody called setFramePosition, or stop() during the write() call
1387                     if (doIO && newFramePosition < 0 && written >= 0) {
1388                         framePos = clipBytePosition / frameSize;
1389                         // since endFrame is the last frame to be played,
1390                         // framePos is after endFrame when all frames, including framePos,
1391                         // are played.
1392                         if (framePos > endFrame) {
1393                             // at end of playback. If looping is on, loop back to the beginning.
1394                             if (loopCount > 0 || loopCount == LOOP_CONTINUOUSLY) {
1395                                 if (loopCount != LOOP_CONTINUOUSLY) {
1396                                     loopCount--;
1397                                 }
1398                                 newFramePosition = loopStartFrame;
1399                             } else {
1400                                 // no looping, stop playback
1401                                 if (Printer.debug) Printer.debug("stop clip in run() loop:");
1402                                 if (Printer.debug) Printer.debug("  doIO="+doIO+" written="+written+" clipBytePosition="+clipBytePosition);
1403                                 if (Printer.debug) Printer.debug("  framePos="+framePos+" endFrame="+endFrame);
1404                                 drain();
1405                                 stop();
1406                             }
1407                         }
1408                     }
1409                 }
1410             }
1411             if (Printer.trace) Printer.trace("<<< DirectClip: run() threadID="+Thread.currentThread().getId());
1412         }
1413 
1414         // AUTO CLOSING CLIP SUPPORT
1415 
1416         /* $$mp 2003-10-01
1417            The following two methods are common between this class and
1418            MixerClip. They should be moved to a base class, together
1419            with the instance variable 'autoclosing'. */
1420 
1421         public boolean isAutoClosing() {
1422             return autoclosing;
1423         }
1424 
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         protected boolean requiresServicing() {
1439             // no need for servicing for Clips
1440             return false;
1441         }
1442 
1443     } // DirectClip
1444 
1445     /*
1446      * private inner class representing a ByteArrayOutputStream
1447      * which allows retrieval of the internal array
1448      */
1449     private static class DirectBAOS extends ByteArrayOutputStream {
1450         DirectBAOS() {
1451             super();
1452         }
1453 
1454         public byte[] getInternalBuffer() {
1455             return buf;
1456         }
1457 
1458     } // class DirectBAOS
1459 
1460     @SuppressWarnings("rawtypes")
1461     private static native void nGetFormats(int mixerIndex, int deviceID,
1462                                            boolean isSource, Vector formats);
1463 
1464     private static native long nOpen(int mixerIndex, int deviceID, boolean isSource,
1465                                      int encoding,
1466                                      float sampleRate,
1467                                      int sampleSizeInBits,
1468                                      int frameSize,
1469                                      int channels,
1470                                      boolean signed,
1471                                      boolean bigEndian,
1472                                      int bufferSize) throws LineUnavailableException;
1473     private static native void nStart(long id, boolean isSource);
1474     private static native void nStop(long id, boolean isSource);
1475     private static native void nClose(long id, boolean isSource);
1476     private static native int nWrite(long id, byte[] b, int off, int len, int conversionSize,
1477                                      float volLeft, float volRight);
1478     private static native int nRead(long id, byte[] b, int off, int len, int conversionSize);
1479     private static native int nGetBufferSize(long id, boolean isSource);
1480     private static native boolean nIsStillDraining(long id, boolean isSource);
1481     private static native void nFlush(long id, boolean isSource);
1482     private static native int nAvailable(long id, boolean isSource);
1483     // javaPos is number of bytes read/written in Java layer
1484     private static native long nGetBytePosition(long id, boolean isSource, long javaPos);
1485     private static native void nSetBytePosition(long id, boolean isSource, long pos);
1486 
1487     // returns if the native implementation needs regular calls to nService()
1488     private static native boolean nRequiresServicing(long id, boolean isSource);
1489     // called in irregular intervals
1490     private static native void nService(long id, boolean isSource);
1491 
1492 }