blob: 1c77a0e05cb4c83a237058618300de4e75a2c7d5 [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
49/**
50 * AU file writer.
51 *
52 * @author Jan Borgersen
53 */
54public class AuFileWriter extends SunFileWriter {
55
56 //$$fb value for length field if length is not known
57 public final static int UNKNOWN_SIZE=-1;
58
59 /**
60 * AU type
61 */
62 private static final AudioFileFormat.Type auTypes[] = {
63 AudioFileFormat.Type.AU
64 };
65
66
67 /**
68 * Constructs a new AuFileWriter object.
69 */
70 public AuFileWriter() {
71 super(auTypes);
72 }
73
74
75
76 public AudioFileFormat.Type[] getAudioFileTypes(AudioInputStream stream) {
77
78 AudioFileFormat.Type[] filetypes = new AudioFileFormat.Type[types.length];
79 System.arraycopy(types, 0, filetypes, 0, types.length);
80
81 // make sure we can write this stream
82 AudioFormat format = stream.getFormat();
83 AudioFormat.Encoding encoding = format.getEncoding();
84
85 if( (AudioFormat.Encoding.ALAW.equals(encoding)) ||
86 (AudioFormat.Encoding.ULAW.equals(encoding)) ||
87 (AudioFormat.Encoding.PCM_SIGNED.equals(encoding)) ||
88 (AudioFormat.Encoding.PCM_UNSIGNED.equals(encoding)) ) {
89
90 return filetypes;
91 }
92
93 return new AudioFileFormat.Type[0];
94 }
95
96
97 public int write(AudioInputStream stream, AudioFileFormat.Type fileType, OutputStream out) throws IOException {
98
99 // we must know the total data length to calculate the file length
100 //$$fb 2001-07-13: fix for bug 4351296: do not throw an exception
101 //if( stream.getFrameLength() == AudioSystem.NOT_SPECIFIED ) {
102 // throw new IOException("stream length not specified");
103 //}
104
105 // throws IllegalArgumentException if not supported
106 AuFileFormat auFileFormat = (AuFileFormat)getAudioFileFormat(fileType, stream);
107
108 int bytesWritten = writeAuFile(stream, auFileFormat, out);
109 return bytesWritten;
110 }
111
112
113
114 public int write(AudioInputStream stream, AudioFileFormat.Type fileType, File out) throws IOException {
115
116 // throws IllegalArgumentException if not supported
117 AuFileFormat auFileFormat = (AuFileFormat)getAudioFileFormat(fileType, stream);
118
119 // first write the file without worrying about length fields
120 FileOutputStream fos = new FileOutputStream( out ); // throws IOException
121 BufferedOutputStream bos = new BufferedOutputStream( fos, bisBufferSize );
122 int bytesWritten = writeAuFile(stream, auFileFormat, bos );
123 bos.close();
124
125 // now, if length fields were not specified, calculate them,
126 // open as a random access file, write the appropriate fields,
127 // close again....
128 if( auFileFormat.getByteLength()== AudioSystem.NOT_SPECIFIED ) {
129
130 // $$kk: 10.22.99: jan: please either implement this or throw an exception!
131 // $$fb: 2001-07-13: done. Fixes Bug 4479981
132 RandomAccessFile raf=new RandomAccessFile(out, "rw");
133 if (raf.length()<=0x7FFFFFFFl) {
134 // skip AU magic and data offset field
135 raf.skipBytes(8);
136 raf.writeInt(bytesWritten-AuFileFormat.AU_HEADERSIZE);
137 // that's all
138 }
139 raf.close();
140 }
141
142 return bytesWritten;
143 }
144
145
146 // -------------------------------------------------------------
147
148 /**
149 * Returns the AudioFileFormat describing the file that will be written from this AudioInputStream.
150 * Throws IllegalArgumentException if not supported.
151 */
152 private AudioFileFormat getAudioFileFormat(AudioFileFormat.Type type, AudioInputStream stream) {
153
154 AudioFormat format = null;
155 AuFileFormat fileFormat = null;
156 AudioFormat.Encoding encoding = AudioFormat.Encoding.PCM_SIGNED;
157
158 AudioFormat streamFormat = stream.getFormat();
159 AudioFormat.Encoding streamEncoding = streamFormat.getEncoding();
160
161
162 float sampleRate;
163 int sampleSizeInBits;
164 int channels;
165 int frameSize;
166 float frameRate;
167 int fileSize;
168
169 if( !types[0].equals(type) ) {
170 throw new IllegalArgumentException("File type " + type + " not supported.");
171 }
172
173 if( (AudioFormat.Encoding.ALAW.equals(streamEncoding)) ||
174 (AudioFormat.Encoding.ULAW.equals(streamEncoding)) ) {
175
176 encoding = streamEncoding;
177 sampleSizeInBits = streamFormat.getSampleSizeInBits();
178
179 } else if ( streamFormat.getSampleSizeInBits()==8 ) {
180
181 encoding = AudioFormat.Encoding.PCM_SIGNED;
182 sampleSizeInBits=8;
183
184 } else {
185
186 encoding = AudioFormat.Encoding.PCM_SIGNED;
187 sampleSizeInBits=streamFormat.getSampleSizeInBits();
188 }
189
190
191 format = new AudioFormat( encoding,
192 streamFormat.getSampleRate(),
193 sampleSizeInBits,
194 streamFormat.getChannels(),
195 streamFormat.getFrameSize(),
196 streamFormat.getFrameRate(),
197 true); // AU is always big endian
198
199
200 if( stream.getFrameLength()!=AudioSystem.NOT_SPECIFIED ) {
201 fileSize = (int)stream.getFrameLength()*streamFormat.getFrameSize() + AuFileFormat.AU_HEADERSIZE;
202 } else {
203 fileSize = AudioSystem.NOT_SPECIFIED;
204 }
205
206 fileFormat = new AuFileFormat( AudioFileFormat.Type.AU,
207 fileSize,
208 format,
209 (int)stream.getFrameLength() );
210
211 return fileFormat;
212 }
213
214
215 private InputStream getFileStream(AuFileFormat auFileFormat, InputStream audioStream) throws IOException {
216
217 // private method ... assumes auFileFormat is a supported file type
218
219 AudioFormat format = auFileFormat.getFormat();
220
221 int magic = AuFileFormat.AU_SUN_MAGIC;
222 int headerSize = AuFileFormat.AU_HEADERSIZE;
223 long dataSize = auFileFormat.getFrameLength();
224 //$$fb fix for Bug 4351296
225 //int dataSizeInBytes = dataSize * format.getFrameSize();
226 long dataSizeInBytes = (dataSize==AudioSystem.NOT_SPECIFIED)?UNKNOWN_SIZE:dataSize * format.getFrameSize();
227 if (dataSizeInBytes>0x7FFFFFFFl) {
228 dataSizeInBytes=UNKNOWN_SIZE;
229 }
230 int encoding_local = auFileFormat.getAuType();
231 int sampleRate = (int)format.getSampleRate();
232 int channels = format.getChannels();
233 //$$fb below is the fix for 4297100.
234 //boolean bigendian = format.isBigEndian();
235 boolean bigendian = true; // force bigendian
236
237 byte header[] = null;
238 ByteArrayInputStream headerStream = null;
239 ByteArrayOutputStream baos = null;
240 DataOutputStream dos = null;
241 SequenceInputStream auStream = null;
242
243 AudioFormat audioStreamFormat = null;
244 AudioFormat.Encoding encoding = null;
245 InputStream codedAudioStream = audioStream;
246
247 // if we need to do any format conversion, do it here.
248
249 codedAudioStream = audioStream;
250
251 if( audioStream instanceof AudioInputStream ) {
252
253
254 audioStreamFormat = ((AudioInputStream)audioStream).getFormat();
255 encoding = audioStreamFormat.getEncoding();
256
257 //$$ fb 2001-07-13: Bug 4391108
258 if( (AudioFormat.Encoding.PCM_UNSIGNED.equals(encoding)) ||
259 (AudioFormat.Encoding.PCM_SIGNED.equals(encoding)
260 && bigendian != audioStreamFormat.isBigEndian()) ) {
261
262 // plug in the transcoder to convert to PCM_SIGNED, bigendian
263 // NOTE: little endian AU is not common, so we're always converting
264 // to big endian unless the passed in audioFileFormat is little.
265 // $$fb this NOTE is superseded. We always write big endian au files, this is by far the standard.
266 codedAudioStream = AudioSystem.getAudioInputStream( new AudioFormat (
267 AudioFormat.Encoding.PCM_SIGNED,
268 audioStreamFormat.getSampleRate(),
269 audioStreamFormat.getSampleSizeInBits(),
270 audioStreamFormat.getChannels(),
271 audioStreamFormat.getFrameSize(),
272 audioStreamFormat.getFrameRate(),
273 bigendian),
274 (AudioInputStream)audioStream );
275
276
277 }
278 }
279
280 baos = new ByteArrayOutputStream();
281 dos = new DataOutputStream(baos);
282
283
284 if (bigendian) {
285 dos.writeInt(AuFileFormat.AU_SUN_MAGIC);
286 dos.writeInt(headerSize);
287 dos.writeInt((int)dataSizeInBytes);
288 dos.writeInt(encoding_local);
289 dos.writeInt(sampleRate);
290 dos.writeInt(channels);
291 } else {
292 dos.writeInt(AuFileFormat.AU_SUN_INV_MAGIC);
293 dos.writeInt(big2little(headerSize));
294 dos.writeInt(big2little((int)dataSizeInBytes));
295 dos.writeInt(big2little(encoding_local));
296 dos.writeInt(big2little(sampleRate));
297 dos.writeInt(big2little(channels));
298 }
299
300 // Now create a new InputStream from headerStream and the InputStream
301 // in audioStream
302
303 dos.close();
304 header = baos.toByteArray();
305 headerStream = new ByteArrayInputStream( header );
306 auStream = new SequenceInputStream(headerStream,codedAudioStream);
307
308 return auStream;
309 }
310
311 private int writeAuFile(InputStream in, AuFileFormat auFileFormat, OutputStream out) throws IOException {
312
313 int bytesRead = 0;
314 int bytesWritten = 0;
315 InputStream fileStream = getFileStream(auFileFormat, in);
316 byte buffer[] = new byte[bisBufferSize];
317 int maxLength = auFileFormat.getByteLength();
318
319 while( (bytesRead = fileStream.read( buffer )) >= 0 ) {
320 if (maxLength>0) {
321 if( bytesRead < maxLength ) {
322 out.write( buffer, 0, (int)bytesRead );
323 bytesWritten += bytesRead;
324 maxLength -= bytesRead;
325 } else {
326 out.write( buffer, 0, (int)maxLength );
327 bytesWritten += maxLength;
328 maxLength = 0;
329 break;
330 }
331 } else {
332 out.write( buffer, 0, (int)bytesRead );
333 bytesWritten += bytesRead;
334 }
335 }
336
337 return bytesWritten;
338 }
339
340
341}