From 9fee0ec4ca0c5b7a334cc29dbb58e76c7a4c736e Mon Sep 17 00:00:00 2001 From: Michiel Van Der Kolk Date: Mon, 11 Jul 2005 15:42:37 +0000 Subject: Songdb java version, source. only 1.5 compatible git-svn-id: svn://svn.rockbox.org/rockbox/trunk@7101 a1c6a512-1295-4272-9138-f99709370657 --- .../share/sampled/file/TAudioFileWriter.java | 484 +++++++++++++++++++++ 1 file changed, 484 insertions(+) create mode 100644 songdbj/org/tritonus/share/sampled/file/TAudioFileWriter.java (limited to 'songdbj/org/tritonus/share/sampled/file/TAudioFileWriter.java') diff --git a/songdbj/org/tritonus/share/sampled/file/TAudioFileWriter.java b/songdbj/org/tritonus/share/sampled/file/TAudioFileWriter.java new file mode 100644 index 0000000000..d9d6ee86ec --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/file/TAudioFileWriter.java @@ -0,0 +1,484 @@ +/* + * TAudioFileWriter.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 1999, 2000 by Matthias Pfisterer + * Copyright (c) 1999, 2000 by Florian Bomers + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.file; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collection; +import java.util.Iterator; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.spi.AudioFileWriter; + +import org.tritonus.share.TDebug; +import org.tritonus.share.sampled.AudioFormats; +import org.tritonus.share.sampled.AudioUtils; +import org.tritonus.share.sampled.TConversionTool; +import org.tritonus.share.ArraySet; + +/** + * Common base class for implementing classes of AudioFileWriter. + *

It provides often-used functionality and the new architecture using + * an AudioOutputStream. + *

There should be only one set of audio formats supported by any given + * class of TAudioFileWriter. This class assumes implicitely that all + * supported file types have a common set of audio formats they can handle. + * + * @author Matthias Pfisterer + * @author Florian Bomers + */ + +public abstract class TAudioFileWriter +extends AudioFileWriter +{ + protected static final int ALL = AudioSystem.NOT_SPECIFIED; + + public static AudioFormat.Encoding PCM_SIGNED=new AudioFormat.Encoding("PCM_SIGNED"); + public static AudioFormat.Encoding PCM_UNSIGNED=new AudioFormat.Encoding("PCM_UNSIGNED"); + + /** Buffer length for the loop in the write() method. + * This is in bytes. Perhaps it should be in frames to give an + * equal amount of latency. + */ + private static final int BUFFER_LENGTH = 16384; + + // only needed for Collection.toArray() + protected static final AudioFileFormat.Type[] NULL_TYPE_ARRAY = new AudioFileFormat.Type[0]; + + + /** The audio file types (AudioFileFormat.Type) that can be + * handled by the AudioFileWriter. + */ + private Collection m_audioFileTypes; + + + + /** The AudioFormats that can be handled by the + * AudioFileWriter. + */ + // IDEA: implement a special collection that uses matches() to test whether an element is already in + private Collection m_audioFormats; + + + /** + * Inheriting classes should call this constructor + * in order to make use of the functionality of TAudioFileWriter. + */ + protected TAudioFileWriter(Collection fileTypes, + Collection audioFormats) + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("TAudioFileWriter.(): begin"); } + m_audioFileTypes = fileTypes; + m_audioFormats = audioFormats; + if (TDebug.TraceAudioFileWriter) { TDebug.out("TAudioFileWriter.(): end"); } + } + + // implementing the interface + public AudioFileFormat.Type[] getAudioFileTypes() + { + return m_audioFileTypes.toArray(NULL_TYPE_ARRAY); + } + + + // implementing the interface + public boolean isFileTypeSupported(AudioFileFormat.Type fileType) + { + return m_audioFileTypes.contains(fileType); + } + + + + // implementing the interface + public AudioFileFormat.Type[] getAudioFileTypes( + AudioInputStream audioInputStream) + { + //$$fb 2000-08-16: rewrote this method. We need to check for *each* + // file type, whether the format is supported ! + AudioFormat format = audioInputStream.getFormat(); + ArraySet res=new ArraySet(); + Iterator it=m_audioFileTypes.iterator(); + while (it.hasNext()) { + AudioFileFormat.Type thisType = it.next(); + if (isAudioFormatSupportedImpl(format, thisType)) { + res.add(thisType); + } + } + return res.toArray(NULL_TYPE_ARRAY); + } + + + + // implementing the interface + public boolean isFileTypeSupported(AudioFileFormat.Type fileType, AudioInputStream audioInputStream) + { + // $$fb 2000-08-16: finally this method works reliably ! + return isFileTypeSupported(fileType) + && (isAudioFormatSupportedImpl(audioInputStream.getFormat(), fileType) + || findConvertableFormat(audioInputStream.getFormat(), fileType)!=null); + // we may soft it up by including the possibility of endian/sign + // changing for PCM formats. + // I prefer to return false if the format is not exactly supported + // but still exectute the write, if only sign/endian changing is necessary. + } + + + + // implementing the interface + public int write(AudioInputStream audioInputStream, + AudioFileFormat.Type fileType, + File file) + throws IOException + { + if (TDebug.TraceAudioFileWriter) + { + TDebug.out(">TAudioFileWriter.write(.., File): called"); + TDebug.out("class: "+getClass().getName()); + } + //$$fb added this check + if (!isFileTypeSupported(fileType)) { + if (TDebug.TraceAudioFileWriter) + { + TDebug.out("< file type is not supported"); + } + throw new IllegalArgumentException("file type is not supported."); + } + + AudioFormat inputFormat = audioInputStream.getFormat(); + if (TDebug.TraceAudioFileWriter) { TDebug.out("input format: " + inputFormat); } + AudioFormat outputFormat = null; + boolean bNeedsConversion = false; + if (isAudioFormatSupportedImpl(inputFormat, fileType)) + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("input format is supported directely"); } + outputFormat = inputFormat; + bNeedsConversion = false; + } + else + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("input format is not supported directely; trying to find a convertable format"); } + outputFormat = findConvertableFormat(inputFormat, fileType); + if (outputFormat != null) + { + bNeedsConversion = true; + // $$fb 2000-08-16 made consistent with new conversion trials + // if 8 bit and only endianness changed, don't convert ! + if (outputFormat.getSampleSizeInBits()==8 + && outputFormat.getEncoding().equals(inputFormat.getEncoding())) { + bNeedsConversion = false; + } + } + else + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("< input format is not supported and not convertable."); } + throw new IllegalArgumentException("format not supported and not convertable"); + } + } + long lLengthInBytes = AudioUtils.getLengthInBytes(audioInputStream); + TDataOutputStream dataOutputStream = new TSeekableDataOutputStream(file); + AudioOutputStream audioOutputStream = + getAudioOutputStream( + outputFormat, + lLengthInBytes, + fileType, + dataOutputStream); + int written=writeImpl(audioInputStream, + audioOutputStream, + bNeedsConversion); + if (TDebug.TraceAudioFileWriter) + { + TDebug.out("< wrote "+written+" bytes."); + } + return written; + } + + + + // implementing the interface + public int write(AudioInputStream audioInputStream, + AudioFileFormat.Type fileType, + OutputStream outputStream) + throws IOException + { + //$$fb added this check + if (!isFileTypeSupported(fileType)) { + throw new IllegalArgumentException("file type is not supported."); + } + if (TDebug.TraceAudioFileWriter) + { + TDebug.out(">TAudioFileWriter.write(.., OutputStream): called"); + TDebug.out("class: "+getClass().getName()); + } + AudioFormat inputFormat = audioInputStream.getFormat(); + if (TDebug.TraceAudioFileWriter) { TDebug.out("input format: " + inputFormat); } + AudioFormat outputFormat = null; + boolean bNeedsConversion = false; + if (isAudioFormatSupportedImpl(inputFormat, fileType)) + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("input format is supported directely"); } + outputFormat = inputFormat; + bNeedsConversion = false; + } + else + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("input format is not supported directely; trying to find a convertable format"); } + outputFormat = findConvertableFormat(inputFormat, fileType); + if (outputFormat != null) + { + bNeedsConversion = true; + // $$fb 2000-08-16 made consistent with new conversion trials + // if 8 bit and only endianness changed, don't convert ! + if (outputFormat.getSampleSizeInBits()==8 + && outputFormat.getEncoding().equals(inputFormat.getEncoding())) { + bNeedsConversion = false; + } + } + else + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("< format is not supported"); } + throw new IllegalArgumentException("format not supported and not convertable"); + } + } + long lLengthInBytes = AudioUtils.getLengthInBytes(audioInputStream); + TDataOutputStream dataOutputStream = new TNonSeekableDataOutputStream(outputStream); + AudioOutputStream audioOutputStream = + getAudioOutputStream( + outputFormat, + lLengthInBytes, + fileType, + dataOutputStream); + int written=writeImpl(audioInputStream, + audioOutputStream, + bNeedsConversion); + if (TDebug.TraceAudioFileWriter) { TDebug.out("< wrote "+written+" bytes."); } + return written; + } + + + + protected int writeImpl( + AudioInputStream audioInputStream, + AudioOutputStream audioOutputStream, + boolean bNeedsConversion) + throws IOException + { + if (TDebug.TraceAudioFileWriter) + { + TDebug.out(">TAudioFileWriter.writeImpl(): called"); + TDebug.out("class: "+getClass().getName()); + } + int nTotalWritten = 0; + AudioFormat inputFormat = audioInputStream.getFormat(); + AudioFormat outputFormat = audioOutputStream.getFormat(); + + // TODO: handle case when frame size is unknown ? + int nBytesPerSample = outputFormat.getFrameSize() / outputFormat.getChannels(); + + //$$fb 2000-07-18: BUFFER_LENGTH must be a multiple of frame size... + int nBufferSize=((int)BUFFER_LENGTH/outputFormat.getFrameSize())*outputFormat.getFrameSize(); + byte[] abBuffer = new byte[nBufferSize]; + while (true) + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("trying to read (bytes): " + abBuffer.length); } + int nBytesRead = audioInputStream.read(abBuffer); + if (TDebug.TraceAudioFileWriter) { TDebug.out("read (bytes): " + nBytesRead); } + if (nBytesRead == -1) + { + break; + } + if (bNeedsConversion) + { + TConversionTool.changeOrderOrSign(abBuffer, 0, + nBytesRead, nBytesPerSample); + } + int nWritten = audioOutputStream.write(abBuffer, 0, nBytesRead); + nTotalWritten += nWritten; + } + if (TDebug.TraceAudioFileWriter) { TDebug.out(" getSupportedAudioFormats(AudioFileFormat.Type fileType) + { + return m_audioFormats.iterator(); + } + + + /** Checks whether the passed AudioFormat can be handled. + * In this simple implementation, it is only checked if the + * passed AudioFormat matches one of the generally handled + * formats (i.e. the fileType argument is ignored). If the + * handled AudioFormats depend on the file type, this method + * or getSupportedAudioFormats() (on which this method relies) + * has to be overwritten by subclasses. + *

+ * This is the central method for checking if a FORMAT is supported. + * Inheriting classes can overwrite this for performance + * or to exclude/include special type/format combinations. + *

+ * This method is only called when the fileType + * is in the list of supported file types ! Overriding + * classes need not check this. + */ + //$$fb 2000-08-16 changed name, changed documentation. Semantics ! + protected boolean isAudioFormatSupportedImpl( + AudioFormat audioFormat, + AudioFileFormat.Type fileType) + { + if (TDebug.TraceAudioFileWriter) + { + TDebug.out("> TAudioFileWriter.isAudioFormatSupportedImpl(): format to test: " + audioFormat); + TDebug.out("class: "+getClass().getName()); + } + Iterator audioFormats = getSupportedAudioFormats(fileType); + while (audioFormats.hasNext()) + { + AudioFormat handledFormat = (AudioFormat) audioFormats.next(); + if (TDebug.TraceAudioFileWriter) { TDebug.out("matching against format : " + handledFormat); } + if (AudioFormats.matches(handledFormat, audioFormat)) + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("<...succeeded."); } + return true; + } + } + if (TDebug.TraceAudioFileWriter) { TDebug.out("< ... failed"); } + return false; + } + + + + protected abstract AudioOutputStream getAudioOutputStream( + AudioFormat audioFormat, + long lLengthInBytes, + AudioFileFormat.Type fileType, + TDataOutputStream dataOutputStream) + throws IOException; + + private AudioFormat findConvertableFormat( + AudioFormat inputFormat, + AudioFileFormat.Type fileType) + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("TAudioFileWriter.findConvertableFormat(): input format: " + inputFormat); } + if (!isFileTypeSupported(fileType)) { + if (TDebug.TraceAudioFileWriter) { TDebug.out("< input file type is not supported."); } + return null; + } + AudioFormat.Encoding inputEncoding = inputFormat.getEncoding(); + if ((inputEncoding.equals(PCM_SIGNED) || inputEncoding.equals(PCM_UNSIGNED)) + && inputFormat.getSampleSizeInBits() == 8) + { + AudioFormat outputFormat = convertFormat(inputFormat, true, false); + if (TDebug.TraceAudioFileWriter) { TDebug.out("trying output format: " + outputFormat); } + if (isAudioFormatSupportedImpl(outputFormat, fileType)) + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("< ... succeeded"); } + return outputFormat; + } + //$$fb 2000-08-16: added trial of other endianness for 8bit. We try harder ! + outputFormat = convertFormat(inputFormat, false, true); + if (TDebug.TraceAudioFileWriter) { TDebug.out("trying output format: " + outputFormat); } + if (isAudioFormatSupportedImpl(outputFormat, fileType)) + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("< ... succeeded"); } + return outputFormat; + } + outputFormat = convertFormat(inputFormat, true, true); + if (TDebug.TraceAudioFileWriter) { TDebug.out("trying output format: " + outputFormat); } + if (isAudioFormatSupportedImpl(outputFormat, fileType)) + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("< ... succeeded"); } + return outputFormat; + } + if (TDebug.TraceAudioFileWriter) { TDebug.out("< ... failed"); } + return null; + } + else if (inputEncoding.equals(PCM_SIGNED) && + (inputFormat.getSampleSizeInBits() == 16 || + inputFormat.getSampleSizeInBits() == 24 || + inputFormat.getSampleSizeInBits() == 32) ) + { + // TODO: possible to allow all sample sized > 8 bit? + // $$ fb: don't think that this is necessary. Well, let's talk about that in 5 years :) + AudioFormat outputFormat = convertFormat(inputFormat, false, true); + if (TDebug.TraceAudioFileWriter) { TDebug.out("trying output format: " + outputFormat); } + if (isAudioFormatSupportedImpl(outputFormat, fileType)) + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("< ... succeeded"); } + return outputFormat; + } + else + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("< ... failed"); } + return null; + } + } + else + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("< ... failed"); } + return null; + } + } + + // $$fb 2000-08-16: added convenience method + private AudioFormat convertFormat(AudioFormat format, boolean changeSign, boolean changeEndian) { + AudioFormat.Encoding enc=PCM_SIGNED; + if (format.getEncoding().equals(PCM_UNSIGNED)!=changeSign) { + enc=PCM_UNSIGNED; + } + return new AudioFormat( + enc, + format.getSampleRate(), + format.getSampleSizeInBits(), + format.getChannels(), + format.getFrameSize(), + format.getFrameRate(), + format.isBigEndian() ^ changeEndian); + } + +} + + + +/*** TAudioFileWriter.java ***/ -- cgit v1.2.3