blob: 27af4ecfbce806110db8b2b0c3f17f209848a06e [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.lang.IllegalArgumentException;
34
35import java.io.BufferedOutputStream;
36import java.io.DataOutputStream;
37import java.io.FileOutputStream;
38import java.io.ByteArrayInputStream;
39import java.io.ByteArrayOutputStream;
40import java.io.RandomAccessFile;
41import java.io.SequenceInputStream;
42
43import javax.sound.sampled.AudioFileFormat;
44import javax.sound.sampled.AudioInputStream;
45import javax.sound.sampled.AudioFormat;
46import javax.sound.sampled.AudioSystem;
47
48//$$fb this class is buggy. Should be replaced in future.
49
50/**
51 * WAVE file writer.
52 *
53 * @author Jan Borgersen
54 */
55public class WaveFileWriter extends SunFileWriter {
56
57 // magic numbers
58 static final int RIFF_MAGIC = 1380533830;
59 static final int WAVE_MAGIC = 1463899717;
60 static final int FMT_MAGIC = 0x666d7420; // "fmt "
61 static final int DATA_MAGIC = 0x64617461; // "data"
62
63 // encodings
64 static final int WAVE_FORMAT_UNKNOWN = 0x0000;
65 static final int WAVE_FORMAT_PCM = 0x0001;
66 static final int WAVE_FORMAT_ADPCM = 0x0002;
67 static final int WAVE_FORMAT_ALAW = 0x0006;
68 static final int WAVE_FORMAT_MULAW = 0x0007;
69 static final int WAVE_FORMAT_OKI_ADPCM = 0x0010;
70 static final int WAVE_FORMAT_DIGISTD = 0x0015;
71 static final int WAVE_FORMAT_DIGIFIX = 0x0016;
72 static final int WAVE_IBM_FORMAT_MULAW = 0x0101;
73 static final int WAVE_IBM_FORMAT_ALAW = 0x0102;
74 static final int WAVE_IBM_FORMAT_ADPCM = 0x0103;
75 static final int WAVE_FORMAT_DVI_ADPCM = 0x0011;
76 static final int WAVE_FORMAT_SX7383 = 0x1C07;
77
78 /**
79 * WAVE type
80 */
81 private static final AudioFileFormat.Type waveTypes[] = {
82 AudioFileFormat.Type.WAVE
83 };
84
85
86 /**
87 * Constructs a new WaveFileWriter object.
88 */
89 public WaveFileWriter() {
90 super(waveTypes);
91 }
92
93
94 // METHODS TO IMPLEMENT AudioFileWriter
95
96
97 public AudioFileFormat.Type[] getAudioFileTypes(AudioInputStream stream) {
98
99 AudioFileFormat.Type[] filetypes = new AudioFileFormat.Type[types.length];
100 System.arraycopy(types, 0, filetypes, 0, types.length);
101
102 // make sure we can write this stream
103 AudioFormat format = stream.getFormat();
104 AudioFormat.Encoding encoding = format.getEncoding();
105
106 if( AudioFormat.Encoding.ALAW.equals(encoding) ||
107 AudioFormat.Encoding.ULAW.equals(encoding) ||
108 AudioFormat.Encoding.PCM_SIGNED.equals(encoding) ||
109 AudioFormat.Encoding.PCM_UNSIGNED.equals(encoding) ) {
110
111 return filetypes;
112 }
113
114 return new AudioFileFormat.Type[0];
115 }
116
117
118 public int write(AudioInputStream stream, AudioFileFormat.Type fileType, OutputStream out) throws IOException {
119
120 //$$fb the following check must come first ! Otherwise
121 // the next frame length check may throw an IOException and
122 // interrupt iterating File Writers. (see bug 4351296)
123
124 // throws IllegalArgumentException if not supported
125 WaveFileFormat waveFileFormat = (WaveFileFormat)getAudioFileFormat(fileType, stream);
126
127 //$$fb when we got this far, we are committed to write this file
128
129 // we must know the total data length to calculate the file length
130 if( stream.getFrameLength() == AudioSystem.NOT_SPECIFIED ) {
131 throw new IOException("stream length not specified");
132 }
133
134 int bytesWritten = writeWaveFile(stream, waveFileFormat, out);
135 return bytesWritten;
136 }
137
138
139 public int write(AudioInputStream stream, AudioFileFormat.Type fileType, File out) throws IOException {
140
141 // throws IllegalArgumentException if not supported
142 WaveFileFormat waveFileFormat = (WaveFileFormat)getAudioFileFormat(fileType, stream);
143
144 // first write the file without worrying about length fields
145 FileOutputStream fos = new FileOutputStream( out ); // throws IOException
146 BufferedOutputStream bos = new BufferedOutputStream( fos, bisBufferSize );
147 int bytesWritten = writeWaveFile(stream, waveFileFormat, bos );
148 bos.close();
149
150 // now, if length fields were not specified, calculate them,
151 // open as a random access file, write the appropriate fields,
152 // close again....
153 if( waveFileFormat.getByteLength()== AudioSystem.NOT_SPECIFIED ) {
154
155 int dataLength=bytesWritten-waveFileFormat.getHeaderSize();
156 int riffLength=dataLength + waveFileFormat.getHeaderSize() - 8;
157
158 RandomAccessFile raf=new RandomAccessFile(out, "rw");
159 // skip RIFF magic
160 raf.skipBytes(4);
161 raf.writeInt(big2little( riffLength ));
162 // skip WAVE magic, fmt_ magic, fmt_ length, fmt_ chunk, data magic
163 raf.skipBytes(4+4+4+WaveFileFormat.getFmtChunkSize(waveFileFormat.getWaveType())+4);
164 raf.writeInt(big2little( dataLength ));
165 // that's all
166 raf.close();
167 }
168
169 return bytesWritten;
170 }
171
172 //--------------------------------------------------------------------
173
174 /**
175 * Returns the AudioFileFormat describing the file that will be written from this AudioInputStream.
176 * Throws IllegalArgumentException if not supported.
177 */
178 private AudioFileFormat getAudioFileFormat(AudioFileFormat.Type type, AudioInputStream stream) {
179 AudioFormat format = null;
180 WaveFileFormat fileFormat = null;
181 AudioFormat.Encoding encoding = AudioFormat.Encoding.PCM_SIGNED;
182
183 AudioFormat streamFormat = stream.getFormat();
184 AudioFormat.Encoding streamEncoding = streamFormat.getEncoding();
185
186 float sampleRate;
187 int sampleSizeInBits;
188 int channels;
189 int frameSize;
190 float frameRate;
191 int fileSize;
192
193 if (!types[0].equals(type)) {
194 throw new IllegalArgumentException("File type " + type + " not supported.");
195 }
196 int waveType = WaveFileFormat.WAVE_FORMAT_PCM;
197
198 if( AudioFormat.Encoding.ALAW.equals(streamEncoding) ||
199 AudioFormat.Encoding.ULAW.equals(streamEncoding) ) {
200
201 encoding = streamEncoding;
202 sampleSizeInBits = streamFormat.getSampleSizeInBits();
203 if (streamEncoding.equals(AudioFormat.Encoding.ALAW)) {
204 waveType = WAVE_FORMAT_ALAW;
205 } else {
206 waveType = WAVE_FORMAT_MULAW;
207 }
208 } else if ( streamFormat.getSampleSizeInBits()==8 ) {
209 encoding = AudioFormat.Encoding.PCM_UNSIGNED;
210 sampleSizeInBits=8;
211 } else {
212 encoding = AudioFormat.Encoding.PCM_SIGNED;
213 sampleSizeInBits=streamFormat.getSampleSizeInBits();
214 }
215
216
217 format = new AudioFormat( encoding,
218 streamFormat.getSampleRate(),
219 sampleSizeInBits,
220 streamFormat.getChannels(),
221 streamFormat.getFrameSize(),
222 streamFormat.getFrameRate(),
223 false); // WAVE is little endian
224
225 if( stream.getFrameLength()!=AudioSystem.NOT_SPECIFIED ) {
226 fileSize = (int)stream.getFrameLength()*streamFormat.getFrameSize()
227 + WaveFileFormat.getHeaderSize(waveType);
228 } else {
229 fileSize = AudioSystem.NOT_SPECIFIED;
230 }
231
232 fileFormat = new WaveFileFormat( AudioFileFormat.Type.WAVE,
233 fileSize,
234 format,
235 (int)stream.getFrameLength() );
236
237 return fileFormat;
238 }
239
240
241 private int writeWaveFile(InputStream in, WaveFileFormat waveFileFormat, OutputStream out) throws IOException {
242
243 int bytesRead = 0;
244 int bytesWritten = 0;
245 InputStream fileStream = getFileStream(waveFileFormat, in);
246 byte buffer[] = new byte[bisBufferSize];
247 int maxLength = waveFileFormat.getByteLength();
248
249 while( (bytesRead = fileStream.read( buffer )) >= 0 ) {
250
251 if (maxLength>0) {
252 if( bytesRead < maxLength ) {
253 out.write( buffer, 0, (int)bytesRead );
254 bytesWritten += bytesRead;
255 maxLength -= bytesRead;
256 } else {
257 out.write( buffer, 0, (int)maxLength );
258 bytesWritten += maxLength;
259 maxLength = 0;
260 break;
261 }
262 } else {
263 out.write( buffer, 0, (int)bytesRead );
264 bytesWritten += bytesRead;
265 }
266 }
267
268 return bytesWritten;
269 }
270
271 private InputStream getFileStream(WaveFileFormat waveFileFormat, InputStream audioStream) throws IOException {
272 // private method ... assumes audioFileFormat is a supported file type
273
274 // WAVE header fields
275 AudioFormat audioFormat = waveFileFormat.getFormat();
276 int headerLength = waveFileFormat.getHeaderSize();
277 int riffMagic = WaveFileFormat.RIFF_MAGIC;
278 int waveMagic = WaveFileFormat.WAVE_MAGIC;
279 int fmtMagic = WaveFileFormat.FMT_MAGIC;
280 int fmtLength = WaveFileFormat.getFmtChunkSize(waveFileFormat.getWaveType());
281 short wav_type = (short) waveFileFormat.getWaveType();
282 short channels = (short) audioFormat.getChannels();
283 short sampleSizeInBits = (short) audioFormat.getSampleSizeInBits();
284 int sampleRate = (int) audioFormat.getSampleRate();
285 int frameSizeInBytes = (int) audioFormat.getFrameSize();
286 int frameRate = (int) audioFormat.getFrameRate();
287 int avgBytesPerSec = channels * sampleSizeInBits * sampleRate / 8;;
288 short blockAlign = (short) ((sampleSizeInBits / 8) * channels);
289 int dataMagic = WaveFileFormat.DATA_MAGIC;
290 int dataLength = waveFileFormat.getFrameLength() * frameSizeInBytes;
291 int length = waveFileFormat.getByteLength();
292 int riffLength = dataLength + headerLength - 8;
293
294 byte header[] = null;
295 ByteArrayInputStream headerStream = null;
296 ByteArrayOutputStream baos = null;
297 DataOutputStream dos = null;
298 SequenceInputStream waveStream = null;
299
300 AudioFormat audioStreamFormat = null;
301 AudioFormat.Encoding encoding = null;
302 InputStream codedAudioStream = audioStream;
303
304 // if audioStream is an AudioInputStream and we need to convert, do it here...
305 if(audioStream instanceof AudioInputStream) {
306 audioStreamFormat = ((AudioInputStream)audioStream).getFormat();
307
308 encoding = audioStreamFormat.getEncoding();
309
310 if(AudioFormat.Encoding.PCM_SIGNED.equals(encoding)) {
311 if( sampleSizeInBits==8 ) {
312 wav_type = WaveFileFormat.WAVE_FORMAT_PCM;
313 // plug in the transcoder to convert from PCM_SIGNED to PCM_UNSIGNED
314 codedAudioStream = AudioSystem.getAudioInputStream( new AudioFormat(
315 AudioFormat.Encoding.PCM_UNSIGNED,
316 audioStreamFormat.getSampleRate(),
317 audioStreamFormat.getSampleSizeInBits(),
318 audioStreamFormat.getChannels(),
319 audioStreamFormat.getFrameSize(),
320 audioStreamFormat.getFrameRate(),
321 false),
322 (AudioInputStream)audioStream);
323 }
324 }
325 if( (AudioFormat.Encoding.PCM_SIGNED.equals(encoding) && audioStreamFormat.isBigEndian()) ||
326 (AudioFormat.Encoding.PCM_UNSIGNED.equals(encoding) && !audioStreamFormat.isBigEndian()) ||
327 (AudioFormat.Encoding.PCM_UNSIGNED.equals(encoding) && audioStreamFormat.isBigEndian()) ) {
328 if( sampleSizeInBits!=8) {
329 wav_type = WaveFileFormat.WAVE_FORMAT_PCM;
330 // plug in the transcoder to convert to PCM_SIGNED_LITTLE_ENDIAN
331 codedAudioStream = AudioSystem.getAudioInputStream( new AudioFormat(
332 AudioFormat.Encoding.PCM_SIGNED,
333 audioStreamFormat.getSampleRate(),
334 audioStreamFormat.getSampleSizeInBits(),
335 audioStreamFormat.getChannels(),
336 audioStreamFormat.getFrameSize(),
337 audioStreamFormat.getFrameRate(),
338 false),
339 (AudioInputStream)audioStream);
340 }
341 }
342 }
343
344
345 // Now push the header into a stream, concat, and return the new SequenceInputStream
346
347 baos = new ByteArrayOutputStream();
348 dos = new DataOutputStream(baos);
349
350 // we write in littleendian...
351 dos.writeInt(riffMagic);
352 dos.writeInt(big2little( riffLength ));
353 dos.writeInt(waveMagic);
354 dos.writeInt(fmtMagic);
355 dos.writeInt(big2little(fmtLength));
356 dos.writeShort(big2littleShort(wav_type));
357 dos.writeShort(big2littleShort(channels));
358 dos.writeInt(big2little(sampleRate));
359 dos.writeInt(big2little(avgBytesPerSec));
360 dos.writeShort(big2littleShort(blockAlign));
361 dos.writeShort(big2littleShort(sampleSizeInBits));
362 //$$fb 2002-04-16: Fix for 4636355: RIFF audio headers could be _more_ spec compliant
363 if (wav_type != WaveFileFormat.WAVE_FORMAT_PCM) {
364 // add length 0 for "codec specific data length"
365 dos.writeShort(0);
366 }
367
368 dos.writeInt(dataMagic);
369 dos.writeInt(big2little(dataLength));
370
371 dos.close();
372 header = baos.toByteArray();
373 headerStream = new ByteArrayInputStream( header );
374 waveStream = new SequenceInputStream(headerStream,codedAudioStream);
375
376 return (InputStream)waveStream;
377 }
378}