1 /*
2 * Copyright (c) 2002, 2011, 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 }