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.AudioFileFormat.Type;
  43 import javax.sound.sampled.AudioFormat;
  44 import javax.sound.sampled.AudioInputStream;
  45 import javax.sound.sampled.AudioSystem;
  46 
  47 /**
  48  * AU file writer.
  49  *
  50  * @author Jan Borgersen
  51  */
  52 public final class AuFileWriter extends SunFileWriter {
  53 
  54     /**
  55      * Value for length field if length is not known.
  56      */
  57     private static final int UNKNOWN_SIZE = -1;
  58 
  59     /**
  60      * Constructs a new AuFileWriter object.
  61      */
  62     public AuFileWriter() {
  63         super(new Type[]{Type.AU});
  64     }
  65 
  66     @Override
  67     public Type[] getAudioFileTypes(AudioInputStream stream) {
  68 
  69         Type[] filetypes = new Type[types.length];
  70         System.arraycopy(types, 0, filetypes, 0, types.length);
  71 
  72         // make sure we can write this stream
  73         AudioFormat format = stream.getFormat();
  74         AudioFormat.Encoding encoding = format.getEncoding();
  75 
  76         if (AudioFormat.Encoding.ALAW.equals(encoding)
  77                 || AudioFormat.Encoding.ULAW.equals(encoding)
  78                 || AudioFormat.Encoding.PCM_SIGNED.equals(encoding)
  79                 || AudioFormat.Encoding.PCM_UNSIGNED.equals(encoding)
  80                 || AudioFormat.Encoding.PCM_FLOAT.equals(encoding)) {
  81             return filetypes;
  82         }
  83 
  84         return new Type[0];
  85     }
  86 
  87     @Override
  88     public int write(AudioInputStream stream, Type fileType, OutputStream out) throws IOException {
  89         Objects.requireNonNull(stream);
  90         Objects.requireNonNull(fileType);
  91         Objects.requireNonNull(out);
  92 
  93         // we must know the total data length to calculate the file length
  94         //$$fb 2001-07-13: fix for bug 4351296: do not throw an exception
  95         //if( stream.getFrameLength() == AudioSystem.NOT_SPECIFIED ) {
  96         //      throw new IOException("stream length not specified");
  97         //}
  98 
  99         // throws IllegalArgumentException if not supported
 100         AuFileFormat auFileFormat = (AuFileFormat)getAudioFileFormat(fileType, stream);
 101         return writeAuFile(stream, auFileFormat, out);
 102     }
 103 
 104     @Override
 105     public int write(AudioInputStream stream, Type fileType, File out) throws IOException {
 106         Objects.requireNonNull(stream);
 107         Objects.requireNonNull(fileType);
 108         Objects.requireNonNull(out);
 109 
 110         // throws IllegalArgumentException if not supported
 111         AuFileFormat auFileFormat = (AuFileFormat)getAudioFileFormat(fileType, stream);
 112 
 113         // first write the file without worrying about length fields
 114         FileOutputStream fos = new FileOutputStream( out );     // throws IOException
 115         BufferedOutputStream bos = new BufferedOutputStream( fos, bisBufferSize );
 116         int bytesWritten = writeAuFile(stream, auFileFormat, bos );
 117         bos.close();
 118 
 119         // now, if length fields were not specified, calculate them,
 120         // open as a random access file, write the appropriate fields,
 121         // close again....
 122         if( auFileFormat.getByteLength()== AudioSystem.NOT_SPECIFIED ) {
 123 
 124             // $$kk: 10.22.99: jan: please either implement this or throw an exception!
 125             // $$fb: 2001-07-13: done. Fixes Bug 4479981
 126             RandomAccessFile raf=new RandomAccessFile(out, "rw");
 127             if (raf.length()<=0x7FFFFFFFl) {
 128                 // skip AU magic and data offset field
 129                 raf.skipBytes(8);
 130                 raf.writeInt(bytesWritten-AuFileFormat.AU_HEADERSIZE);
 131                 // that's all
 132             }
 133             raf.close();
 134         }
 135 
 136         return bytesWritten;
 137     }
 138 
 139     // -------------------------------------------------------------
 140 
 141     /**
 142      * Returns the AudioFileFormat describing the file that will be written from this AudioInputStream.
 143      * Throws IllegalArgumentException if not supported.
 144      */
 145     private AudioFileFormat getAudioFileFormat(Type type, AudioInputStream stream) {
 146         if (!isFileTypeSupported(type, stream)) {
 147             throw new IllegalArgumentException("File type " + type + " not supported.");
 148         }
 149 
 150         AudioFormat streamFormat = stream.getFormat();
 151         AudioFormat.Encoding encoding = streamFormat.getEncoding();
 152 
 153         if (AudioFormat.Encoding.PCM_UNSIGNED.equals(encoding)) {
 154             encoding = AudioFormat.Encoding.PCM_SIGNED;
 155         }
 156 
 157         // We always write big endian au files, this is by far the standard
 158         AudioFormat format = new AudioFormat(encoding,
 159                                              streamFormat.getSampleRate(),
 160                                              streamFormat.getSampleSizeInBits(),
 161                                              streamFormat.getChannels(),
 162                                              streamFormat.getFrameSize(),
 163                                              streamFormat.getFrameRate(), true);
 164 
 165         int fileSize;
 166         if (stream.getFrameLength() != AudioSystem.NOT_SPECIFIED) {
 167             fileSize = (int)stream.getFrameLength()*streamFormat.getFrameSize() + AuFileFormat.AU_HEADERSIZE;
 168         } else {
 169             fileSize = AudioSystem.NOT_SPECIFIED;
 170         }
 171 
 172         return new AuFileFormat(Type.AU, fileSize, format,
 173                                 (int) stream.getFrameLength());
 174     }
 175 
 176     private InputStream getFileStream(AuFileFormat auFileFormat, AudioInputStream audioStream) throws IOException {
 177 
 178         // private method ... assumes auFileFormat is a supported file type
 179 
 180         AudioFormat format            = auFileFormat.getFormat();
 181 
 182         int headerSize     = AuFileFormat.AU_HEADERSIZE;
 183         long dataSize       = auFileFormat.getFrameLength();
 184         //$$fb fix for Bug 4351296
 185         //int dataSizeInBytes = dataSize * format.getFrameSize();
 186         long dataSizeInBytes = (dataSize==AudioSystem.NOT_SPECIFIED)?UNKNOWN_SIZE:dataSize * format.getFrameSize();
 187         if (dataSizeInBytes>0x7FFFFFFFl) {
 188             dataSizeInBytes=UNKNOWN_SIZE;
 189         }
 190         int auType = auFileFormat.getAuType();
 191         int sampleRate     = (int)format.getSampleRate();
 192         int channels       = format.getChannels();
 193 
 194         byte header[] = null;
 195         ByteArrayInputStream headerStream = null;
 196         ByteArrayOutputStream baos = null;
 197         DataOutputStream dos = null;
 198         SequenceInputStream auStream = null;
 199 
 200         // if we need to do any format conversion, we do it here.
 201         //$$ fb 2001-07-13: Bug 4391108
 202         audioStream = AudioSystem.getAudioInputStream(format, audioStream);
 203 
 204         baos = new ByteArrayOutputStream();
 205         dos = new DataOutputStream(baos);
 206 
 207         dos.writeInt(AuFileFormat.AU_SUN_MAGIC);
 208         dos.writeInt(headerSize);
 209         dos.writeInt((int)dataSizeInBytes);
 210         dos.writeInt(auType);
 211         dos.writeInt(sampleRate);
 212         dos.writeInt(channels);
 213 
 214         // Now create a new InputStream from headerStream and the InputStream
 215         // in audioStream
 216 
 217         dos.close();
 218         header = baos.toByteArray();
 219         headerStream = new ByteArrayInputStream( header );
 220         auStream = new SequenceInputStream(headerStream,
 221                         new NoCloseInputStream(audioStream));
 222 
 223         return auStream;
 224     }
 225 
 226     private int writeAuFile(AudioInputStream in, AuFileFormat auFileFormat, OutputStream out) throws IOException {
 227 
 228         int bytesRead = 0;
 229         int bytesWritten = 0;
 230         InputStream fileStream = getFileStream(auFileFormat, in);
 231         byte buffer[] = new byte[bisBufferSize];
 232         int maxLength = auFileFormat.getByteLength();
 233 
 234         while( (bytesRead = fileStream.read( buffer )) >= 0 ) {
 235             if (maxLength>0) {
 236                 if( bytesRead < maxLength ) {
 237                     out.write( buffer, 0, bytesRead );
 238                     bytesWritten += bytesRead;
 239                     maxLength -= bytesRead;
 240                 } else {
 241                     out.write( buffer, 0, maxLength );
 242                     bytesWritten += maxLength;
 243                     maxLength = 0;
 244                     break;
 245                 }
 246             } else {
 247                 out.write( buffer, 0, bytesRead );
 248                 bytesWritten += bytesRead;
 249             }
 250         }
 251 
 252         return bytesWritten;
 253     }
 254 }