9fee0ec4ca
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@7101 a1c6a512-1295-4272-9138-f99709370657
484 lines
16 KiB
Java
484 lines
16 KiB
Java
/*
|
|
* 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 <http://www.bomers.de>
|
|
*
|
|
* 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.
|
|
* <p>It provides often-used functionality and the new architecture using
|
|
* an AudioOutputStream.
|
|
* <p>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<AudioFileFormat.Type> 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<AudioFormat> m_audioFormats;
|
|
|
|
|
|
/**
|
|
* Inheriting classes should call this constructor
|
|
* in order to make use of the functionality of TAudioFileWriter.
|
|
*/
|
|
protected TAudioFileWriter(Collection<AudioFileFormat.Type> fileTypes,
|
|
Collection<AudioFormat> audioFormats)
|
|
{
|
|
if (TDebug.TraceAudioFileWriter) { TDebug.out("TAudioFileWriter.<init>(): begin"); }
|
|
m_audioFileTypes = fileTypes;
|
|
m_audioFormats = audioFormats;
|
|
if (TDebug.TraceAudioFileWriter) { TDebug.out("TAudioFileWriter.<init>(): 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<AudioFileFormat.Type> res=new ArraySet<AudioFileFormat.Type>();
|
|
Iterator<AudioFileFormat.Type> 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("<TAudioFileWriter.writeImpl(): after main loop. Wrote "+nTotalWritten+" bytes"); }
|
|
audioOutputStream.close();
|
|
// TODO: get bytes written for header etc. from AudioOutputStrem and add to nTotalWrittenBytes
|
|
return nTotalWritten;
|
|
}
|
|
|
|
|
|
/** Returns the AudioFormat that can be handled for the given file type.
|
|
* In this simple implementation, all handled AudioFormats are
|
|
* returned (i.e. the fileType argument is ignored). If the
|
|
* handled AudioFormats depend on the file type, this method
|
|
* has to be overwritten by subclasses.
|
|
*/
|
|
protected Iterator<AudioFormat> getSupportedAudioFormats(AudioFileFormat.Type fileType)
|
|
{
|
|
return m_audioFormats.iterator();
|
|
}
|
|
|
|
|
|
/** Checks whether the passed <b>AudioFormat</b> 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.
|
|
* <p>
|
|
* 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.
|
|
* <p>
|
|
* This method is only called when the <code>fileType</code>
|
|
* is in the list of supported file types ! Overriding
|
|
* classes <b>need not</b> 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 ***/
|