1/*
2 * Copyright (c) 1999, 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.util.Objects;
30import java.util.Vector;
31
32import javax.sound.sampled.AudioFormat;
33import javax.sound.sampled.AudioFormat.Encoding;
34import javax.sound.sampled.AudioInputStream;
35import javax.sound.sampled.AudioSystem;
36import javax.sound.sampled.spi.FormatConversionProvider;
37
38/**
39 * Converts among signed/unsigned and little/big endianness of sampled.
40 *
41 * @author Jan Borgersen
42 */
43public final class PCMtoPCMCodec extends FormatConversionProvider {
44
45    @Override
46    public AudioFormat.Encoding[] getSourceEncodings() {
47        return new Encoding[]{Encoding.PCM_SIGNED, Encoding.PCM_UNSIGNED};
48    }
49
50    @Override
51    public AudioFormat.Encoding[] getTargetEncodings() {
52        return getSourceEncodings();
53    }
54
55    @Override
56    public AudioFormat.Encoding[] getTargetEncodings(AudioFormat sourceFormat) {
57
58        final int sampleSize = sourceFormat.getSampleSizeInBits();
59        AudioFormat.Encoding encoding = sourceFormat.getEncoding();
60        if (sampleSize == 8) {
61            if (encoding.equals(AudioFormat.Encoding.PCM_SIGNED)) {
62                return new AudioFormat.Encoding[]{
63                        AudioFormat.Encoding.PCM_UNSIGNED
64                };
65            }
66            if (encoding.equals(AudioFormat.Encoding.PCM_UNSIGNED)) {
67                return new AudioFormat.Encoding[]{
68                        AudioFormat.Encoding.PCM_SIGNED
69                };
70            }
71        } else if (sampleSize == 16) {
72            if (encoding.equals(AudioFormat.Encoding.PCM_SIGNED)
73                    || encoding.equals(AudioFormat.Encoding.PCM_UNSIGNED)) {
74                return new AudioFormat.Encoding[]{
75                        AudioFormat.Encoding.PCM_UNSIGNED,
76                        AudioFormat.Encoding.PCM_SIGNED
77                };
78            }
79        }
80        return new AudioFormat.Encoding[0];
81    }
82
83    @Override
84    public AudioFormat[] getTargetFormats(AudioFormat.Encoding targetEncoding, AudioFormat sourceFormat){
85        Objects.requireNonNull(targetEncoding);
86
87        // filter out targetEncoding from the old getOutputFormats( sourceFormat ) method
88
89        AudioFormat[] formats = getOutputFormats( sourceFormat );
90        Vector<AudioFormat> newFormats = new Vector<>();
91        for(int i=0; i<formats.length; i++ ) {
92            if( formats[i].getEncoding().equals( targetEncoding ) ) {
93                newFormats.addElement( formats[i] );
94            }
95        }
96
97        AudioFormat[] formatArray = new AudioFormat[newFormats.size()];
98
99        for (int i = 0; i < formatArray.length; i++) {
100            formatArray[i] = newFormats.elementAt(i);
101        }
102
103        return formatArray;
104    }
105
106    @Override
107    public AudioInputStream getAudioInputStream(AudioFormat.Encoding targetEncoding, AudioInputStream sourceStream) {
108
109        if( isConversionSupported(targetEncoding, sourceStream.getFormat()) ) {
110
111            AudioFormat sourceFormat = sourceStream.getFormat();
112            AudioFormat targetFormat = new AudioFormat( targetEncoding,
113                                                        sourceFormat.getSampleRate(),
114                                                        sourceFormat.getSampleSizeInBits(),
115                                                        sourceFormat.getChannels(),
116                                                        sourceFormat.getFrameSize(),
117                                                        sourceFormat.getFrameRate(),
118                                                        sourceFormat.isBigEndian() );
119
120            return getConvertedStream(targetFormat, sourceStream);
121
122        } else {
123            throw new IllegalArgumentException("Unsupported conversion: " + sourceStream.getFormat().toString() + " to " + targetEncoding.toString() );
124        }
125
126    }
127
128    @Override
129    public AudioInputStream getAudioInputStream(AudioFormat targetFormat, AudioInputStream sourceStream){
130        if (!isConversionSupported(targetFormat, sourceStream.getFormat()))
131            throw new IllegalArgumentException("Unsupported conversion: "
132                                               + sourceStream.getFormat().toString() + " to "
133                                               + targetFormat.toString());
134        return getConvertedStream( targetFormat, sourceStream );
135    }
136
137    /**
138     * Opens the codec with the specified parameters.
139     * @param stream stream from which data to be processed should be read
140     * @param outputFormat desired data format of the stream after processing
141     * @return stream from which processed data may be read
142     * @throws IllegalArgumentException if the format combination supplied is
143     * not supported.
144     */
145    private AudioInputStream getConvertedStream(AudioFormat outputFormat, AudioInputStream stream) {
146
147        AudioInputStream cs = null;
148
149        AudioFormat inputFormat = stream.getFormat();
150
151        if( inputFormat.matches(outputFormat) ) {
152
153            cs = stream;
154        } else {
155
156            cs = new PCMtoPCMCodecStream(stream, outputFormat);
157        }
158        return cs;
159    }
160
161    /**
162     * Obtains the set of output formats supported by the codec
163     * given a particular input format.
164     * If no output formats are supported for this input format,
165     * returns an array of length 0.
166     * @return array of supported output formats.
167     */
168    private AudioFormat[] getOutputFormats(AudioFormat inputFormat) {
169
170        Vector<AudioFormat> formats = new Vector<>();
171        AudioFormat format;
172
173        int sampleSize = inputFormat.getSampleSizeInBits();
174        boolean isBigEndian = inputFormat.isBigEndian();
175
176
177        if ( sampleSize==8 ) {
178            if ( AudioFormat.Encoding.PCM_SIGNED.equals(inputFormat.getEncoding()) ) {
179
180                format = new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED,
181                                         inputFormat.getSampleRate(),
182                                         inputFormat.getSampleSizeInBits(),
183                                         inputFormat.getChannels(),
184                                         inputFormat.getFrameSize(),
185                                         inputFormat.getFrameRate(),
186                                         false );
187                formats.addElement(format);
188            }
189
190            if ( AudioFormat.Encoding.PCM_UNSIGNED.equals(inputFormat.getEncoding()) ) {
191
192                format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
193                                         inputFormat.getSampleRate(),
194                                         inputFormat.getSampleSizeInBits(),
195                                         inputFormat.getChannels(),
196                                         inputFormat.getFrameSize(),
197                                         inputFormat.getFrameRate(),
198                                         false );
199                formats.addElement(format);
200            }
201
202        } else if ( sampleSize==16 ) {
203
204            if ( AudioFormat.Encoding.PCM_SIGNED.equals(inputFormat.getEncoding()) && isBigEndian ) {
205
206                format = new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED,
207                                         inputFormat.getSampleRate(),
208                                         inputFormat.getSampleSizeInBits(),
209                                         inputFormat.getChannels(),
210                                         inputFormat.getFrameSize(),
211                                         inputFormat.getFrameRate(),
212                                         true );
213                formats.addElement(format);
214                format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
215                                         inputFormat.getSampleRate(),
216                                         inputFormat.getSampleSizeInBits(),
217                                         inputFormat.getChannels(),
218                                         inputFormat.getFrameSize(),
219                                         inputFormat.getFrameRate(),
220                                         false );
221                formats.addElement(format);
222                format = new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED,
223                                         inputFormat.getSampleRate(),
224                                         inputFormat.getSampleSizeInBits(),
225                                         inputFormat.getChannels(),
226                                         inputFormat.getFrameSize(),
227                                         inputFormat.getFrameRate(),
228                                         false );
229                formats.addElement(format);
230            }
231
232            if ( AudioFormat.Encoding.PCM_UNSIGNED.equals(inputFormat.getEncoding()) && isBigEndian ) {
233
234                format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
235                                         inputFormat.getSampleRate(),
236                                         inputFormat.getSampleSizeInBits(),
237                                         inputFormat.getChannels(),
238                                         inputFormat.getFrameSize(),
239                                         inputFormat.getFrameRate(),
240                                         true );
241                formats.addElement(format);
242                format = new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED,
243                                         inputFormat.getSampleRate(),
244                                         inputFormat.getSampleSizeInBits(),
245                                         inputFormat.getChannels(),
246                                         inputFormat.getFrameSize(),
247                                         inputFormat.getFrameRate(),
248                                         false );
249                formats.addElement(format);
250                format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
251                                         inputFormat.getSampleRate(),
252                                         inputFormat.getSampleSizeInBits(),
253                                         inputFormat.getChannels(),
254                                         inputFormat.getFrameSize(),
255                                         inputFormat.getFrameRate(),
256                                         false );
257                formats.addElement(format);
258            }
259
260            if ( AudioFormat.Encoding.PCM_SIGNED.equals(inputFormat.getEncoding()) && !isBigEndian ) {
261
262                format = new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED,
263                                         inputFormat.getSampleRate(),
264                                         inputFormat.getSampleSizeInBits(),
265                                         inputFormat.getChannels(),
266                                         inputFormat.getFrameSize(),
267                                         inputFormat.getFrameRate(),
268                                         false );
269                formats.addElement(format);
270                format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
271                                         inputFormat.getSampleRate(),
272                                         inputFormat.getSampleSizeInBits(),
273                                         inputFormat.getChannels(),
274                                         inputFormat.getFrameSize(),
275                                         inputFormat.getFrameRate(),
276                                         true );
277                formats.addElement(format);
278                format = new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED,
279                                         inputFormat.getSampleRate(),
280                                         inputFormat.getSampleSizeInBits(),
281                                         inputFormat.getChannels(),
282                                         inputFormat.getFrameSize(),
283                                         inputFormat.getFrameRate(),
284                                         true );
285                formats.addElement(format);
286            }
287
288            if ( AudioFormat.Encoding.PCM_UNSIGNED.equals(inputFormat.getEncoding()) && !isBigEndian ) {
289
290                format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
291                                         inputFormat.getSampleRate(),
292                                         inputFormat.getSampleSizeInBits(),
293                                         inputFormat.getChannels(),
294                                         inputFormat.getFrameSize(),
295                                         inputFormat.getFrameRate(),
296                                         false );
297                formats.addElement(format);
298                format = new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED,
299                                         inputFormat.getSampleRate(),
300                                         inputFormat.getSampleSizeInBits(),
301                                         inputFormat.getChannels(),
302                                         inputFormat.getFrameSize(),
303                                         inputFormat.getFrameRate(),
304                                         true );
305                formats.addElement(format);
306                format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
307                                         inputFormat.getSampleRate(),
308                                         inputFormat.getSampleSizeInBits(),
309                                         inputFormat.getChannels(),
310                                         inputFormat.getFrameSize(),
311                                         inputFormat.getFrameRate(),
312                                         true );
313                formats.addElement(format);
314            }
315        }
316        AudioFormat[] formatArray;
317
318        synchronized(formats) {
319
320            formatArray = new AudioFormat[formats.size()];
321
322            for (int i = 0; i < formatArray.length; i++) {
323
324                formatArray[i] = formats.elementAt(i);
325            }
326        }
327
328        return formatArray;
329    }
330
331    class PCMtoPCMCodecStream extends AudioInputStream {
332
333        private final int PCM_SWITCH_SIGNED_8BIT                = 1;
334        private final int PCM_SWITCH_ENDIAN                             = 2;
335        private final int PCM_SWITCH_SIGNED_LE                  = 3;
336        private final int PCM_SWITCH_SIGNED_BE                  = 4;
337        private final int PCM_UNSIGNED_LE2SIGNED_BE             = 5;
338        private final int PCM_SIGNED_LE2UNSIGNED_BE             = 6;
339        private final int PCM_UNSIGNED_BE2SIGNED_LE             = 7;
340        private final int PCM_SIGNED_BE2UNSIGNED_LE             = 8;
341
342        private final int sampleSizeInBytes;
343        private int conversionType = 0;
344
345
346        PCMtoPCMCodecStream(AudioInputStream stream, AudioFormat outputFormat) {
347
348            super(stream, outputFormat, -1);
349
350            int sampleSizeInBits = 0;
351            AudioFormat.Encoding inputEncoding = null;
352            AudioFormat.Encoding outputEncoding = null;
353            boolean inputIsBigEndian;
354            boolean outputIsBigEndian;
355
356            AudioFormat inputFormat = stream.getFormat();
357
358            // throw an IllegalArgumentException if not ok
359            if ( ! (isConversionSupported(inputFormat, outputFormat)) ) {
360
361                throw new IllegalArgumentException("Unsupported conversion: " + inputFormat.toString() + " to " + outputFormat.toString());
362            }
363
364            inputEncoding = inputFormat.getEncoding();
365            outputEncoding = outputFormat.getEncoding();
366            inputIsBigEndian = inputFormat.isBigEndian();
367            outputIsBigEndian = outputFormat.isBigEndian();
368            sampleSizeInBits = inputFormat.getSampleSizeInBits();
369            sampleSizeInBytes = sampleSizeInBits/8;
370
371            // determine conversion to perform
372
373            if( sampleSizeInBits==8 ) {
374                if( AudioFormat.Encoding.PCM_UNSIGNED.equals(inputEncoding) &&
375                    AudioFormat.Encoding.PCM_SIGNED.equals(outputEncoding) ) {
376                    conversionType = PCM_SWITCH_SIGNED_8BIT;
377                    if(Printer.debug) Printer.debug("PCMtoPCMCodecStream: conversionType = PCM_SWITCH_SIGNED_8BIT");
378
379                } else if( AudioFormat.Encoding.PCM_SIGNED.equals(inputEncoding) &&
380                           AudioFormat.Encoding.PCM_UNSIGNED.equals(outputEncoding) ) {
381                    conversionType = PCM_SWITCH_SIGNED_8BIT;
382                    if(Printer.debug) Printer.debug("PCMtoPCMCodecStream: conversionType = PCM_SWITCH_SIGNED_8BIT");
383                }
384            } else {
385
386                if( inputEncoding.equals(outputEncoding) && (inputIsBigEndian != outputIsBigEndian) ) {
387
388                    conversionType = PCM_SWITCH_ENDIAN;
389                    if(Printer.debug) Printer.debug("PCMtoPCMCodecStream: conversionType = PCM_SWITCH_ENDIAN");
390
391
392                } else if (AudioFormat.Encoding.PCM_UNSIGNED.equals(inputEncoding) && !inputIsBigEndian &&
393                            AudioFormat.Encoding.PCM_SIGNED.equals(outputEncoding) && outputIsBigEndian) {
394
395                    conversionType = PCM_UNSIGNED_LE2SIGNED_BE;
396                    if(Printer.debug) Printer.debug("PCMtoPCMCodecStream: conversionType = PCM_UNSIGNED_LE2SIGNED_BE");
397
398                } else if (AudioFormat.Encoding.PCM_SIGNED.equals(inputEncoding) && !inputIsBigEndian &&
399                           AudioFormat.Encoding.PCM_UNSIGNED.equals(outputEncoding) && outputIsBigEndian) {
400
401                    conversionType = PCM_SIGNED_LE2UNSIGNED_BE;
402                    if(Printer.debug) Printer.debug("PCMtoPCMCodecStream: conversionType = PCM_SIGNED_LE2UNSIGNED_BE");
403
404                } else if (AudioFormat.Encoding.PCM_UNSIGNED.equals(inputEncoding) && inputIsBigEndian &&
405                           AudioFormat.Encoding.PCM_SIGNED.equals(outputEncoding) && !outputIsBigEndian) {
406
407                    conversionType = PCM_UNSIGNED_BE2SIGNED_LE;
408                    if(Printer.debug) Printer.debug("PCMtoPCMCodecStream: conversionType = PCM_UNSIGNED_BE2SIGNED_LE");
409
410                } else if (AudioFormat.Encoding.PCM_SIGNED.equals(inputEncoding) && inputIsBigEndian &&
411                           AudioFormat.Encoding.PCM_UNSIGNED.equals(outputEncoding) && !outputIsBigEndian) {
412
413                    conversionType = PCM_SIGNED_BE2UNSIGNED_LE;
414                    if(Printer.debug) Printer.debug("PCMtoPCMCodecStream: conversionType = PCM_SIGNED_BE2UNSIGNED_LE");
415
416                }
417            }
418
419            // set the audio stream length in frames if we know it
420
421            frameSize = inputFormat.getFrameSize();
422            if( frameSize == AudioSystem.NOT_SPECIFIED ) {
423                frameSize=1;
424            }
425            if( stream instanceof AudioInputStream ) {
426                frameLength = stream.getFrameLength();
427            } else {
428                frameLength = AudioSystem.NOT_SPECIFIED;
429            }
430
431            // set framePos to zero
432            framePos = 0;
433
434        }
435
436        /**
437         * Note that this only works for sign conversions.
438         * Other conversions require a read of at least 2 bytes.
439         */
440        @Override
441        public int read() throws IOException {
442
443            // $$jb: do we want to implement this function?
444
445            int temp;
446            byte tempbyte;
447
448            if( frameSize==1 ) {
449                if( conversionType == PCM_SWITCH_SIGNED_8BIT ) {
450                    temp = super.read();
451
452                    if( temp < 0 ) return temp;         // EOF or error
453
454                    tempbyte = (byte) (temp & 0xf);
455                    tempbyte = (tempbyte >= 0) ? (byte)(0x80 | tempbyte) : (byte)(0x7F & tempbyte);
456                    temp = (int) tempbyte & 0xf;
457
458                    return temp;
459
460                } else {
461                    // $$jb: what to return here?
462                    throw new IOException("cannot read a single byte if frame size > 1");
463                }
464            } else {
465                throw new IOException("cannot read a single byte if frame size > 1");
466            }
467        }
468
469        @Override
470        public int read(byte[] b) throws IOException {
471
472            return read(b, 0, b.length);
473        }
474
475        @Override
476        public int read(byte[] b, int off, int len) throws IOException {
477
478
479            int i;
480
481            // don't read fractional frames
482            if ( len%frameSize != 0 ) {
483                len -= (len%frameSize);
484            }
485            // don't read past our own set length
486            if( (frameLength!=AudioSystem.NOT_SPECIFIED) && ( (len/frameSize) >(frameLength-framePos)) ) {
487                len = (int)(frameLength-framePos) * frameSize;
488            }
489
490            int readCount = super.read(b, off, len);
491            byte tempByte;
492
493            if(readCount<0) {   // EOF or error
494                return readCount;
495            }
496
497            // now do the conversions
498
499            switch( conversionType ) {
500
501            case PCM_SWITCH_SIGNED_8BIT:
502                switchSigned8bit(b,off,len,readCount);
503                break;
504
505            case PCM_SWITCH_ENDIAN:
506                switchEndian(b,off,len,readCount);
507                break;
508
509            case PCM_SWITCH_SIGNED_LE:
510                switchSignedLE(b,off,len,readCount);
511                break;
512
513            case PCM_SWITCH_SIGNED_BE:
514                switchSignedBE(b,off,len,readCount);
515                break;
516
517            case PCM_UNSIGNED_LE2SIGNED_BE:
518            case PCM_SIGNED_LE2UNSIGNED_BE:
519                switchSignedLE(b,off,len,readCount);
520                switchEndian(b,off,len,readCount);
521                break;
522
523            case PCM_UNSIGNED_BE2SIGNED_LE:
524            case PCM_SIGNED_BE2UNSIGNED_LE:
525                switchSignedBE(b,off,len,readCount);
526                switchEndian(b,off,len,readCount);
527                break;
528
529            default:
530                                // do nothing
531            }
532
533            // we've done the conversion, just return the readCount
534            return readCount;
535
536        }
537
538        private void switchSigned8bit(byte[] b, int off, int len, int readCount) {
539
540            for(int i=off; i < (off+readCount); i++) {
541                b[i] = (b[i] >= 0) ? (byte)(0x80 | b[i]) : (byte)(0x7F & b[i]);
542            }
543        }
544
545        private void switchSignedBE(byte[] b, int off, int len, int readCount) {
546
547            for(int i=off; i < (off+readCount); i+= sampleSizeInBytes ) {
548                b[i] = (b[i] >= 0) ? (byte)(0x80 | b[i]) : (byte)(0x7F & b[i]);
549            }
550        }
551
552        private void switchSignedLE(byte[] b, int off, int len, int readCount) {
553
554            for(int i=(off+sampleSizeInBytes-1); i < (off+readCount); i+= sampleSizeInBytes ) {
555                b[i] = (b[i] >= 0) ? (byte)(0x80 | b[i]) : (byte)(0x7F & b[i]);
556            }
557        }
558
559        private void switchEndian(byte[] b, int off, int len, int readCount) {
560
561            if(sampleSizeInBytes == 2) {
562                for(int i=off; i < (off+readCount); i += sampleSizeInBytes ) {
563                    byte temp;
564                    temp = b[i];
565                    b[i] = b[i+1];
566                    b[i+1] = temp;
567                }
568            }
569        }
570    } // end class PCMtoPCMCodecStream
571} // end class PCMtoPCMCodec
572