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: