annotate 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
rev   line source
samer@1 1 /*
samer@1 2 * StreamSource.java
samer@1 3 *
samer@1 4 * Copyright (c) 2000, Samer Abdallah, King's College London.
samer@1 5 * All rights reserved.
samer@1 6 *
samer@1 7 * This software is provided AS iS and WITHOUT ANY WARRANTY;
samer@1 8 * without even the implied warranty of MERCHANTABILITY or
samer@1 9 * FITNESS FOR A PARTICULAR PURPOSE.
samer@1 10 */
samer@1 11
samer@1 12 package samer.audio;
samer@1 13
samer@1 14 import samer.core.*;
samer@1 15 import samer.core.types.*;
samer@1 16 import samer.core.util.*;
samer@1 17 import samer.tools.*;
samer@1 18 import javax.sound.sampled.*;
samer@1 19 import javax.swing.*;
samer@1 20 import java.io.*;
samer@1 21 import java.net.URL;
samer@1 22 import java.util.*;
samer@1 23
samer@1 24 /**
samer@1 25 An AudioSource that read from an input stream. Can read any
samer@1 26 format for which the appropriate JavaSound plug-in is installed on
samer@1 27 your system, eg WAV, AU, MP3, OGG. The implementation of
samer@1 28 AudioSource.reader() returns a Task which gets samples from the audio
samer@1 29 files, going through the play list one by one. If the loop flag
samer@1 30 is true, then samples are returned indefinitely by looping through the
samer@1 31 playlist; otherwise, an EOFException is thrown if an attempt is made
samer@1 32 to read beyond the end of the last file.
samer@1 33
samer@1 34 Properties read from current environment:
samer@1 35 <dl>
samer@1 36 <dt>current<dd>Current file (String)
samer@1 37 <dt>loop<dd>Loop playlist? (Boolean) [true]
samer@1 38 </dl>
samer@1 39
samer@1 40 */
samer@1 41
samer@1 42 public class StreamSource implements AudioSource
samer@1 43 {
samer@1 44 int channelsToMix;
samer@1 45 AudioInputStream source;
samer@1 46 InputStream in=null;
samer@1 47 AudioFormat format=null;
samer@1 48 byte[] byte_buf=null;
samer@1 49 int chunk=0;
samer@1 50
samer@1 51 /**
samer@1 52 * Construct a StreamSource initialised with current file and loop initialised
samer@1 53 * from the current Environment. No exception is thrown if the current file
samer@1 54 */
samer@1 55 public StreamSource(AudioInputStream s) throws Exception { source=s; }
samer@1 56 public StreamSource(InputStream s) throws Exception { source=AudioSystem.getAudioInputStream(s); }
samer@1 57 public StreamSource(File file) throws Exception { source=AudioSystem.getAudioInputStream(file); }
samer@1 58 public StreamSource(URL url) throws Exception { source=AudioSystem.getAudioInputStream(url); }
samer@1 59
samer@1 60 public void dispose() { close(); }
samer@1 61
samer@1 62 /** Returns current file */
samer@1 63 public void setTargetFormat(AudioFormat f) { format=f; }
samer@1 64
samer@1 65 public AudioFormat getFormat() { return format; }
samer@1 66
samer@1 67 public boolean isOpen() { return in!=null; }
samer@1 68
samer@1 69 /** Closes current input stream */
samer@1 70 public synchronized void close() {
samer@1 71 try {
samer@1 72 if (in!=null) {
samer@1 73 Shell.trace("Closing audio stream...");
samer@1 74 in.close(); in=null; byte_buf=null;
samer@1 75 }
samer@1 76 }
samer@1 77 catch (IOException ex) {}
samer@1 78 }
samer@1 79
samer@1 80 /** Opens the playlist starting with the first file. */
samer@1 81 public synchronized void open() throws Exception { if (!isOpen()) openCurrent(); }
samer@1 82
samer@1 83 /** Opens the current file. Next read will returns samples
samer@1 84 * from head of given file. If setFormat() was called previously, then
samer@1 85 * we will attempt to do format conversion. */
samer@1 86 private synchronized void openCurrent() throws Exception
samer@1 87 {
samer@1 88 AudioInputStream s=source;
samer@1 89 AudioFormat fmt1, af=s.getFormat();
samer@1 90
samer@1 91 Shell.trace(" format: "+af);
samer@1 92
samer@1 93 // convert to target format if required
samer@1 94 if (format!=null) {
samer@1 95 if (!format.equals(af)) {
samer@1 96 Shell.trace(" converting to "+format);
samer@1 97 if (af.getChannels()>format.getChannels()) {
samer@1 98 Shell.trace(" channels mix down required.");
samer@1 99 fmt1 = new AudioFormat( format.getEncoding(), format.getSampleRate(),
samer@1 100 format.getSampleSizeInBits(),
samer@1 101 af.getChannels(), format.getFrameSize(), format.getFrameRate(), format.isBigEndian());
samer@1 102
samer@1 103 channelsToMix = af.getChannels();
samer@1 104 } else {
samer@1 105 channelsToMix = 0;
samer@1 106 fmt1=format;
samer@1 107 }
samer@1 108 s=convertFormat(s,af,fmt1);
samer@1 109 }
samer@1 110 }
samer@1 111
samer@1 112 // If we have a reader task, then update the byte buffer
samer@1 113 if (chunk>0) setByteBuffer();
samer@1 114
samer@1 115 // Shell.trace("stream format: "+s.getFormat());
samer@1 116 in = new BufferedInputStream(s,64*1024);
samer@1 117 }
samer@1 118
samer@1 119 /** Returns number of bytes available in current file */
samer@1 120 public int available() throws Exception { return in.available(); }
samer@1 121
samer@1 122 private void setChunkSize(int s) { chunk=s; }
samer@1 123 private void setByteBuffer() {
samer@1 124 byte_buf = new byte[2*chunk*(channelsToMix>0 ? channelsToMix : 1)];
samer@1 125 }
samer@1 126
samer@1 127 /** Returns a Task which copies samples as doubles into the given
samer@1 128 * buffer between the given positions. */
samer@1 129 public Task reader(final double [] dbuf, final int off, final int len) {
samer@1 130 setChunkSize(len);
samer@1 131 if (in!=null) setByteBuffer();
samer@1 132
samer@1 133 return new AnonymousTask() {
samer@1 134 public void run() throws Exception {
samer@1 135 int n = 0;
samer@1 136
samer@1 137 synchronized (StreamSource.this) {
samer@1 138 int blen = byte_buf.length;
samer@1 139 while (n < blen) {
samer@1 140 int count = in.read(byte_buf, n, blen - n);
samer@1 141 if (count > 0) n+=count;
samer@1 142 else throw new EOFException();
samer@1 143 }
samer@1 144 }
samer@1 145 if (channelsToMix>0) Util.shortToDoubleMixDown(byte_buf,dbuf,off,len,channelsToMix);
samer@1 146 else Util.shortToDouble(byte_buf,dbuf,off,len);
samer@1 147 }
samer@1 148 };
samer@1 149 }
samer@1 150
samer@1 151 /** Returns a Task which copies samples as floats into the given
samer@1 152 * buffer between the given positions. */
samer@1 153 public Task reader(final float [] dbuf, final int off, final int len) {
samer@1 154 setChunkSize(len);
samer@1 155 if (in!=null) setByteBuffer();
samer@1 156
samer@1 157 return new AnonymousTask() {
samer@1 158 public synchronized void run() throws Exception {
samer@1 159 int n = 0;
samer@1 160
samer@1 161 synchronized (StreamSource.this) {
samer@1 162 int blen = byte_buf.length;
samer@1 163 while (n < blen) {
samer@1 164 int count = in.read(byte_buf, n, blen - n);
samer@1 165 if (count > 0) n+=count;
samer@1 166 else throw new EOFException();
samer@1 167 }
samer@1 168 }
samer@1 169 if (channelsToMix>0) Util.shortToFloatMixDown(byte_buf,dbuf,off,len,channelsToMix);
samer@1 170 else Util.shortToFloat(byte_buf,dbuf,off,len);
samer@1 171 }
samer@1 172 };
samer@1 173 }
samer@1 174
samer@1 175 private static AudioInputStream convertFormat(AudioInputStream sin, AudioFormat fin, AudioFormat fout) throws Exception
samer@1 176 {
samer@1 177 if (fin.equals(fout)) return sin;
samer@1 178 else if (fin.getEncoding()==AudioFormat.Encoding.PCM_SIGNED) {
samer@1 179 try { return AudioSystem.getAudioInputStream(fout,sin); }
samer@1 180 catch (IllegalArgumentException ex) { Shell.trace("Direct conversion failed"); }
samer@1 181
samer@1 182 AudioFormat fint = new AudioFormat( // PCM
samer@1 183 fout.getSampleRate(), fout.getSampleSizeInBits(),
samer@1 184 fin.getChannels(), true, fout.isBigEndian());
samer@1 185 Shell.trace("Trying PCM conversion via "+fint.toString());
samer@1 186 return AudioSystem.getAudioInputStream(fout,AudioSystem.getAudioInputStream(fint,sin));
samer@1 187 } else {
samer@1 188 // First, check for MP3 - if so, cannot convert number of channels
samer@1 189 if (fin.getChannels()==fout.getChannels() && fin.getSampleRate()==fout.getSampleRate()) {
samer@1 190 Shell.trace("Trying direct decoding from "+fin.getEncoding().toString());
samer@1 191 return AudioSystem.getAudioInputStream(fout,sin);
samer@1 192 } else {
samer@1 193 AudioFormat fint = new AudioFormat(
samer@1 194 fin.getSampleRate(), fout.getSampleSizeInBits(),
samer@1 195 fin.getChannels(), true, fout.isBigEndian());
samer@1 196 Shell.trace("Trying conversion via "+fint.toString());
samer@1 197 return convertFormat(AudioSystem.getAudioInputStream(fint,sin),fint,fout);
samer@1 198 }
samer@1 199 }
samer@1 200 }
samer@1 201 }
samer@1 202