1 /*
2  * Copyright (c) 1999, 2018, 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  * U-law encodes linear data, and decodes u-law data to linear data.
40  *
41  * @author Kara Kytle
42  */
43 public final class UlawCodec extends FormatConversionProvider {
44 
45     /* Tables used for U-law decoding */
46 
47     private static final byte[] ULAW_TABH = new byte[256];
48     private static final byte[] ULAW_TABL = new byte[256];
49 
50     private static final short[] seg_end = {
51             0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF, 0x3FFF, 0x7FFF
52     };
53 
54     /**
55      * Initializes the decode tables.
56      */
57     static {
58         for (int i=0;i<256;i++) {
59             int ulaw = ~i;
60             int t;
61 
62             ulaw &= 0xFF;
63             t = ((ulaw & 0xf)<<3) + 132;
t(ulaw & 0x70) >> 464             t <<= ((ulaw & 0x70) >> 4);
65             t = ( (ulaw&0x80) != 0 ) ? (132-t) : (t-132);
66 
67             ULAW_TABL[i] = (byte) (t&0xff);
68             ULAW_TABH[i] = (byte) ((t>>8) & 0xff);
69         }
70     }
71 
72     @Override
getSourceEncodings()73     public AudioFormat.Encoding[] getSourceEncodings() {
74         return new Encoding[]{Encoding.ULAW, Encoding.PCM_SIGNED};
75     }
76 
77     @Override
getTargetEncodings()78     public AudioFormat.Encoding[] getTargetEncodings() {
79         return getSourceEncodings();
80     }
81 
82     @Override
getTargetEncodings(AudioFormat sourceFormat)83     public AudioFormat.Encoding[] getTargetEncodings(AudioFormat sourceFormat){
84         if( AudioFormat.Encoding.PCM_SIGNED.equals(sourceFormat.getEncoding()) ) {
85             if( sourceFormat.getSampleSizeInBits() == 16 ) {
86                 AudioFormat.Encoding[] enc = new AudioFormat.Encoding[1];
87                 enc[0] = AudioFormat.Encoding.ULAW;
88                 return enc;
89             } else {
90                 return new AudioFormat.Encoding[0];
91             }
92         } else if (AudioFormat.Encoding.ULAW.equals(sourceFormat.getEncoding())) {
93             if (sourceFormat.getSampleSizeInBits() == 8) {
94                 AudioFormat.Encoding[] enc = new AudioFormat.Encoding[1];
95                 enc[0] = AudioFormat.Encoding.PCM_SIGNED;
96                 return enc;
97             } else {
98                 return new AudioFormat.Encoding[0];
99             }
100         } else {
101             return new AudioFormat.Encoding[0];
102         }
103     }
104 
105     @Override
getTargetFormats(AudioFormat.Encoding targetEncoding, AudioFormat sourceFormat)106     public AudioFormat[] getTargetFormats(AudioFormat.Encoding targetEncoding, AudioFormat sourceFormat){
107         Objects.requireNonNull(targetEncoding);
108         Objects.requireNonNull(sourceFormat);
109         if( (AudioFormat.Encoding.PCM_SIGNED.equals(targetEncoding)
110              && AudioFormat.Encoding.ULAW.equals(sourceFormat.getEncoding()))
111             ||
112             (AudioFormat.Encoding.ULAW.equals(targetEncoding)
113              && AudioFormat.Encoding.PCM_SIGNED.equals(sourceFormat.getEncoding()))) {
114                 return getOutputFormats(sourceFormat);
115             } else {
116                 return new AudioFormat[0];
117             }
118     }
119 
120     @Override
getAudioInputStream(AudioFormat.Encoding targetEncoding, AudioInputStream sourceStream)121     public AudioInputStream getAudioInputStream(AudioFormat.Encoding targetEncoding, AudioInputStream sourceStream){
122         AudioFormat sourceFormat = sourceStream.getFormat();
123         AudioFormat.Encoding sourceEncoding = sourceFormat.getEncoding();
124 
125         if (!isConversionSupported(targetEncoding,sourceStream.getFormat())) {
126             throw new IllegalArgumentException("Unsupported conversion: " + sourceStream.getFormat().toString() + " to " + targetEncoding.toString());
127         }
128         if (sourceEncoding.equals(targetEncoding)) {
129             return sourceStream;
130         }
131         AudioFormat targetFormat = null;
132         if (AudioFormat.Encoding.ULAW.equals(sourceEncoding) &&
133             AudioFormat.Encoding.PCM_SIGNED.equals(targetEncoding) ) {
134             targetFormat = new AudioFormat( targetEncoding,
135                                             sourceFormat.getSampleRate(),
136                                             16,
137                                             sourceFormat.getChannels(),
138                                             2*sourceFormat.getChannels(),
139                                             sourceFormat.getSampleRate(),
140                                             sourceFormat.isBigEndian());
141         } else if (AudioFormat.Encoding.PCM_SIGNED.equals(sourceEncoding) &&
142                    AudioFormat.Encoding.ULAW.equals(targetEncoding)) {
143             targetFormat = new AudioFormat( targetEncoding,
144                                             sourceFormat.getSampleRate(),
145                                             8,
146                                             sourceFormat.getChannels(),
147                                             sourceFormat.getChannels(),
148                                             sourceFormat.getSampleRate(),
149                                             false);
150         } else {
151             throw new IllegalArgumentException("Unsupported conversion: " + sourceStream.getFormat().toString() + " to " + targetEncoding.toString());
152         }
153 
154         return getConvertedStream(targetFormat, sourceStream);
155     }
156 
157     @Override
getAudioInputStream(AudioFormat targetFormat, AudioInputStream sourceStream)158     public AudioInputStream getAudioInputStream(AudioFormat targetFormat, AudioInputStream sourceStream){
159         if (!isConversionSupported(targetFormat, sourceStream.getFormat()))
160             throw new IllegalArgumentException("Unsupported conversion: "
161                                                + sourceStream.getFormat().toString() + " to "
162                                                + targetFormat.toString());
163         return getConvertedStream(targetFormat, sourceStream);
164     }
165 
166     /**
167      * Opens the codec with the specified parameters.
168      * @param stream stream from which data to be processed should be read
169      * @param outputFormat desired data format of the stream after processing
170      * @return stream from which processed data may be read
171      * @throws IllegalArgumentException if the format combination supplied is
172      * not supported.
173      */
getConvertedStream(AudioFormat outputFormat, AudioInputStream stream)174     private AudioInputStream getConvertedStream(AudioFormat outputFormat, AudioInputStream stream) {
175         AudioInputStream cs = null;
176 
177         AudioFormat inputFormat = stream.getFormat();
178 
179         if( inputFormat.matches(outputFormat) ) {
180             cs = stream;
181         } else {
182             cs = new UlawCodecStream(stream, outputFormat);
183         }
184         return cs;
185     }
186 
187     /**
188      * Obtains the set of output formats supported by the codec
189      * given a particular input format.
190      * If no output formats are supported for this input format,
191      * returns an array of length 0.
192      * @return array of supported output formats.
193      */
getOutputFormats(AudioFormat inputFormat)194     private AudioFormat[] getOutputFormats(AudioFormat inputFormat) {
195 
196         Vector<AudioFormat> formats = new Vector<>();
197         AudioFormat format;
198 
199         if ((inputFormat.getSampleSizeInBits() == 16)
200             && AudioFormat.Encoding.PCM_SIGNED.equals(inputFormat.getEncoding())) {
201             format = new AudioFormat(AudioFormat.Encoding.ULAW,
202                                      inputFormat.getSampleRate(),
203                                      8,
204                                      inputFormat.getChannels(),
205                                      inputFormat.getChannels(),
206                                      inputFormat.getSampleRate(),
207                                      false );
208             formats.addElement(format);
209         }
210         if (inputFormat.getSampleSizeInBits() == 8
211                 && AudioFormat.Encoding.ULAW.equals(inputFormat.getEncoding())) {
212             format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
213                                      inputFormat.getSampleRate(), 16,
214                                      inputFormat.getChannels(),
215                                      inputFormat.getChannels() * 2,
216                                      inputFormat.getSampleRate(), false);
217             formats.addElement(format);
218 
219             format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
220                                      inputFormat.getSampleRate(), 16,
221                                      inputFormat.getChannels(),
222                                      inputFormat.getChannels() * 2,
223                                      inputFormat.getSampleRate(), true);
224             formats.addElement(format);
225         }
226 
227         AudioFormat[] formatArray = new AudioFormat[formats.size()];
228         for (int i = 0; i < formatArray.length; i++) {
229             formatArray[i] = formats.elementAt(i);
230         }
231         return formatArray;
232     }
233 
234     private final class UlawCodecStream extends AudioInputStream {
235 
236         private static final int tempBufferSize = 64;
237         private byte[] tempBuffer  = null;
238 
239         /**
240          * True to encode to u-law, false to decode to linear.
241          */
242         boolean encode = false;
243 
244         AudioFormat encodeFormat;
245         AudioFormat decodeFormat;
246 
247         byte[] tabByte1 = null;
248         byte[] tabByte2 = null;
249         int highByte = 0;
250         int lowByte  = 1;
251 
UlawCodecStream(AudioInputStream stream, AudioFormat outputFormat)252         UlawCodecStream(AudioInputStream stream, AudioFormat outputFormat) {
253             super(stream, outputFormat, AudioSystem.NOT_SPECIFIED);
254 
255             AudioFormat inputFormat = stream.getFormat();
256 
257             // throw an IllegalArgumentException if not ok
258             if (!(isConversionSupported(outputFormat, inputFormat))) {
259                 throw new IllegalArgumentException("Unsupported conversion: " + inputFormat.toString() + " to " + outputFormat.toString());
260             }
261 
262             //$$fb 2002-07-18: fix for 4714846: JavaSound ULAW (8-bit) encoder erroneously depends on endian-ness
263             boolean PCMIsBigEndian;
264 
265             // determine whether we are encoding or decoding
266             if (AudioFormat.Encoding.ULAW.equals(inputFormat.getEncoding())) {
267                 encode = false;
268                 encodeFormat = inputFormat;
269                 decodeFormat = outputFormat;
270                 PCMIsBigEndian = outputFormat.isBigEndian();
271             } else {
272                 encode = true;
273                 encodeFormat = outputFormat;
274                 decodeFormat = inputFormat;
275                 PCMIsBigEndian = inputFormat.isBigEndian();
276                 tempBuffer = new byte[tempBufferSize];
277             }
278 
279             // setup tables according to byte order
280             if (PCMIsBigEndian) {
281                 tabByte1 = ULAW_TABH;
282                 tabByte2 = ULAW_TABL;
283                 highByte = 0;
284                 lowByte  = 1;
285             } else {
286                 tabByte1 = ULAW_TABL;
287                 tabByte2 = ULAW_TABH;
288                 highByte = 1;
289                 lowByte  = 0;
290             }
291 
292             // set the AudioInputStream length in frames if we know it
293             if (stream instanceof AudioInputStream) {
294                 frameLength = stream.getFrameLength();
295             }
296             // set framePos to zero
297             framePos = 0;
298             frameSize = inputFormat.getFrameSize();
299             if (frameSize == AudioSystem.NOT_SPECIFIED) {
300                 frameSize = 1;
301             }
302         }
303 
304         /*
305          * $$jb 2/23/99
306          * Used to determine segment number in uLaw encoding
307          */
search(short val, short[] table, short size)308         private short search(short val, short[] table, short size) {
309             for(short i = 0; i < size; i++) {
310                 if (val <= table[i]) { return i; }
311             }
312             return size;
313         }
314 
315         /**
316          * Note that this won't actually read anything; must read in
317          * two-byte units.
318          */
319         @Override
read()320         public int read() throws IOException {
321             byte[] b = new byte[1];
322             if (read(b, 0, b.length) == 1) {
323                 return b[1] & 0xFF;
324             }
325             return -1;
326         }
327 
328         @Override
read(byte[] b)329         public int read(byte[] b) throws IOException {
330             return read(b, 0, b.length);
331         }
332 
333         @Override
read(byte[] b, int off, int len)334         public int read(byte[] b, int off, int len) throws IOException {
335             // don't read fractional frames
336             if( len%frameSize != 0 ) {
337                 len -= (len%frameSize);
338             }
339             if (encode) {
340                 short BIAS = 0x84;
341                 short mask;
342                 short seg;
343                 int i;
344 
345                 short sample;
346                 byte enc;
347 
348                 int readCount = 0;
349                 int currentPos = off;
350                 int readLeft = len*2;
351                 int readLen = ( (readLeft>tempBufferSize) ? tempBufferSize : readLeft );
352 
353                 while ((readCount = super.read(tempBuffer,0,readLen))>0) {
354                     for(i = 0; i < readCount; i+=2) {
355                         /* Get the sample from the tempBuffer */
356                         sample = (short)(( (tempBuffer[i + highByte]) << 8) & 0xFF00);
357                         sample |= (short)( (short) (tempBuffer[i + lowByte]) & 0xFF);
358 
359                         /* Get the sign and the magnitude of the value. */
360                         if(sample < 0) {
361                             sample = (short) (BIAS - sample);
362                             mask = 0x7F;
363                         } else {
364                             sample += BIAS;
365                             mask = 0xFF;
366                         }
367                         /* Convert the scaled magnitude to segment number. */
368                         seg = search(sample, seg_end, (short) 8);
369                         /*
370                          * Combine the sign, segment, quantization bits;
371                          * and complement the code word.
372                          */
373                         if (seg >= 8) {  /* out of range, return maximum value. */
374                             enc = (byte) (0x7F ^ mask);
375                         } else {
376                             enc = (byte) ((seg << 4) | ((sample >> (seg+3)) & 0xF));
377                             enc ^= mask;
378                         }
379                         /* Now put the encoded sample where it belongs */
380                         b[currentPos] = enc;
381                         currentPos++;
382                     }
383                     /* And update pointers and counters for next iteration */
384                     readLeft -= readCount;
385                     readLen = ( (readLeft>tempBufferSize) ? tempBufferSize : readLeft );
386                 }
387                 if( currentPos==off && readCount<0 ) {  // EOF or error on read
388                     return readCount;
389                 }
390                 return (currentPos - off);  /* Number of bytes written to new buffer */
391             } else {
392                 int i;
393                 int readLen = len/2;
394                 int readOffset = off + len/2;
395                 int readCount = super.read(b, readOffset, readLen);
396 
397                 if(readCount<0) {               // EOF or error
398                     return readCount;
399                 }
400                 for (i = off; i < (off + (readCount*2)); i+=2) {
401                     b[i]        = tabByte1[b[readOffset] & 0xFF];
402                     b[i+1]      = tabByte2[b[readOffset] & 0xFF];
403                     readOffset++;
404                 }
405                 return (i - off);
406             }
407         }
408 
409         @Override
skip(final long n)410         public long skip(final long n) throws IOException {
411             // Implementation of this method assumes that we support
412             // encoding/decoding from/to 8/16 bits only
413             return encode ? super.skip(n * 2) / 2 : super.skip(n / 2) * 2;
414         }
415     } // end class UlawCodecStream
416 } // end class ULAW
417