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