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.io.InputStream; 30 import java.util.Arrays; 31 32 import javax.sound.sampled.AudioFormat; 33 import javax.sound.sampled.AudioInputStream; 34 import javax.sound.sampled.AudioSystem; 35 import javax.sound.sampled.DataLine; 36 import javax.sound.sampled.LineEvent; 37 import javax.sound.sampled.LineUnavailableException; 38 import javax.sound.sampled.SourceDataLine; 39 40 /** 41 * SourceDataLine implementation for the SoftMixingMixer. 42 * 43 * @author Karl Helgason 44 */ 45 public final class SoftMixingSourceDataLine extends SoftMixingDataLine 46 implements SourceDataLine { 47 48 private boolean open = false; 49 50 private AudioFormat format = new AudioFormat(44100.0f, 16, 2, true, false); 51 52 private int framesize; 53 54 private int bufferSize = -1; 55 56 private float[] readbuffer; 57 58 private boolean active = false; 59 60 private byte[] cycling_buffer; 61 62 private int cycling_read_pos = 0; 63 64 private int cycling_write_pos = 0; 65 66 private int cycling_avail = 0; 67 68 private long cycling_framepos = 0; 69 70 private AudioFloatInputStream afis; 71 72 private static class NonBlockingFloatInputStream extends 73 AudioFloatInputStream { 74 AudioFloatInputStream ais; 75 NonBlockingFloatInputStream(AudioFloatInputStream ais)76 NonBlockingFloatInputStream(AudioFloatInputStream ais) { 77 this.ais = ais; 78 } 79 80 @Override available()81 public int available() throws IOException { 82 return ais.available(); 83 } 84 85 @Override close()86 public void close() throws IOException { 87 ais.close(); 88 } 89 90 @Override getFormat()91 public AudioFormat getFormat() { 92 return ais.getFormat(); 93 } 94 95 @Override getFrameLength()96 public long getFrameLength() { 97 return ais.getFrameLength(); 98 } 99 100 @Override mark(int readlimit)101 public void mark(int readlimit) { 102 ais.mark(readlimit); 103 } 104 105 @Override markSupported()106 public boolean markSupported() { 107 return ais.markSupported(); 108 } 109 110 @Override read(float[] b, int off, int len)111 public int read(float[] b, int off, int len) throws IOException { 112 int avail = available(); 113 if (len > avail) { 114 int ret = ais.read(b, off, avail); 115 Arrays.fill(b, off + ret, off + len, 0); 116 return len; 117 } 118 return ais.read(b, off, len); 119 } 120 121 @Override reset()122 public void reset() throws IOException { 123 ais.reset(); 124 } 125 126 @Override skip(long len)127 public long skip(long len) throws IOException { 128 return ais.skip(len); 129 } 130 131 } 132 SoftMixingSourceDataLine(SoftMixingMixer mixer, DataLine.Info info)133 SoftMixingSourceDataLine(SoftMixingMixer mixer, DataLine.Info info) { 134 super(mixer, info); 135 } 136 137 @Override write(byte[] b, int off, int len)138 public int write(byte[] b, int off, int len) { 139 if (!isOpen()) 140 return 0; 141 if (len % framesize != 0) 142 throw new IllegalArgumentException( 143 "Number of bytes does not represent an integral number of sample frames."); 144 if (off < 0) { 145 throw new ArrayIndexOutOfBoundsException(off); 146 } 147 if ((long)off + (long)len > (long)b.length) { 148 throw new ArrayIndexOutOfBoundsException(b.length); 149 } 150 151 byte[] buff = cycling_buffer; 152 int buff_len = cycling_buffer.length; 153 154 int l = 0; 155 while (l != len) { 156 int avail; 157 synchronized (cycling_buffer) { 158 int pos = cycling_write_pos; 159 avail = cycling_avail; 160 while (l != len) { 161 if (avail == buff_len) 162 break; 163 buff[pos++] = b[off++]; 164 l++; 165 avail++; 166 if (pos == buff_len) 167 pos = 0; 168 } 169 cycling_avail = avail; 170 cycling_write_pos = pos; 171 if (l == len) 172 return l; 173 } 174 if (avail == buff_len) { 175 try { 176 Thread.sleep(1); 177 } catch (InterruptedException e) { 178 return l; 179 } 180 if (!isRunning()) 181 return l; 182 } 183 } 184 185 return l; 186 } 187 188 // 189 // BooleanControl.Type.APPLY_REVERB 190 // BooleanControl.Type.MUTE 191 // EnumControl.Type.REVERB 192 // 193 // FloatControl.Type.SAMPLE_RATE 194 // FloatControl.Type.REVERB_SEND 195 // FloatControl.Type.VOLUME 196 // FloatControl.Type.PAN 197 // FloatControl.Type.MASTER_GAIN 198 // FloatControl.Type.BALANCE 199 200 private boolean _active = false; 201 202 private AudioFormat outputformat; 203 204 private int out_nrofchannels; 205 206 private int in_nrofchannels; 207 208 private float _rightgain; 209 210 private float _leftgain; 211 212 private float _eff1gain; 213 214 private float _eff2gain; 215 216 @Override processControlLogic()217 protected void processControlLogic() { 218 _active = active; 219 _rightgain = rightgain; 220 _leftgain = leftgain; 221 _eff1gain = eff1gain; 222 _eff2gain = eff2gain; 223 } 224 225 @Override processAudioLogic(SoftAudioBuffer[] buffers)226 protected void processAudioLogic(SoftAudioBuffer[] buffers) { 227 if (_active) { 228 float[] left = buffers[SoftMixingMainMixer.CHANNEL_LEFT].array(); 229 float[] right = buffers[SoftMixingMainMixer.CHANNEL_RIGHT].array(); 230 int bufferlen = buffers[SoftMixingMainMixer.CHANNEL_LEFT].getSize(); 231 232 int readlen = bufferlen * in_nrofchannels; 233 if (readbuffer == null || readbuffer.length < readlen) { 234 readbuffer = new float[readlen]; 235 } 236 int ret = 0; 237 try { 238 ret = afis.read(readbuffer); 239 if (ret != in_nrofchannels) 240 Arrays.fill(readbuffer, ret, readlen, 0); 241 } catch (IOException e) { 242 } 243 244 int in_c = in_nrofchannels; 245 for (int i = 0, ix = 0; i < bufferlen; i++, ix += in_c) { 246 left[i] += readbuffer[ix] * _leftgain; 247 } 248 if (out_nrofchannels != 1) { 249 if (in_nrofchannels == 1) { 250 for (int i = 0, ix = 0; i < bufferlen; i++, ix += in_c) { 251 right[i] += readbuffer[ix] * _rightgain; 252 } 253 } else { 254 for (int i = 0, ix = 1; i < bufferlen; i++, ix += in_c) { 255 right[i] += readbuffer[ix] * _rightgain; 256 } 257 } 258 259 } 260 261 if (_eff1gain > 0.0001) { 262 float[] eff1 = buffers[SoftMixingMainMixer.CHANNEL_EFFECT1] 263 .array(); 264 for (int i = 0, ix = 0; i < bufferlen; i++, ix += in_c) { 265 eff1[i] += readbuffer[ix] * _eff1gain; 266 } 267 if (in_nrofchannels == 2) { 268 for (int i = 0, ix = 1; i < bufferlen; i++, ix += in_c) { 269 eff1[i] += readbuffer[ix] * _eff1gain; 270 } 271 } 272 } 273 274 if (_eff2gain > 0.0001) { 275 float[] eff2 = buffers[SoftMixingMainMixer.CHANNEL_EFFECT2] 276 .array(); 277 for (int i = 0, ix = 0; i < bufferlen; i++, ix += in_c) { 278 eff2[i] += readbuffer[ix] * _eff2gain; 279 } 280 if (in_nrofchannels == 2) { 281 for (int i = 0, ix = 1; i < bufferlen; i++, ix += in_c) { 282 eff2[i] += readbuffer[ix] * _eff2gain; 283 } 284 } 285 } 286 287 } 288 } 289 290 @Override open()291 public void open() throws LineUnavailableException { 292 open(format); 293 } 294 295 @Override open(AudioFormat format)296 public void open(AudioFormat format) throws LineUnavailableException { 297 if (bufferSize == -1) 298 bufferSize = ((int) (format.getFrameRate() / 2)) 299 * format.getFrameSize(); 300 open(format, bufferSize); 301 } 302 303 @Override open(AudioFormat format, int bufferSize)304 public void open(AudioFormat format, int bufferSize) 305 throws LineUnavailableException { 306 307 LineEvent event = null; 308 309 if (bufferSize < format.getFrameSize() * 32) 310 bufferSize = format.getFrameSize() * 32; 311 312 synchronized (control_mutex) { 313 314 if (!isOpen()) { 315 if (!mixer.isOpen()) { 316 mixer.open(); 317 mixer.implicitOpen = true; 318 } 319 320 event = new LineEvent(this, LineEvent.Type.OPEN, 0); 321 322 this.bufferSize = bufferSize - bufferSize 323 % format.getFrameSize(); 324 this.format = format; 325 this.framesize = format.getFrameSize(); 326 this.outputformat = mixer.getFormat(); 327 out_nrofchannels = outputformat.getChannels(); 328 in_nrofchannels = format.getChannels(); 329 330 open = true; 331 332 mixer.getMainMixer().openLine(this); 333 334 cycling_buffer = new byte[framesize * bufferSize]; 335 cycling_read_pos = 0; 336 cycling_write_pos = 0; 337 cycling_avail = 0; 338 cycling_framepos = 0; 339 340 InputStream cycling_inputstream = new InputStream() { 341 342 @Override 343 public int read() throws IOException { 344 byte[] b = new byte[1]; 345 int ret = read(b); 346 if (ret < 0) 347 return ret; 348 return b[0] & 0xFF; 349 } 350 351 @Override 352 public int available() throws IOException { 353 synchronized (cycling_buffer) { 354 return cycling_avail; 355 } 356 } 357 358 @Override 359 public int read(byte[] b, int off, int len) 360 throws IOException { 361 362 synchronized (cycling_buffer) { 363 if (len > cycling_avail) 364 len = cycling_avail; 365 int pos = cycling_read_pos; 366 byte[] buff = cycling_buffer; 367 int buff_len = buff.length; 368 for (int i = 0; i < len; i++) { 369 b[off++] = buff[pos]; 370 pos++; 371 if (pos == buff_len) 372 pos = 0; 373 } 374 cycling_read_pos = pos; 375 cycling_avail -= len; 376 cycling_framepos += len / framesize; 377 } 378 return len; 379 } 380 381 }; 382 383 afis = AudioFloatInputStream 384 .getInputStream(new AudioInputStream( 385 cycling_inputstream, format, 386 AudioSystem.NOT_SPECIFIED)); 387 afis = new NonBlockingFloatInputStream(afis); 388 389 if (Math.abs(format.getSampleRate() 390 - outputformat.getSampleRate()) > 0.000001) 391 afis = new AudioFloatInputStreamResampler(afis, 392 outputformat); 393 394 } else { 395 if (!format.matches(getFormat())) { 396 throw new IllegalStateException( 397 "Line is already open with format " + getFormat() 398 + " and bufferSize " + getBufferSize()); 399 } 400 } 401 402 } 403 404 if (event != null) 405 sendEvent(event); 406 407 } 408 409 @Override available()410 public int available() { 411 synchronized (cycling_buffer) { 412 return cycling_buffer.length - cycling_avail; 413 } 414 } 415 416 @Override drain()417 public void drain() { 418 while (true) { 419 int avail; 420 synchronized (cycling_buffer) { 421 avail = cycling_avail; 422 } 423 if (avail != 0) 424 return; 425 try { 426 Thread.sleep(1); 427 } catch (InterruptedException e) { 428 return; 429 } 430 } 431 } 432 433 @Override flush()434 public void flush() { 435 synchronized (cycling_buffer) { 436 cycling_read_pos = 0; 437 cycling_write_pos = 0; 438 cycling_avail = 0; 439 } 440 } 441 442 @Override getBufferSize()443 public int getBufferSize() { 444 synchronized (control_mutex) { 445 return bufferSize; 446 } 447 } 448 449 @Override getFormat()450 public AudioFormat getFormat() { 451 synchronized (control_mutex) { 452 return format; 453 } 454 } 455 456 @Override getFramePosition()457 public int getFramePosition() { 458 return (int) getLongFramePosition(); 459 } 460 461 @Override getLevel()462 public float getLevel() { 463 return AudioSystem.NOT_SPECIFIED; 464 } 465 466 @Override getLongFramePosition()467 public long getLongFramePosition() { 468 synchronized (cycling_buffer) { 469 return cycling_framepos; 470 } 471 } 472 473 @Override getMicrosecondPosition()474 public long getMicrosecondPosition() { 475 return (long) (getLongFramePosition() * (1000000.0 / (double) getFormat() 476 .getSampleRate())); 477 } 478 479 @Override isActive()480 public boolean isActive() { 481 synchronized (control_mutex) { 482 return active; 483 } 484 } 485 486 @Override isRunning()487 public boolean isRunning() { 488 synchronized (control_mutex) { 489 return active; 490 } 491 } 492 493 @Override start()494 public void start() { 495 496 LineEvent event = null; 497 498 synchronized (control_mutex) { 499 if (isOpen()) { 500 if (active) 501 return; 502 active = true; 503 event = new LineEvent(this, LineEvent.Type.START, 504 getLongFramePosition()); 505 } 506 } 507 508 if (event != null) 509 sendEvent(event); 510 } 511 512 @Override stop()513 public void stop() { 514 LineEvent event = null; 515 516 synchronized (control_mutex) { 517 if (isOpen()) { 518 if (!active) 519 return; 520 active = false; 521 event = new LineEvent(this, LineEvent.Type.STOP, 522 getLongFramePosition()); 523 } 524 } 525 526 if (event != null) 527 sendEvent(event); 528 } 529 530 @Override close()531 public void close() { 532 533 LineEvent event = null; 534 535 synchronized (control_mutex) { 536 if (!isOpen()) 537 return; 538 stop(); 539 540 event = new LineEvent(this, LineEvent.Type.CLOSE, 541 getLongFramePosition()); 542 543 open = false; 544 mixer.getMainMixer().closeLine(this); 545 } 546 547 if (event != null) 548 sendEvent(event); 549 550 } 551 552 @Override isOpen()553 public boolean isOpen() { 554 synchronized (control_mutex) { 555 return open; 556 } 557 } 558 } 559