1 /*
2  * Copyright (c) 2008, 2013, 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.IOException;
29 import java.util.ArrayList;
30 import java.util.List;
31 
32 import javax.sound.sampled.AudioFormat;
33 import javax.sound.sampled.AudioFormat.Encoding;
34 import javax.sound.sampled.AudioInputStream;
35 import javax.sound.sampled.AudioSystem;
36 import javax.sound.sampled.Clip;
37 import javax.sound.sampled.Control;
38 import javax.sound.sampled.Control.Type;
39 import javax.sound.sampled.DataLine;
40 import javax.sound.sampled.Line;
41 import javax.sound.sampled.LineEvent;
42 import javax.sound.sampled.LineListener;
43 import javax.sound.sampled.LineUnavailableException;
44 import javax.sound.sampled.Mixer;
45 import javax.sound.sampled.SourceDataLine;
46 
47 /**
48  * Software audio mixer.
49  *
50  * @author Karl Helgason
51  */
52 public final class SoftMixingMixer implements Mixer {
53 
54     private static class Info extends Mixer.Info {
Info()55         Info() {
56             super(INFO_NAME, INFO_VENDOR, INFO_DESCRIPTION, INFO_VERSION);
57         }
58     }
59 
60     static final String INFO_NAME = "Gervill Sound Mixer";
61 
62     static final String INFO_VENDOR = "OpenJDK Proposal";
63 
64     static final String INFO_DESCRIPTION = "Software Sound Mixer";
65 
66     static final String INFO_VERSION = "1.0";
67 
68     static final Mixer.Info info = new Info();
69 
70     final Object control_mutex = this;
71 
72     boolean implicitOpen = false;
73 
74     private boolean open = false;
75 
76     private SoftMixingMainMixer mainmixer = null;
77 
78     private AudioFormat format = new AudioFormat(44100, 16, 2, true, false);
79 
80     private SourceDataLine sourceDataLine = null;
81 
82     private SoftAudioPusher pusher = null;
83 
84     private AudioInputStream pusher_stream = null;
85 
86     private final float controlrate = 147f;
87 
88     private final long latency = 100000; // 100 msec
89 
90     private final boolean jitter_correction = false;
91 
92     private final List<LineListener> listeners = new ArrayList<>();
93 
94     private final javax.sound.sampled.Line.Info[] sourceLineInfo;
95 
SoftMixingMixer()96     public SoftMixingMixer() {
97 
98         sourceLineInfo = new javax.sound.sampled.Line.Info[2];
99 
100         ArrayList<AudioFormat> formats = new ArrayList<>();
101         for (int channels = 1; channels <= 2; channels++) {
102             formats.add(new AudioFormat(Encoding.PCM_SIGNED,
103                     AudioSystem.NOT_SPECIFIED, 8, channels, channels,
104                     AudioSystem.NOT_SPECIFIED, false));
105             formats.add(new AudioFormat(Encoding.PCM_UNSIGNED,
106                     AudioSystem.NOT_SPECIFIED, 8, channels, channels,
107                     AudioSystem.NOT_SPECIFIED, false));
108             for (int bits = 16; bits < 32; bits += 8) {
109                 formats.add(new AudioFormat(Encoding.PCM_SIGNED,
110                         AudioSystem.NOT_SPECIFIED, bits, channels, channels
111                                 * bits / 8, AudioSystem.NOT_SPECIFIED, false));
112                 formats.add(new AudioFormat(Encoding.PCM_UNSIGNED,
113                         AudioSystem.NOT_SPECIFIED, bits, channels, channels
114                                 * bits / 8, AudioSystem.NOT_SPECIFIED, false));
115                 formats.add(new AudioFormat(Encoding.PCM_SIGNED,
116                         AudioSystem.NOT_SPECIFIED, bits, channels, channels
117                                 * bits / 8, AudioSystem.NOT_SPECIFIED, true));
118                 formats.add(new AudioFormat(Encoding.PCM_UNSIGNED,
119                         AudioSystem.NOT_SPECIFIED, bits, channels, channels
120                                 * bits / 8, AudioSystem.NOT_SPECIFIED, true));
121             }
122             formats.add(new AudioFormat(Encoding.PCM_FLOAT,
123                     AudioSystem.NOT_SPECIFIED, 32, channels, channels * 4,
124                     AudioSystem.NOT_SPECIFIED, false));
125             formats.add(new AudioFormat(Encoding.PCM_FLOAT,
126                     AudioSystem.NOT_SPECIFIED, 32, channels, channels * 4,
127                     AudioSystem.NOT_SPECIFIED, true));
128             formats.add(new AudioFormat(Encoding.PCM_FLOAT,
129                     AudioSystem.NOT_SPECIFIED, 64, channels, channels * 8,
130                     AudioSystem.NOT_SPECIFIED, false));
131             formats.add(new AudioFormat(Encoding.PCM_FLOAT,
132                     AudioSystem.NOT_SPECIFIED, 64, channels, channels * 8,
133                     AudioSystem.NOT_SPECIFIED, true));
134         }
135         AudioFormat[] formats_array = formats.toArray(new AudioFormat[formats
136                 .size()]);
137         sourceLineInfo[0] = new DataLine.Info(SourceDataLine.class,
138                 formats_array, AudioSystem.NOT_SPECIFIED,
139                 AudioSystem.NOT_SPECIFIED);
140         sourceLineInfo[1] = new DataLine.Info(Clip.class, formats_array,
141                 AudioSystem.NOT_SPECIFIED, AudioSystem.NOT_SPECIFIED);
142     }
143 
144     @Override
getLine(Line.Info info)145     public Line getLine(Line.Info info) throws LineUnavailableException {
146 
147         if (!isLineSupported(info))
148             throw new IllegalArgumentException("Line unsupported: " + info);
149 
150         if ((info.getLineClass() == SourceDataLine.class)) {
151             return new SoftMixingSourceDataLine(this, (DataLine.Info) info);
152         }
153         if ((info.getLineClass() == Clip.class)) {
154             return new SoftMixingClip(this, (DataLine.Info) info);
155         }
156 
157         throw new IllegalArgumentException("Line unsupported: " + info);
158     }
159 
160     @Override
getMaxLines(Line.Info info)161     public int getMaxLines(Line.Info info) {
162         if (info.getLineClass() == SourceDataLine.class)
163             return AudioSystem.NOT_SPECIFIED;
164         if (info.getLineClass() == Clip.class)
165             return AudioSystem.NOT_SPECIFIED;
166         return 0;
167     }
168 
169     @Override
getMixerInfo()170     public javax.sound.sampled.Mixer.Info getMixerInfo() {
171         return info;
172     }
173 
174     @Override
getSourceLineInfo()175     public javax.sound.sampled.Line.Info[] getSourceLineInfo() {
176         Line.Info[] localArray = new Line.Info[sourceLineInfo.length];
177         System.arraycopy(sourceLineInfo, 0, localArray, 0,
178                 sourceLineInfo.length);
179         return localArray;
180     }
181 
182     @Override
getSourceLineInfo( javax.sound.sampled.Line.Info info)183     public javax.sound.sampled.Line.Info[] getSourceLineInfo(
184             javax.sound.sampled.Line.Info info) {
185         int i;
186         ArrayList<javax.sound.sampled.Line.Info> infos = new ArrayList<>();
187 
188         for (i = 0; i < sourceLineInfo.length; i++) {
189             if (info.matches(sourceLineInfo[i])) {
190                 infos.add(sourceLineInfo[i]);
191             }
192         }
193         return infos.toArray(new Line.Info[infos.size()]);
194     }
195 
196     @Override
getSourceLines()197     public Line[] getSourceLines() {
198 
199         Line[] localLines;
200 
201         synchronized (control_mutex) {
202 
203             if (mainmixer == null)
204                 return new Line[0];
205             SoftMixingDataLine[] sourceLines = mainmixer.getOpenLines();
206 
207             localLines = new Line[sourceLines.length];
208 
209             for (int i = 0; i < localLines.length; i++) {
210                 localLines[i] = sourceLines[i];
211             }
212         }
213 
214         return localLines;
215     }
216 
217     @Override
getTargetLineInfo()218     public javax.sound.sampled.Line.Info[] getTargetLineInfo() {
219         return new javax.sound.sampled.Line.Info[0];
220     }
221 
222     @Override
getTargetLineInfo( javax.sound.sampled.Line.Info info)223     public javax.sound.sampled.Line.Info[] getTargetLineInfo(
224             javax.sound.sampled.Line.Info info) {
225         return new javax.sound.sampled.Line.Info[0];
226     }
227 
228     @Override
getTargetLines()229     public Line[] getTargetLines() {
230         return new Line[0];
231     }
232 
233     @Override
isLineSupported(javax.sound.sampled.Line.Info info)234     public boolean isLineSupported(javax.sound.sampled.Line.Info info) {
235         if (info != null) {
236             for (int i = 0; i < sourceLineInfo.length; i++) {
237                 if (info.matches(sourceLineInfo[i])) {
238                     return true;
239                 }
240             }
241         }
242         return false;
243     }
244 
245     @Override
isSynchronizationSupported(Line[] lines, boolean maintainSync)246     public boolean isSynchronizationSupported(Line[] lines, boolean maintainSync) {
247         return false;
248     }
249 
250     @Override
synchronize(Line[] lines, boolean maintainSync)251     public void synchronize(Line[] lines, boolean maintainSync) {
252         throw new IllegalArgumentException(
253                 "Synchronization not supported by this mixer.");
254     }
255 
256     @Override
unsynchronize(Line[] lines)257     public void unsynchronize(Line[] lines) {
258         throw new IllegalArgumentException(
259                 "Synchronization not supported by this mixer.");
260     }
261 
262     @Override
addLineListener(LineListener listener)263     public void addLineListener(LineListener listener) {
264         synchronized (control_mutex) {
265             listeners.add(listener);
266         }
267     }
268 
sendEvent(LineEvent event)269     private void sendEvent(LineEvent event) {
270         if (listeners.size() == 0)
271             return;
272         LineListener[] listener_array = listeners
273                 .toArray(new LineListener[listeners.size()]);
274         for (LineListener listener : listener_array) {
275             listener.update(event);
276         }
277     }
278 
279     @Override
close()280     public void close() {
281         if (!isOpen())
282             return;
283 
284         sendEvent(new LineEvent(this, LineEvent.Type.CLOSE,
285                 AudioSystem.NOT_SPECIFIED));
286 
287         SoftAudioPusher pusher_to_be_closed = null;
288         AudioInputStream pusher_stream_to_be_closed = null;
289         synchronized (control_mutex) {
290             if (pusher != null) {
291                 pusher_to_be_closed = pusher;
292                 pusher_stream_to_be_closed = pusher_stream;
293                 pusher = null;
294                 pusher_stream = null;
295             }
296         }
297 
298         if (pusher_to_be_closed != null) {
299             // Pusher must not be closed synchronized against control_mutex
300             // this may result in synchronized conflict between pusher and
301             // current thread.
302             pusher_to_be_closed.stop();
303 
304             try {
305                 pusher_stream_to_be_closed.close();
306             } catch (IOException e) {
307                 e.printStackTrace();
308             }
309         }
310 
311         synchronized (control_mutex) {
312 
313             if (mainmixer != null)
314                 mainmixer.close();
315             open = false;
316 
317             if (sourceDataLine != null) {
318                 sourceDataLine.drain();
319                 sourceDataLine.close();
320                 sourceDataLine = null;
321             }
322 
323         }
324 
325     }
326 
327     @Override
getControl(Type control)328     public Control getControl(Type control) {
329         throw new IllegalArgumentException("Unsupported control type : "
330                 + control);
331     }
332 
333     @Override
getControls()334     public Control[] getControls() {
335         return new Control[0];
336     }
337 
338     @Override
getLineInfo()339     public javax.sound.sampled.Line.Info getLineInfo() {
340         return new Line.Info(Mixer.class);
341     }
342 
343     @Override
isControlSupported(Type control)344     public boolean isControlSupported(Type control) {
345         return false;
346     }
347 
348     @Override
isOpen()349     public boolean isOpen() {
350         synchronized (control_mutex) {
351             return open;
352         }
353     }
354 
355     @Override
open()356     public void open() throws LineUnavailableException {
357         if (isOpen()) {
358             implicitOpen = false;
359             return;
360         }
361         open(null);
362     }
363 
open(SourceDataLine line)364     public void open(SourceDataLine line) throws LineUnavailableException {
365         if (isOpen()) {
366             implicitOpen = false;
367             return;
368         }
369         synchronized (control_mutex) {
370 
371             try {
372 
373                 if (line != null)
374                     format = line.getFormat();
375 
376                 AudioInputStream ais = openStream(getFormat());
377 
378                 if (line == null) {
379                     synchronized (SoftMixingMixerProvider.mutex) {
380                         SoftMixingMixerProvider.lockthread = Thread
381                                 .currentThread();
382                     }
383 
384                     try {
385                         Mixer defaultmixer = AudioSystem.getMixer(null);
386                         if (defaultmixer != null)
387                         {
388                             // Search for suitable line
389 
390                             DataLine.Info idealinfo = null;
391                             AudioFormat idealformat = null;
392 
393                             Line.Info[] lineinfos = defaultmixer.getSourceLineInfo();
394                             idealFound:
395                             for (int i = 0; i < lineinfos.length; i++) {
396                                 if(lineinfos[i].getLineClass() == SourceDataLine.class)
397                                 {
398                                     DataLine.Info info = (DataLine.Info)lineinfos[i];
399                                     AudioFormat[] formats = info.getFormats();
400                                     for (int j = 0; j < formats.length; j++) {
401                                         AudioFormat format = formats[j];
402                                         if(format.getChannels() == 2 ||
403                                                 format.getChannels() == AudioSystem.NOT_SPECIFIED)
404                                         if(format.getEncoding().equals(Encoding.PCM_SIGNED) ||
405                                                 format.getEncoding().equals(Encoding.PCM_UNSIGNED))
406                                         if(format.getSampleRate() == AudioSystem.NOT_SPECIFIED ||
407                                                 format.getSampleRate() == 48000.0)
408                                         if(format.getSampleSizeInBits() == AudioSystem.NOT_SPECIFIED ||
409                                                 format.getSampleSizeInBits() == 16)
410                                         {
411                                             idealinfo = info;
412                                             int ideal_channels = format.getChannels();
413                                             boolean ideal_signed = format.getEncoding().equals(Encoding.PCM_SIGNED);
414                                             float ideal_rate = format.getSampleRate();
415                                             boolean ideal_endian = format.isBigEndian();
416                                             int ideal_bits = format.getSampleSizeInBits();
417                                             if(ideal_bits == AudioSystem.NOT_SPECIFIED) ideal_bits = 16;
418                                             if(ideal_channels == AudioSystem.NOT_SPECIFIED) ideal_channels = 2;
419                                             if(ideal_rate == AudioSystem.NOT_SPECIFIED) ideal_rate = 48000;
420                                             idealformat = new AudioFormat(ideal_rate, ideal_bits,
421                                                     ideal_channels, ideal_signed, ideal_endian);
422                                             break idealFound;
423                                         }
424                                     }
425                                 }
426                             }
427 
428                             if(idealformat != null)
429                             {
430                                 format = idealformat;
431                                 line = (SourceDataLine) defaultmixer.getLine(idealinfo);
432                             }
433                         }
434 
435                         if(line == null)
436                             line = AudioSystem.getSourceDataLine(format);
437                     } finally {
438                         synchronized (SoftMixingMixerProvider.mutex) {
439                             SoftMixingMixerProvider.lockthread = null;
440                         }
441                     }
442 
443                     if (line == null)
444                         throw new IllegalArgumentException("No line matching "
445                                 + info.toString() + " is supported.");
446                 }
447 
448                 double latency = this.latency;
449 
450                 if (!line.isOpen()) {
451                     int bufferSize = getFormat().getFrameSize()
452                             * (int) (getFormat().getFrameRate() * (latency / 1000000f));
453                     line.open(getFormat(), bufferSize);
454 
455                     // Remember that we opened that line
456                     // so we can close again in SoftSynthesizer.close()
457                     sourceDataLine = line;
458                 }
459                 if (!line.isActive())
460                     line.start();
461 
462                 int controlbuffersize = 512;
463                 try {
464                     controlbuffersize = ais.available();
465                 } catch (IOException e) {
466                 }
467 
468                 // Tell mixer not fill read buffers fully.
469                 // This lowers latency, and tells DataPusher
470                 // to read in smaller amounts.
471                 // mainmixer.readfully = false;
472                 // pusher = new DataPusher(line, ais);
473 
474                 int buffersize = line.getBufferSize();
475                 buffersize -= buffersize % controlbuffersize;
476 
477                 if (buffersize < 3 * controlbuffersize)
478                     buffersize = 3 * controlbuffersize;
479 
480                 if (jitter_correction) {
481                     ais = new SoftJitterCorrector(ais, buffersize,
482                             controlbuffersize);
483                 }
484                 pusher = new SoftAudioPusher(line, ais, controlbuffersize);
485                 pusher_stream = ais;
486                 pusher.start();
487 
488             } catch (LineUnavailableException e) {
489                 if (isOpen())
490                     close();
491                 throw new LineUnavailableException(e.toString());
492             }
493 
494         }
495     }
496 
openStream(AudioFormat targetFormat)497     public AudioInputStream openStream(AudioFormat targetFormat)
498             throws LineUnavailableException {
499 
500         if (isOpen())
501             throw new LineUnavailableException("Mixer is already open");
502 
503         synchronized (control_mutex) {
504 
505             open = true;
506 
507             implicitOpen = false;
508 
509             if (targetFormat != null)
510                 format = targetFormat;
511 
512             mainmixer = new SoftMixingMainMixer(this);
513 
514             sendEvent(new LineEvent(this, LineEvent.Type.OPEN,
515                     AudioSystem.NOT_SPECIFIED));
516 
517             return mainmixer.getInputStream();
518 
519         }
520 
521     }
522 
523     @Override
removeLineListener(LineListener listener)524     public void removeLineListener(LineListener listener) {
525         synchronized (control_mutex) {
526             listeners.remove(listener);
527         }
528     }
529 
getLatency()530     public long getLatency() {
531         synchronized (control_mutex) {
532             return latency;
533         }
534     }
535 
getFormat()536     public AudioFormat getFormat() {
537         synchronized (control_mutex) {
538             return format;
539         }
540     }
541 
getControlRate()542     float getControlRate() {
543         return controlrate;
544     }
545 
getMainMixer()546     SoftMixingMainMixer getMainMixer() {
547         if (!isOpen())
548             return null;
549         return mainmixer;
550     }
551 }
552