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