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 }