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 }