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