/**
* Copyright 2000-2009 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.IOException;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
public class MonoAudioInputStream extends AudioInputStream {
protected int inputChannels;
protected int inputMode;
protected AudioFormat newFormat;
public MonoAudioInputStream(AudioInputStream input) {
this(input, AudioPlayer.STEREO);
}
/**
*
* @param input
* input
* @param inputMode
* if AudioPlayer.STEREO, average both input streams; if AudioPlayer.LEFT_ONLY, use only the left channel; if
* AudioPlayer.RIGHT_ONLY, use only the right channel.
*/
public MonoAudioInputStream(AudioInputStream input, int inputMode) {
super(input, input.getFormat(), input.getFrameLength());
this.newFormat = new AudioFormat(input.getFormat().getEncoding(), input.getFormat().getSampleRate(), input.getFormat()
.getSampleSizeInBits(), 1, input.getFormat().getFrameSize() / input.getFormat().getChannels(), input.getFormat()
.getFrameRate(), input.getFormat().isBigEndian());
this.inputChannels = input.getFormat().getChannels();
if (inputChannels < 2)
throw new IllegalArgumentException("expected more than one input channel!");
this.inputMode = inputMode;
if (inputMode == AudioPlayer.MONO)
throw new IllegalArgumentException("expected non-mono input mode");
}
/**
* 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 sampleSizeInBytes = frameSize / inputChannels;
int outputFrameSize = sampleSizeInBytes; // mono output
int nFrames = len / outputFrameSize;
boolean bigEndian = getFormat().isBigEndian();
byte[] inputBytes = new byte[nFrames * frameSize];
int nInputBytes = super.read(inputBytes, 0, inputBytes.length);
if (nInputBytes <= 0)
return nInputBytes;
if (inputMode == AudioPlayer.STEREO) {
for (int i = 0, j = off; i < nInputBytes; i += frameSize, j += outputFrameSize) {
int sample = 0;
for (int c = 0; c < inputChannels; c++) {
if (sampleSizeInBytes == 1) {
sample += inputBytes[i] << 8;
} else if (sampleSizeInBytes == 2) { // 16 bit
byte lobyte;
byte hibyte;
if (!bigEndian) {
lobyte = inputBytes[i];
hibyte = inputBytes[i + 1];
} else {
lobyte = inputBytes[i + 1];
hibyte = inputBytes[i];
}
sample += hibyte << 8 | lobyte & 0xFF;
} else { // bytesPerSample == 3, i.e. 24 bit
assert sampleSizeInBytes == 3 : "Unsupported sample size in bytes: " + sampleSizeInBytes;
byte lobyte;
byte midbyte;
byte hibyte;
if (!bigEndian) {
lobyte = inputBytes[i];
midbyte = inputBytes[i + 1];
hibyte = inputBytes[i + 2];
} else {
lobyte = inputBytes[i + 2];
midbyte = inputBytes[i + 1];
hibyte = inputBytes[i];
}
sample += hibyte << 16 | (midbyte & 0xFF) << 8 | lobyte & 0xFF;
}
}
sample /= inputChannels; // here is where we average the three samples
if (sampleSizeInBytes == 1) {
b[j] = (byte) ((sample >> 8) & 0xFF);
} else if (sampleSizeInBytes == 2) { // 16 bit
byte lobyte = (byte) (sample & 0xFF);
byte hibyte = (byte) (sample >> 8);
if (!bigEndian) {
b[j] = lobyte;
b[j + 1] = hibyte;
} else {
b[j] = hibyte;
b[j + 1] = lobyte;
}
} else { // bytesPerSample == 3, i.e. 24 bit
assert sampleSizeInBytes == 3 : "Unsupported sample size in bytes: " + sampleSizeInBytes;
byte lobyte = (byte) (sample & 0xFF);
byte midbyte = (byte) ((sample >> 8) & 0xFF);
byte hibyte = (byte) (sample >> 16);
if (!bigEndian) {
b[j] = lobyte;
b[j + 1] = midbyte;
b[j + 2] = hibyte;
} else {
b[j] = hibyte;
b[j + 1] = midbyte;
b[j + 2] = lobyte;
}
}
} // for all frames
} else if (inputMode == AudioPlayer.LEFT_ONLY) {
for (int i = 0, j = off; i < nInputBytes; i += frameSize, j += outputFrameSize) {
for (int k = 0; k < sampleSizeInBytes; k++) {
b[j + k] = inputBytes[i + k];
}
}
} else {
assert inputMode == AudioPlayer.RIGHT_ONLY : "unexpected input mode: " + inputMode;
for (int i = 0, j = off; i < nInputBytes; i += frameSize, j += outputFrameSize) {
for (int k = 0; k < sampleSizeInBytes; k++) {
b[j + k] = inputBytes[i + k + sampleSizeInBytes];
}
}
}
return nInputBytes / inputChannels;
}
/**
* 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 {
return super.skip(n * inputChannels) / inputChannels;
}
/**
* 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 {
int av = super.available();
if (av <= 0)
return av;
return av / inputChannels;
}
public AudioFormat getFormat() {
return newFormat;
}
}