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