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