Save This Page
Home » openjdk-7 » com.sun.media » sound » [javadoc | source]
    1   /*
    2    * Copyright (c) 1999, 2011, 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    * AIFF file writer.
   50    *
   51    * @author Jan Borgersen
   52    */
   53   public class AiffFileWriter extends SunFileWriter {
   54   
   55       /**
   56        * AIFF type
   57        */
   58       private static final AudioFileFormat.Type aiffTypes[] = {
   59           AudioFileFormat.Type.AIFF
   60       };
   61   
   62   
   63       /**
   64        * Constructs a new AiffFileWriter object.
   65        */
   66       public AiffFileWriter() {
   67           super(aiffTypes);
   68       }
   69   
   70   
   71       // METHODS TO IMPLEMENT AudioFileWriter
   72   
   73       public AudioFileFormat.Type[] getAudioFileTypes(AudioInputStream stream) {
   74   
   75           AudioFileFormat.Type[] filetypes = new AudioFileFormat.Type[types.length];
   76           System.arraycopy(types, 0, filetypes, 0, types.length);
   77   
   78           // make sure we can write this stream
   79           AudioFormat format = stream.getFormat();
   80           AudioFormat.Encoding encoding = format.getEncoding();
   81   
   82           if( (AudioFormat.Encoding.ALAW.equals(encoding)) ||
   83               (AudioFormat.Encoding.ULAW.equals(encoding)) ||
   84               (AudioFormat.Encoding.PCM_SIGNED.equals(encoding)) ||
   85               (AudioFormat.Encoding.PCM_UNSIGNED.equals(encoding)) ) {
   86   
   87               return filetypes;
   88           }
   89   
   90           return new AudioFileFormat.Type[0];
   91       }
   92   
   93   
   94       public int write(AudioInputStream stream, AudioFileFormat.Type fileType, OutputStream out) throws IOException {
   95   
   96           //$$fb the following check must come first ! Otherwise
   97           // the next frame length check may throw an IOException and
   98           // interrupt iterating File Writers. (see bug 4351296)
   99   
  100           // throws IllegalArgumentException if not supported
  101           AiffFileFormat aiffFileFormat = (AiffFileFormat)getAudioFileFormat(fileType, stream);
  102   
  103           // we must know the total data length to calculate the file length
  104           if( stream.getFrameLength() == AudioSystem.NOT_SPECIFIED ) {
  105               throw new IOException("stream length not specified");
  106           }
  107   
  108           int bytesWritten = writeAiffFile(stream, aiffFileFormat, out);
  109           return bytesWritten;
  110       }
  111   
  112   
  113       public int write(AudioInputStream stream, AudioFileFormat.Type fileType, File out) throws IOException {
  114   
  115           // throws IllegalArgumentException if not supported
  116           AiffFileFormat aiffFileFormat = (AiffFileFormat)getAudioFileFormat(fileType, stream);
  117   
  118           // first write the file without worrying about length fields
  119           FileOutputStream fos = new FileOutputStream( out );     // throws IOException
  120           BufferedOutputStream bos = new BufferedOutputStream( fos, bisBufferSize );
  121           int bytesWritten = writeAiffFile(stream, aiffFileFormat, bos );
  122           bos.close();
  123   
  124           // now, if length fields were not specified, calculate them,
  125           // open as a random access file, write the appropriate fields,
  126           // close again....
  127           if( aiffFileFormat.getByteLength()== AudioSystem.NOT_SPECIFIED ) {
  128   
  129               // $$kk: 10.22.99: jan: please either implement this or throw an exception!
  130               // $$fb: 2001-07-13: done. Fixes Bug 4479981
  131               int ssndBlockSize           = (aiffFileFormat.getFormat().getChannels() * aiffFileFormat.getFormat().getSampleSizeInBits());
  132   
  133               int aiffLength=bytesWritten;
  134               int ssndChunkSize=aiffLength-aiffFileFormat.getHeaderSize()+16;
  135               long dataSize=ssndChunkSize-16;
  136               int numFrames=(int) (dataSize*8/ssndBlockSize);
  137   
  138               RandomAccessFile raf=new RandomAccessFile(out, "rw");
  139               // skip FORM magic
  140               raf.skipBytes(4);
  141               raf.writeInt(aiffLength-8);
  142               // skip aiff2 magic, fver chunk, comm magic, comm size, channel count,
  143               raf.skipBytes(4+aiffFileFormat.getFverChunkSize()+4+4+2);
  144               // write frame count
  145               raf.writeInt(numFrames);
  146               // skip sample size, samplerate, SSND magic
  147               raf.skipBytes(2+10+4);
  148               raf.writeInt(ssndChunkSize-8);
  149               // that's all
  150               raf.close();
  151           }
  152   
  153           return bytesWritten;
  154       }
  155   
  156   
  157       // -----------------------------------------------------------------------
  158   
  159       /**
  160        * Returns the AudioFileFormat describing the file that will be written from this AudioInputStream.
  161        * Throws IllegalArgumentException if not supported.
  162        */
  163       private AudioFileFormat getAudioFileFormat(AudioFileFormat.Type type, AudioInputStream stream) {
  164   
  165           AudioFormat format = null;
  166           AiffFileFormat fileFormat = null;
  167           AudioFormat.Encoding encoding = AudioFormat.Encoding.PCM_SIGNED;
  168   
  169           AudioFormat streamFormat = stream.getFormat();
  170           AudioFormat.Encoding streamEncoding = streamFormat.getEncoding();
  171   
  172   
  173           float sampleRate;
  174           int sampleSizeInBits;
  175           int channels;
  176           int frameSize;
  177           float frameRate;
  178           int fileSize;
  179           boolean convert8to16 = false;
  180   
  181           if( !types[0].equals(type) ) {
  182               throw new IllegalArgumentException("File type " + type + " not supported.");
  183           }
  184   
  185           if( (AudioFormat.Encoding.ALAW.equals(streamEncoding)) ||
  186               (AudioFormat.Encoding.ULAW.equals(streamEncoding)) ) {
  187   
  188               if( streamFormat.getSampleSizeInBits()==8 ) {
  189   
  190                   encoding = AudioFormat.Encoding.PCM_SIGNED;
  191                   sampleSizeInBits=16;
  192                   convert8to16 = true;
  193   
  194               } else {
  195   
  196                   // can't convert non-8-bit ALAW,ULAW
  197                   throw new IllegalArgumentException("Encoding " + streamEncoding + " supported only for 8-bit data.");
  198               }
  199           } else if ( streamFormat.getSampleSizeInBits()==8 ) {
  200   
  201               encoding = AudioFormat.Encoding.PCM_UNSIGNED;
  202               sampleSizeInBits=8;
  203   
  204           } else {
  205   
  206               encoding = AudioFormat.Encoding.PCM_SIGNED;
  207               sampleSizeInBits=streamFormat.getSampleSizeInBits();
  208           }
  209   
  210   
  211           format = new AudioFormat( encoding,
  212                                     streamFormat.getSampleRate(),
  213                                     sampleSizeInBits,
  214                                     streamFormat.getChannels(),
  215                                     streamFormat.getFrameSize(),
  216                                     streamFormat.getFrameRate(),
  217                                     true);        // AIFF is big endian
  218   
  219   
  220           if( stream.getFrameLength()!=AudioSystem.NOT_SPECIFIED ) {
  221               if( convert8to16 ) {
  222                   fileSize = (int)stream.getFrameLength()*streamFormat.getFrameSize()*2 + AiffFileFormat.AIFF_HEADERSIZE;
  223               } else {
  224                   fileSize = (int)stream.getFrameLength()*streamFormat.getFrameSize() + AiffFileFormat.AIFF_HEADERSIZE;
  225               }
  226           } else {
  227               fileSize = AudioSystem.NOT_SPECIFIED;
  228           }
  229   
  230           fileFormat = new AiffFileFormat( AudioFileFormat.Type.AIFF,
  231                                            fileSize,
  232                                            format,
  233                                            (int)stream.getFrameLength() );
  234   
  235           return fileFormat;
  236       }
  237   
  238   
  239       private int writeAiffFile(InputStream in, AiffFileFormat aiffFileFormat, OutputStream out) throws IOException {
  240   
  241           int bytesRead = 0;
  242           int bytesWritten = 0;
  243           InputStream fileStream = getFileStream(aiffFileFormat, in);
  244           byte buffer[] = new byte[bisBufferSize];
  245           int maxLength = aiffFileFormat.getByteLength();
  246   
  247           while( (bytesRead = fileStream.read( buffer )) >= 0 ) {
  248               if (maxLength>0) {
  249                   if( bytesRead < maxLength ) {
  250                       out.write( buffer, 0, (int)bytesRead );
  251                       bytesWritten += bytesRead;
  252                       maxLength -= bytesRead;
  253                   } else {
  254                       out.write( buffer, 0, (int)maxLength );
  255                       bytesWritten += maxLength;
  256                       maxLength = 0;
  257                       break;
  258                   }
  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(AiffFileFormat aiffFileFormat, InputStream audioStream) throws IOException  {
  270   
  271           // private method ... assumes aiffFileFormat is a supported file format
  272   
  273           AudioFormat format = aiffFileFormat.getFormat();
  274           AudioFormat streamFormat = null;
  275           AudioFormat.Encoding encoding = null;
  276   
  277           //$$fb a little bit nicer handling of constants
  278   
  279           //int headerSize          = 54;
  280           int headerSize          = aiffFileFormat.getHeaderSize();
  281   
  282           //int fverChunkSize       = 0;
  283           int fverChunkSize       = aiffFileFormat.getFverChunkSize();
  284           //int commChunkSize       = 26;
  285           int commChunkSize       = aiffFileFormat.getCommChunkSize();
  286           int aiffLength          = -1;
  287           int ssndChunkSize       = -1;
  288           //int ssndOffset                        = headerSize - 16;
  289           int ssndOffset                  = aiffFileFormat.getSsndChunkOffset();
  290           short channels = (short) format.getChannels();
  291           short sampleSize = (short) format.getSampleSizeInBits();
  292           int ssndBlockSize               = (channels * sampleSize);
  293           int numFrames                   = aiffFileFormat.getFrameLength();
  294           long dataSize            = -1;
  295           if( numFrames != AudioSystem.NOT_SPECIFIED) {
  296               dataSize = (long) numFrames * ssndBlockSize / 8;
  297               ssndChunkSize = (int)dataSize + 16;
  298               aiffLength = (int)dataSize+headerSize;
  299           }
  300           float sampleFramesPerSecond = format.getSampleRate();
  301           int compCode = AiffFileFormat.AIFC_PCM;
  302   
  303           byte header[] = null;
  304           ByteArrayInputStream headerStream = null;
  305           ByteArrayOutputStream baos = null;
  306           DataOutputStream dos = null;
  307           SequenceInputStream aiffStream = null;
  308           InputStream codedAudioStream = audioStream;
  309   
  310           // if we need to do any format conversion, do it here....
  311   
  312           if( audioStream instanceof AudioInputStream ) {
  313   
  314               streamFormat = ((AudioInputStream)audioStream).getFormat();
  315               encoding = streamFormat.getEncoding();
  316   
  317   
  318               // $$jb: Note that AIFF samples are ALWAYS signed
  319               if( (AudioFormat.Encoding.PCM_UNSIGNED.equals(encoding)) ||
  320                   ( (AudioFormat.Encoding.PCM_SIGNED.equals(encoding)) && !streamFormat.isBigEndian() ) ) {
  321   
  322                   // plug in the transcoder to convert to PCM_SIGNED. big endian
  323                   codedAudioStream = AudioSystem.getAudioInputStream( new AudioFormat (
  324                                                                                        AudioFormat.Encoding.PCM_SIGNED,
  325                                                                                        streamFormat.getSampleRate(),
  326                                                                                        streamFormat.getSampleSizeInBits(),
  327                                                                                        streamFormat.getChannels(),
  328                                                                                        streamFormat.getFrameSize(),
  329                                                                                        streamFormat.getFrameRate(),
  330                                                                                        true ),
  331                                                                       (AudioInputStream)audioStream );
  332   
  333               } else if( (AudioFormat.Encoding.ULAW.equals(encoding)) ||
  334                          (AudioFormat.Encoding.ALAW.equals(encoding)) ) {
  335   
  336                   if( streamFormat.getSampleSizeInBits() != 8 ) {
  337                       throw new IllegalArgumentException("unsupported encoding");
  338                   }
  339   
  340                                   //$$fb 2001-07-13: this is probably not what we want:
  341                                   //     writing PCM when ULAW/ALAW is requested. AIFC is able to write ULAW !
  342   
  343                                   // plug in the transcoder to convert to PCM_SIGNED_BIG_ENDIAN
  344                   codedAudioStream = AudioSystem.getAudioInputStream( new AudioFormat (
  345                                                                                        AudioFormat.Encoding.PCM_SIGNED,
  346                                                                                        streamFormat.getSampleRate(),
  347                                                                                        streamFormat.getSampleSizeInBits() * 2,
  348                                                                                        streamFormat.getChannels(),
  349                                                                                        streamFormat.getFrameSize() * 2,
  350                                                                                        streamFormat.getFrameRate(),
  351                                                                                        true ),
  352                                                                       (AudioInputStream)audioStream );
  353               }
  354           }
  355   
  356   
  357           // Now create an AIFF stream header...
  358           baos = new ByteArrayOutputStream();
  359           dos = new DataOutputStream(baos);
  360   
  361           // Write the outer FORM chunk
  362           dos.writeInt(AiffFileFormat.AIFF_MAGIC);
  363           dos.writeInt( (aiffLength-8) );
  364           dos.writeInt(AiffFileFormat.AIFF_MAGIC2);
  365   
  366           // Write a FVER chunk - only for AIFC
  367           //dos.writeInt(FVER_MAGIC);
  368           //dos.writeInt( (fverChunkSize-8) );
  369           //dos.writeInt(FVER_TIMESTAMP);
  370   
  371           // Write a COMM chunk
  372           dos.writeInt(AiffFileFormat.COMM_MAGIC);
  373           dos.writeInt( (commChunkSize-8) );
  374           dos.writeShort(channels);
  375           dos.writeInt(numFrames);
  376           dos.writeShort(sampleSize);
  377           write_ieee_extended(dos, sampleFramesPerSecond);   // 10 bytes
  378   
  379           //Only for AIFC
  380           //dos.writeInt(compCode);
  381           //dos.writeInt(compCode);
  382           //dos.writeShort(0);
  383   
  384           // Write the SSND chunk header
  385           dos.writeInt(AiffFileFormat.SSND_MAGIC);
  386           dos.writeInt( (ssndChunkSize-8) );
  387           // ssndOffset and ssndBlockSize set to 0 upon
  388           // recommendation in "Sound Manager" chapter in
  389           // "Inside Macintosh Sound", pp 2-87  (from Babu)
  390           dos.writeInt(0);        // ssndOffset
  391           dos.writeInt(0);        // ssndBlockSize
  392   
  393           // Concat this with the audioStream and return it
  394   
  395           dos.close();
  396           header = baos.toByteArray();
  397           headerStream = new ByteArrayInputStream( header );
  398   
  399           aiffStream = new SequenceInputStream(headerStream,
  400                               new NoCloseInputStream(codedAudioStream));
  401   
  402           return aiffStream;
  403   
  404       }
  405   
  406   
  407   
  408   
  409       // HELPER METHODS
  410   
  411       private static final int DOUBLE_MANTISSA_LENGTH = 52;
  412       private static final int DOUBLE_EXPONENT_LENGTH = 11;
  413       private static final long DOUBLE_SIGN_MASK     = 0x8000000000000000L;
  414       private static final long DOUBLE_EXPONENT_MASK = 0x7FF0000000000000L;
  415       private static final long DOUBLE_MANTISSA_MASK = 0x000FFFFFFFFFFFFFL;
  416       private static final int DOUBLE_EXPONENT_OFFSET = 1023;
  417   
  418       private static final int EXTENDED_EXPONENT_OFFSET = 16383;
  419       private static final int EXTENDED_MANTISSA_LENGTH = 63;
  420       private static final int EXTENDED_EXPONENT_LENGTH = 15;
  421       private static final long EXTENDED_INTEGER_MASK = 0x8000000000000000L;
  422   
  423       /**
  424        * Extended precision IEEE floating-point conversion routine.
  425        * @argument DataOutputStream
  426        * @argument double
  427        * @exception IOException
  428        */
  429       private void write_ieee_extended(DataOutputStream dos, float f) throws IOException {
  430           /* The special cases NaN, Infinity and Zero are ignored, since
  431              they do not represent useful sample rates anyway.
  432              Denormalized number aren't handled, too. Below, there is a cast
  433              from float to double. We hope that in this conversion,
  434              numbers are normalized. Numbers that cannot be normalized are
  435              ignored, too, as they, too, do not represent useful sample rates. */
  436           long doubleBits = Double.doubleToLongBits((double) f);
  437   
  438           long sign = (doubleBits & DOUBLE_SIGN_MASK)
  439               >> (DOUBLE_EXPONENT_LENGTH + DOUBLE_MANTISSA_LENGTH);
  440           long doubleExponent = (doubleBits & DOUBLE_EXPONENT_MASK)
  441               >> DOUBLE_MANTISSA_LENGTH;
  442           long doubleMantissa = doubleBits & DOUBLE_MANTISSA_MASK;
  443   
  444           long extendedExponent = doubleExponent - DOUBLE_EXPONENT_OFFSET
  445               + EXTENDED_EXPONENT_OFFSET;
  446           long extendedMantissa = doubleMantissa
  447               << (EXTENDED_MANTISSA_LENGTH - DOUBLE_MANTISSA_LENGTH);
  448           long extendedSign = sign << EXTENDED_EXPONENT_LENGTH;
  449           short extendedBits79To64 = (short) (extendedSign | extendedExponent);
  450           long extendedBits63To0 = EXTENDED_INTEGER_MASK | extendedMantissa;
  451   
  452           dos.writeShort(extendedBits79To64);
  453           dos.writeLong(extendedBits63To0);
  454       }
  455   
  456   
  457   }

Save This Page
Home » openjdk-7 » com.sun.media » sound » [javadoc | source]