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