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