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 }