view src/samer/audio/StreamSource.java @ 1:5df24c91468d

Oh my what a mess.
author samer
date Fri, 05 Apr 2019 16:26:00 +0100
parents
children 15b93db27c04
line wrap: on
line source
/*
 *	StreamSource.java
 *
 *	Copyright (c) 2000, Samer Abdallah, King's College London.
 *	All rights reserved.
 *
 *	This software is provided AS iS and WITHOUT ANY WARRANTY;
 *	without even the implied warranty of MERCHANTABILITY or
 *	FITNESS FOR A PARTICULAR PURPOSE.
 */

package samer.audio;

import  samer.core.*;
import  samer.core.types.*;
import  samer.core.util.*;
import  samer.tools.*;
import  javax.sound.sampled.*;
import  javax.swing.*;
import  java.io.*;
import  java.net.URL;
import  java.util.*;

/**
	An AudioSource that read from an input stream. Can read any
	format for which the appropriate JavaSound plug-in is installed on
	your system, eg WAV, AU, MP3, OGG. The implementation of 
	AudioSource.reader() returns a Task which gets samples from the audio 
	files, going through the play list one by one. If the loop flag
	is true, then samples are returned indefinitely by looping through the
	playlist; otherwise, an EOFException is thrown if an attempt is made
	to read beyond the end of the last file.
	
	Properties read from current environment:
	<dl>
		<dt>current<dd>Current file (String)
		<dt>loop<dd>Loop playlist? (Boolean) [true]
	</dl>
					
*/

public class StreamSource implements AudioSource
{
	int				channelsToMix;
	AudioInputStream source;
	InputStream		in=null;
	AudioFormat		format=null;
	byte[]  			byte_buf=null;
	int				chunk=0;

	/**
	 * Construct a StreamSource initialised with current file and loop initialised
	 * from the current Environment. No exception is thrown if the current file
	 */
	public StreamSource(AudioInputStream s) throws Exception { source=s; }
	public StreamSource(InputStream s) throws Exception { source=AudioSystem.getAudioInputStream(s); }
	public StreamSource(File file) throws Exception { source=AudioSystem.getAudioInputStream(file); }
	public StreamSource(URL url) throws Exception { source=AudioSystem.getAudioInputStream(url); }

	public void dispose() { close(); }

	/** Returns current file */
	public void setTargetFormat(AudioFormat f) { format=f; }

	public AudioFormat getFormat() { return format; }

	public boolean isOpen() { return in!=null; }

	/** Closes current input stream */
	public synchronized void close() {
		try { 
			if (in!=null) {
				Shell.trace("Closing audio stream...");
				in.close(); in=null; byte_buf=null; 
			}
		}
		catch (IOException ex) {}
	}

	/** Opens the playlist starting with the first file.  */
	public synchronized void open() throws Exception { if (!isOpen()) openCurrent(); }

	/** Opens the current file. Next read will returns samples
	 *  from head of given file. If setFormat() was called previously, then
	 *  we will attempt to do format conversion. */
	private synchronized void openCurrent() throws Exception
	{
		AudioInputStream     s=source;
		AudioFormat				fmt1, af=s.getFormat();

		Shell.trace("  format: "+af);

		// convert to target format if required
		if (format!=null) {
			if (!format.equals(af)) {
				Shell.trace("  converting to "+format);
				if (af.getChannels()>format.getChannels()) {
					Shell.trace("  channels mix down required.");
					fmt1 = new AudioFormat( format.getEncoding(), format.getSampleRate(), 
								format.getSampleSizeInBits(), 
								af.getChannels(), format.getFrameSize(), format.getFrameRate(), format.isBigEndian());

					channelsToMix = af.getChannels();
				} else {
					channelsToMix = 0;
					fmt1=format;
				}
				s=convertFormat(s,af,fmt1);
			}
		}

		// If we have a reader task, then update the byte buffer
		if (chunk>0) setByteBuffer();

		// Shell.trace("stream format: "+s.getFormat());
		in = new BufferedInputStream(s,64*1024);
	}

	/** Returns number of bytes available in current file */
	public int available() throws Exception { return in.available(); }

	private void setChunkSize(int s) { chunk=s; }
	private void setByteBuffer() {
		byte_buf = new byte[2*chunk*(channelsToMix>0 ? channelsToMix : 1)];
	}

	/** Returns a Task which copies samples as doubles into the given
	 *  buffer between the given positions. */
	public Task reader(final double [] dbuf, final int off, final int len) {
		setChunkSize(len);
		if (in!=null) setByteBuffer();

		return new AnonymousTask() {
			public void run() throws Exception {
				int n = 0;

				synchronized (StreamSource.this) {
					int blen = byte_buf.length;
					while (n < blen) {
						int count = in.read(byte_buf, n, blen - n);
						if (count > 0) n+=count;
						else throw new EOFException();
					}
				}
				if (channelsToMix>0) Util.shortToDoubleMixDown(byte_buf,dbuf,off,len,channelsToMix);
				else Util.shortToDouble(byte_buf,dbuf,off,len);
			}
		};
	}

	/** Returns a Task which copies samples as floats into the given
	 *  buffer between the given positions. */
	public Task reader(final float [] dbuf, final int off, final int len) {
		setChunkSize(len);
		if (in!=null) setByteBuffer();

		return new AnonymousTask() {
			public synchronized void run() throws Exception {
				int n = 0;

				synchronized (StreamSource.this) {
					int blen = byte_buf.length;
					while (n < blen) {
						int count = in.read(byte_buf, n, blen - n);
						if (count > 0) n+=count;
						else throw new EOFException();
					}
				}
				if (channelsToMix>0) Util.shortToFloatMixDown(byte_buf,dbuf,off,len,channelsToMix);
				else Util.shortToFloat(byte_buf,dbuf,off,len);
			}
		};
	}

	private static AudioInputStream convertFormat(AudioInputStream sin, AudioFormat fin, AudioFormat fout) throws Exception
	{
		if (fin.equals(fout)) return sin;
		else if (fin.getEncoding()==AudioFormat.Encoding.PCM_SIGNED) {
			try { return AudioSystem.getAudioInputStream(fout,sin); }
			catch (IllegalArgumentException ex) { Shell.trace("Direct conversion failed"); }

			AudioFormat fint = new AudioFormat(  // PCM
				fout.getSampleRate(), fout.getSampleSizeInBits(),
				fin.getChannels(), true, fout.isBigEndian());
			Shell.trace("Trying PCM conversion via "+fint.toString());
			return AudioSystem.getAudioInputStream(fout,AudioSystem.getAudioInputStream(fint,sin));
		} else {
		// First, check for MP3 - if so, cannot convert number of channels
			if (fin.getChannels()==fout.getChannels() && fin.getSampleRate()==fout.getSampleRate()) {
				Shell.trace("Trying direct decoding from "+fin.getEncoding().toString());
				return AudioSystem.getAudioInputStream(fout,sin);
			} else {
				AudioFormat fint = new AudioFormat( 
					fin.getSampleRate(), fout.getSampleSizeInBits(),
					fin.getChannels(), true, fout.isBigEndian());
				Shell.trace("Trying conversion via "+fint.toString());
				return convertFormat(AudioSystem.getAudioInputStream(fint,sin),fint,fout);
			}
		}
	}
}