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