/**
* 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;
import javax.sound.sampled.AudioFormat.Encoding;
public class StereoAudioInputStream extends AudioInputStream {
protected int inputChannels;
protected int outputMode;
protected AudioFormat newFormat;
public StereoAudioInputStream(AudioInputStream input) {
this(input, AudioPlayer.STEREO);
}
/**
*
* @param input
* input
* @param outputMode
* as defined in AudioPlayer: STEREO, LEFT_ONLY or RIGHT_ONLY.
*/
public StereoAudioInputStream(AudioInputStream input, int outputMode) {
super(input, input.getFormat(), input.getFrameLength());
this.newFormat = new AudioFormat(input.getFormat().getEncoding(), input.getFormat().getSampleRate(), input.getFormat()
.getSampleSizeInBits(), 2, 2 * input.getFormat().getFrameSize() / input.getFormat().getChannels(), input
.getFormat().getFrameRate(), input.getFormat().isBigEndian());
this.inputChannels = input.getFormat().getChannels();
this.outputMode = outputMode;
}
/**
* 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 * 2;
int nFrames = len / outputFrameSize;
byte[] inputBytes = new byte[nFrames * frameSize];
int nInputBytes = super.read(inputBytes, 0, inputBytes.length);
if (nInputBytes <= 0)
return nInputBytes;
// For mono input, copy the mono signal to the output channels indicated in outputMode:
if (inputChannels == 1) {
if (outputMode == AudioPlayer.STEREO) {
for (int i = 0, j = off; i < nInputBytes; i += frameSize, j += outputFrameSize) {
for (int k = 0; k < sampleSizeInBytes; k++) {
b[j + k] = b[j + sampleSizeInBytes + k] = inputBytes[i + k];
}
}
} else if (outputMode == AudioPlayer.LEFT_ONLY) {
if (!getFormat().getEncoding().equals(Encoding.PCM_SIGNED)) {
throw new IllegalArgumentException("Channel muting supported only for PCM_SIGNED encoding, got "
+ getFormat().getEncoding());
}
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];
b[j + sampleSizeInBytes + k] = 0;
}
}
} else {
assert outputMode == AudioPlayer.RIGHT_ONLY : "Unexpected output mode: " + outputMode;
if (!getFormat().getEncoding().equals(Encoding.PCM_SIGNED)) {
throw new IllegalArgumentException("Channel muting supported only for PCM_SIGNED encoding, got "
+ getFormat().getEncoding());
}
for (int i = 0, j = off; i < nInputBytes; i += frameSize, j += outputFrameSize) {
for (int k = 0; k < sampleSizeInBytes; k++) {
b[j + k] = 0;
b[j + sampleSizeInBytes + k] = inputBytes[i + k];
}
}
}
} else {
// For stereo or more channels' input, retain the first two channels according to outputMode:
if (outputMode == AudioPlayer.STEREO) {
for (int i = 0, j = off; i < nInputBytes; i += frameSize, j += outputFrameSize) {
// copy the first two samples in every frame:
System.arraycopy(inputBytes, i, b, j, outputFrameSize);
}
} else if (outputMode == AudioPlayer.LEFT_ONLY) {
if (!getFormat().getEncoding().equals(Encoding.PCM_SIGNED)) {
throw new IllegalArgumentException("Channel muting supported only for PCM_SIGNED encoding, got "
+ getFormat().getEncoding());
}
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];
b[j + sampleSizeInBytes + k] = 0;
}
}
} else {
assert outputMode == AudioPlayer.RIGHT_ONLY : "Unexpected output mode: " + outputMode;
if (!getFormat().getEncoding().equals(Encoding.PCM_SIGNED)) {
throw new IllegalArgumentException("Channel muting supported only for PCM_SIGNED encoding, got "
+ getFormat().getEncoding());
}
for (int i = 0, j = off; i < nInputBytes; i += frameSize, j += outputFrameSize) {
for (int k = 0; k < sampleSizeInBytes; k++) {
b[j + k] = 0;
b[j + sampleSizeInBytes + k] = inputBytes[i + sampleSizeInBytes + k];
}
}
}
}
return 2 * 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 2 * super.skip(n / 2 * 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 2 * av / inputChannels;
}
public AudioFormat getFormat() {
return newFormat;
}
}