1 /*
   2  * Copyright (c) 1999, 2014, 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.DataInputStream;
  29 import java.io.DataOutputStream;
  30 import java.io.File;
  31 import java.io.FileInputStream;
  32 import java.io.IOException;
  33 import java.io.InputStream;
  34 import java.net.URL;
  35 
  36 import javax.sound.sampled.AudioFileFormat;
  37 import javax.sound.sampled.AudioFormat;
  38 import javax.sound.sampled.AudioInputStream;
  39 import javax.sound.sampled.AudioSystem;
  40 import javax.sound.sampled.UnsupportedAudioFileException;
  41 
  42 
  43 /**
  44  * AIFF file reader and writer.
  45  *
  46  * @author Kara Kytle
  47  * @author Jan Borgersen
  48  * @author Florian Bomers
  49  */
  50 public final class AiffFileReader extends SunFileReader {
  51 
  52     private static final int MAX_READ_LENGTH = 8;
  53 
  54     // METHODS TO IMPLEMENT AudioFileReader
  55 
  56     /**
  57      * Obtains the audio file format of the input stream provided.  The stream must
  58      * point to valid audio file data.  In general, audio file providers may
  59      * need to read some data from the stream before determining whether they
  60      * support it.  These parsers must
  61      * be able to mark the stream, read enough data to determine whether they
  62      * support the stream, and, if not, reset the stream's read pointer to its original
  63      * position.  If the input stream does not support this, this method may fail
  64      * with an IOException.
  65      * @param stream the input stream from which file format information should be
  66      * extracted
  67      * @return an <code>AudioFileFormat</code> object describing the audio file format
  68      * @throws UnsupportedAudioFileException if the stream does not point to valid audio
  69      * file data recognized by the system
  70      * @throws IOException if an I/O exception occurs
  71      * @see InputStream#markSupported
  72      * @see InputStream#mark
  73      */
  74     public AudioFileFormat getAudioFileFormat(InputStream stream) throws UnsupportedAudioFileException, IOException {
  75         // fix for 4489272: AudioSystem.getAudioFileFormat() fails for InputStream, but works for URL
  76         AudioFileFormat aff = getCOMM(stream, true);
  77         // the following is not strictly necessary - but was implemented like that in 1.3.0 - 1.4.1
  78         // so I leave it as it was. May remove this for 1.5.0
  79         stream.reset();
  80         return aff;
  81     }
  82 
  83 
  84     /**
  85      * Obtains the audio file format of the URL provided.  The URL must
  86      * point to valid audio file data.
  87      * @param url the URL from which file format information should be
  88      * extracted
  89      * @return an <code>AudioFileFormat</code> object describing the audio file format
  90      * @throws UnsupportedAudioFileException if the URL does not point to valid audio
  91      * file data recognized by the system
  92      * @throws IOException if an I/O exception occurs
  93      */
  94     public AudioFileFormat getAudioFileFormat(URL url) throws UnsupportedAudioFileException, IOException {
  95         AudioFileFormat fileFormat = null;
  96         InputStream urlStream = url.openStream();       // throws IOException
  97         try {
  98             fileFormat = getCOMM(urlStream, false);
  99         } finally {
 100             urlStream.close();
 101         }
 102         return fileFormat;
 103     }
 104 
 105 
 106     /**
 107      * Obtains the audio file format of the File provided.  The File must
 108      * point to valid audio file data.
 109      * @param file the File from which file format information should be
 110      * extracted
 111      * @return an <code>AudioFileFormat</code> object describing the audio file format
 112      * @throws UnsupportedAudioFileException if the File does not point to valid audio
 113      * file data recognized by the system
 114      * @throws IOException if an I/O exception occurs
 115      */
 116     public AudioFileFormat getAudioFileFormat(File file) throws UnsupportedAudioFileException, IOException {
 117         AudioFileFormat fileFormat = null;
 118         FileInputStream fis = new FileInputStream(file);       // throws IOException
 119         // part of fix for 4325421
 120         try {
 121             fileFormat = getCOMM(fis, false);
 122         } finally {
 123             fis.close();
 124         }
 125 
 126         return fileFormat;
 127     }
 128 
 129 
 130 
 131 
 132     /**
 133      * Obtains an audio stream from the input stream provided.  The stream must
 134      * point to valid audio file data.  In general, audio file providers may
 135      * need to read some data from the stream before determining whether they
 136      * support it.  These parsers must
 137      * be able to mark the stream, read enough data to determine whether they
 138      * support the stream, and, if not, reset the stream's read pointer to its original
 139      * position.  If the input stream does not support this, this method may fail
 140      * with an IOException.
 141      * @param stream the input stream from which the <code>AudioInputStream</code> should be
 142      * constructed
 143      * @return an <code>AudioInputStream</code> object based on the audio file data contained
 144      * in the input stream.
 145      * @throws UnsupportedAudioFileException if the stream does not point to valid audio
 146      * file data recognized by the system
 147      * @throws IOException if an I/O exception occurs
 148      * @see InputStream#markSupported
 149      * @see InputStream#mark
 150      */
 151     public AudioInputStream getAudioInputStream(InputStream stream) throws UnsupportedAudioFileException, IOException {
 152         // getCOMM leaves the input stream at the beginning of the audio data
 153         AudioFileFormat fileFormat = getCOMM(stream, true);     // throws UnsupportedAudioFileException, IOException
 154 
 155         // we've got everything, and the stream is at the
 156         // beginning of the audio data, so return an AudioInputStream.
 157         return new AudioInputStream(stream, fileFormat.getFormat(), fileFormat.getFrameLength());
 158     }
 159 
 160 
 161     /**
 162      * Obtains an audio stream from the URL provided.  The URL must
 163      * point to valid audio file data.
 164      * @param url the URL for which the <code>AudioInputStream</code> should be
 165      * constructed
 166      * @return an <code>AudioInputStream</code> object based on the audio file data pointed
 167      * to by the URL
 168      * @throws UnsupportedAudioFileException if the URL does not point to valid audio
 169      * file data recognized by the system
 170      * @throws IOException if an I/O exception occurs
 171      */
 172     public AudioInputStream getAudioInputStream(URL url) throws UnsupportedAudioFileException, IOException {
 173         InputStream urlStream = url.openStream();  // throws IOException
 174         AudioFileFormat fileFormat = null;
 175         try {
 176             fileFormat = getCOMM(urlStream, false);
 177         } finally {
 178             if (fileFormat == null) {
 179                 urlStream.close();
 180             }
 181         }
 182         return new AudioInputStream(urlStream, fileFormat.getFormat(), fileFormat.getFrameLength());
 183     }
 184 
 185 
 186     /**
 187      * Obtains an audio stream from the File provided.  The File must
 188      * point to valid audio file data.
 189      * @param file the File for which the <code>AudioInputStream</code> should be
 190      * constructed
 191      * @return an <code>AudioInputStream</code> object based on the audio file data pointed
 192      * to by the File
 193      * @throws UnsupportedAudioFileException if the File does not point to valid audio
 194      * file data recognized by the system
 195      * @throws IOException if an I/O exception occurs
 196      */
 197     public AudioInputStream getAudioInputStream(File file)
 198         throws UnsupportedAudioFileException, IOException {
 199 
 200         FileInputStream fis = new FileInputStream(file); // throws IOException
 201         AudioFileFormat fileFormat = null;
 202         // part of fix for 4325421
 203         try {
 204             fileFormat = getCOMM(fis, false);
 205         } finally {
 206             if (fileFormat == null) {
 207                 fis.close();
 208             }
 209         }
 210         return new AudioInputStream(fis, fileFormat.getFormat(), fileFormat.getFrameLength());
 211     }
 212 
 213     //--------------------------------------------------------------------
 214 
 215     private AudioFileFormat getCOMM(InputStream is, boolean doReset)
 216         throws UnsupportedAudioFileException, IOException {
 217 
 218         DataInputStream dis = new DataInputStream(is);
 219 
 220         if (doReset) {
 221             dis.mark(MAX_READ_LENGTH);
 222         }
 223 
 224         // assumes a stream at the beginning of the file which has already
 225         // passed the magic number test...
 226         // leaves the input stream at the beginning of the audio data
 227         int fileRead = 0;
 228         int dataLength = 0;
 229         AudioFormat format = null;
 230 
 231         // Read the magic number
 232         int magic = dis.readInt();
 233 
 234         // $$fb: fix for 4369044: javax.sound.sampled.AudioSystem.getAudioInputStream() works wrong with Cp037
 235         if (magic != AiffFileFormat.AIFF_MAGIC) {
 236             // not AIFF, throw exception
 237             if (doReset) {
 238                 dis.reset();
 239             }
 240             throw new UnsupportedAudioFileException("not an AIFF file");
 241         }
 242 
 243         int length = dis.readInt();
 244         int iffType = dis.readInt();
 245         fileRead += 12;
 246 
 247         int totallength;
 248         if(length <= 0 ) {
 249             length = AudioSystem.NOT_SPECIFIED;
 250             totallength = AudioSystem.NOT_SPECIFIED;
 251         } else {
 252             totallength = length + 8;
 253         }
 254 
 255         // Is this an AIFC or just plain AIFF file.
 256         boolean aifc = false;
 257         // $$fb: fix for 4369044: javax.sound.sampled.AudioSystem.getAudioInputStream() works wrong with Cp037
 258         if (iffType ==  AiffFileFormat.AIFC_MAGIC) {
 259             aifc = true;
 260         }
 261 
 262         // Loop through the AIFF chunks until
 263         // we get to the SSND chunk.
 264         boolean ssndFound = false;
 265         while (!ssndFound) {
 266             // Read the chunk name
 267             int chunkName = dis.readInt();
 268             int chunkLen = dis.readInt();
 269             fileRead += 8;
 270 
 271             int chunkRead = 0;
 272 
 273             // Switch on the chunk name.
 274             switch (chunkName) {
 275             case AiffFileFormat.FVER_MAGIC:
 276                 // Ignore format version for now.
 277                 break;
 278 
 279             case AiffFileFormat.COMM_MAGIC:
 280                 // AIFF vs. AIFC
 281                 // $$fb: fix for 4399551: Repost of bug candidate: cannot replay aif file (Review ID: 108108)
 282                 if ((!aifc && chunkLen < 18) || (aifc && chunkLen < 22)) {
 283                     throw new UnsupportedAudioFileException("Invalid AIFF/COMM chunksize");
 284                 }
 285                 // Read header info.
 286                 int channels = dis.readUnsignedShort();
 287                 if (channels <= 0) {
 288                     throw new UnsupportedAudioFileException("Invalid number of channels");
 289                 }
 290                 dis.readInt(); // numSampleFrames
 291                 int sampleSizeInBits = dis.readUnsignedShort();
 292                 if (sampleSizeInBits < 1 || sampleSizeInBits > 32) {
 293                     throw new UnsupportedAudioFileException("Invalid AIFF/COMM sampleSize");
 294                 }
 295                 float sampleRate = (float) read_ieee_extended(dis);
 296                 chunkRead += (2 + 4 + 2 + 10);
 297 
 298                 // If this is not AIFC then we assume it's
 299                 // a linearly encoded file.
 300                 AudioFormat.Encoding encoding = AudioFormat.Encoding.PCM_SIGNED;
 301 
 302                 if (aifc) {
 303                     int enc = dis.readInt(); chunkRead += 4;
 304                     switch (enc) {
 305                     case AiffFileFormat.AIFC_PCM:
 306                         encoding = AudioFormat.Encoding.PCM_SIGNED;
 307                         break;
 308                     case AiffFileFormat.AIFC_ULAW:
 309                         encoding = AudioFormat.Encoding.ULAW;
 310                         sampleSizeInBits = 8; // Java Sound convention
 311                         break;
 312                     default:
 313                         throw new UnsupportedAudioFileException("Invalid AIFF encoding");
 314                     }
 315                 }
 316                 int frameSize = calculatePCMFrameSize(sampleSizeInBits, channels);
 317                 //$fb what's that ??
 318                 //if (sampleSizeInBits == 8) {
 319                 //    encoding = AudioFormat.Encoding.PCM_SIGNED;
 320                 //}
 321                 format =  new AudioFormat(encoding, sampleRate,
 322                                           sampleSizeInBits, channels,
 323                                           frameSize, sampleRate, true);
 324                 break;
 325             case AiffFileFormat.SSND_MAGIC:
 326                 // Data chunk.
 327                 // we are getting *weird* numbers for chunkLen sometimes;
 328                 // this really should be the size of the data chunk....
 329                 int dataOffset = dis.readInt();
 330                 int blocksize = dis.readInt();
 331                 chunkRead += 8;
 332 
 333                 // okay, now we are done reading the header.  we need to set the size
 334                 // of the data segment.  we know that sometimes the value we get for
 335                 // the chunksize is absurd.  this is the best i can think of:if the
 336                 // value seems okay, use it.  otherwise, we get our value of
 337                 // length by assuming that everything left is the data segment;
 338                 // its length should be our original length (for all AIFF data chunks)
 339                 // minus what we've read so far.
 340                 // $$kk: we should be able to get length for the data chunk right after
 341                 // we find "SSND."  however, some aiff files give *weird* numbers.  what
 342                 // is going on??
 343 
 344                 if (chunkLen < length) {
 345                     dataLength = chunkLen - chunkRead;
 346                 } else {
 347                     // $$kk: 11.03.98: this seems dangerous!
 348                     dataLength = length - (fileRead + chunkRead);
 349                 }
 350                 ssndFound = true;
 351                 break;
 352             } // switch
 353             fileRead += chunkRead;
 354             // skip the remainder of this chunk
 355             if (!ssndFound) {
 356                 int toSkip = chunkLen - chunkRead;
 357                 if (toSkip > 0) {
 358                     fileRead += dis.skipBytes(toSkip);
 359                 }
 360             }
 361         } // while
 362 
 363         if (format == null) {
 364             throw new UnsupportedAudioFileException("missing COMM chunk");
 365         }
 366         AudioFileFormat.Type type = aifc?AudioFileFormat.Type.AIFC:AudioFileFormat.Type.AIFF;
 367 
 368         return new AiffFileFormat(type, totallength, format, dataLength / format.getFrameSize());
 369     }
 370 
 371     // HELPER METHODS
 372     /** write_ieee_extended(DataOutputStream dos, double f) throws IOException {
 373      * Extended precision IEEE floating-point conversion routine.
 374      * @argument DataOutputStream
 375      * @argument double
 376      * @return void
 377      * @exception IOException
 378      */
 379     private void write_ieee_extended(DataOutputStream dos, double f) throws IOException {
 380 
 381         int exponent = 16398;
 382         double highMantissa = f;
 383 
 384         // For now write the integer portion of f
 385         // $$jb: 03.30.99: stay in synch with JMF on this!!!!
 386         while (highMantissa < 44000) {
 387             highMantissa *= 2;
 388             exponent--;
 389         }
 390         dos.writeShort(exponent);
 391         dos.writeInt( ((int) highMantissa) << 16);
 392         dos.writeInt(0); // low Mantissa
 393     }
 394 
 395 
 396     /**
 397      * read_ieee_extended
 398      * Extended precision IEEE floating-point conversion routine.
 399      * @argument DataInputStream
 400      * @return double
 401      * @exception IOException
 402      */
 403     private double read_ieee_extended(DataInputStream dis) throws IOException {
 404 
 405         double f = 0;
 406         int expon = 0;
 407         long hiMant = 0, loMant = 0;
 408         long t1, t2;
 409         double HUGE = 3.40282346638528860e+38;
 410 
 411 
 412         expon = dis.readUnsignedShort();
 413 
 414         t1 = (long)dis.readUnsignedShort();
 415         t2 = (long)dis.readUnsignedShort();
 416         hiMant = t1 << 16 | t2;
 417 
 418         t1 = (long)dis.readUnsignedShort();
 419         t2 = (long)dis.readUnsignedShort();
 420         loMant = t1 << 16 | t2;
 421 
 422         if (expon == 0 && hiMant == 0 && loMant == 0) {
 423             f = 0;
 424         } else {
 425             if (expon == 0x7FFF)
 426                 f = HUGE;
 427             else {
 428                 expon -= 16383;
 429                 expon -= 31;
 430                 f = (hiMant * Math.pow(2, expon));
 431                 expon -= 32;
 432                 f += (loMant * Math.pow(2, expon));
 433             }
 434         }
 435 
 436         return f;
 437     }
 438 }