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