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