package net.sf.fmj.media.multiplexer.audio;
import java.io.IOException;
import java.io.OutputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.media.Buffer;
import javax.media.Format;
import javax.media.ResourceUnavailableException;
import javax.media.format.AudioFormat;
import javax.media.protocol.FileTypeDescriptor;
import javax.media.protocol.Seekable;
import javax.media.protocol.DataSource;
import javax.media.protocol.PushSourceStream;
import net.sf.fmj.media.codec.JavaSoundCodec;
import net.sf.fmj.media.multiplexer.AbstractInputStreamMux;
import net.sf.fmj.media.renderer.audio.JavaSoundUtils;
import net.sf.fmj.utility.LoggerSingleton;
import net.sf.fmj.media.multiplexer.InputStreamPushDataSource;
import net.sf.fmj.media.multiplexer.InputStreamPushSourceStream;
/**
* AU Mux that doesn't use javasound.
* @author Ken Larson
*
*/
public class AUMux extends AbstractInputStreamMux
{
private static final Logger logger = LoggerSingleton.logger;
public AUMux()
{
super(new FileTypeDescriptor(FileTypeDescriptor.BASIC_AUDIO));
}
@Override
public Format[] getSupportedInputFormats()
{
return new Format[] {
new AudioFormat(AudioFormat.LINEAR, -1, 8, -1, -1, AudioFormat.SIGNED),
new AudioFormat(AudioFormat.LINEAR, -1, 16, -1, AudioFormat.BIG_ENDIAN, AudioFormat.SIGNED),
new AudioFormat(AudioFormat.LINEAR, -1, 24, -1, AudioFormat.BIG_ENDIAN, AudioFormat.SIGNED),
new AudioFormat(AudioFormat.LINEAR, -1, 32, -1, AudioFormat.BIG_ENDIAN, AudioFormat.SIGNED),
new AudioFormat(AudioFormat.ULAW), // TODO: narrow down
new AudioFormat(AudioFormat.ALAW) // TODO: narrow down
};
}
@Override
public Format setInputFormat(Format format, int trackID)
{
logger.finer("setInputFormat " + format + " " + trackID);
boolean match = false;
for (Format supported : getSupportedInputFormats())
{
if (format.matches(supported))
{ match = true;
break;
}
}
if (!match)
{
logger.warning("Input format does not match any supported input format: " + format);
return null;
}
if (inputFormats != null) // TODO: should we save this somewhere and apply once inputFormats is not null?
inputFormats[trackID] = format;
return format;
}
private void outputHeader(OutputStream os) throws IOException
{
byte[] header = JavaSoundCodec.createAuHeader(JavaSoundUtils.convertFormat((AudioFormat) inputFormats[0])); // TODO: no length
if (header == null)
throw new IOException("Unable to create AU header");
os.write(header);
}
private void outputTrailer(OutputStream os) throws IOException
{
// mgodehardt: updating data length field, some java versions cannot handle length of -1 in au file
// ( microsoft mediaplayer is not playing au files with length -1 )
// should we throw a IOException if length could not be written ?
DataSource ds = getDataOutput();
if ( ds instanceof InputStreamPushDataSource )
{
InputStreamPushDataSource pds = (InputStreamPushDataSource)ds;
PushSourceStream pss = pds.getStreams()[0];
if ( pss instanceof InputStreamPushSourceStream )
{
InputStreamPushSourceStream ispss = (InputStreamPushSourceStream)pss;
if ( ((Seekable)ispss.getTransferHandler()) instanceof Seekable )
{
((Seekable)ispss.getTransferHandler()).seek(8);
writeInt(os, bytesWritten);
if (getDataOutputNoInit() != null)
{
getDataOutputNoInit().notifyDataAvailable(0); // only 1 track
}
}
}
}
}
private boolean headerWritten = false;
private boolean trailerWritten = false;
// when stream is closed, data length is written to the header ( otherwise some programs will not play the au file )
private long bytesWritten;
public void open() throws ResourceUnavailableException
{
super.open();
if (!headerWritten)
{ try
{
outputHeader(getOutputStream());
} catch (IOException e)
{
logger.log(Level.WARNING, "" + e, e);
throw new ResourceUnavailableException("" + e);
}
headerWritten = true;
}
}
@Override
public void close()
{
if (!trailerWritten)
{
try
{
outputTrailer(getOutputStream());
} catch (IOException e)
{
logger.log(Level.WARNING, "" + e, e);
throw new RuntimeException(e);
}
trailerWritten = true;
}
super.close();
}
@Override
protected void doProcess(Buffer buffer, int trackID, OutputStream os) throws IOException
{
if (!headerWritten)
{ outputHeader(os);
headerWritten = true;
}
if (buffer.isEOM())
{
if (!trailerWritten)
{ outputTrailer(os);
trailerWritten = true;
}
os.close();
return; // TODO: what if there is data in buffer?
}
if (buffer.isDiscard())
return;
os.write((byte[]) buffer.getData(), buffer.getOffset(), buffer.getLength());
bytesWritten += buffer.getLength();
}
}