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