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