0N/A/*
6321N/A * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/A
0N/Apackage com.sun.media.sound;
0N/A
0N/Aimport java.io.File;
0N/Aimport java.io.InputStream;
0N/Aimport java.io.OutputStream;
0N/Aimport java.io.IOException;
0N/A
0N/Aimport java.io.BufferedOutputStream;
0N/Aimport java.io.DataOutputStream;
0N/Aimport java.io.FileOutputStream;
0N/Aimport java.io.ByteArrayInputStream;
0N/Aimport java.io.ByteArrayOutputStream;
0N/Aimport java.io.RandomAccessFile;
0N/Aimport java.io.SequenceInputStream;
0N/A
0N/Aimport javax.sound.sampled.AudioFileFormat;
0N/Aimport javax.sound.sampled.AudioInputStream;
0N/Aimport javax.sound.sampled.AudioFormat;
0N/Aimport javax.sound.sampled.AudioSystem;
0N/A
0N/A//$$fb this class is buggy. Should be replaced in future.
0N/A
0N/A/**
0N/A * AIFF file writer.
0N/A *
0N/A * @author Jan Borgersen
0N/A */
6321N/Apublic final class AiffFileWriter extends SunFileWriter {
0N/A
0N/A /**
0N/A * Constructs a new AiffFileWriter object.
0N/A */
0N/A public AiffFileWriter() {
6321N/A super(new AudioFileFormat.Type[]{AudioFileFormat.Type.AIFF});
0N/A }
0N/A
0N/A
0N/A // METHODS TO IMPLEMENT AudioFileWriter
0N/A
0N/A public AudioFileFormat.Type[] getAudioFileTypes(AudioInputStream stream) {
0N/A
0N/A AudioFileFormat.Type[] filetypes = new AudioFileFormat.Type[types.length];
0N/A System.arraycopy(types, 0, filetypes, 0, types.length);
0N/A
0N/A // make sure we can write this stream
0N/A AudioFormat format = stream.getFormat();
0N/A AudioFormat.Encoding encoding = format.getEncoding();
0N/A
0N/A if( (AudioFormat.Encoding.ALAW.equals(encoding)) ||
0N/A (AudioFormat.Encoding.ULAW.equals(encoding)) ||
0N/A (AudioFormat.Encoding.PCM_SIGNED.equals(encoding)) ||
0N/A (AudioFormat.Encoding.PCM_UNSIGNED.equals(encoding)) ) {
0N/A
0N/A return filetypes;
0N/A }
0N/A
0N/A return new AudioFileFormat.Type[0];
0N/A }
0N/A
0N/A
0N/A public int write(AudioInputStream stream, AudioFileFormat.Type fileType, OutputStream out) throws IOException {
0N/A
0N/A //$$fb the following check must come first ! Otherwise
0N/A // the next frame length check may throw an IOException and
0N/A // interrupt iterating File Writers. (see bug 4351296)
0N/A
0N/A // throws IllegalArgumentException if not supported
0N/A AiffFileFormat aiffFileFormat = (AiffFileFormat)getAudioFileFormat(fileType, stream);
0N/A
0N/A // we must know the total data length to calculate the file length
0N/A if( stream.getFrameLength() == AudioSystem.NOT_SPECIFIED ) {
0N/A throw new IOException("stream length not specified");
0N/A }
0N/A
0N/A int bytesWritten = writeAiffFile(stream, aiffFileFormat, out);
0N/A return bytesWritten;
0N/A }
0N/A
0N/A
0N/A public int write(AudioInputStream stream, AudioFileFormat.Type fileType, File out) throws IOException {
0N/A
0N/A // throws IllegalArgumentException if not supported
0N/A AiffFileFormat aiffFileFormat = (AiffFileFormat)getAudioFileFormat(fileType, stream);
0N/A
0N/A // first write the file without worrying about length fields
0N/A FileOutputStream fos = new FileOutputStream( out ); // throws IOException
0N/A BufferedOutputStream bos = new BufferedOutputStream( fos, bisBufferSize );
0N/A int bytesWritten = writeAiffFile(stream, aiffFileFormat, bos );
0N/A bos.close();
0N/A
0N/A // now, if length fields were not specified, calculate them,
0N/A // open as a random access file, write the appropriate fields,
0N/A // close again....
0N/A if( aiffFileFormat.getByteLength()== AudioSystem.NOT_SPECIFIED ) {
0N/A
0N/A // $$kk: 10.22.99: jan: please either implement this or throw an exception!
0N/A // $$fb: 2001-07-13: done. Fixes Bug 4479981
0N/A int ssndBlockSize = (aiffFileFormat.getFormat().getChannels() * aiffFileFormat.getFormat().getSampleSizeInBits());
0N/A
0N/A int aiffLength=bytesWritten;
0N/A int ssndChunkSize=aiffLength-aiffFileFormat.getHeaderSize()+16;
0N/A long dataSize=ssndChunkSize-16;
0N/A int numFrames=(int) (dataSize*8/ssndBlockSize);
0N/A
0N/A RandomAccessFile raf=new RandomAccessFile(out, "rw");
0N/A // skip FORM magic
0N/A raf.skipBytes(4);
0N/A raf.writeInt(aiffLength-8);
0N/A // skip aiff2 magic, fver chunk, comm magic, comm size, channel count,
0N/A raf.skipBytes(4+aiffFileFormat.getFverChunkSize()+4+4+2);
0N/A // write frame count
0N/A raf.writeInt(numFrames);
0N/A // skip sample size, samplerate, SSND magic
0N/A raf.skipBytes(2+10+4);
0N/A raf.writeInt(ssndChunkSize-8);
0N/A // that's all
0N/A raf.close();
0N/A }
0N/A
0N/A return bytesWritten;
0N/A }
0N/A
0N/A
0N/A // -----------------------------------------------------------------------
0N/A
0N/A /**
0N/A * Returns the AudioFileFormat describing the file that will be written from this AudioInputStream.
0N/A * Throws IllegalArgumentException if not supported.
0N/A */
0N/A private AudioFileFormat getAudioFileFormat(AudioFileFormat.Type type, AudioInputStream stream) {
0N/A
0N/A AudioFormat format = null;
0N/A AiffFileFormat fileFormat = null;
0N/A AudioFormat.Encoding encoding = AudioFormat.Encoding.PCM_SIGNED;
0N/A
0N/A AudioFormat streamFormat = stream.getFormat();
0N/A AudioFormat.Encoding streamEncoding = streamFormat.getEncoding();
0N/A
0N/A
0N/A float sampleRate;
0N/A int sampleSizeInBits;
0N/A int channels;
0N/A int frameSize;
0N/A float frameRate;
0N/A int fileSize;
0N/A boolean convert8to16 = false;
0N/A
0N/A if( !types[0].equals(type) ) {
0N/A throw new IllegalArgumentException("File type " + type + " not supported.");
0N/A }
0N/A
0N/A if( (AudioFormat.Encoding.ALAW.equals(streamEncoding)) ||
0N/A (AudioFormat.Encoding.ULAW.equals(streamEncoding)) ) {
0N/A
0N/A if( streamFormat.getSampleSizeInBits()==8 ) {
0N/A
0N/A encoding = AudioFormat.Encoding.PCM_SIGNED;
0N/A sampleSizeInBits=16;
0N/A convert8to16 = true;
0N/A
0N/A } else {
0N/A
0N/A // can't convert non-8-bit ALAW,ULAW
0N/A throw new IllegalArgumentException("Encoding " + streamEncoding + " supported only for 8-bit data.");
0N/A }
0N/A } else if ( streamFormat.getSampleSizeInBits()==8 ) {
0N/A
0N/A encoding = AudioFormat.Encoding.PCM_UNSIGNED;
0N/A sampleSizeInBits=8;
0N/A
0N/A } else {
0N/A
0N/A encoding = AudioFormat.Encoding.PCM_SIGNED;
0N/A sampleSizeInBits=streamFormat.getSampleSizeInBits();
0N/A }
0N/A
0N/A
0N/A format = new AudioFormat( encoding,
0N/A streamFormat.getSampleRate(),
0N/A sampleSizeInBits,
0N/A streamFormat.getChannels(),
0N/A streamFormat.getFrameSize(),
0N/A streamFormat.getFrameRate(),
0N/A true); // AIFF is big endian
0N/A
0N/A
0N/A if( stream.getFrameLength()!=AudioSystem.NOT_SPECIFIED ) {
0N/A if( convert8to16 ) {
0N/A fileSize = (int)stream.getFrameLength()*streamFormat.getFrameSize()*2 + AiffFileFormat.AIFF_HEADERSIZE;
0N/A } else {
0N/A fileSize = (int)stream.getFrameLength()*streamFormat.getFrameSize() + AiffFileFormat.AIFF_HEADERSIZE;
0N/A }
0N/A } else {
0N/A fileSize = AudioSystem.NOT_SPECIFIED;
0N/A }
0N/A
0N/A fileFormat = new AiffFileFormat( AudioFileFormat.Type.AIFF,
0N/A fileSize,
0N/A format,
0N/A (int)stream.getFrameLength() );
0N/A
0N/A return fileFormat;
0N/A }
0N/A
0N/A
0N/A private int writeAiffFile(InputStream in, AiffFileFormat aiffFileFormat, OutputStream out) throws IOException {
0N/A
0N/A int bytesRead = 0;
0N/A int bytesWritten = 0;
0N/A InputStream fileStream = getFileStream(aiffFileFormat, in);
0N/A byte buffer[] = new byte[bisBufferSize];
0N/A int maxLength = aiffFileFormat.getByteLength();
0N/A
0N/A while( (bytesRead = fileStream.read( buffer )) >= 0 ) {
0N/A if (maxLength>0) {
0N/A if( bytesRead < maxLength ) {
0N/A out.write( buffer, 0, (int)bytesRead );
0N/A bytesWritten += bytesRead;
0N/A maxLength -= bytesRead;
0N/A } else {
0N/A out.write( buffer, 0, (int)maxLength );
0N/A bytesWritten += maxLength;
0N/A maxLength = 0;
0N/A break;
0N/A }
0N/A
0N/A } else {
0N/A out.write( buffer, 0, (int)bytesRead );
0N/A bytesWritten += bytesRead;
0N/A }
0N/A }
0N/A
0N/A return bytesWritten;
0N/A }
0N/A
0N/A private InputStream getFileStream(AiffFileFormat aiffFileFormat, InputStream audioStream) throws IOException {
0N/A
0N/A // private method ... assumes aiffFileFormat is a supported file format
0N/A
0N/A AudioFormat format = aiffFileFormat.getFormat();
0N/A AudioFormat streamFormat = null;
0N/A AudioFormat.Encoding encoding = null;
0N/A
0N/A //$$fb a little bit nicer handling of constants
0N/A
0N/A //int headerSize = 54;
0N/A int headerSize = aiffFileFormat.getHeaderSize();
0N/A
0N/A //int fverChunkSize = 0;
0N/A int fverChunkSize = aiffFileFormat.getFverChunkSize();
0N/A //int commChunkSize = 26;
0N/A int commChunkSize = aiffFileFormat.getCommChunkSize();
0N/A int aiffLength = -1;
0N/A int ssndChunkSize = -1;
0N/A //int ssndOffset = headerSize - 16;
0N/A int ssndOffset = aiffFileFormat.getSsndChunkOffset();
0N/A short channels = (short) format.getChannels();
0N/A short sampleSize = (short) format.getSampleSizeInBits();
0N/A int ssndBlockSize = (channels * sampleSize);
0N/A int numFrames = aiffFileFormat.getFrameLength();
0N/A long dataSize = -1;
0N/A if( numFrames != AudioSystem.NOT_SPECIFIED) {
0N/A dataSize = (long) numFrames * ssndBlockSize / 8;
0N/A ssndChunkSize = (int)dataSize + 16;
0N/A aiffLength = (int)dataSize+headerSize;
0N/A }
0N/A float sampleFramesPerSecond = format.getSampleRate();
0N/A int compCode = AiffFileFormat.AIFC_PCM;
0N/A
0N/A byte header[] = null;
0N/A ByteArrayInputStream headerStream = null;
0N/A ByteArrayOutputStream baos = null;
0N/A DataOutputStream dos = null;
0N/A SequenceInputStream aiffStream = null;
0N/A InputStream codedAudioStream = audioStream;
0N/A
0N/A // if we need to do any format conversion, do it here....
0N/A
0N/A if( audioStream instanceof AudioInputStream ) {
0N/A
0N/A streamFormat = ((AudioInputStream)audioStream).getFormat();
0N/A encoding = streamFormat.getEncoding();
0N/A
0N/A
0N/A // $$jb: Note that AIFF samples are ALWAYS signed
0N/A if( (AudioFormat.Encoding.PCM_UNSIGNED.equals(encoding)) ||
0N/A ( (AudioFormat.Encoding.PCM_SIGNED.equals(encoding)) && !streamFormat.isBigEndian() ) ) {
0N/A
0N/A // plug in the transcoder to convert to PCM_SIGNED. big endian
0N/A codedAudioStream = AudioSystem.getAudioInputStream( new AudioFormat (
0N/A AudioFormat.Encoding.PCM_SIGNED,
0N/A streamFormat.getSampleRate(),
0N/A streamFormat.getSampleSizeInBits(),
0N/A streamFormat.getChannels(),
0N/A streamFormat.getFrameSize(),
0N/A streamFormat.getFrameRate(),
0N/A true ),
0N/A (AudioInputStream)audioStream );
0N/A
0N/A } else if( (AudioFormat.Encoding.ULAW.equals(encoding)) ||
0N/A (AudioFormat.Encoding.ALAW.equals(encoding)) ) {
0N/A
0N/A if( streamFormat.getSampleSizeInBits() != 8 ) {
0N/A throw new IllegalArgumentException("unsupported encoding");
0N/A }
0N/A
0N/A //$$fb 2001-07-13: this is probably not what we want:
0N/A // writing PCM when ULAW/ALAW is requested. AIFC is able to write ULAW !
0N/A
0N/A // plug in the transcoder to convert to PCM_SIGNED_BIG_ENDIAN
0N/A codedAudioStream = AudioSystem.getAudioInputStream( new AudioFormat (
0N/A AudioFormat.Encoding.PCM_SIGNED,
0N/A streamFormat.getSampleRate(),
0N/A streamFormat.getSampleSizeInBits() * 2,
0N/A streamFormat.getChannels(),
0N/A streamFormat.getFrameSize() * 2,
0N/A streamFormat.getFrameRate(),
0N/A true ),
0N/A (AudioInputStream)audioStream );
0N/A }
0N/A }
0N/A
0N/A
0N/A // Now create an AIFF stream header...
0N/A baos = new ByteArrayOutputStream();
0N/A dos = new DataOutputStream(baos);
0N/A
0N/A // Write the outer FORM chunk
0N/A dos.writeInt(AiffFileFormat.AIFF_MAGIC);
0N/A dos.writeInt( (aiffLength-8) );
0N/A dos.writeInt(AiffFileFormat.AIFF_MAGIC2);
0N/A
0N/A // Write a FVER chunk - only for AIFC
0N/A //dos.writeInt(FVER_MAGIC);
0N/A //dos.writeInt( (fverChunkSize-8) );
0N/A //dos.writeInt(FVER_TIMESTAMP);
0N/A
0N/A // Write a COMM chunk
0N/A dos.writeInt(AiffFileFormat.COMM_MAGIC);
0N/A dos.writeInt( (commChunkSize-8) );
0N/A dos.writeShort(channels);
0N/A dos.writeInt(numFrames);
0N/A dos.writeShort(sampleSize);
0N/A write_ieee_extended(dos, sampleFramesPerSecond); // 10 bytes
0N/A
0N/A //Only for AIFC
0N/A //dos.writeInt(compCode);
0N/A //dos.writeInt(compCode);
0N/A //dos.writeShort(0);
0N/A
0N/A // Write the SSND chunk header
0N/A dos.writeInt(AiffFileFormat.SSND_MAGIC);
0N/A dos.writeInt( (ssndChunkSize-8) );
0N/A // ssndOffset and ssndBlockSize set to 0 upon
0N/A // recommendation in "Sound Manager" chapter in
0N/A // "Inside Macintosh Sound", pp 2-87 (from Babu)
0N/A dos.writeInt(0); // ssndOffset
0N/A dos.writeInt(0); // ssndBlockSize
0N/A
0N/A // Concat this with the audioStream and return it
0N/A
0N/A dos.close();
0N/A header = baos.toByteArray();
0N/A headerStream = new ByteArrayInputStream( header );
0N/A
3628N/A aiffStream = new SequenceInputStream(headerStream,
3628N/A new NoCloseInputStream(codedAudioStream));
0N/A
0N/A return aiffStream;
0N/A
0N/A }
0N/A
0N/A
0N/A
0N/A
0N/A // HELPER METHODS
0N/A
0N/A private static final int DOUBLE_MANTISSA_LENGTH = 52;
0N/A private static final int DOUBLE_EXPONENT_LENGTH = 11;
0N/A private static final long DOUBLE_SIGN_MASK = 0x8000000000000000L;
0N/A private static final long DOUBLE_EXPONENT_MASK = 0x7FF0000000000000L;
0N/A private static final long DOUBLE_MANTISSA_MASK = 0x000FFFFFFFFFFFFFL;
0N/A private static final int DOUBLE_EXPONENT_OFFSET = 1023;
0N/A
0N/A private static final int EXTENDED_EXPONENT_OFFSET = 16383;
0N/A private static final int EXTENDED_MANTISSA_LENGTH = 63;
0N/A private static final int EXTENDED_EXPONENT_LENGTH = 15;
0N/A private static final long EXTENDED_INTEGER_MASK = 0x8000000000000000L;
0N/A
0N/A /**
0N/A * Extended precision IEEE floating-point conversion routine.
0N/A * @argument DataOutputStream
0N/A * @argument double
0N/A * @exception IOException
0N/A */
0N/A private void write_ieee_extended(DataOutputStream dos, float f) throws IOException {
0N/A /* The special cases NaN, Infinity and Zero are ignored, since
0N/A they do not represent useful sample rates anyway.
0N/A Denormalized number aren't handled, too. Below, there is a cast
0N/A from float to double. We hope that in this conversion,
0N/A numbers are normalized. Numbers that cannot be normalized are
0N/A ignored, too, as they, too, do not represent useful sample rates. */
0N/A long doubleBits = Double.doubleToLongBits((double) f);
0N/A
0N/A long sign = (doubleBits & DOUBLE_SIGN_MASK)
0N/A >> (DOUBLE_EXPONENT_LENGTH + DOUBLE_MANTISSA_LENGTH);
0N/A long doubleExponent = (doubleBits & DOUBLE_EXPONENT_MASK)
0N/A >> DOUBLE_MANTISSA_LENGTH;
0N/A long doubleMantissa = doubleBits & DOUBLE_MANTISSA_MASK;
0N/A
0N/A long extendedExponent = doubleExponent - DOUBLE_EXPONENT_OFFSET
0N/A + EXTENDED_EXPONENT_OFFSET;
0N/A long extendedMantissa = doubleMantissa
0N/A << (EXTENDED_MANTISSA_LENGTH - DOUBLE_MANTISSA_LENGTH);
0N/A long extendedSign = sign << EXTENDED_EXPONENT_LENGTH;
0N/A short extendedBits79To64 = (short) (extendedSign | extendedExponent);
0N/A long extendedBits63To0 = EXTENDED_INTEGER_MASK | extendedMantissa;
0N/A
0N/A dos.writeShort(extendedBits79To64);
0N/A dos.writeLong(extendedBits63To0);
0N/A }
0N/A
0N/A
0N/A}