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