1/*
2 * Copyright (c) 2007, 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.HashMap;
31import java.util.Map;
32
33import javax.sound.sampled.AudioFileFormat;
34import javax.sound.sampled.AudioFormat;
35import javax.sound.sampled.AudioFormat.Encoding;
36import javax.sound.sampled.AudioInputStream;
37import javax.sound.sampled.UnsupportedAudioFileException;
38
39/**
40 * WAVE file reader for files using format WAVE_FORMAT_EXTENSIBLE (0xFFFE).
41 *
42 * @author Karl Helgason
43 */
44public final class WaveExtensibleFileReader extends SunFileReader {
45
46    private static final class GUID {
47        private long i1;
48        private int s1;
49        private int s2;
50        private int x1;
51        private int x2;
52        private int x3;
53        private int x4;
54        private int x5;
55        private int x6;
56        private int x7;
57        private int x8;
58        private GUID() {
59        }
60
61        GUID(long i1, int s1, int s2, int x1, int x2, int x3, int x4,
62                int x5, int x6, int x7, int x8) {
63            this.i1 = i1;
64            this.s1 = s1;
65            this.s2 = s2;
66            this.x1 = x1;
67            this.x2 = x2;
68            this.x3 = x3;
69            this.x4 = x4;
70            this.x5 = x5;
71            this.x6 = x6;
72            this.x7 = x7;
73            this.x8 = x8;
74        }
75
76        public static GUID read(RIFFReader riff) throws IOException {
77            GUID d = new GUID();
78            d.i1 = riff.readUnsignedInt();
79            d.s1 = riff.readUnsignedShort();
80            d.s2 = riff.readUnsignedShort();
81            d.x1 = riff.readUnsignedByte();
82            d.x2 = riff.readUnsignedByte();
83            d.x3 = riff.readUnsignedByte();
84            d.x4 = riff.readUnsignedByte();
85            d.x5 = riff.readUnsignedByte();
86            d.x6 = riff.readUnsignedByte();
87            d.x7 = riff.readUnsignedByte();
88            d.x8 = riff.readUnsignedByte();
89            return d;
90        }
91
92        @Override
93        public int hashCode() {
94            return (int) i1;
95        }
96
97        @Override
98        public boolean equals(Object obj) {
99            if (!(obj instanceof GUID))
100                return false;
101            GUID t = (GUID) obj;
102            if (i1 != t.i1)
103                return false;
104            if (s1 != t.s1)
105                return false;
106            if (s2 != t.s2)
107                return false;
108            if (x1 != t.x1)
109                return false;
110            if (x2 != t.x2)
111                return false;
112            if (x3 != t.x3)
113                return false;
114            if (x4 != t.x4)
115                return false;
116            if (x5 != t.x5)
117                return false;
118            if (x6 != t.x6)
119                return false;
120            if (x7 != t.x7)
121                return false;
122            if (x8 != t.x8)
123                return false;
124            return true;
125        }
126    }
127
128    private static final String[] channelnames = { "FL", "FR", "FC", "LF",
129            "BL",
130            "BR", // 5.1
131            "FLC", "FLR", "BC", "SL", "SR", "TC", "TFL", "TFC", "TFR", "TBL",
132            "TBC", "TBR" };
133
134    private static final String[] allchannelnames = { "w1", "w2", "w3", "w4", "w5",
135            "w6", "w7", "w8", "w9", "w10", "w11", "w12", "w13", "w14", "w15",
136            "w16", "w17", "w18", "w19", "w20", "w21", "w22", "w23", "w24",
137            "w25", "w26", "w27", "w28", "w29", "w30", "w31", "w32", "w33",
138            "w34", "w35", "w36", "w37", "w38", "w39", "w40", "w41", "w42",
139            "w43", "w44", "w45", "w46", "w47", "w48", "w49", "w50", "w51",
140            "w52", "w53", "w54", "w55", "w56", "w57", "w58", "w59", "w60",
141            "w61", "w62", "w63", "w64" };
142
143    private static final GUID SUBTYPE_PCM = new GUID(0x00000001, 0x0000, 0x0010,
144            0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
145
146    private static final GUID SUBTYPE_IEEE_FLOAT = new GUID(0x00000003, 0x0000,
147            0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
148
149    private static String decodeChannelMask(long channelmask) {
150        StringBuilder sb = new StringBuilder();
151        long m = 1;
152        for (int i = 0; i < allchannelnames.length; i++) {
153            if ((channelmask & m) != 0L) {
154                if (i < channelnames.length) {
155                    sb.append(channelnames[i]).append(' ');
156                } else {
157                    sb.append(allchannelnames[i]).append(' ');
158                }
159            }
160            m *= 2L;
161        }
162        if (sb.length() == 0)
163            return null;
164        return sb.substring(0, sb.length() - 1);
165
166    }
167
168    @Override
169    StandardFileFormat getAudioFileFormatImpl(final InputStream stream)
170            throws UnsupportedAudioFileException, IOException {
171
172        RIFFReader riffiterator = new RIFFReader(stream);
173        if (!riffiterator.getFormat().equals("RIFF"))
174            throw new UnsupportedAudioFileException();
175        if (!riffiterator.getType().equals("WAVE"))
176            throw new UnsupportedAudioFileException();
177
178        boolean fmt_found = false;
179        boolean data_found = false;
180
181        int channels = 1;
182        long samplerate = 1;
183        // long framerate = 1;
184        int framesize = 1;
185        int bits = 1;
186        long dataSize = 0;
187        int validBitsPerSample = 1;
188        long channelMask = 0;
189        GUID subFormat = null;
190
191        while (riffiterator.hasNextChunk()) {
192            RIFFReader chunk = riffiterator.nextChunk();
193
194            if (chunk.getFormat().equals("fmt ")) {
195                fmt_found = true;
196
197                int format = chunk.readUnsignedShort();
198                if (format != WaveFileFormat.WAVE_FORMAT_EXTENSIBLE) {
199                    throw new UnsupportedAudioFileException();
200                }
201                channels = chunk.readUnsignedShort();
202                samplerate = chunk.readUnsignedInt();
203                /* framerate = */chunk.readUnsignedInt();
204                framesize = chunk.readUnsignedShort();
205                bits = chunk.readUnsignedShort();
206                int cbSize = chunk.readUnsignedShort();
207                if (cbSize != 22)
208                    throw new UnsupportedAudioFileException();
209                validBitsPerSample = chunk.readUnsignedShort();
210                if (validBitsPerSample > bits)
211                    throw new UnsupportedAudioFileException();
212                channelMask = chunk.readUnsignedInt();
213                subFormat = GUID.read(chunk);
214
215            }
216            if (chunk.getFormat().equals("data")) {
217                dataSize = chunk.getSize();
218                data_found = true;
219                break;
220            }
221        }
222        if (!fmt_found || !data_found) {
223            throw new UnsupportedAudioFileException();
224        }
225        Map<String, Object> p = new HashMap<>();
226        String s_channelmask = decodeChannelMask(channelMask);
227        if (s_channelmask != null)
228            p.put("channelOrder", s_channelmask);
229        if (channelMask != 0)
230            p.put("channelMask", channelMask);
231        // validBitsPerSample is only informational for PCM data,
232        // data is still encode according to SampleSizeInBits.
233        p.put("validBitsPerSample", validBitsPerSample);
234
235        AudioFormat audioformat = null;
236        if (subFormat.equals(SUBTYPE_PCM)) {
237            if (bits == 8) {
238                audioformat = new AudioFormat(Encoding.PCM_UNSIGNED,
239                        samplerate, bits, channels, framesize, samplerate,
240                        false, p);
241            } else {
242                audioformat = new AudioFormat(Encoding.PCM_SIGNED, samplerate,
243                        bits, channels, framesize, samplerate, false, p);
244            }
245        } else if (subFormat.equals(SUBTYPE_IEEE_FLOAT)) {
246            audioformat = new AudioFormat(Encoding.PCM_FLOAT,
247                    samplerate, bits, channels, framesize, samplerate, false, p);
248        } else {
249            throw new UnsupportedAudioFileException();
250        }
251        return new StandardFileFormat(AudioFileFormat.Type.WAVE, audioformat,
252                                      dataSize / audioformat.getFrameSize());
253    }
254
255    @Override
256    public AudioInputStream getAudioInputStream(final InputStream stream)
257            throws UnsupportedAudioFileException, IOException {
258
259        final StandardFileFormat format = getAudioFileFormat(stream);
260        final AudioFormat af = format.getFormat();
261        final long length = format.getLongFrameLength();
262        // we've got everything, the stream is supported and it is at the
263        // beginning of the header, so find the data chunk again and return an
264        // AudioInputStream
265        final RIFFReader riffiterator = new RIFFReader(stream);
266        while (riffiterator.hasNextChunk()) {
267            RIFFReader chunk = riffiterator.nextChunk();
268            if (chunk.getFormat().equals("data")) {
269                return new AudioInputStream(chunk, af, length);
270            }
271        }
272        throw new UnsupportedAudioFileException();
273    }
274}
275