1 /* 2 * Copyright (c) 1999, 2016, Oracle and/or its affiliates. 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.media.sound; 27 28 import java.io.BufferedOutputStream; 29 import java.io.ByteArrayInputStream; 30 import java.io.ByteArrayOutputStream; 31 import java.io.DataOutputStream; 32 import java.io.File; 33 import java.io.FileOutputStream; 34 import java.io.IOException; 35 import java.io.InputStream; 36 import java.io.OutputStream; 37 import java.io.RandomAccessFile; 38 import java.io.SequenceInputStream; 39 import java.util.Objects; 40 41 import javax.sound.sampled.AudioFileFormat; 42 import javax.sound.sampled.AudioFormat; 43 import javax.sound.sampled.AudioInputStream; 44 import javax.sound.sampled.AudioSystem; 45 46 /** 47 * AU file writer. 48 * 49 * @author Jan Borgersen 50 */ 51 public final class AuFileWriter extends SunFileWriter { 52 53 /** 54 * Value for length field if length is not known. 55 */ 56 private static final int UNKNOWN_SIZE = -1; 57 58 /** 59 * Constructs a new AuFileWriter object. 60 */ 61 public AuFileWriter() { 62 super(new AudioFileFormat.Type[]{AudioFileFormat.Type.AU}); 63 } 64 65 @Override 66 public AudioFileFormat.Type[] getAudioFileTypes(AudioInputStream stream) { 67 68 AudioFileFormat.Type[] filetypes = new AudioFileFormat.Type[types.length]; 69 System.arraycopy(types, 0, filetypes, 0, types.length); 70 71 // make sure we can write this stream 72 AudioFormat format = stream.getFormat(); 73 AudioFormat.Encoding encoding = format.getEncoding(); 74 75 if( (AudioFormat.Encoding.ALAW.equals(encoding)) || 76 (AudioFormat.Encoding.ULAW.equals(encoding)) || 77 (AudioFormat.Encoding.PCM_SIGNED.equals(encoding)) || 78 (AudioFormat.Encoding.PCM_UNSIGNED.equals(encoding)) ) { 79 80 return filetypes; 81 } 82 83 return new AudioFileFormat.Type[0]; 84 } 85 86 @Override 87 public int write(AudioInputStream stream, AudioFileFormat.Type fileType, OutputStream out) throws IOException { 88 Objects.requireNonNull(stream); 89 Objects.requireNonNull(fileType); 90 Objects.requireNonNull(out); 91 92 // we must know the total data length to calculate the file length 93 //$$fb 2001-07-13: fix for bug 4351296: do not throw an exception 94 //if( stream.getFrameLength() == AudioSystem.NOT_SPECIFIED ) { 95 // throw new IOException("stream length not specified"); 96 //} 97 98 // throws IllegalArgumentException if not supported 99 AuFileFormat auFileFormat = (AuFileFormat)getAudioFileFormat(fileType, stream); 100 return writeAuFile(stream, auFileFormat, out); 101 } 102 103 @Override 104 public int write(AudioInputStream stream, AudioFileFormat.Type fileType, File out) throws IOException { 105 Objects.requireNonNull(stream); 106 Objects.requireNonNull(fileType); 107 Objects.requireNonNull(out); 108 109 // throws IllegalArgumentException if not supported 110 AuFileFormat auFileFormat = (AuFileFormat)getAudioFileFormat(fileType, stream); 111 112 // first write the file without worrying about length fields 113 FileOutputStream fos = new FileOutputStream( out ); // throws IOException 114 BufferedOutputStream bos = new BufferedOutputStream( fos, bisBufferSize ); 115 int bytesWritten = writeAuFile(stream, auFileFormat, bos ); 116 bos.close(); 117 118 // now, if length fields were not specified, calculate them, 119 // open as a random access file, write the appropriate fields, 120 // close again.... 121 if( auFileFormat.getByteLength()== AudioSystem.NOT_SPECIFIED ) { 122 123 // $$kk: 10.22.99: jan: please either implement this or throw an exception! 124 // $$fb: 2001-07-13: done. Fixes Bug 4479981 125 RandomAccessFile raf=new RandomAccessFile(out, "rw"); 126 if (raf.length()<=0x7FFFFFFFl) { 127 // skip AU magic and data offset field 128 raf.skipBytes(8); 129 raf.writeInt(bytesWritten-AuFileFormat.AU_HEADERSIZE); 130 // that's all 131 } 132 raf.close(); 133 } 134 135 return bytesWritten; 136 } 137 138 // ------------------------------------------------------------- 139 140 /** 141 * Returns the AudioFileFormat describing the file that will be written from this AudioInputStream. 142 * Throws IllegalArgumentException if not supported. 143 */ 144 private AudioFileFormat getAudioFileFormat(AudioFileFormat.Type type, AudioInputStream stream) { 145 if (!isFileTypeSupported(type, stream)) { 146 throw new IllegalArgumentException("File type " + type + " not supported."); 147 } 148 149 AudioFormat format = null; 150 AuFileFormat fileFormat = null; 151 AudioFormat.Encoding encoding = AudioFormat.Encoding.PCM_SIGNED; 152 153 AudioFormat streamFormat = stream.getFormat(); 154 AudioFormat.Encoding streamEncoding = streamFormat.getEncoding(); 155 156 157 int sampleSizeInBits; 158 int fileSize; 159 160 if( (AudioFormat.Encoding.ALAW.equals(streamEncoding)) || 161 (AudioFormat.Encoding.ULAW.equals(streamEncoding)) ) { 162 163 encoding = streamEncoding; 164 sampleSizeInBits = streamFormat.getSampleSizeInBits(); 165 166 } else if ( streamFormat.getSampleSizeInBits()==8 ) { 167 168 encoding = AudioFormat.Encoding.PCM_SIGNED; 169 sampleSizeInBits=8; 170 171 } else { 172 173 encoding = AudioFormat.Encoding.PCM_SIGNED; 174 sampleSizeInBits=streamFormat.getSampleSizeInBits(); 175 } 176 177 178 format = new AudioFormat( encoding, 179 streamFormat.getSampleRate(), 180 sampleSizeInBits, 181 streamFormat.getChannels(), 182 streamFormat.getFrameSize(), 183 streamFormat.getFrameRate(), 184 true); // AU is always big endian 185 186 187 if( stream.getFrameLength()!=AudioSystem.NOT_SPECIFIED ) { 188 fileSize = (int)stream.getFrameLength()*streamFormat.getFrameSize() + AuFileFormat.AU_HEADERSIZE; 189 } else { 190 fileSize = AudioSystem.NOT_SPECIFIED; 191 } 192 193 fileFormat = new AuFileFormat( AudioFileFormat.Type.AU, 194 fileSize, 195 format, 196 (int)stream.getFrameLength() ); 197 198 return fileFormat; 199 } 200 201 private InputStream getFileStream(AuFileFormat auFileFormat, AudioInputStream audioStream) throws IOException { 202 203 // private method ... assumes auFileFormat is a supported file type 204 205 AudioFormat format = auFileFormat.getFormat(); 206 207 int headerSize = AuFileFormat.AU_HEADERSIZE; 208 long dataSize = auFileFormat.getFrameLength(); 209 //$$fb fix for Bug 4351296 210 //int dataSizeInBytes = dataSize * format.getFrameSize(); 211 long dataSizeInBytes = (dataSize==AudioSystem.NOT_SPECIFIED)?UNKNOWN_SIZE:dataSize * format.getFrameSize(); 212 if (dataSizeInBytes>0x7FFFFFFFl) { 213 dataSizeInBytes=UNKNOWN_SIZE; 214 } 215 int encoding_local = auFileFormat.getAuType(); 216 int sampleRate = (int)format.getSampleRate(); 217 int channels = format.getChannels(); 218 219 byte header[] = null; 220 ByteArrayInputStream headerStream = null; 221 ByteArrayOutputStream baos = null; 222 DataOutputStream dos = null; 223 SequenceInputStream auStream = null; 224 225 AudioFormat audioStreamFormat = null; 226 AudioFormat.Encoding encoding = null; 227 InputStream codedAudioStream = audioStream; 228 229 // if we need to do any format conversion, do it here. 230 231 codedAudioStream = audioStream; 232 233 audioStreamFormat = audioStream.getFormat(); 234 encoding = audioStreamFormat.getEncoding(); 235 236 //$$ fb 2001-07-13: Bug 4391108 237 if( (AudioFormat.Encoding.PCM_UNSIGNED.equals(encoding)) || 238 (AudioFormat.Encoding.PCM_SIGNED.equals(encoding) 239 && !audioStreamFormat.isBigEndian()) ) { 240 // We always write big endian au files, this is by far the standard 241 codedAudioStream = AudioSystem.getAudioInputStream( new AudioFormat ( 242 AudioFormat.Encoding.PCM_SIGNED, 243 audioStreamFormat.getSampleRate(), 244 audioStreamFormat.getSampleSizeInBits(), 245 audioStreamFormat.getChannels(), 246 audioStreamFormat.getFrameSize(), 247 audioStreamFormat.getFrameRate(), 248 true), 249 audioStream ); 250 251 252 } 253 254 baos = new ByteArrayOutputStream(); 255 dos = new DataOutputStream(baos); 256 257 258 dos.writeInt(AuFileFormat.AU_SUN_MAGIC); 259 dos.writeInt(headerSize); 260 dos.writeInt((int)dataSizeInBytes); 261 dos.writeInt(encoding_local); 262 dos.writeInt(sampleRate); 263 dos.writeInt(channels); 264 265 // Now create a new InputStream from headerStream and the InputStream 266 // in audioStream 267 268 dos.close(); 269 header = baos.toByteArray(); 270 headerStream = new ByteArrayInputStream( header ); 271 auStream = new SequenceInputStream(headerStream, 272 new NoCloseInputStream(codedAudioStream)); 273 274 return auStream; 275 } 276 277 private int writeAuFile(AudioInputStream in, AuFileFormat auFileFormat, OutputStream out) throws IOException { 278 279 int bytesRead = 0; 280 int bytesWritten = 0; 281 InputStream fileStream = getFileStream(auFileFormat, in); 282 byte buffer[] = new byte[bisBufferSize]; 283 int maxLength = auFileFormat.getByteLength(); 284 285 while( (bytesRead = fileStream.read( buffer )) >= 0 ) { 286 if (maxLength>0) { 287 if( bytesRead < maxLength ) { 288 out.write( buffer, 0, bytesRead ); 289 bytesWritten += bytesRead; 290 maxLength -= bytesRead; 291 } else { 292 out.write( buffer, 0, maxLength ); 293 bytesWritten += maxLength; 294 maxLength = 0; 295 break; 296 } 297 } else { 298 out.write( buffer, 0, bytesRead ); 299 bytesWritten += bytesRead; 300 } 301 } 302 303 return bytesWritten; 304 } 305 }