diff options
Diffstat (limited to 'songdbj/org/tritonus/file/WaveAudioFileReader.java')
-rw-r--r-- | songdbj/org/tritonus/file/WaveAudioFileReader.java | 300 |
1 files changed, 300 insertions, 0 deletions
diff --git a/songdbj/org/tritonus/file/WaveAudioFileReader.java b/songdbj/org/tritonus/file/WaveAudioFileReader.java new file mode 100644 index 0000000000..62d3f1a9ea --- /dev/null +++ b/songdbj/org/tritonus/file/WaveAudioFileReader.java | |||
@@ -0,0 +1,300 @@ | |||
1 | /* | ||
2 | * WaveAudioFileReader.java | ||
3 | * | ||
4 | * This file is part of Tritonus: http://www.tritonus.org/ | ||
5 | */ | ||
6 | |||
7 | /* | ||
8 | * Copyright (c) 1999,2000 by Florian Bomers <http://www.bomers.de> | ||
9 | * Copyright (c) 1999 by Matthias Pfisterer | ||
10 | * | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or modify | ||
13 | * it under the terms of the GNU Library General Public License as published | ||
14 | * by the Free Software Foundation; either version 2 of the License, or | ||
15 | * (at your option) any later version. | ||
16 | * | ||
17 | * This program is distributed in the hope that it will be useful, | ||
18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
20 | * GNU Library General Public License for more details. | ||
21 | * | ||
22 | * You should have received a copy of the GNU Library General Public | ||
23 | * License along with this program; if not, write to the Free Software | ||
24 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
25 | * | ||
26 | */ | ||
27 | |||
28 | /* | ||
29 | |<--- this code is formatted to fit into 80 columns --->| | ||
30 | */ | ||
31 | |||
32 | package org.tritonus.sampled.file; | ||
33 | |||
34 | import java.io.DataInputStream; | ||
35 | import java.io.File; | ||
36 | import java.io.InputStream; | ||
37 | import java.io.IOException; | ||
38 | |||
39 | import javax.sound.sampled.AudioSystem; | ||
40 | import javax.sound.sampled.AudioFormat; | ||
41 | import javax.sound.sampled.AudioFileFormat; | ||
42 | import javax.sound.sampled.AudioInputStream; | ||
43 | import javax.sound.sampled.UnsupportedAudioFileException; | ||
44 | import javax.sound.sampled.spi.AudioFileReader; | ||
45 | |||
46 | import org.tritonus.share.TDebug; | ||
47 | import org.tritonus.share.sampled.file.TAudioFileFormat; | ||
48 | import org.tritonus.share.sampled.file.TAudioFileReader; | ||
49 | |||
50 | |||
51 | /** | ||
52 | * Class for reading wave files. | ||
53 | * | ||
54 | * @author Florian Bomers | ||
55 | * @author Matthias Pfisterer | ||
56 | */ | ||
57 | |||
58 | public class WaveAudioFileReader extends TAudioFileReader | ||
59 | { | ||
60 | private static final int READ_LIMIT = 1000; | ||
61 | |||
62 | |||
63 | |||
64 | public WaveAudioFileReader() | ||
65 | { | ||
66 | super(READ_LIMIT); | ||
67 | } | ||
68 | |||
69 | |||
70 | |||
71 | protected void advanceChunk(DataInputStream dis, long prevLength, long prevRead) | ||
72 | throws IOException { | ||
73 | if (prevLength>0) { | ||
74 | dis.skip(((prevLength+1) & 0xFFFFFFFE)-prevRead); | ||
75 | } | ||
76 | } | ||
77 | |||
78 | |||
79 | protected long findChunk(DataInputStream dis, int key) | ||
80 | throws UnsupportedAudioFileException, IOException { | ||
81 | // $$fb 1999-12-18: we should take care that we don't exceed | ||
82 | // the mark of this stream. When we exceeded the mark and | ||
83 | // we notice that we don't support this wave file, | ||
84 | // other potential wave file readers have no chance. | ||
85 | int thisKey; | ||
86 | long chunkLength=0; | ||
87 | do { | ||
88 | advanceChunk(dis, chunkLength, 0); | ||
89 | try { | ||
90 | thisKey = dis.readInt(); | ||
91 | } catch (IOException e) | ||
92 | { | ||
93 | if (TDebug.TraceAllExceptions) | ||
94 | { | ||
95 | TDebug.out(e); | ||
96 | } | ||
97 | // $$fb: when we come here, we skipped past the end of the wave file | ||
98 | // without finding the chunk. | ||
99 | // IMHO, this is not an IOException, as there are incarnations | ||
100 | // of WAVE files which store data in different chunks. | ||
101 | // maybe we can find a nice description of the "required chunk" ? | ||
102 | throw new UnsupportedAudioFileException( | ||
103 | "unsupported WAVE file: required chunk not found."); | ||
104 | } | ||
105 | chunkLength = readLittleEndianInt(dis) & 0xFFFFFFFF; // unsigned | ||
106 | } | ||
107 | while (thisKey != key); | ||
108 | return chunkLength; | ||
109 | } | ||
110 | |||
111 | protected AudioFormat readFormatChunk(DataInputStream dis, | ||
112 | long chunkLength) throws UnsupportedAudioFileException, IOException { | ||
113 | String debugAdd=""; | ||
114 | |||
115 | int read=WaveTool.MIN_FMT_CHUNK_LENGTH; | ||
116 | |||
117 | if (chunkLength<WaveTool.MIN_FMT_CHUNK_LENGTH) { | ||
118 | throw new UnsupportedAudioFileException( | ||
119 | "corrupt WAVE file: format chunk is too small"); | ||
120 | } | ||
121 | |||
122 | short formatCode=readLittleEndianShort(dis); | ||
123 | short channelCount = readLittleEndianShort(dis); | ||
124 | if (channelCount <= 0) { | ||
125 | throw new UnsupportedAudioFileException( | ||
126 | "corrupt WAVE file: number of channels must be positive"); | ||
127 | } | ||
128 | |||
129 | int sampleRate = readLittleEndianInt(dis); | ||
130 | if (sampleRate <= 0) { | ||
131 | throw new UnsupportedAudioFileException( | ||
132 | "corrupt WAVE file: sample rate must be positive"); | ||
133 | } | ||
134 | |||
135 | int avgBytesPerSecond=readLittleEndianInt(dis); | ||
136 | int blockAlign=readLittleEndianShort(dis); | ||
137 | |||
138 | AudioFormat.Encoding encoding; | ||
139 | int sampleSizeInBits; | ||
140 | int frameSize=0; | ||
141 | float frameRate=(float) sampleRate; | ||
142 | |||
143 | int cbSize = 0; | ||
144 | switch (formatCode) { | ||
145 | case WaveTool.WAVE_FORMAT_PCM: | ||
146 | if (chunkLength<WaveTool.MIN_FMT_CHUNK_LENGTH+2) { | ||
147 | throw new UnsupportedAudioFileException( | ||
148 | "corrupt WAVE file: format chunk is too small"); | ||
149 | } | ||
150 | sampleSizeInBits = readLittleEndianShort(dis); | ||
151 | if (sampleSizeInBits <= 0) { | ||
152 | throw new UnsupportedAudioFileException( | ||
153 | "corrupt WAVE file: sample size must be positive"); | ||
154 | } | ||
155 | encoding = (sampleSizeInBits <= 8) ? | ||
156 | AudioFormat.Encoding.PCM_UNSIGNED : AudioFormat.Encoding.PCM_SIGNED; | ||
157 | if (TDebug.TraceAudioFileReader) { | ||
158 | debugAdd+=", wBitsPerSample="+sampleSizeInBits; | ||
159 | } | ||
160 | read+=2; | ||
161 | break; | ||
162 | case WaveTool.WAVE_FORMAT_ALAW: | ||
163 | sampleSizeInBits = 8; | ||
164 | encoding = AudioFormat.Encoding.ALAW; | ||
165 | break; | ||
166 | case WaveTool.WAVE_FORMAT_ULAW: | ||
167 | sampleSizeInBits = 8; | ||
168 | encoding = AudioFormat.Encoding.ULAW; | ||
169 | break; | ||
170 | case WaveTool.WAVE_FORMAT_GSM610: | ||
171 | if (chunkLength<WaveTool.MIN_FMT_CHUNK_LENGTH+6) { | ||
172 | throw new UnsupportedAudioFileException( | ||
173 | "corrupt WAVE file: extra GSM bytes are missing"); | ||
174 | } | ||
175 | sampleSizeInBits = readLittleEndianShort(dis); // sample Size (is 0 for GSM) | ||
176 | cbSize=readLittleEndianShort(dis); | ||
177 | if (cbSize < 2) { | ||
178 | throw new UnsupportedAudioFileException( | ||
179 | "corrupt WAVE file: extra GSM bytes are corrupt"); | ||
180 | } | ||
181 | int decodedSamplesPerBlock=readLittleEndianShort(dis) & 0xFFFF; // unsigned | ||
182 | if (TDebug.TraceAudioFileReader) { | ||
183 | debugAdd+=", wBitsPerSample="+sampleSizeInBits | ||
184 | +", cbSize="+cbSize | ||
185 | +", wSamplesPerBlock="+decodedSamplesPerBlock; | ||
186 | } | ||
187 | sampleSizeInBits = AudioSystem.NOT_SPECIFIED; | ||
188 | encoding = WaveTool.GSM0610; | ||
189 | frameSize=blockAlign; | ||
190 | frameRate=((float) sampleRate)/((float) decodedSamplesPerBlock); | ||
191 | read+=6; | ||
192 | break; | ||
193 | |||
194 | case WaveTool.WAVE_FORMAT_IMA_ADPCM: | ||
195 | if (chunkLength < WaveTool.MIN_FMT_CHUNK_LENGTH + 2) | ||
196 | { | ||
197 | throw new UnsupportedAudioFileException( | ||
198 | "corrupt WAVE file: extra GSM bytes are missing"); | ||
199 | } | ||
200 | sampleSizeInBits = readLittleEndianShort(dis); | ||
201 | cbSize = readLittleEndianShort(dis); | ||
202 | if (cbSize < 2) | ||
203 | { | ||
204 | throw new UnsupportedAudioFileException( | ||
205 | "corrupt WAVE file: extra IMA ADPCM bytes are corrupt"); | ||
206 | } | ||
207 | int samplesPerBlock = readLittleEndianShort(dis) & 0xFFFF; // unsigned | ||
208 | if (TDebug.TraceAudioFileReader) { | ||
209 | debugAdd+=", wBitsPerSample="+sampleSizeInBits | ||
210 | +", cbSize="+cbSize | ||
211 | +", wSamplesPerBlock=" + samplesPerBlock; | ||
212 | } | ||
213 | sampleSizeInBits = AudioSystem.NOT_SPECIFIED; | ||
214 | encoding = WaveTool.GSM0610; | ||
215 | frameSize = blockAlign; | ||
216 | frameRate = ((float) sampleRate)/((float) samplesPerBlock); | ||
217 | read += 6; | ||
218 | break; | ||
219 | |||
220 | default: | ||
221 | throw new UnsupportedAudioFileException( | ||
222 | "unsupported WAVE file: unknown format code "+formatCode); | ||
223 | } | ||
224 | // if frameSize isn't set, calculate it (the default) | ||
225 | if (frameSize==0) { | ||
226 | frameSize = calculateFrameSize(sampleSizeInBits, channelCount); | ||
227 | } | ||
228 | |||
229 | if (TDebug.TraceAudioFileReader) { | ||
230 | TDebug.out("WaveAudioFileReader.readFormatChunk():"); | ||
231 | TDebug.out(" read values: wFormatTag="+formatCode | ||
232 | +", nChannels="+channelCount | ||
233 | +", nSamplesPerSec="+sampleRate | ||
234 | +", nAvgBytesPerSec="+avgBytesPerSecond | ||
235 | +", nBlockAlign=="+blockAlign | ||
236 | +debugAdd); | ||
237 | TDebug.out(" constructed values: " | ||
238 | +"encoding="+encoding | ||
239 | +", sampleRate="+((float) sampleRate) | ||
240 | +", sampleSizeInBits="+sampleSizeInBits | ||
241 | +", channels="+channelCount | ||
242 | +", frameSize="+frameSize | ||
243 | +", frameRate="+frameRate); | ||
244 | } | ||
245 | |||
246 | // go to next chunk | ||
247 | advanceChunk(dis, chunkLength, read); | ||
248 | return new AudioFormat( | ||
249 | encoding, | ||
250 | (float) sampleRate, | ||
251 | sampleSizeInBits, | ||
252 | channelCount, | ||
253 | frameSize, | ||
254 | frameRate, | ||
255 | false); | ||
256 | } | ||
257 | |||
258 | |||
259 | |||
260 | protected AudioFileFormat getAudioFileFormat(InputStream inputStream, long lFileLengthInBytes) | ||
261 | throws UnsupportedAudioFileException, IOException { | ||
262 | DataInputStream dataInputStream = new DataInputStream(inputStream); | ||
263 | int magic = dataInputStream.readInt(); | ||
264 | if (magic != WaveTool.WAVE_RIFF_MAGIC) { | ||
265 | throw new UnsupportedAudioFileException( | ||
266 | "not a WAVE file: wrong header magic"); | ||
267 | } | ||
268 | long totalLength = readLittleEndianInt(dataInputStream) & 0xFFFFFFFF; // unsigned | ||
269 | magic = dataInputStream.readInt(); | ||
270 | if (magic != WaveTool.WAVE_WAVE_MAGIC) { | ||
271 | throw new UnsupportedAudioFileException("not a WAVE file: wrong header magic"); | ||
272 | } | ||
273 | // search for "fmt " chunk | ||
274 | long chunkLength = findChunk(dataInputStream, WaveTool.WAVE_FMT_MAGIC); | ||
275 | AudioFormat format = readFormatChunk(dataInputStream, chunkLength); | ||
276 | |||
277 | // search for "data" chunk | ||
278 | long dataChunkLength = findChunk(dataInputStream, WaveTool.WAVE_DATA_MAGIC); | ||
279 | |||
280 | long frameLength = dataChunkLength / format.getFrameSize(); | ||
281 | if (format.getEncoding().equals(WaveTool.GSM0610)) { | ||
282 | // TODO: should not be necessary | ||
283 | frameLength = dataChunkLength; | ||
284 | } | ||
285 | |||
286 | if (TDebug.TraceAudioFileReader) { | ||
287 | TDebug.out("WaveAudioFileReader.getAudioFileFormat(): total length: " | ||
288 | +totalLength+", frame length = "+frameLength); | ||
289 | } | ||
290 | return new TAudioFileFormat(AudioFileFormat.Type.WAVE, | ||
291 | format, | ||
292 | (int) frameLength, | ||
293 | (int) (totalLength + WaveTool.CHUNK_HEADER_SIZE)); | ||
294 | } | ||
295 | } | ||
296 | |||
297 | |||
298 | |||
299 | /*** WaveAudioFileReader.java ***/ | ||
300 | |||