1 /*
2  * Copyright (c) 1999, 2019, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package com.sun.media.sound;
27 
28 import java.io.IOException;
29 import java.util.Objects;
30 import java.util.Vector;
31 
32 import javax.sound.sampled.AudioFormat;
33 import javax.sound.sampled.AudioFormat.Encoding;
34 import javax.sound.sampled.AudioInputStream;
35 import javax.sound.sampled.AudioSystem;
36 import javax.sound.sampled.spi.FormatConversionProvider;
37 
38 /**
39  * Converts among signed/unsigned and little/big endianness of sampled.
40  *
41  * @author Jan Borgersen
42  */
43 public final class PCMtoPCMCodec extends FormatConversionProvider {
44 
45     @Override
getSourceEncodings()46     public AudioFormat.Encoding[] getSourceEncodings() {
47         return new Encoding[]{Encoding.PCM_SIGNED, Encoding.PCM_UNSIGNED};
48     }
49 
50     @Override
getTargetEncodings()51     public AudioFormat.Encoding[] getTargetEncodings() {
52         return getSourceEncodings();
53     }
54 
55     @Override
getTargetEncodings(AudioFormat sourceFormat)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
getTargetFormats(AudioFormat.Encoding targetEncoding, AudioFormat sourceFormat)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
getAudioInputStream(AudioFormat.Encoding targetEncoding, AudioInputStream sourceStream)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
getAudioInputStream(AudioFormat targetFormat, AudioInputStream sourceStream)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      */
getConvertedStream(AudioFormat outputFormat, AudioInputStream stream)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      */
getOutputFormats(AudioFormat inputFormat)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 
PCMtoPCMCodecStream(AudioInputStream stream, AudioFormat outputFormat)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                 } else if( AudioFormat.Encoding.PCM_SIGNED.equals(inputEncoding) &&
378                            AudioFormat.Encoding.PCM_UNSIGNED.equals(outputEncoding) ) {
379                     conversionType = PCM_SWITCH_SIGNED_8BIT;
380                 }
381             } else {
382 
383                 if( inputEncoding.equals(outputEncoding) && (inputIsBigEndian != outputIsBigEndian) ) {
384 
385                     conversionType = PCM_SWITCH_ENDIAN;
386                 } else if (AudioFormat.Encoding.PCM_UNSIGNED.equals(inputEncoding) && !inputIsBigEndian &&
387                             AudioFormat.Encoding.PCM_SIGNED.equals(outputEncoding) && outputIsBigEndian) {
388 
389                     conversionType = PCM_UNSIGNED_LE2SIGNED_BE;
390                 } else if (AudioFormat.Encoding.PCM_SIGNED.equals(inputEncoding) && !inputIsBigEndian &&
391                            AudioFormat.Encoding.PCM_UNSIGNED.equals(outputEncoding) && outputIsBigEndian) {
392 
393                     conversionType = PCM_SIGNED_LE2UNSIGNED_BE;
394                 } else if (AudioFormat.Encoding.PCM_UNSIGNED.equals(inputEncoding) && inputIsBigEndian &&
395                            AudioFormat.Encoding.PCM_SIGNED.equals(outputEncoding) && !outputIsBigEndian) {
396 
397                     conversionType = PCM_UNSIGNED_BE2SIGNED_LE;
398                 } else if (AudioFormat.Encoding.PCM_SIGNED.equals(inputEncoding) && inputIsBigEndian &&
399                            AudioFormat.Encoding.PCM_UNSIGNED.equals(outputEncoding) && !outputIsBigEndian) {
400 
401                     conversionType = PCM_SIGNED_BE2UNSIGNED_LE;
402                 }
403             }
404 
405             // set the audio stream length in frames if we know it
406 
407             frameSize = inputFormat.getFrameSize();
408             if( frameSize == AudioSystem.NOT_SPECIFIED ) {
409                 frameSize=1;
410             }
411             if( stream instanceof AudioInputStream ) {
412                 frameLength = stream.getFrameLength();
413             } else {
414                 frameLength = AudioSystem.NOT_SPECIFIED;
415             }
416 
417             // set framePos to zero
418             framePos = 0;
419 
420         }
421 
422         /**
423          * Note that this only works for sign conversions.
424          * Other conversions require a read of at least 2 bytes.
425          */
426         @Override
read()427         public int read() throws IOException {
428 
429             // $$jb: do we want to implement this function?
430 
431             int temp;
432             byte tempbyte;
433 
434             if( frameSize==1 ) {
435                 if( conversionType == PCM_SWITCH_SIGNED_8BIT ) {
436                     temp = super.read();
437 
438                     if( temp < 0 ) return temp;         // EOF or error
439 
440                     tempbyte = (byte) (temp & 0xf);
441                     tempbyte = (tempbyte >= 0) ? (byte)(0x80 | tempbyte) : (byte)(0x7F & tempbyte);
442                     temp = (int) tempbyte & 0xf;
443 
444                     return temp;
445 
446                 } else {
447                     // $$jb: what to return here?
448                     throw new IOException("cannot read a single byte if frame size > 1");
449                 }
450             } else {
451                 throw new IOException("cannot read a single byte if frame size > 1");
452             }
453         }
454 
455         @Override
read(byte[] b)456         public int read(byte[] b) throws IOException {
457 
458             return read(b, 0, b.length);
459         }
460 
461         @Override
read(byte[] b, int off, int len)462         public int read(byte[] b, int off, int len) throws IOException {
463 
464 
465             int i;
466 
467             // don't read fractional frames
468             if ( len%frameSize != 0 ) {
469                 len -= (len%frameSize);
470             }
471             // don't read past our own set length
472             if( (frameLength!=AudioSystem.NOT_SPECIFIED) && ( (len/frameSize) >(frameLength-framePos)) ) {
473                 len = (int)(frameLength-framePos) * frameSize;
474             }
475 
476             int readCount = super.read(b, off, len);
477             byte tempByte;
478 
479             if(readCount<0) {   // EOF or error
480                 return readCount;
481             }
482 
483             // now do the conversions
484 
485             switch( conversionType ) {
486 
487             case PCM_SWITCH_SIGNED_8BIT:
488                 switchSigned8bit(b,off,len,readCount);
489                 break;
490 
491             case PCM_SWITCH_ENDIAN:
492                 switchEndian(b,off,len,readCount);
493                 break;
494 
495             case PCM_SWITCH_SIGNED_LE:
496                 switchSignedLE(b,off,len,readCount);
497                 break;
498 
499             case PCM_SWITCH_SIGNED_BE:
500                 switchSignedBE(b,off,len,readCount);
501                 break;
502 
503             case PCM_UNSIGNED_LE2SIGNED_BE:
504             case PCM_SIGNED_LE2UNSIGNED_BE:
505                 switchSignedLE(b,off,len,readCount);
506                 switchEndian(b,off,len,readCount);
507                 break;
508 
509             case PCM_UNSIGNED_BE2SIGNED_LE:
510             case PCM_SIGNED_BE2UNSIGNED_LE:
511                 switchSignedBE(b,off,len,readCount);
512                 switchEndian(b,off,len,readCount);
513                 break;
514 
515             default:
516                                 // do nothing
517             }
518 
519             // we've done the conversion, just return the readCount
520             return readCount;
521 
522         }
523 
switchSigned8bit(byte[] b, int off, int len, int readCount)524         private void switchSigned8bit(byte[] b, int off, int len, int readCount) {
525 
526             for(int i=off; i < (off+readCount); i++) {
527                 b[i] = (b[i] >= 0) ? (byte)(0x80 | b[i]) : (byte)(0x7F & b[i]);
528             }
529         }
530 
switchSignedBE(byte[] b, int off, int len, int readCount)531         private void switchSignedBE(byte[] b, int off, int len, int readCount) {
532 
533             for(int i=off; i < (off+readCount); i+= sampleSizeInBytes ) {
534                 b[i] = (b[i] >= 0) ? (byte)(0x80 | b[i]) : (byte)(0x7F & b[i]);
535             }
536         }
537 
switchSignedLE(byte[] b, int off, int len, int readCount)538         private void switchSignedLE(byte[] b, int off, int len, int readCount) {
539 
540             for(int i=(off+sampleSizeInBytes-1); i < (off+readCount); i+= sampleSizeInBytes ) {
541                 b[i] = (b[i] >= 0) ? (byte)(0x80 | b[i]) : (byte)(0x7F & b[i]);
542             }
543         }
544 
switchEndian(byte[] b, int off, int len, int readCount)545         private void switchEndian(byte[] b, int off, int len, int readCount) {
546 
547             if(sampleSizeInBytes == 2) {
548                 for(int i=off; i < (off+readCount); i += sampleSizeInBytes ) {
549                     byte temp;
550                     temp = b[i];
551                     b[i] = b[i+1];
552                     b[i+1] = temp;
553                 }
554             }
555         }
556     } // end class PCMtoPCMCodecStream
557 } // end class PCMtoPCMCodec
558