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.EOFException;
  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 /**
  45  * WAVE file reader.
  46  *
  47  * @author Kara Kytle
  48  * @author Jan Borgersen
  49  * @author Florian Bomers
  50  */
  51 public final class WaveFileReader extends SunFileReader {
  52 
  53     private static final int MAX_READ_LENGTH = 12;
  54 
  55     /**
  56      * Constructs a new WaveFileReader object.
  57      */
  58     public WaveFileReader() {
  59     }
  60 
  61 
  62     /**
  63      * Obtains the audio file format of the input stream provided.  The stream must
  64      * point to valid audio file data.  In general, audio file providers may
  65      * need to read some data from the stream before determining whether they
  66      * support it.  These parsers must
  67      * be able to mark the stream, read enough data to determine whether they
  68      * support the stream, and, if not, reset the stream's read pointer to its original
  69      * position.  If the input stream does not support this, this method may fail
  70      * with an IOException.
  71      * @param stream the input stream from which file format information should be
  72      * extracted
  73      * @return an <code>AudioFileFormat</code> object describing the audio file format
  74      * @throws UnsupportedAudioFileException if the stream does not point to valid audio
  75      * file data recognized by the system
  76      * @throws IOException if an I/O exception occurs
  77      * @see InputStream#markSupported
  78      * @see InputStream#mark
  79      */
  80     public AudioFileFormat getAudioFileFormat(InputStream stream) throws UnsupportedAudioFileException, IOException {
  81         // fix for 4489272: AudioSystem.getAudioFileFormat() fails for InputStream, but works for URL
  82         AudioFileFormat aff = getFMT(stream, true);
  83         // the following is not strictly necessary - but was implemented like that in 1.3.0 - 1.4.1
  84         // so I leave it as it was. May remove this for 1.5.0
  85         stream.reset();
  86         return aff;
  87     }
  88 
  89 
  90     /**
  91      * Obtains the audio file format of the URL provided.  The URL must
  92      * point to valid audio file data.
  93      * @param url the URL from which file format information should be
  94      * extracted
  95      * @return an <code>AudioFileFormat</code> object describing the audio file format
  96      * @throws UnsupportedAudioFileException if the URL does not point to valid audio
  97      * file data recognized by the system
  98      * @throws IOException if an I/O exception occurs
  99      */
 100     public AudioFileFormat getAudioFileFormat(URL url) throws UnsupportedAudioFileException, IOException {
 101         InputStream urlStream = url.openStream(); // throws IOException
 102         AudioFileFormat fileFormat = null;
 103         try {
 104             fileFormat = getFMT(urlStream, false);
 105         } finally {
 106             urlStream.close();
 107         }
 108         return fileFormat;
 109     }
 110 
 111 
 112     /**
 113      * Obtains the audio file format of the File provided.  The File must
 114      * point to valid audio file data.
 115      * @param file the File from which file format information should be
 116      * extracted
 117      * @return an <code>AudioFileFormat</code> object describing the audio file format
 118      * @throws UnsupportedAudioFileException if the File does not point to valid audio
 119      * file data recognized by the system
 120      * @throws IOException if an I/O exception occurs
 121      */
 122     public AudioFileFormat getAudioFileFormat(File file) throws UnsupportedAudioFileException, IOException {
 123         AudioFileFormat fileFormat = null;
 124         FileInputStream fis = new FileInputStream(file);       // throws IOException
 125         // part of fix for 4325421
 126         try {
 127             fileFormat = getFMT(fis, false);
 128         } finally {
 129             fis.close();
 130         }
 131 
 132         return fileFormat;
 133     }
 134 
 135 
 136     /**
 137      * Obtains an audio stream from the input stream provided.  The stream must
 138      * point to valid audio file data.  In general, audio file providers may
 139      * need to read some data from the stream before determining whether they
 140      * support it.  These parsers must
 141      * be able to mark the stream, read enough data to determine whether they
 142      * support the stream, and, if not, reset the stream's read pointer to its original
 143      * position.  If the input stream does not support this, this method may fail
 144      * with an IOException.
 145      * @param stream the input stream from which the <code>AudioInputStream</code> should be
 146      * constructed
 147      * @return an <code>AudioInputStream</code> object based on the audio file data contained
 148      * in the input stream.
 149      * @throws UnsupportedAudioFileException if the stream does not point to valid audio
 150      * file data recognized by the system
 151      * @throws IOException if an I/O exception occurs
 152      * @see InputStream#markSupported
 153      * @see InputStream#mark
 154      */
 155     public AudioInputStream getAudioInputStream(InputStream stream) throws UnsupportedAudioFileException, IOException {
 156         // getFMT leaves the input stream at the beginning of the audio data
 157         AudioFileFormat fileFormat = getFMT(stream, true); // throws UnsupportedAudioFileException, IOException
 158 
 159         // we've got everything, and the stream is at the
 160         // beginning of the audio data, so return an AudioInputStream.
 161         return new AudioInputStream(stream, fileFormat.getFormat(), fileFormat.getFrameLength());
 162     }
 163 
 164 
 165     /**
 166      * Obtains an audio stream from the URL provided.  The URL must
 167      * point to valid audio file data.
 168      * @param url the URL for which the <code>AudioInputStream</code> should be
 169      * constructed
 170      * @return an <code>AudioInputStream</code> object based on the audio file data pointed
 171      * to by the URL
 172      * @throws UnsupportedAudioFileException if the URL does not point to valid audio
 173      * file data recognized by the system
 174      * @throws IOException if an I/O exception occurs
 175      */
 176     public AudioInputStream getAudioInputStream(URL url) throws UnsupportedAudioFileException, IOException {
 177         InputStream urlStream = url.openStream();  // throws IOException
 178         AudioFileFormat fileFormat = null;
 179         try {
 180             fileFormat = getFMT(urlStream, false);
 181         } finally {
 182             if (fileFormat == null) {
 183                 urlStream.close();
 184             }
 185         }
 186         return new AudioInputStream(urlStream, fileFormat.getFormat(), fileFormat.getFrameLength());
 187     }
 188 
 189 
 190     /**
 191      * Obtains an audio stream from the File provided.  The File must
 192      * point to valid audio file data.
 193      * @param file the File for which the <code>AudioInputStream</code> should be
 194      * constructed
 195      * @return an <code>AudioInputStream</code> object based on the audio file data pointed
 196      * to by the File
 197      * @throws UnsupportedAudioFileException if the File does not point to valid audio
 198      * file data recognized by the system
 199      * @throws IOException if an I/O exception occurs
 200      */
 201     public AudioInputStream getAudioInputStream(File file) throws UnsupportedAudioFileException, IOException {
 202         FileInputStream fis = new FileInputStream(file); // throws IOException
 203         AudioFileFormat fileFormat = null;
 204         // part of fix for 4325421
 205         try {
 206             fileFormat = getFMT(fis, false);
 207         } finally {
 208             if (fileFormat == null) {
 209                 fis.close();
 210             }
 211         }
 212         return new AudioInputStream(fis, fileFormat.getFormat(), fileFormat.getFrameLength());
 213     }
 214 
 215 
 216     //--------------------------------------------------------------------
 217 
 218 
 219     private AudioFileFormat getFMT(InputStream stream, boolean doReset) throws UnsupportedAudioFileException, IOException {
 220 
 221         // assumes sream is rewound
 222 
 223         int bytesRead;
 224         int nread = 0;
 225         int fmt;
 226         int length = 0;
 227         int wav_type = 0;
 228         short channels;
 229         long sampleRate;
 230         long avgBytesPerSec;
 231         short blockAlign;
 232         int sampleSizeInBits;
 233         AudioFormat.Encoding encoding = null;
 234 
 235         DataInputStream dis = new DataInputStream( stream );
 236 
 237         if (doReset) {
 238             dis.mark(MAX_READ_LENGTH);
 239         }
 240 
 241         int magic = dis.readInt();
 242         int fileLength = rllong(dis);
 243         int waveMagic = dis.readInt();
 244         int totallength;
 245         if (fileLength <= 0) {
 246             fileLength = AudioSystem.NOT_SPECIFIED;
 247             totallength = AudioSystem.NOT_SPECIFIED;
 248         } else {
 249             totallength = fileLength + 8;
 250         }
 251 
 252         if ((magic != WaveFileFormat.RIFF_MAGIC) || (waveMagic != WaveFileFormat.WAVE_MAGIC)) {
 253             // not WAVE, throw UnsupportedAudioFileException
 254             if (doReset) {
 255                 dis.reset();
 256             }
 257             throw new UnsupportedAudioFileException("not a WAVE file");
 258         }
 259 
 260         // find and read the "fmt" chunk
 261         // we break out of this loop either by hitting EOF or finding "fmt "
 262         while(true) {
 263 
 264             try {
 265                 fmt = dis.readInt();
 266                 nread += 4;
 267                 if( fmt==WaveFileFormat.FMT_MAGIC ) {
 268                     // we've found the 'fmt' chunk
 269                     break;
 270                 } else {
 271                     // else not 'fmt', skip this chunk
 272                     length = rllong(dis);
 273                     nread += 4;
 274                     if (length % 2 > 0) length++;
 275                     nread += dis.skipBytes(length);
 276                 }
 277             } catch (EOFException eof) {
 278                                 // we've reached the end of the file without finding the 'fmt' chunk
 279                 throw new UnsupportedAudioFileException("Not a valid WAV file");
 280             }
 281         }
 282 
 283         // Read the format chunk size.
 284         length = rllong(dis);
 285         nread += 4;
 286 
 287         // This is the nread position at the end of the format chunk
 288         int endLength = nread + length;
 289 
 290         // Read the wave format data out of the format chunk.
 291 
 292         // encoding.
 293         wav_type = rlshort(dis); nread += 2;
 294 
 295         if (wav_type == WaveFileFormat.WAVE_FORMAT_PCM)
 296             encoding = AudioFormat.Encoding.PCM_SIGNED;  // if 8-bit, we need PCM_UNSIGNED, below...
 297         else if ( wav_type == WaveFileFormat.WAVE_FORMAT_ALAW )
 298             encoding = AudioFormat.Encoding.ALAW;
 299         else if ( wav_type == WaveFileFormat.WAVE_FORMAT_MULAW )
 300             encoding = AudioFormat.Encoding.ULAW;
 301         else {
 302             // we don't support any other WAVE formats....
 303             throw new UnsupportedAudioFileException("Not a supported WAV file");
 304         }
 305         // channels
 306         channels = rlshort(dis); nread += 2;
 307 
 308         // sample rate.
 309         sampleRate = rllong(dis); nread += 4;
 310 
 311         // this is the avgBytesPerSec
 312         avgBytesPerSec = rllong(dis); nread += 4;
 313 
 314         // this is blockAlign value
 315         blockAlign = rlshort(dis); nread += 2;
 316 
 317         // this is the PCM-specific value bitsPerSample
 318         sampleSizeInBits = (int)rlshort(dis); nread += 2;
 319 
 320         // if sampleSizeInBits==8, we need to use PCM_UNSIGNED
 321         if ((sampleSizeInBits==8) && encoding.equals(AudioFormat.Encoding.PCM_SIGNED))
 322             encoding = AudioFormat.Encoding.PCM_UNSIGNED;
 323 
 324         // skip any difference between the length of the format chunk
 325         // and what we read
 326 
 327         // if the length of the chunk is odd, there's an extra pad byte
 328         // at the end.  i've never seen this in the fmt chunk, but we
 329         // should check to make sure.
 330 
 331         if (length % 2 != 0) length += 1;
 332 
 333         // $$jb: 07.28.99: endLength>nread, not length>nread.
 334         //       This fixes #4257986
 335         if (endLength > nread)
 336             nread += dis.skipBytes(endLength - nread);
 337 
 338         // we have a format now, so find the "data" chunk
 339         // we break out of this loop either by hitting EOF or finding "data"
 340         // $$kk: if "data" chunk precedes "fmt" chunk we are hosed -- can this legally happen?
 341         nread = 0;
 342         while(true) {
 343             try{
 344                 int datahdr = dis.readInt();
 345                 nread+=4;
 346                 if (datahdr == WaveFileFormat.DATA_MAGIC) {
 347                     // we've found the 'data' chunk
 348                     break;
 349                 } else {
 350                     // else not 'data', skip this chunk
 351                     int thisLength = rllong(dis); nread += 4;
 352                     if (thisLength % 2 > 0) thisLength++;
 353                     nread += dis.skipBytes(thisLength);
 354                 }
 355             } catch (EOFException eof) {
 356                 // we've reached the end of the file without finding the 'data' chunk
 357                 throw new UnsupportedAudioFileException("Not a valid WAV file");
 358             }
 359         }
 360         // this is the length of the data chunk
 361         int dataLength = rllong(dis); nread += 4;
 362 
 363         // now build the new AudioFileFormat and return
 364 
 365         AudioFormat format = new AudioFormat(encoding,
 366                                              (float)sampleRate,
 367                                              sampleSizeInBits, channels,
 368                                              calculatePCMFrameSize(sampleSizeInBits, channels),
 369                                              (float)sampleRate, false);
 370 
 371         return new WaveFileFormat(AudioFileFormat.Type.WAVE,
 372                                   totallength,
 373                                   format,
 374                                   dataLength / format.getFrameSize());
 375     }
 376 
 377 }