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