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
26package com.sun.media.sound;
27
28import java.io.IOException;
29import java.util.ArrayList;
30import java.util.List;
31
32import javax.sound.sampled.AudioFormat;
33import javax.sound.sampled.AudioFormat.Encoding;
34import javax.sound.sampled.AudioInputStream;
35import javax.sound.sampled.AudioSystem;
36import javax.sound.sampled.Clip;
37import javax.sound.sampled.Control;
38import javax.sound.sampled.Control.Type;
39import javax.sound.sampled.DataLine;
40import javax.sound.sampled.Line;
41import javax.sound.sampled.LineEvent;
42import javax.sound.sampled.LineListener;
43import javax.sound.sampled.LineUnavailableException;
44import javax.sound.sampled.Mixer;
45import javax.sound.sampled.SourceDataLine;
46
47/**
48 * Software audio mixer.
49 *
50 * @author Karl Helgason
51 */
52public final class SoftMixingMixer implements Mixer {
53
54    private static class Info extends Mixer.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
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
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
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
170    public javax.sound.sampled.Mixer.Info getMixerInfo() {
171        return info;
172    }
173
174    @Override
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
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
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
218    public javax.sound.sampled.Line.Info[] getTargetLineInfo() {
219        return new javax.sound.sampled.Line.Info[0];
220    }
221
222    @Override
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
229    public Line[] getTargetLines() {
230        return new Line[0];
231    }
232
233    @Override
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
246    public boolean isSynchronizationSupported(Line[] lines, boolean maintainSync) {
247        return false;
248    }
249
250    @Override
251    public void synchronize(Line[] lines, boolean maintainSync) {
252        throw new IllegalArgumentException(
253                "Synchronization not supported by this mixer.");
254    }
255
256    @Override
257    public void unsynchronize(Line[] lines) {
258        throw new IllegalArgumentException(
259                "Synchronization not supported by this mixer.");
260    }
261
262    @Override
263    public void addLineListener(LineListener listener) {
264        synchronized (control_mutex) {
265            listeners.add(listener);
266        }
267    }
268
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
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
328    public Control getControl(Type control) {
329        throw new IllegalArgumentException("Unsupported control type : "
330                + control);
331    }
332
333    @Override
334    public Control[] getControls() {
335        return new Control[0];
336    }
337
338    @Override
339    public javax.sound.sampled.Line.Info getLineInfo() {
340        return new Line.Info(Mixer.class);
341    }
342
343    @Override
344    public boolean isControlSupported(Type control) {
345        return false;
346    }
347
348    @Override
349    public boolean isOpen() {
350        synchronized (control_mutex) {
351            return open;
352        }
353    }
354
355    @Override
356    public void open() throws LineUnavailableException {
357        if (isOpen()) {
358            implicitOpen = false;
359            return;
360        }
361        open(null);
362    }
363
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
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
524    public void removeLineListener(LineListener listener) {
525        synchronized (control_mutex) {
526            listeners.remove(listener);
527        }
528    }
529
530    public long getLatency() {
531        synchronized (control_mutex) {
532            return latency;
533        }
534    }
535
536    public AudioFormat getFormat() {
537        synchronized (control_mutex) {
538            return format;
539        }
540    }
541
542    float getControlRate() {
543        return controlrate;
544    }
545
546    SoftMixingMainMixer getMainMixer() {
547        if (!isOpen())
548            return null;
549        return mainmixer;
550    }
551}
552