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 }