J. Duke | 319a3b9 | 2007-12-01 00:00:00 +0000 | [diff] [blame^] | 1 | /* |
| 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 | |
| 26 | package com.sun.media.sound; |
| 27 | |
| 28 | import java.util.Vector; |
| 29 | import java.io.File; |
| 30 | import java.io.InputStream; |
| 31 | import java.io.OutputStream; |
| 32 | import java.io.IOException; |
| 33 | import java.lang.IllegalArgumentException; |
| 34 | |
| 35 | import java.io.BufferedOutputStream; |
| 36 | import java.io.DataOutputStream; |
| 37 | import java.io.FileOutputStream; |
| 38 | import java.io.ByteArrayInputStream; |
| 39 | import java.io.ByteArrayOutputStream; |
| 40 | import java.io.RandomAccessFile; |
| 41 | import java.io.SequenceInputStream; |
| 42 | |
| 43 | import javax.sound.sampled.AudioFileFormat; |
| 44 | import javax.sound.sampled.AudioInputStream; |
| 45 | import javax.sound.sampled.AudioFormat; |
| 46 | import javax.sound.sampled.AudioSystem; |
| 47 | |
| 48 | |
| 49 | /** |
| 50 | * AU file writer. |
| 51 | * |
| 52 | * @author Jan Borgersen |
| 53 | */ |
| 54 | public 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 | } |