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 * U-law encodes linear data, and decodes u-law data to linear data.
40 *
41 * @author Kara Kytle
42 */
43public 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;
64            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
73    public AudioFormat.Encoding[] getSourceEncodings() {
74        return new Encoding[]{Encoding.ULAW, Encoding.PCM_SIGNED};
75    }
76
77    @Override
78    public AudioFormat.Encoding[] getTargetEncodings() {
79        return getSourceEncodings();
80    }
81
82    @Override
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
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
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
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     */
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     */
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
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         */
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
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
329        public int read(byte[] b) throws IOException {
330            return read(b, 0, b.length);
331        }
332
333        @Override
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
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