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.ArrayList; 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.AudioFormat.Encoding; 36 import javax.sound.sampled.spi.FormatConversionProvider; 37 38 /** 39 * This class is used to convert between 8,16,24,32 bit signed/unsigned 40 * big/litle endian fixed/floating stereo/mono/multi-channel audio streams and 41 * perform sample-rate conversion if needed. 42 * 43 * @author Karl Helgason 44 */ 45 public final class AudioFloatFormatConverter extends FormatConversionProvider { 46 47 private static class AudioFloatFormatConverterInputStream extends 48 InputStream { 49 private final AudioFloatConverter converter; 50 51 private final AudioFloatInputStream stream; 52 53 private float[] readfloatbuffer; 54 55 private final int fsize; 56 57 AudioFloatFormatConverterInputStream(AudioFormat targetFormat, 58 AudioFloatInputStream stream) { 59 this.stream = stream; 60 converter = AudioFloatConverter.getConverter(targetFormat); 61 fsize = ((targetFormat.getSampleSizeInBits() + 7) / 8); 62 } 63 64 public int read() throws IOException { 65 byte[] b = new byte[1]; 66 int ret = read(b); 67 if (ret < 0) 68 return ret; 69 return b[0] & 0xFF; 70 } 71 72 public int read(byte[] b, int off, int len) throws IOException { 73 74 int flen = len / fsize; 75 if (readfloatbuffer == null || readfloatbuffer.length < flen) 76 readfloatbuffer = new float[flen]; 77 int ret = stream.read(readfloatbuffer, 0, flen); 78 if (ret < 0) 79 return ret; 80 converter.toByteArray(readfloatbuffer, 0, ret, b, off); 81 return ret * fsize; 82 } 83 84 public int available() throws IOException { 85 int ret = stream.available(); 86 if (ret < 0) 87 return ret; 88 return ret * fsize; 89 } 90 91 public void close() throws IOException { 92 stream.close(); 93 } 94 95 public synchronized void mark(int readlimit) { 96 stream.mark(readlimit * fsize); 97 } 98 99 public boolean markSupported() { 100 return stream.markSupported(); 101 } 102 103 public synchronized void reset() throws IOException { 104 stream.reset(); 105 } 106 107 public long skip(long n) throws IOException { 108 long ret = stream.skip(n / fsize); 109 if (ret < 0) 110 return ret; 111 return ret * fsize; 112 } 113 114 } 115 116 private static class AudioFloatInputStreamChannelMixer extends 117 AudioFloatInputStream { 118 119 private final int targetChannels; 120 121 private final int sourceChannels; 122 123 private final AudioFloatInputStream ais; 124 125 private final AudioFormat targetFormat; 126 127 private float[] conversion_buffer; 128 129 AudioFloatInputStreamChannelMixer(AudioFloatInputStream ais, 130 int targetChannels) { 131 this.sourceChannels = ais.getFormat().getChannels(); 132 this.targetChannels = targetChannels; 133 this.ais = ais; 134 AudioFormat format = ais.getFormat(); 135 targetFormat = new AudioFormat(format.getEncoding(), format 136 .getSampleRate(), format.getSampleSizeInBits(), 137 targetChannels, (format.getFrameSize() / sourceChannels) 138 * targetChannels, format.getFrameRate(), format 139 .isBigEndian()); 140 } 141 142 public int available() throws IOException { 143 return (ais.available() / sourceChannels) * targetChannels; 144 } 145 146 public void close() throws IOException { 147 ais.close(); 148 } 149 150 public AudioFormat getFormat() { 151 return targetFormat; 152 } 153 154 public long getFrameLength() { 155 return ais.getFrameLength(); 156 } 157 158 public void mark(int readlimit) { 159 ais.mark((readlimit / targetChannels) * sourceChannels); 160 } 161 162 public boolean markSupported() { 163 return ais.markSupported(); 164 } 165 166 public int read(float[] b, int off, int len) throws IOException { 167 int len2 = (len / targetChannels) * sourceChannels; 168 if (conversion_buffer == null || conversion_buffer.length < len2) 169 conversion_buffer = new float[len2]; 170 int ret = ais.read(conversion_buffer, 0, len2); 171 if (ret < 0) 172 return ret; 173 if (sourceChannels == 1) { 174 int cs = targetChannels; 175 for (int c = 0; c < targetChannels; c++) { 176 for (int i = 0, ix = off + c; i < len2; i++, ix += cs) { 177 b[ix] = conversion_buffer[i]; 178 } 179 } 180 } else if (targetChannels == 1) { 181 int cs = sourceChannels; 182 for (int i = 0, ix = off; i < len2; i += cs, ix++) { 183 b[ix] = conversion_buffer[i]; 184 } 185 for (int c = 1; c < sourceChannels; c++) { 186 for (int i = c, ix = off; i < len2; i += cs, ix++) { 187 b[ix] += conversion_buffer[i]; 188 } 189 } 190 float vol = 1f / ((float) sourceChannels); 191 for (int i = 0, ix = off; i < len2; i += cs, ix++) { 192 b[ix] *= vol; 193 } 194 } else { 195 int minChannels = Math.min(sourceChannels, targetChannels); 196 int off_len = off + len; 197 int ct = targetChannels; 198 int cs = sourceChannels; 199 for (int c = 0; c < minChannels; c++) { 200 for (int i = off + c, ix = c; i < off_len; i += ct, ix += cs) { 201 b[i] = conversion_buffer[ix]; 202 } 203 } 204 for (int c = minChannels; c < targetChannels; c++) { 205 for (int i = off + c; i < off_len; i += ct) { 206 b[i] = 0; 207 } 208 } 209 } 210 return (ret / sourceChannels) * targetChannels; 211 } 212 213 public void reset() throws IOException { 214 ais.reset(); 215 } 216 217 public long skip(long len) throws IOException { 218 long ret = ais.skip((len / targetChannels) * sourceChannels); 219 if (ret < 0) 220 return ret; 221 return (ret / sourceChannels) * targetChannels; 222 } 223 224 } 225 226 private static class AudioFloatInputStreamResampler extends 227 AudioFloatInputStream { 228 229 private final AudioFloatInputStream ais; 230 231 private final AudioFormat targetFormat; 232 233 private float[] skipbuffer; 234 235 private SoftAbstractResampler resampler; 236 237 private final float[] pitch = new float[1]; 238 239 private final float[] ibuffer2; 240 241 private final float[][] ibuffer; 242 243 private float ibuffer_index = 0; 244 245 private int ibuffer_len = 0; 246 247 private final int nrofchannels; 248 249 private float[][] cbuffer; 250 251 private final int buffer_len = 512; 252 253 private final int pad; 254 255 private final int pad2; 256 257 private final float[] ix = new float[1]; 258 259 private final int[] ox = new int[1]; 260 261 private float[][] mark_ibuffer = null; 262 263 private float mark_ibuffer_index = 0; 264 265 private int mark_ibuffer_len = 0; 266 267 AudioFloatInputStreamResampler(AudioFloatInputStream ais, 268 AudioFormat format) { 269 this.ais = ais; 270 AudioFormat sourceFormat = ais.getFormat(); 271 targetFormat = new AudioFormat(sourceFormat.getEncoding(), format 272 .getSampleRate(), sourceFormat.getSampleSizeInBits(), 273 sourceFormat.getChannels(), sourceFormat.getFrameSize(), 274 format.getSampleRate(), sourceFormat.isBigEndian()); 275 nrofchannels = targetFormat.getChannels(); 276 Object interpolation = format.getProperty("interpolation"); 277 if (interpolation != null && (interpolation instanceof String)) { 278 String resamplerType = (String) interpolation; 279 if (resamplerType.equalsIgnoreCase("point")) 280 this.resampler = new SoftPointResampler(); 281 if (resamplerType.equalsIgnoreCase("linear")) 282 this.resampler = new SoftLinearResampler2(); 283 if (resamplerType.equalsIgnoreCase("linear1")) 284 this.resampler = new SoftLinearResampler(); 285 if (resamplerType.equalsIgnoreCase("linear2")) 286 this.resampler = new SoftLinearResampler2(); 287 if (resamplerType.equalsIgnoreCase("cubic")) 288 this.resampler = new SoftCubicResampler(); 289 if (resamplerType.equalsIgnoreCase("lanczos")) 290 this.resampler = new SoftLanczosResampler(); 291 if (resamplerType.equalsIgnoreCase("sinc")) 292 this.resampler = new SoftSincResampler(); 293 } 294 if (resampler == null) 295 resampler = new SoftLinearResampler2(); // new 296 // SoftLinearResampler2(); 297 pitch[0] = sourceFormat.getSampleRate() / format.getSampleRate(); 298 pad = resampler.getPadding(); 299 pad2 = pad * 2; 300 ibuffer = new float[nrofchannels][buffer_len + pad2]; 301 ibuffer2 = new float[nrofchannels * buffer_len]; 302 ibuffer_index = buffer_len + pad; 303 ibuffer_len = buffer_len; 304 } 305 306 public int available() throws IOException { 307 return 0; 308 } 309 310 public void close() throws IOException { 311 ais.close(); 312 } 313 314 public AudioFormat getFormat() { 315 return targetFormat; 316 } 317 318 public long getFrameLength() { 319 return AudioSystem.NOT_SPECIFIED; // ais.getFrameLength(); 320 } 321 322 public void mark(int readlimit) { 323 ais.mark((int) (readlimit * pitch[0])); 324 mark_ibuffer_index = ibuffer_index; 325 mark_ibuffer_len = ibuffer_len; 326 if (mark_ibuffer == null) { 327 mark_ibuffer = new float[ibuffer.length][ibuffer[0].length]; 328 } 329 for (int c = 0; c < ibuffer.length; c++) { 330 float[] from = ibuffer[c]; 331 float[] to = mark_ibuffer[c]; 332 for (int i = 0; i < to.length; i++) { 333 to[i] = from[i]; 334 } 335 } 336 } 337 338 public boolean markSupported() { 339 return ais.markSupported(); 340 } 341 342 private void readNextBuffer() throws IOException { 343 344 if (ibuffer_len == -1) 345 return; 346 347 for (int c = 0; c < nrofchannels; c++) { 348 float[] buff = ibuffer[c]; 349 int buffer_len_pad = ibuffer_len + pad2; 350 for (int i = ibuffer_len, ix = 0; i < buffer_len_pad; i++, ix++) { 351 buff[ix] = buff[i]; 352 } 353 } 354 355 ibuffer_index -= (ibuffer_len); 356 357 ibuffer_len = ais.read(ibuffer2); 358 if (ibuffer_len >= 0) { 359 while (ibuffer_len < ibuffer2.length) { 360 int ret = ais.read(ibuffer2, ibuffer_len, ibuffer2.length 361 - ibuffer_len); 362 if (ret == -1) 363 break; 364 ibuffer_len += ret; 365 } 366 Arrays.fill(ibuffer2, ibuffer_len, ibuffer2.length, 0); 367 ibuffer_len /= nrofchannels; 368 } else { 369 Arrays.fill(ibuffer2, 0, ibuffer2.length, 0); 370 } 371 372 int ibuffer2_len = ibuffer2.length; 373 for (int c = 0; c < nrofchannels; c++) { 374 float[] buff = ibuffer[c]; 375 for (int i = c, ix = pad2; i < ibuffer2_len; i += nrofchannels, ix++) { 376 buff[ix] = ibuffer2[i]; 377 } 378 } 379 380 } 381 382 public int read(float[] b, int off, int len) throws IOException { 383 384 if (cbuffer == null || cbuffer[0].length < len / nrofchannels) { 385 cbuffer = new float[nrofchannels][len / nrofchannels]; 386 } 387 if (ibuffer_len == -1) 388 return -1; 389 if (len < 0) 390 return 0; 391 int offlen = off + len; 392 int remain = len / nrofchannels; 393 int destPos = 0; 394 int in_end = ibuffer_len; 395 while (remain > 0) { 396 if (ibuffer_len >= 0) { 397 if (ibuffer_index >= (ibuffer_len + pad)) 398 readNextBuffer(); 399 in_end = ibuffer_len + pad; 400 } 401 402 if (ibuffer_len < 0) { 403 in_end = pad2; 404 if (ibuffer_index >= in_end) 405 break; 406 } 407 408 if (ibuffer_index < 0) 409 break; 410 int preDestPos = destPos; 411 for (int c = 0; c < nrofchannels; c++) { 412 ix[0] = ibuffer_index; 413 ox[0] = destPos; 414 float[] buff = ibuffer[c]; 415 resampler.interpolate(buff, ix, in_end, pitch, 0, 416 cbuffer[c], ox, len / nrofchannels); 417 } 418 ibuffer_index = ix[0]; 419 destPos = ox[0]; 420 remain -= destPos - preDestPos; 421 } 422 for (int c = 0; c < nrofchannels; c++) { 423 int ix = 0; 424 float[] buff = cbuffer[c]; 425 for (int i = c + off; i < offlen; i += nrofchannels) { 426 b[i] = buff[ix++]; 427 } 428 } 429 return len - remain * nrofchannels; 430 } 431 432 public void reset() throws IOException { 433 ais.reset(); 434 if (mark_ibuffer == null) 435 return; 436 ibuffer_index = mark_ibuffer_index; 437 ibuffer_len = mark_ibuffer_len; 438 for (int c = 0; c < ibuffer.length; c++) { 439 float[] from = mark_ibuffer[c]; 440 float[] to = ibuffer[c]; 441 for (int i = 0; i < to.length; i++) { 442 to[i] = from[i]; 443 } 444 } 445 446 } 447 448 public long skip(long len) throws IOException { 449 if (len < 0) 450 return 0; 451 if (skipbuffer == null) 452 skipbuffer = new float[1024 * targetFormat.getFrameSize()]; 453 float[] l_skipbuffer = skipbuffer; 454 long remain = len; 455 while (remain > 0) { 456 int ret = read(l_skipbuffer, 0, (int) Math.min(remain, 457 skipbuffer.length)); 458 if (ret < 0) { 459 if (remain == len) 460 return ret; 461 break; 462 } 463 remain -= ret; 464 } 465 return len - remain; 466 467 } 468 469 } 470 471 private final Encoding[] formats = {Encoding.PCM_SIGNED, 472 Encoding.PCM_UNSIGNED, 473 Encoding.PCM_FLOAT}; 474 475 public AudioInputStream getAudioInputStream(Encoding targetEncoding, 476 AudioInputStream sourceStream) { 477 if (sourceStream.getFormat().getEncoding().equals(targetEncoding)) 478 return sourceStream; 479 AudioFormat format = sourceStream.getFormat(); 480 int channels = format.getChannels(); 481 Encoding encoding = targetEncoding; 482 float samplerate = format.getSampleRate(); 483 int bits = format.getSampleSizeInBits(); 484 boolean bigendian = format.isBigEndian(); 485 if (targetEncoding.equals(Encoding.PCM_FLOAT)) 486 bits = 32; 487 AudioFormat targetFormat = new AudioFormat(encoding, samplerate, bits, 488 channels, channels * bits / 8, samplerate, bigendian); 489 return getAudioInputStream(targetFormat, sourceStream); 490 } 491 492 public AudioInputStream getAudioInputStream(AudioFormat targetFormat, 493 AudioInputStream sourceStream) { 494 if (!isConversionSupported(targetFormat, sourceStream.getFormat())) 495 throw new IllegalArgumentException("Unsupported conversion: " 496 + sourceStream.getFormat().toString() + " to " 497 + targetFormat.toString()); 498 return getAudioInputStream(targetFormat, AudioFloatInputStream 499 .getInputStream(sourceStream)); 500 } 501 502 public AudioInputStream getAudioInputStream(AudioFormat targetFormat, 503 AudioFloatInputStream sourceStream) { 504 505 if (!isConversionSupported(targetFormat, sourceStream.getFormat())) 506 throw new IllegalArgumentException("Unsupported conversion: " 507 + sourceStream.getFormat().toString() + " to " 508 + targetFormat.toString()); 509 if (targetFormat.getChannels() != sourceStream.getFormat() 510 .getChannels()) 511 sourceStream = new AudioFloatInputStreamChannelMixer(sourceStream, 512 targetFormat.getChannels()); 513 if (Math.abs(targetFormat.getSampleRate() 514 - sourceStream.getFormat().getSampleRate()) > 0.000001) 515 sourceStream = new AudioFloatInputStreamResampler(sourceStream, 516 targetFormat); 517 return new AudioInputStream(new AudioFloatFormatConverterInputStream( 518 targetFormat, sourceStream), targetFormat, sourceStream 519 .getFrameLength()); 520 } 521 522 public Encoding[] getSourceEncodings() { 523 return new Encoding[] { Encoding.PCM_SIGNED, Encoding.PCM_UNSIGNED, 524 Encoding.PCM_FLOAT }; 525 } 526 527 public Encoding[] getTargetEncodings() { 528 return new Encoding[] { Encoding.PCM_SIGNED, Encoding.PCM_UNSIGNED, 529 Encoding.PCM_FLOAT }; 530 } 531 532 public Encoding[] getTargetEncodings(AudioFormat sourceFormat) { 533 if (AudioFloatConverter.getConverter(sourceFormat) == null) 534 return new Encoding[0]; 535 return new Encoding[] { Encoding.PCM_SIGNED, Encoding.PCM_UNSIGNED, 536 Encoding.PCM_FLOAT }; 537 } 538 539 public AudioFormat[] getTargetFormats(Encoding targetEncoding, 540 AudioFormat sourceFormat) { 541 if (AudioFloatConverter.getConverter(sourceFormat) == null) 542 return new AudioFormat[0]; 543 int channels = sourceFormat.getChannels(); 544 545 ArrayList<AudioFormat> formats = new ArrayList<AudioFormat>(); 546 547 if (targetEncoding.equals(Encoding.PCM_SIGNED)) 548 formats.add(new AudioFormat(Encoding.PCM_SIGNED, 549 AudioSystem.NOT_SPECIFIED, 8, channels, channels, 550 AudioSystem.NOT_SPECIFIED, false)); 551 if (targetEncoding.equals(Encoding.PCM_UNSIGNED)) 552 formats.add(new AudioFormat(Encoding.PCM_UNSIGNED, 553 AudioSystem.NOT_SPECIFIED, 8, channels, channels, 554 AudioSystem.NOT_SPECIFIED, false)); 555 556 for (int bits = 16; bits < 32; bits += 8) { 557 if (targetEncoding.equals(Encoding.PCM_SIGNED)) { 558 formats.add(new AudioFormat(Encoding.PCM_SIGNED, 559 AudioSystem.NOT_SPECIFIED, bits, channels, channels 560 * bits / 8, AudioSystem.NOT_SPECIFIED, false)); 561 formats.add(new AudioFormat(Encoding.PCM_SIGNED, 562 AudioSystem.NOT_SPECIFIED, bits, channels, channels 563 * bits / 8, AudioSystem.NOT_SPECIFIED, true)); 564 } 565 if (targetEncoding.equals(Encoding.PCM_UNSIGNED)) { 566 formats.add(new AudioFormat(Encoding.PCM_UNSIGNED, 567 AudioSystem.NOT_SPECIFIED, bits, channels, channels 568 * bits / 8, AudioSystem.NOT_SPECIFIED, true)); 569 formats.add(new AudioFormat(Encoding.PCM_UNSIGNED, 570 AudioSystem.NOT_SPECIFIED, bits, channels, channels 571 * bits / 8, AudioSystem.NOT_SPECIFIED, false)); 572 } 573 } 574 575 if (targetEncoding.equals(Encoding.PCM_FLOAT)) { 576 formats.add(new AudioFormat(Encoding.PCM_FLOAT, 577 AudioSystem.NOT_SPECIFIED, 32, channels, channels * 4, 578 AudioSystem.NOT_SPECIFIED, false)); 579 formats.add(new AudioFormat(Encoding.PCM_FLOAT, 580 AudioSystem.NOT_SPECIFIED, 32, channels, channels * 4, 581 AudioSystem.NOT_SPECIFIED, true)); 582 formats.add(new AudioFormat(Encoding.PCM_FLOAT, 583 AudioSystem.NOT_SPECIFIED, 64, channels, channels * 8, 584 AudioSystem.NOT_SPECIFIED, false)); 585 formats.add(new AudioFormat(Encoding.PCM_FLOAT, 586 AudioSystem.NOT_SPECIFIED, 64, channels, channels * 8, 587 AudioSystem.NOT_SPECIFIED, true)); 588 } 589 590 return formats.toArray(new AudioFormat[formats.size()]); 591 } 592 593 public boolean isConversionSupported(AudioFormat targetFormat, 594 AudioFormat sourceFormat) { 595 if (AudioFloatConverter.getConverter(sourceFormat) == null) 596 return false; 597 if (AudioFloatConverter.getConverter(targetFormat) == null) 598 return false; 599 if (sourceFormat.getChannels() <= 0) 600 return false; 601 if (targetFormat.getChannels() <= 0) 602 return false; 603 return true; 604 } 605 606 public boolean isConversionSupported(Encoding targetEncoding, 607 AudioFormat sourceFormat) { 608 if (AudioFloatConverter.getConverter(sourceFormat) == null) 609 return false; 610 for (int i = 0; i < formats.length; i++) { 611 if (targetEncoding.equals(formats[i])) 612 return true; 613 } 614 return false; 615 } 616 617 }