/*
* Copyright (C) 1999-2000 Matthias Pfisterer
* 2013 Trilarion
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sound.sampled.convert;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import javax.sound.sampled.AudioFormat;
import org.sound.TCircularBuffer;
import org.sound.TDebug;
import org.sound.Trigger;
/**
* Base class for asynchronous converters. This class serves as base class for
* converters that do not have a fixed ratio between the size of a block of
* input data and the size of a block of output data. These types of converters
* therefore need an internal buffer, which is realized in this class.
*/
public abstract class TAsynchronousFilteredAudioInputStream extends TAudioInputStream implements Trigger {
private static final int DEFAULT_BUFFER_SIZE = 327670;
private static final int DEFAULT_MIN_AVAILABLE = 4096;
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
// must be protected because it's accessed by the native CDDA lib
protected TCircularBuffer m_circularBuffer;
private int m_nMinAvailable;
private byte[] m_abSingleByte;
/**
* Constructor. This constructor uses the default buffer size and the
* default min available amount.
*
* @param lLength length of this stream in frames. May be
* AudioSystem.NOT_SPECIFIED.
*/
public TAsynchronousFilteredAudioInputStream(AudioFormat outputFormat, long lLength) {
this(outputFormat, lLength, DEFAULT_BUFFER_SIZE, DEFAULT_MIN_AVAILABLE);
}
/**
* Constructor. With this constructor, the buffer size and the minimum
* available amount can be specified as parameters.
*
* @param lLength length of this stream in frames. May be
* AudioSystem.NOT_SPECIFIED.
*
* @param nBufferSize size of the circular buffer in bytes.
*/
public TAsynchronousFilteredAudioInputStream(AudioFormat outputFormat, long lLength,
int nBufferSize, int nMinAvailable) {
/* The usage of a ByteArrayInputStream is a hack.
* (the infamous "JavaOne hack", because I did it on June
* 6th 2000 in San Francisco, only hours before a
* JavaOne session where I wanted to show mp3 playback
* with Java Sound.) It is necessary because in the FCS
* version of the Sun jdk1.3, the constructor of
* AudioInputStream throws an exception if its first
* argument is null. So we have to pass a dummy non-null
* value.
*/
super(new ByteArrayInputStream(EMPTY_BYTE_ARRAY), outputFormat, lLength);
if (TDebug.TraceAudioConverter) {
TDebug.out("TAsynchronousFilteredAudioInputStream.<init>(): begin");
}
// blocking read, write and this as trigger
m_circularBuffer = new TCircularBuffer(nBufferSize, false, true, this);
m_nMinAvailable = nMinAvailable;
if (TDebug.TraceAudioConverter) {
TDebug.out("TAsynchronousFilteredAudioInputStream.<init>(): end");
}
}
/**
* Returns the circular buffer.
*/
protected TCircularBuffer getCircularBuffer() {
return m_circularBuffer;
}
/**
* Check if writing more data to the circular buffer is recommended. This
* checks the available write space in the circular buffer against the
* minimum available property. If the available write space is greater than
* th minimum available property, more writing is encouraged, so this method
* returns true. Note that this is only a hint to subclasses. However, it is
* an important hint.
*
* @return true if more writing to the circular buffer is recommended.
* Otherwise, false is returned.
*/
protected boolean writeMore() {
return getCircularBuffer().availableWrite() > m_nMinAvailable;
}
@Override
public int read()
throws IOException {
// if (TDebug.TraceAudioConverter) { TDebug.out("TAsynchronousFilteredAudioInputStream.read(): begin"); }
int nByte;
if (m_abSingleByte == null) {
m_abSingleByte = new byte[1];
}
int nReturn = read(m_abSingleByte);
if (nReturn == -1) {
nByte = -1;
} else {
//$$fb 2001-04-14 nobody really knows that...
nByte = m_abSingleByte[0] & 0xFF;
}
// if (TDebug.TraceAudioConverter) { TDebug.out("TAsynchronousFilteredAudioInputStream.read(): end"); }
return nByte;
}
@Override
public int read(byte[] abData)
throws IOException {
if (TDebug.TraceAudioConverter) {
TDebug.out("TAsynchronousFilteredAudioInputStream.read(byte[]): begin");
}
int nRead = read(abData, 0, abData.length);
if (TDebug.TraceAudioConverter) {
TDebug.out("TAsynchronousFilteredAudioInputStream.read(byte[]): end");
}
return nRead;
}
@Override
public int read(byte[] abData, int nOffset, int nLength)
throws IOException {
if (TDebug.TraceAudioConverter) {
TDebug.out("TAsynchronousFilteredAudioInputStream.read(byte[], int, int): begin");
}
//$$fb 2001-04-22: this returns at maximum circular buffer
// length. This is not very efficient...
//$$fb 2001-04-25: we should check that we do not exceed getFrameLength() !
int nRead = m_circularBuffer.read(abData, nOffset, nLength);
if (TDebug.TraceAudioConverter) {
TDebug.out("TAsynchronousFilteredAudioInputStream.read(byte[], int, int): end");
}
return nRead;
}
@Override
public long skip(long lSkip)
throws IOException {
// TODO: this is quite inefficient
for (long lSkipped = 0; lSkipped < lSkip; lSkipped++) {
int nReturn = read();
if (nReturn == -1) {
return lSkipped;
}
}
return lSkip;
}
@Override
public int available()
throws IOException {
return m_circularBuffer.availableRead();
}
@Override
public void close()
throws IOException {
m_circularBuffer.close();
}
@Override
public boolean markSupported() {
return false;
}
@Override
public void mark(int nReadLimit) {
}
@Override
public void reset()
throws IOException {
throw new IOException("mark not supported");
}
}