samer@1: /* samer@1: * StreamSource.java samer@1: * samer@1: * Copyright (c) 2000, Samer Abdallah, King's College London. samer@1: * All rights reserved. samer@1: * samer@1: * This software is provided AS iS and WITHOUT ANY WARRANTY; samer@1: * without even the implied warranty of MERCHANTABILITY or samer@1: * FITNESS FOR A PARTICULAR PURPOSE. samer@1: */ samer@1: samer@1: package samer.audio; samer@1: samer@1: import samer.core.*; samer@1: import samer.core.types.*; samer@1: import samer.core.util.*; samer@1: import samer.tools.*; samer@1: import javax.sound.sampled.*; samer@1: import javax.swing.*; samer@1: import java.io.*; samer@1: import java.net.URL; samer@1: import java.util.*; samer@1: samer@1: /** samer@1: An AudioSource that read from an input stream. Can read any samer@1: format for which the appropriate JavaSound plug-in is installed on samer@1: your system, eg WAV, AU, MP3, OGG. The implementation of samer@1: AudioSource.reader() returns a Task which gets samples from the audio samer@1: files, going through the play list one by one. If the loop flag samer@1: is true, then samples are returned indefinitely by looping through the samer@1: playlist; otherwise, an EOFException is thrown if an attempt is made samer@1: to read beyond the end of the last file. samer@1: samer@1: Properties read from current environment: samer@1:
samer@1:
current
Current file (String) samer@1:
loop
Loop playlist? (Boolean) [true] samer@1:
samer@1: samer@1: */ samer@1: samer@1: public class StreamSource implements AudioSource samer@1: { samer@1: int channelsToMix; samer@1: AudioInputStream source; samer@1: InputStream in=null; samer@1: AudioFormat format=null; samer@1: byte[] byte_buf=null; samer@1: int chunk=0; samer@1: samer@1: /** samer@1: * Construct a StreamSource initialised with current file and loop initialised samer@1: * from the current Environment. No exception is thrown if the current file samer@1: */ samer@1: public StreamSource(AudioInputStream s) throws Exception { source=s; } samer@1: public StreamSource(InputStream s) throws Exception { source=AudioSystem.getAudioInputStream(s); } samer@1: public StreamSource(File file) throws Exception { source=AudioSystem.getAudioInputStream(file); } samer@1: public StreamSource(URL url) throws Exception { source=AudioSystem.getAudioInputStream(url); } samer@1: samer@1: public void dispose() { close(); } samer@1: samer@1: /** Returns current file */ samer@1: public void setTargetFormat(AudioFormat f) { format=f; } samer@1: samer@1: public AudioFormat getFormat() { return format; } samer@1: samer@1: public boolean isOpen() { return in!=null; } samer@1: samer@1: /** Closes current input stream */ samer@1: public synchronized void close() { samer@1: try { samer@1: if (in!=null) { samer@1: Shell.trace("Closing audio stream..."); samer@1: in.close(); in=null; byte_buf=null; samer@1: } samer@1: } samer@1: catch (IOException ex) {} samer@1: } samer@1: samer@1: /** Opens the playlist starting with the first file. */ samer@1: public synchronized void open() throws Exception { if (!isOpen()) openCurrent(); } samer@1: samer@1: /** Opens the current file. Next read will returns samples samer@1: * from head of given file. If setFormat() was called previously, then samer@1: * we will attempt to do format conversion. */ samer@1: private synchronized void openCurrent() throws Exception samer@1: { samer@1: AudioInputStream s=source; samer@1: AudioFormat fmt1, af=s.getFormat(); samer@1: samer@1: Shell.trace(" format: "+af); samer@1: samer@1: // convert to target format if required samer@1: if (format!=null) { samer@1: if (!format.equals(af)) { samer@1: Shell.trace(" converting to "+format); samer@1: if (af.getChannels()>format.getChannels()) { samer@1: Shell.trace(" channels mix down required."); samer@1: fmt1 = new AudioFormat( format.getEncoding(), format.getSampleRate(), samer@1: format.getSampleSizeInBits(), samer@1: af.getChannels(), format.getFrameSize(), format.getFrameRate(), format.isBigEndian()); samer@1: samer@1: channelsToMix = af.getChannels(); samer@1: } else { samer@1: channelsToMix = 0; samer@1: fmt1=format; samer@1: } samer@1: s=convertFormat(s,af,fmt1); samer@1: } samer@1: } samer@1: samer@1: // If we have a reader task, then update the byte buffer samer@1: if (chunk>0) setByteBuffer(); samer@1: samer@1: // Shell.trace("stream format: "+s.getFormat()); samer@1: in = new BufferedInputStream(s,64*1024); samer@1: } samer@1: samer@1: /** Returns number of bytes available in current file */ samer@1: public int available() throws Exception { return in.available(); } samer@1: samer@3: public float getRate() { return source.getFormat().getFrameRate(); } samer@3: public int getChannels() { return source.getFormat().getChannels(); } samer@3: samer@1: private void setChunkSize(int s) { chunk=s; } samer@1: private void setByteBuffer() { samer@1: byte_buf = new byte[2*chunk*(channelsToMix>0 ? channelsToMix : 1)]; samer@1: } samer@1: samer@1: /** Returns a Task which copies samples as doubles into the given samer@1: * buffer between the given positions. */ samer@1: public Task reader(final double [] dbuf, final int off, final int len) { samer@1: setChunkSize(len); samer@1: if (in!=null) setByteBuffer(); samer@1: samer@1: return new AnonymousTask() { samer@1: public void run() throws Exception { samer@1: int n = 0; samer@1: samer@1: synchronized (StreamSource.this) { samer@1: int blen = byte_buf.length; samer@1: while (n < blen) { samer@1: int count = in.read(byte_buf, n, blen - n); samer@1: if (count > 0) n+=count; samer@1: else throw new EOFException(); samer@1: } samer@1: } samer@1: if (channelsToMix>0) Util.shortToDoubleMixDown(byte_buf,dbuf,off,len,channelsToMix); samer@1: else Util.shortToDouble(byte_buf,dbuf,off,len); samer@1: } samer@1: }; samer@1: } samer@1: samer@1: /** Returns a Task which copies samples as floats into the given samer@1: * buffer between the given positions. */ samer@1: public Task reader(final float [] dbuf, final int off, final int len) { samer@1: setChunkSize(len); samer@1: if (in!=null) setByteBuffer(); samer@1: samer@1: return new AnonymousTask() { samer@1: public synchronized void run() throws Exception { samer@1: int n = 0; samer@1: samer@1: synchronized (StreamSource.this) { samer@1: int blen = byte_buf.length; samer@1: while (n < blen) { samer@1: int count = in.read(byte_buf, n, blen - n); samer@1: if (count > 0) n+=count; samer@1: else throw new EOFException(); samer@1: } samer@1: } samer@1: if (channelsToMix>0) Util.shortToFloatMixDown(byte_buf,dbuf,off,len,channelsToMix); samer@1: else Util.shortToFloat(byte_buf,dbuf,off,len); samer@1: } samer@1: }; samer@1: } samer@1: samer@1: private static AudioInputStream convertFormat(AudioInputStream sin, AudioFormat fin, AudioFormat fout) throws Exception samer@1: { samer@1: if (fin.equals(fout)) return sin; samer@1: else if (fin.getEncoding()==AudioFormat.Encoding.PCM_SIGNED) { samer@1: try { return AudioSystem.getAudioInputStream(fout,sin); } samer@1: catch (IllegalArgumentException ex) { Shell.trace("Direct conversion failed"); } samer@1: samer@1: AudioFormat fint = new AudioFormat( // PCM samer@1: fout.getSampleRate(), fout.getSampleSizeInBits(), samer@1: fin.getChannels(), true, fout.isBigEndian()); samer@1: Shell.trace("Trying PCM conversion via "+fint.toString()); samer@1: return AudioSystem.getAudioInputStream(fout,AudioSystem.getAudioInputStream(fint,sin)); samer@1: } else { samer@1: // First, check for MP3 - if so, cannot convert number of channels samer@1: if (fin.getChannels()==fout.getChannels() && fin.getSampleRate()==fout.getSampleRate()) { samer@1: Shell.trace("Trying direct decoding from "+fin.getEncoding().toString()); samer@1: return AudioSystem.getAudioInputStream(fout,sin); samer@1: } else { samer@1: AudioFormat fint = new AudioFormat( samer@1: fin.getSampleRate(), fout.getSampleSizeInBits(), samer@1: fin.getChannels(), true, fout.isBigEndian()); samer@1: Shell.trace("Trying conversion via "+fint.toString()); samer@1: return convertFormat(AudioSystem.getAudioInputStream(fint,sin),fint,fout); samer@1: } samer@1: } samer@1: } samer@1: } samer@1: