Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/com/sun/media/sound/AiffFileWriter.java
38924 views
/*1* Copyright (c) 1999, 2013, 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.File;28import java.io.InputStream;29import java.io.OutputStream;30import java.io.IOException;3132import java.io.BufferedOutputStream;33import java.io.DataOutputStream;34import java.io.FileOutputStream;35import java.io.ByteArrayInputStream;36import java.io.ByteArrayOutputStream;37import java.io.RandomAccessFile;38import java.io.SequenceInputStream;3940import javax.sound.sampled.AudioFileFormat;41import javax.sound.sampled.AudioInputStream;42import javax.sound.sampled.AudioFormat;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}606162// METHODS TO IMPLEMENT AudioFileWriter6364public 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}838485public int write(AudioInputStream stream, AudioFileFormat.Type fileType, OutputStream out) throws IOException {8687//$$fb the following check must come first ! Otherwise88// the next frame length check may throw an IOException and89// interrupt iterating File Writers. (see bug 4351296)9091// throws IllegalArgumentException if not supported92AiffFileFormat aiffFileFormat = (AiffFileFormat)getAudioFileFormat(fileType, stream);9394// we must know the total data length to calculate the file length95if( stream.getFrameLength() == AudioSystem.NOT_SPECIFIED ) {96throw new IOException("stream length not specified");97}9899int bytesWritten = writeAiffFile(stream, aiffFileFormat, out);100return bytesWritten;101}102103104public int write(AudioInputStream stream, AudioFileFormat.Type fileType, File out) throws IOException {105106// throws IllegalArgumentException if not supported107AiffFileFormat aiffFileFormat = (AiffFileFormat)getAudioFileFormat(fileType, stream);108109// first write the file without worrying about length fields110FileOutputStream fos = new FileOutputStream( out ); // throws IOException111BufferedOutputStream bos = new BufferedOutputStream( fos, bisBufferSize );112int bytesWritten = writeAiffFile(stream, aiffFileFormat, bos );113bos.close();114115// now, if length fields were not specified, calculate them,116// open as a random access file, write the appropriate fields,117// close again....118if( aiffFileFormat.getByteLength()== AudioSystem.NOT_SPECIFIED ) {119120// $$kk: 10.22.99: jan: please either implement this or throw an exception!121// $$fb: 2001-07-13: done. Fixes Bug 4479981122int ssndBlockSize = (aiffFileFormat.getFormat().getChannels() * aiffFileFormat.getFormat().getSampleSizeInBits());123124int aiffLength=bytesWritten;125int ssndChunkSize=aiffLength-aiffFileFormat.getHeaderSize()+16;126long dataSize=ssndChunkSize-16;127int numFrames=(int) (dataSize*8/ssndBlockSize);128129RandomAccessFile raf=new RandomAccessFile(out, "rw");130// skip FORM magic131raf.skipBytes(4);132raf.writeInt(aiffLength-8);133// skip aiff2 magic, fver chunk, comm magic, comm size, channel count,134raf.skipBytes(4+aiffFileFormat.getFverChunkSize()+4+4+2);135// write frame count136raf.writeInt(numFrames);137// skip sample size, samplerate, SSND magic138raf.skipBytes(2+10+4);139raf.writeInt(ssndChunkSize-8);140// that's all141raf.close();142}143144return bytesWritten;145}146147148// -----------------------------------------------------------------------149150/**151* Returns the AudioFileFormat describing the file that will be written from this AudioInputStream.152* Throws IllegalArgumentException if not supported.153*/154private AudioFileFormat getAudioFileFormat(AudioFileFormat.Type type, AudioInputStream stream) {155156AudioFormat format = null;157AiffFileFormat fileFormat = null;158AudioFormat.Encoding encoding = AudioFormat.Encoding.PCM_SIGNED;159160AudioFormat streamFormat = stream.getFormat();161AudioFormat.Encoding streamEncoding = streamFormat.getEncoding();162163164float sampleRate;165int sampleSizeInBits;166int channels;167int frameSize;168float frameRate;169int fileSize;170boolean convert8to16 = false;171172if( !types[0].equals(type) ) {173throw new IllegalArgumentException("File type " + type + " not supported.");174}175176if( (AudioFormat.Encoding.ALAW.equals(streamEncoding)) ||177(AudioFormat.Encoding.ULAW.equals(streamEncoding)) ) {178179if( streamFormat.getSampleSizeInBits()==8 ) {180181encoding = AudioFormat.Encoding.PCM_SIGNED;182sampleSizeInBits=16;183convert8to16 = true;184185} else {186187// can't convert non-8-bit ALAW,ULAW188throw new IllegalArgumentException("Encoding " + streamEncoding + " supported only for 8-bit data.");189}190} else if ( streamFormat.getSampleSizeInBits()==8 ) {191192encoding = AudioFormat.Encoding.PCM_UNSIGNED;193sampleSizeInBits=8;194195} else {196197encoding = AudioFormat.Encoding.PCM_SIGNED;198sampleSizeInBits=streamFormat.getSampleSizeInBits();199}200201202format = new AudioFormat( encoding,203streamFormat.getSampleRate(),204sampleSizeInBits,205streamFormat.getChannels(),206streamFormat.getFrameSize(),207streamFormat.getFrameRate(),208true); // AIFF is big endian209210211if( stream.getFrameLength()!=AudioSystem.NOT_SPECIFIED ) {212if( convert8to16 ) {213fileSize = (int)stream.getFrameLength()*streamFormat.getFrameSize()*2 + AiffFileFormat.AIFF_HEADERSIZE;214} else {215fileSize = (int)stream.getFrameLength()*streamFormat.getFrameSize() + AiffFileFormat.AIFF_HEADERSIZE;216}217} else {218fileSize = AudioSystem.NOT_SPECIFIED;219}220221fileFormat = new AiffFileFormat( AudioFileFormat.Type.AIFF,222fileSize,223format,224(int)stream.getFrameLength() );225226return fileFormat;227}228229230private int writeAiffFile(InputStream in, AiffFileFormat aiffFileFormat, OutputStream out) throws IOException {231232int bytesRead = 0;233int bytesWritten = 0;234InputStream fileStream = getFileStream(aiffFileFormat, in);235byte buffer[] = new byte[bisBufferSize];236int maxLength = aiffFileFormat.getByteLength();237238while( (bytesRead = fileStream.read( buffer )) >= 0 ) {239if (maxLength>0) {240if( bytesRead < maxLength ) {241out.write( buffer, 0, (int)bytesRead );242bytesWritten += bytesRead;243maxLength -= bytesRead;244} else {245out.write( buffer, 0, (int)maxLength );246bytesWritten += maxLength;247maxLength = 0;248break;249}250251} else {252out.write( buffer, 0, (int)bytesRead );253bytesWritten += bytesRead;254}255}256257return bytesWritten;258}259260private InputStream getFileStream(AiffFileFormat aiffFileFormat, InputStream audioStream) throws IOException {261262// private method ... assumes aiffFileFormat is a supported file format263264AudioFormat format = aiffFileFormat.getFormat();265AudioFormat streamFormat = null;266AudioFormat.Encoding encoding = null;267268//$$fb a little bit nicer handling of constants269270//int headerSize = 54;271int headerSize = aiffFileFormat.getHeaderSize();272273//int fverChunkSize = 0;274int fverChunkSize = aiffFileFormat.getFverChunkSize();275//int commChunkSize = 26;276int commChunkSize = aiffFileFormat.getCommChunkSize();277int aiffLength = -1;278int ssndChunkSize = -1;279//int ssndOffset = headerSize - 16;280int ssndOffset = aiffFileFormat.getSsndChunkOffset();281short channels = (short) format.getChannels();282short sampleSize = (short) format.getSampleSizeInBits();283int ssndBlockSize = (channels * sampleSize);284int numFrames = aiffFileFormat.getFrameLength();285long dataSize = -1;286if( numFrames != AudioSystem.NOT_SPECIFIED) {287dataSize = (long) numFrames * ssndBlockSize / 8;288ssndChunkSize = (int)dataSize + 16;289aiffLength = (int)dataSize+headerSize;290}291float sampleFramesPerSecond = format.getSampleRate();292int compCode = AiffFileFormat.AIFC_PCM;293294byte header[] = null;295ByteArrayInputStream headerStream = null;296ByteArrayOutputStream baos = null;297DataOutputStream dos = null;298SequenceInputStream aiffStream = null;299InputStream codedAudioStream = audioStream;300301// if we need to do any format conversion, do it here....302303if( audioStream instanceof AudioInputStream ) {304305streamFormat = ((AudioInputStream)audioStream).getFormat();306encoding = streamFormat.getEncoding();307308309// $$jb: Note that AIFF samples are ALWAYS signed310if( (AudioFormat.Encoding.PCM_UNSIGNED.equals(encoding)) ||311( (AudioFormat.Encoding.PCM_SIGNED.equals(encoding)) && !streamFormat.isBigEndian() ) ) {312313// plug in the transcoder to convert to PCM_SIGNED. big endian314codedAudioStream = AudioSystem.getAudioInputStream( new AudioFormat (315AudioFormat.Encoding.PCM_SIGNED,316streamFormat.getSampleRate(),317streamFormat.getSampleSizeInBits(),318streamFormat.getChannels(),319streamFormat.getFrameSize(),320streamFormat.getFrameRate(),321true ),322(AudioInputStream)audioStream );323324} else if( (AudioFormat.Encoding.ULAW.equals(encoding)) ||325(AudioFormat.Encoding.ALAW.equals(encoding)) ) {326327if( streamFormat.getSampleSizeInBits() != 8 ) {328throw new IllegalArgumentException("unsupported encoding");329}330331//$$fb 2001-07-13: this is probably not what we want:332// writing PCM when ULAW/ALAW is requested. AIFC is able to write ULAW !333334// plug in the transcoder to convert to PCM_SIGNED_BIG_ENDIAN335codedAudioStream = AudioSystem.getAudioInputStream( new AudioFormat (336AudioFormat.Encoding.PCM_SIGNED,337streamFormat.getSampleRate(),338streamFormat.getSampleSizeInBits() * 2,339streamFormat.getChannels(),340streamFormat.getFrameSize() * 2,341streamFormat.getFrameRate(),342true ),343(AudioInputStream)audioStream );344}345}346347348// Now create an AIFF stream header...349baos = new ByteArrayOutputStream();350dos = new DataOutputStream(baos);351352// Write the outer FORM chunk353dos.writeInt(AiffFileFormat.AIFF_MAGIC);354dos.writeInt( (aiffLength-8) );355dos.writeInt(AiffFileFormat.AIFF_MAGIC2);356357// Write a FVER chunk - only for AIFC358//dos.writeInt(FVER_MAGIC);359//dos.writeInt( (fverChunkSize-8) );360//dos.writeInt(FVER_TIMESTAMP);361362// Write a COMM chunk363dos.writeInt(AiffFileFormat.COMM_MAGIC);364dos.writeInt( (commChunkSize-8) );365dos.writeShort(channels);366dos.writeInt(numFrames);367dos.writeShort(sampleSize);368write_ieee_extended(dos, sampleFramesPerSecond); // 10 bytes369370//Only for AIFC371//dos.writeInt(compCode);372//dos.writeInt(compCode);373//dos.writeShort(0);374375// Write the SSND chunk header376dos.writeInt(AiffFileFormat.SSND_MAGIC);377dos.writeInt( (ssndChunkSize-8) );378// ssndOffset and ssndBlockSize set to 0 upon379// recommendation in "Sound Manager" chapter in380// "Inside Macintosh Sound", pp 2-87 (from Babu)381dos.writeInt(0); // ssndOffset382dos.writeInt(0); // ssndBlockSize383384// Concat this with the audioStream and return it385386dos.close();387header = baos.toByteArray();388headerStream = new ByteArrayInputStream( header );389390aiffStream = new SequenceInputStream(headerStream,391new NoCloseInputStream(codedAudioStream));392393return aiffStream;394395}396397398399400// HELPER METHODS401402private static final int DOUBLE_MANTISSA_LENGTH = 52;403private static final int DOUBLE_EXPONENT_LENGTH = 11;404private static final long DOUBLE_SIGN_MASK = 0x8000000000000000L;405private static final long DOUBLE_EXPONENT_MASK = 0x7FF0000000000000L;406private static final long DOUBLE_MANTISSA_MASK = 0x000FFFFFFFFFFFFFL;407private static final int DOUBLE_EXPONENT_OFFSET = 1023;408409private static final int EXTENDED_EXPONENT_OFFSET = 16383;410private static final int EXTENDED_MANTISSA_LENGTH = 63;411private static final int EXTENDED_EXPONENT_LENGTH = 15;412private static final long EXTENDED_INTEGER_MASK = 0x8000000000000000L;413414/**415* Extended precision IEEE floating-point conversion routine.416* @argument DataOutputStream417* @argument double418* @exception IOException419*/420private void write_ieee_extended(DataOutputStream dos, float f) throws IOException {421/* The special cases NaN, Infinity and Zero are ignored, since422they do not represent useful sample rates anyway.423Denormalized number aren't handled, too. Below, there is a cast424from float to double. We hope that in this conversion,425numbers are normalized. Numbers that cannot be normalized are426ignored, too, as they, too, do not represent useful sample rates. */427long doubleBits = Double.doubleToLongBits((double) f);428429long sign = (doubleBits & DOUBLE_SIGN_MASK)430>> (DOUBLE_EXPONENT_LENGTH + DOUBLE_MANTISSA_LENGTH);431long doubleExponent = (doubleBits & DOUBLE_EXPONENT_MASK)432>> DOUBLE_MANTISSA_LENGTH;433long doubleMantissa = doubleBits & DOUBLE_MANTISSA_MASK;434435long extendedExponent = doubleExponent - DOUBLE_EXPONENT_OFFSET436+ EXTENDED_EXPONENT_OFFSET;437long extendedMantissa = doubleMantissa438<< (EXTENDED_MANTISSA_LENGTH - DOUBLE_MANTISSA_LENGTH);439long extendedSign = sign << EXTENDED_EXPONENT_LENGTH;440short extendedBits79To64 = (short) (extendedSign | extendedExponent);441long extendedBits63To0 = EXTENDED_INTEGER_MASK | extendedMantissa;442443dos.writeShort(extendedBits79To64);444dos.writeLong(extendedBits63To0);445}446447448}449450451