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