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 }