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.DataInputStream;
29import java.io.IOException;
30import java.io.InputStream;
31
32import javax.sound.sampled.AudioFileFormat.Type;
33import javax.sound.sampled.AudioFormat;
34import javax.sound.sampled.AudioSystem;
35import javax.sound.sampled.UnsupportedAudioFileException;
36
37/**
38 * AIFF file reader and writer.
39 *
40 * @author Kara Kytle
41 * @author Jan Borgersen
42 * @author Florian Bomers
43 */
44public final class AiffFileReader extends SunFileReader {
45
46    @Override
47    StandardFileFormat getAudioFileFormatImpl(final InputStream stream)
48            throws UnsupportedAudioFileException, IOException {
49        DataInputStream dis = new DataInputStream(stream);
50
51        AudioFormat format = null;
52
53        // Read the magic number
54        int magic = dis.readInt();
55
56        // $$fb: fix for 4369044: javax.sound.sampled.AudioSystem.getAudioInputStream() works wrong with Cp037
57        if (magic != AiffFileFormat.AIFF_MAGIC) {
58            // not AIFF, throw exception
59            throw new UnsupportedAudioFileException("not an AIFF file");
60        }
61
62        long /* unsigned 32bit */ frameLength = 0;
63        int length = dis.readInt();
64        int iffType = dis.readInt();
65
66        final long totallength;
67        if(length <= 0 ) {
68            length = AudioSystem.NOT_SPECIFIED;
69            totallength = AudioSystem.NOT_SPECIFIED;
70        } else {
71            totallength = length + 8;
72        }
73
74        // Is this an AIFC or just plain AIFF file.
75        boolean aifc = false;
76        // $$fb: fix for 4369044: javax.sound.sampled.AudioSystem.getAudioInputStream() works wrong with Cp037
77        if (iffType ==  AiffFileFormat.AIFC_MAGIC) {
78            aifc = true;
79        }
80
81        // Loop through the AIFF chunks until
82        // we get to the SSND chunk.
83        boolean ssndFound = false;
84        while (!ssndFound) {
85            // Read the chunk name
86            int chunkName = dis.readInt();
87            int chunkLen = dis.readInt();
88
89            int chunkRead = 0;
90
91            // Switch on the chunk name.
92            switch (chunkName) {
93            case AiffFileFormat.FVER_MAGIC:
94                // Ignore format version for now.
95                break;
96
97            case AiffFileFormat.COMM_MAGIC:
98                // AIFF vs. AIFC
99                // $$fb: fix for 4399551: Repost of bug candidate: cannot replay aif file (Review ID: 108108)
100                if ((!aifc && chunkLen < 18) || (aifc && chunkLen < 22)) {
101                    throw new UnsupportedAudioFileException("Invalid AIFF/COMM chunksize");
102                }
103                // Read header info.
104                int channels = dis.readUnsignedShort();
105                if (channels <= 0) {
106                    throw new UnsupportedAudioFileException("Invalid number of channels");
107                }
108                frameLength = dis.readInt() & 0xffffffffL; // numSampleFrames
109
110                int sampleSizeInBits = dis.readUnsignedShort();
111                if (sampleSizeInBits < 1 || sampleSizeInBits > 32) {
112                    throw new UnsupportedAudioFileException("Invalid AIFF/COMM sampleSize");
113                }
114                float sampleRate = (float) read_ieee_extended(dis);
115                chunkRead += (2 + 4 + 2 + 10);
116
117                // If this is not AIFC then we assume it's
118                // a linearly encoded file.
119                AudioFormat.Encoding encoding = AudioFormat.Encoding.PCM_SIGNED;
120
121                if (aifc) {
122                    int enc = dis.readInt(); chunkRead += 4;
123                    switch (enc) {
124                    case AiffFileFormat.AIFC_PCM:
125                        encoding = AudioFormat.Encoding.PCM_SIGNED;
126                        break;
127                    case AiffFileFormat.AIFC_ULAW:
128                        encoding = AudioFormat.Encoding.ULAW;
129                        sampleSizeInBits = 8; // Java Sound convention
130                        break;
131                    default:
132                        throw new UnsupportedAudioFileException("Invalid AIFF encoding");
133                    }
134                }
135                int frameSize = calculatePCMFrameSize(sampleSizeInBits, channels);
136                //$fb what's that ??
137                //if (sampleSizeInBits == 8) {
138                //    encoding = AudioFormat.Encoding.PCM_SIGNED;
139                //}
140                format =  new AudioFormat(encoding, sampleRate,
141                                          sampleSizeInBits, channels,
142                                          frameSize, sampleRate, true);
143                break;
144            case AiffFileFormat.SSND_MAGIC:
145                // Data chunk.
146                int dataOffset = dis.readInt(); // for now unused in javasound
147                int blocksize = dis.readInt();  // for now unused in javasound
148                chunkRead += 8;
149                ssndFound = true;
150                break;
151            } // switch
152            // skip the remainder of this chunk
153            if (!ssndFound) {
154                int toSkip = chunkLen - chunkRead;
155                if (toSkip > 0) {
156                    dis.skipBytes(toSkip);
157                }
158            }
159        } // while
160
161        if (format == null) {
162            throw new UnsupportedAudioFileException("missing COMM chunk");
163        }
164        Type type = aifc ? Type.AIFC : Type.AIFF;
165
166        return new AiffFileFormat(type, totallength, format, frameLength);
167    }
168
169    // HELPER METHODS
170    /**
171     * read_ieee_extended
172     * Extended precision IEEE floating-point conversion routine.
173     * @argument DataInputStream
174     * @return double
175     * @exception IOException
176     */
177    private double read_ieee_extended(DataInputStream dis) throws IOException {
178
179        double f = 0;
180        int expon = 0;
181        long hiMant = 0, loMant = 0;
182        long t1, t2;
183        double HUGE = 3.40282346638528860e+38;
184
185
186        expon = dis.readUnsignedShort();
187
188        t1 = (long)dis.readUnsignedShort();
189        t2 = (long)dis.readUnsignedShort();
190        hiMant = t1 << 16 | t2;
191
192        t1 = (long)dis.readUnsignedShort();
193        t2 = (long)dis.readUnsignedShort();
194        loMant = t1 << 16 | t2;
195
196        if (expon == 0 && hiMant == 0 && loMant == 0) {
197            f = 0;
198        } else {
199            if (expon == 0x7FFF)
200                f = HUGE;
201            else {
202                expon -= 16383;
203                expon -= 31;
204                f = (hiMant * Math.pow(2, expon));
205                expon -= 32;
206                f += (loMant * Math.pow(2, expon));
207            }
208        }
209
210        return f;
211    }
212}
213