Path: blob/master/src/java.desktop/share/classes/com/sun/media/sound/AiffFileWriter.java
41161 views
/*1* Copyright (c) 1999, 2018, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package com.sun.media.sound;2627import java.io.BufferedOutputStream;28import java.io.ByteArrayInputStream;29import java.io.ByteArrayOutputStream;30import java.io.DataOutputStream;31import java.io.File;32import java.io.FileOutputStream;33import java.io.IOException;34import java.io.InputStream;35import java.io.OutputStream;36import java.io.RandomAccessFile;37import java.io.SequenceInputStream;38import java.util.Objects;3940import javax.sound.sampled.AudioFileFormat;41import javax.sound.sampled.AudioFormat;42import javax.sound.sampled.AudioInputStream;43import javax.sound.sampled.AudioSystem;4445//$$fb this class is buggy. Should be replaced in future.4647/**48* AIFF file writer.49*50* @author Jan Borgersen51*/52public final class AiffFileWriter extends SunFileWriter {5354/**55* Constructs a new AiffFileWriter object.56*/57public AiffFileWriter() {58super(new AudioFileFormat.Type[]{AudioFileFormat.Type.AIFF});59}6061// METHODS TO IMPLEMENT AudioFileWriter6263@Override64public AudioFileFormat.Type[] getAudioFileTypes(AudioInputStream stream) {6566AudioFileFormat.Type[] filetypes = new AudioFileFormat.Type[types.length];67System.arraycopy(types, 0, filetypes, 0, types.length);6869// make sure we can write this stream70AudioFormat format = stream.getFormat();71AudioFormat.Encoding encoding = format.getEncoding();7273if( (AudioFormat.Encoding.ALAW.equals(encoding)) ||74(AudioFormat.Encoding.ULAW.equals(encoding)) ||75(AudioFormat.Encoding.PCM_SIGNED.equals(encoding)) ||76(AudioFormat.Encoding.PCM_UNSIGNED.equals(encoding)) ) {7778return filetypes;79}8081return new AudioFileFormat.Type[0];82}8384@Override85public int write(AudioInputStream stream, AudioFileFormat.Type fileType, OutputStream out) throws IOException {86Objects.requireNonNull(stream);87Objects.requireNonNull(fileType);88Objects.requireNonNull(out);8990//$$fb the following check must come first ! Otherwise91// the next frame length check may throw an IOException and92// interrupt iterating File Writers. (see bug 4351296)9394// throws IllegalArgumentException if not supported95AiffFileFormat aiffFileFormat = (AiffFileFormat)getAudioFileFormat(fileType, stream);9697// we must know the total data length to calculate the file length98if( stream.getFrameLength() == AudioSystem.NOT_SPECIFIED ) {99throw new IOException("stream length not specified");100}101102return writeAiffFile(stream, aiffFileFormat, out);103}104105@Override106public int write(AudioInputStream stream, AudioFileFormat.Type fileType, File out) throws IOException {107Objects.requireNonNull(stream);108Objects.requireNonNull(fileType);109Objects.requireNonNull(out);110111// throws IllegalArgumentException if not supported112AiffFileFormat aiffFileFormat = (AiffFileFormat)getAudioFileFormat(fileType, stream);113114// first write the file without worrying about length fields115final int bytesWritten;116try (final FileOutputStream fos = new FileOutputStream(out);117final BufferedOutputStream bos = new BufferedOutputStream(fos)) {118bytesWritten = writeAiffFile(stream, aiffFileFormat, bos);119}120121// now, if length fields were not specified, calculate them,122// open as a random access file, write the appropriate fields,123// close again....124if( aiffFileFormat.getByteLength()== AudioSystem.NOT_SPECIFIED ) {125126// $$kk: 10.22.99: jan: please either implement this or throw an exception!127// $$fb: 2001-07-13: done. Fixes Bug 4479981128int channels = aiffFileFormat.getFormat().getChannels();129int sampleSize = aiffFileFormat.getFormat().getSampleSizeInBits();130int ssndBlockSize = channels * ((sampleSize + 7) / 8);131132int aiffLength=bytesWritten;133int ssndChunkSize=aiffLength-aiffFileFormat.getHeaderSize()+16;134long dataSize=ssndChunkSize-16;135//TODO possibly incorrect round136int numFrames = (int) (dataSize / ssndBlockSize);137try (final RandomAccessFile raf = new RandomAccessFile(out, "rw")) {138// skip FORM magic139raf.skipBytes(4);140raf.writeInt(aiffLength - 8);141// skip aiff2 magic, fver chunk, comm magic, comm size, channel count,142raf.skipBytes(4 + aiffFileFormat.getFverChunkSize() + 4 + 4 + 2);143// write frame count144raf.writeInt(numFrames);145// skip sample size, samplerate, SSND magic146raf.skipBytes(2 + 10 + 4);147raf.writeInt(ssndChunkSize - 8);148// that's all149}150}151152return bytesWritten;153}154155156// -----------------------------------------------------------------------157158/**159* Returns the AudioFileFormat describing the file that will be written from this AudioInputStream.160* Throws IllegalArgumentException if not supported.161*/162private AudioFileFormat getAudioFileFormat(AudioFileFormat.Type type, AudioInputStream stream) {163if (!isFileTypeSupported(type, stream)) {164throw new IllegalArgumentException("File type " + type + " not supported.");165}166167AudioFormat format = null;168AiffFileFormat fileFormat = null;169AudioFormat.Encoding encoding = AudioFormat.Encoding.PCM_SIGNED;170171AudioFormat streamFormat = stream.getFormat();172AudioFormat.Encoding streamEncoding = streamFormat.getEncoding();173174int sampleSizeInBits;175int fileSize;176boolean convert8to16 = false;177178if( (AudioFormat.Encoding.ALAW.equals(streamEncoding)) ||179(AudioFormat.Encoding.ULAW.equals(streamEncoding)) ) {180181if( streamFormat.getSampleSizeInBits()==8 ) {182183encoding = AudioFormat.Encoding.PCM_SIGNED;184sampleSizeInBits=16;185convert8to16 = true;186187} else {188189// can't convert non-8-bit ALAW,ULAW190throw new IllegalArgumentException("Encoding " + streamEncoding + " supported only for 8-bit data.");191}192} else if ( streamFormat.getSampleSizeInBits()==8 ) {193194encoding = AudioFormat.Encoding.PCM_UNSIGNED;195sampleSizeInBits=8;196197} else {198199encoding = AudioFormat.Encoding.PCM_SIGNED;200sampleSizeInBits=streamFormat.getSampleSizeInBits();201}202203204format = new AudioFormat( encoding,205streamFormat.getSampleRate(),206sampleSizeInBits,207streamFormat.getChannels(),208streamFormat.getFrameSize(),209streamFormat.getFrameRate(),210true); // AIFF is big endian211212213if( stream.getFrameLength()!=AudioSystem.NOT_SPECIFIED ) {214if( convert8to16 ) {215fileSize = (int)stream.getFrameLength()*streamFormat.getFrameSize()*2 + AiffFileFormat.AIFF_HEADERSIZE;216} else {217fileSize = (int)stream.getFrameLength()*streamFormat.getFrameSize() + AiffFileFormat.AIFF_HEADERSIZE;218}219} else {220fileSize = AudioSystem.NOT_SPECIFIED;221}222223fileFormat = new AiffFileFormat( AudioFileFormat.Type.AIFF,224fileSize,225format,226(int)stream.getFrameLength() );227228return fileFormat;229}230231private int writeAiffFile(InputStream in, AiffFileFormat aiffFileFormat, OutputStream out) throws IOException {232233int bytesRead = 0;234int bytesWritten = 0;235InputStream fileStream = getFileStream(aiffFileFormat, in);236byte[] buffer = new byte[bisBufferSize];237int maxLength = aiffFileFormat.getByteLength();238239while( (bytesRead = fileStream.read( buffer )) >= 0 ) {240if (maxLength>0) {241if( bytesRead < maxLength ) {242out.write( buffer, 0, bytesRead );243bytesWritten += bytesRead;244maxLength -= bytesRead;245} else {246out.write( buffer, 0, maxLength );247bytesWritten += maxLength;248maxLength = 0;249break;250}251252} else {253out.write( buffer, 0, bytesRead );254bytesWritten += bytesRead;255}256}257258return bytesWritten;259}260261private InputStream getFileStream(AiffFileFormat aiffFileFormat, InputStream audioStream) throws IOException {262263// private method ... assumes aiffFileFormat is a supported file format264265AudioFormat format = aiffFileFormat.getFormat();266AudioFormat streamFormat = null;267AudioFormat.Encoding encoding = null;268269//$$fb a little bit nicer handling of constants270int headerSize = aiffFileFormat.getHeaderSize();271//int fverChunkSize = 0;272int fverChunkSize = aiffFileFormat.getFverChunkSize();273int commChunkSize = aiffFileFormat.getCommChunkSize();274int aiffLength = -1;275int ssndChunkSize = -1;276int ssndOffset = aiffFileFormat.getSsndChunkOffset();277short channels = (short) format.getChannels();278short sampleSize = (short) format.getSampleSizeInBits();279int ssndBlockSize = channels * ((sampleSize + 7) / 8);280int numFrames = aiffFileFormat.getFrameLength();281long dataSize = -1;282if( numFrames != AudioSystem.NOT_SPECIFIED) {283dataSize = (long) numFrames * ssndBlockSize;284ssndChunkSize = (int)dataSize + 16;285aiffLength = (int)dataSize+headerSize;286}287float sampleFramesPerSecond = format.getSampleRate();288int compCode = AiffFileFormat.AIFC_PCM;289290byte[] header = null;291InputStream codedAudioStream = audioStream;292293// if we need to do any format conversion, do it here....294295if( audioStream instanceof AudioInputStream ) {296297streamFormat = ((AudioInputStream)audioStream).getFormat();298encoding = streamFormat.getEncoding();299300301// $$jb: Note that AIFF samples are ALWAYS signed302if( (AudioFormat.Encoding.PCM_UNSIGNED.equals(encoding)) ||303( (AudioFormat.Encoding.PCM_SIGNED.equals(encoding)) && !streamFormat.isBigEndian() ) ) {304305// plug in the transcoder to convert to PCM_SIGNED. big endian306codedAudioStream = AudioSystem.getAudioInputStream( new AudioFormat (307AudioFormat.Encoding.PCM_SIGNED,308streamFormat.getSampleRate(),309streamFormat.getSampleSizeInBits(),310streamFormat.getChannels(),311streamFormat.getFrameSize(),312streamFormat.getFrameRate(),313true ),314(AudioInputStream)audioStream );315316} else if( (AudioFormat.Encoding.ULAW.equals(encoding)) ||317(AudioFormat.Encoding.ALAW.equals(encoding)) ) {318319if( streamFormat.getSampleSizeInBits() != 8 ) {320throw new IllegalArgumentException("unsupported encoding");321}322323//$$fb 2001-07-13: this is probably not what we want:324// writing PCM when ULAW/ALAW is requested. AIFC is able to write ULAW !325326// plug in the transcoder to convert to PCM_SIGNED_BIG_ENDIAN327codedAudioStream = AudioSystem.getAudioInputStream( new AudioFormat (328AudioFormat.Encoding.PCM_SIGNED,329streamFormat.getSampleRate(),330streamFormat.getSampleSizeInBits() * 2,331streamFormat.getChannels(),332streamFormat.getFrameSize() * 2,333streamFormat.getFrameRate(),334true ),335(AudioInputStream)audioStream );336}337}338339340// Now create an AIFF stream header...341try (final ByteArrayOutputStream baos = new ByteArrayOutputStream();342final DataOutputStream dos = new DataOutputStream(baos)) {343// Write the outer FORM chunk344dos.writeInt(AiffFileFormat.AIFF_MAGIC);345dos.writeInt((aiffLength - 8));346dos.writeInt(AiffFileFormat.AIFF_MAGIC2);347// Write a FVER chunk - only for AIFC348//dos.writeInt(FVER_MAGIC);349//dos.writeInt( (fverChunkSize-8) );350//dos.writeInt(FVER_TIMESTAMP);351// Write a COMM chunk352dos.writeInt(AiffFileFormat.COMM_MAGIC);353dos.writeInt((commChunkSize - 8));354dos.writeShort(channels);355dos.writeInt(numFrames);356dos.writeShort(sampleSize);357write_ieee_extended(dos, sampleFramesPerSecond); // 10 bytes358//Only for AIFC359//dos.writeInt(compCode);360//dos.writeInt(compCode);361//dos.writeShort(0);362// Write the SSND chunk header363dos.writeInt(AiffFileFormat.SSND_MAGIC);364dos.writeInt((ssndChunkSize - 8));365// ssndOffset and ssndBlockSize set to 0 upon366// recommendation in "Sound Manager" chapter in367// "Inside Macintosh Sound", pp 2-87 (from Babu)368dos.writeInt(0); // ssndOffset369dos.writeInt(0); // ssndBlockSize370header = baos.toByteArray();371}372return new SequenceInputStream(new ByteArrayInputStream(header),373new NoCloseInputStream(codedAudioStream));374}375376// HELPER METHODS377378private static final int DOUBLE_MANTISSA_LENGTH = 52;379private static final int DOUBLE_EXPONENT_LENGTH = 11;380private static final long DOUBLE_SIGN_MASK = 0x8000000000000000L;381private static final long DOUBLE_EXPONENT_MASK = 0x7FF0000000000000L;382private static final long DOUBLE_MANTISSA_MASK = 0x000FFFFFFFFFFFFFL;383private static final int DOUBLE_EXPONENT_OFFSET = 1023;384385private static final int EXTENDED_EXPONENT_OFFSET = 16383;386private static final int EXTENDED_MANTISSA_LENGTH = 63;387private static final int EXTENDED_EXPONENT_LENGTH = 15;388private static final long EXTENDED_INTEGER_MASK = 0x8000000000000000L;389390/**391* Extended precision IEEE floating-point conversion routine.392* @argument DataOutputStream393* @argument double394* @exception IOException395*/396private void write_ieee_extended(DataOutputStream dos, float f) throws IOException {397/* The special cases NaN, Infinity and Zero are ignored, since398they do not represent useful sample rates anyway.399Denormalized number aren't handled, too. Below, there is a cast400from float to double. We hope that in this conversion,401numbers are normalized. Numbers that cannot be normalized are402ignored, too, as they, too, do not represent useful sample rates. */403long doubleBits = Double.doubleToLongBits((double) f);404405long sign = (doubleBits & DOUBLE_SIGN_MASK)406>> (DOUBLE_EXPONENT_LENGTH + DOUBLE_MANTISSA_LENGTH);407long doubleExponent = (doubleBits & DOUBLE_EXPONENT_MASK)408>> DOUBLE_MANTISSA_LENGTH;409long doubleMantissa = doubleBits & DOUBLE_MANTISSA_MASK;410411long extendedExponent = doubleExponent - DOUBLE_EXPONENT_OFFSET412+ EXTENDED_EXPONENT_OFFSET;413long extendedMantissa = doubleMantissa414<< (EXTENDED_MANTISSA_LENGTH - DOUBLE_MANTISSA_LENGTH);415long extendedSign = sign << EXTENDED_EXPONENT_LENGTH;416short extendedBits79To64 = (short) (extendedSign | extendedExponent);417long extendedBits63To0 = EXTENDED_INTEGER_MASK | extendedMantissa;418419dos.writeShort(extendedBits79To64);420dos.writeLong(extendedBits63To0);421}422}423424425