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
26package com.sun.media.sound;
27
28import java.io.IOException;
29import java.io.InputStream;
30import java.util.ArrayList;
31import java.util.Arrays;
32import java.util.Objects;
33
34import javax.sound.sampled.AudioFormat;
35import javax.sound.sampled.AudioFormat.Encoding;
36import javax.sound.sampled.AudioInputStream;
37import javax.sound.sampled.AudioSystem;
38import 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 */
47public 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}
661