/**
* Copyright 2004-2006 DFKI GmbH.
* All Rights Reserved. Use is subject to license terms.
*
* This file is part of MARY TTS.
*
* MARY TTS is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package marytts.util.data.audio;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import marytts.util.data.DoubleDataSource;
/**
* @author Marc Schröder An audio input stream that draws its audio data from a DoubleDataSource.
*/
public class DDSAudioInputStream extends AudioInputStream {
public static final int MAX_AMPLITUDE = 32767;
protected DoubleDataSource source;
protected double[] sampleBuf;
protected static final int SAMPLEBUFFERSIZE = 8192;
/**
* From the given DoubleDataSource, create an AudioInputStream of the given audio format.
*
* @param source
* source
* @param format
* format
* @throws IllegalArgumentException
* if the format is not mono, not PCM_SIGNED or PCM_UNSIGNED, or has a sample size in bits other than 8 or 16.
*/
public DDSAudioInputStream(DoubleDataSource source, AudioFormat format) {
super(new ByteArrayInputStream(new byte[0]), format, AudioSystem.NOT_SPECIFIED);
if (format.getChannels() > 1) {
throw new IllegalArgumentException("Can only produce mono audio");
}
if (!format.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED)
&& !format.getEncoding().equals(AudioFormat.Encoding.PCM_UNSIGNED)) {
throw new IllegalArgumentException("Can only produce PCM_SIGNED or PCM_UNSIGNED audio");
}
int bitsPerSample = format.getSampleSizeInBits();
if (bitsPerSample != 8 && bitsPerSample != 16 && bitsPerSample != 24) {
throw new IllegalArgumentException("Can deal with sample size 8 or 16 or 24, but not " + bitsPerSample);
}
this.source = source;
this.sampleBuf = new double[SAMPLEBUFFERSIZE];
assert frameSize == bitsPerSample / 8;
}
/**
* Reads up to a specified maximum number of bytes of data from the audio stream, putting them into the given byte array.
* <p>
* This method will always read an integral number of frames. If <code>len</code> does not specify an integral number of
* frames, a maximum of <code>len - (len % frameSize)
* </code> bytes will be read.
*
* @param b
* the buffer into which the data is read
* @param off
* the offset, from the beginning of array <code>b</code>, at which the data will be written
* @param len
* the maximum number of bytes to read
* @return the total number of bytes read into the buffer, or -1 if there is no more data because the end of the stream has
* been reached
* @throws IOException
* if an input or output error occurs
* @see #read(byte[])
* @see #read()
* @see #skip
* @see #available
*/
public int read(byte[] b, int off, int len) throws IOException {
int nSamples = len / frameSize;
int totalRead = 0;
int currentPos = off;
do {
int toRead = nSamples - totalRead;
if (toRead > sampleBuf.length)
toRead = sampleBuf.length;
int nRead = source.getData(sampleBuf, 0, toRead);
// System.err.println("DDSAudioInputStream: read " + nRead + " samples from source");
if (frameSize == 1) { // bytes per sample
for (int i = 0; i < nRead; i++, currentPos++) {
int sample = (int) Math.round(sampleBuf[i] * 127.0); // de-normalise to value range
b[currentPos] = (byte) ((sample >> 8) & 0xFF);
}
} else if (frameSize == 2) { // 16 bit
boolean bigEndian = format.isBigEndian();
for (int i = 0; i < nRead; i++, currentPos += 2) {
int sample = (int) Math.round(sampleBuf[i] * 32767.0); // de-normalise to value range
if (sample > MAX_AMPLITUDE || sample < -MAX_AMPLITUDE) {
System.err.println("Warning: signal amplitude out of range: " + sample);
}
byte hibyte = (byte) (sample >> 8);
byte lobyte = (byte) (sample & 0xFF);
if (!bigEndian) {
b[currentPos] = lobyte;
b[currentPos + 1] = hibyte;
} else {
b[currentPos] = hibyte;
b[currentPos + 1] = lobyte;
}
// System.err.println("out sample["+i+"]="+sample+" hi:"+Integer.toBinaryString(hibyte)+"/"+hibyte+" lo:"+Integer.toBinaryString(lobyte)+"/"+lobyte);
}
} else { // 24 bit
boolean bigEndian = format.isBigEndian();
for (int i = 0; i < nRead; i++, currentPos += 3) {
int sample = (int) Math.round(sampleBuf[i] * 8388605.0); // de-normalise to value range
byte hibyte = (byte) (sample >> 16);
byte midbyte = (byte) ((sample >> 8) & 0xFF);
byte lobyte = (byte) (sample & 0xFF);
if (!bigEndian) {
b[currentPos] = lobyte;
b[currentPos + 1] = midbyte;
b[currentPos + 2] = hibyte;
} else {
b[currentPos] = hibyte;
b[currentPos + 1] = midbyte;
b[currentPos + 2] = lobyte;
}
// System.err.println("out sample["+i+"]="+sample+" hi:"+Integer.toBinaryString(hibyte)+"/"+hibyte+" lo:"+Integer.toBinaryString(lobyte)+"/"+lobyte);
}
}
totalRead += nRead;
assert currentPos <= off + len;
} while (source.hasMoreData() && totalRead < nSamples);
if (totalRead == 0)
return -1;
else
return totalRead * frameSize;
}
/**
* Skips over and discards a specified number of bytes from this audio input stream.
*
* @param n
* the requested number of bytes to be skipped
* @return the actual number of bytes skipped
* @throws IOException
* if an input or output error occurs
* @see #read
* @see #available
*/
public long skip(long n) throws IOException {
double[] data = source.getData((int) n);
return data.length;
}
/**
* Returns the maximum number of bytes that can be read (or skipped over) from this audio input stream without blocking. This
* limit applies only to the next invocation of a <code>read</code> or <code>skip</code> method for this audio input stream;
* the limit can vary each time these methods are invoked. Depending on the underlying stream,an IOException may be thrown if
* this stream is closed.
*
* @return the number of bytes that can be read from this audio input stream without blocking
* @throws IOException
* if an input or output error occurs
* @see #read(byte[], int, int)
* @see #read(byte[])
* @see #read()
* @see #skip
*/
public int available() throws IOException {
return frameSize * source.available();
}
/**
* Closes this audio input stream and releases any system resources associated with the stream.
*
* @throws IOException
* if an input or output error occurs
*/
public void close() throws IOException {
}
/**
* Marks the current position in this audio input stream.
*
* @param readlimit
* the maximum number of bytes that can be read before the mark position becomes invalid.
* @see #reset
* @see #markSupported
*/
public void mark(int readlimit) {
}
/**
* Repositions this audio input stream to the position it had at the time its <code>mark</code> method was last invoked.
*
* @throws IOException
* if an input or output error occurs.
* @see #mark
* @see #markSupported
*/
public void reset() throws IOException {
}
/**
* Tests whether this audio input stream supports the <code>mark</code> and <code>reset</code> methods.
*
* @return <code>true</code> if this stream supports the <code>mark</code> and <code>reset</code> methods; <code>false</code>
* otherwise
* @see #mark
* @see #reset
*/
public boolean markSupported() {
return false;
}
public long getFrameLength() {
long dataLength = source.getDataLength();
if (dataLength == DoubleDataSource.NOT_SPECIFIED)
return AudioSystem.NOT_SPECIFIED;
else
return dataLength;
}
}