annotate src/samer/audio/StreamSource.java @ 8:5e3cbbf173aa tip

Reorganise some more
author samer
date Fri, 05 Apr 2019 22:41:58 +0100
parents 15b93db27c04
children
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@3 122 public float getRate() { return source.getFormat().getFrameRate(); }
samer@3 123 public int getChannels() { return source.getFormat().getChannels(); }
samer@3 124
samer@1 125 private void setChunkSize(int s) { chunk=s; }
samer@1 126 private void setByteBuffer() {
samer@1 127 byte_buf = new byte[2*chunk*(channelsToMix>0 ? channelsToMix : 1)];
samer@1 128 }
samer@1 129
samer@1 130 /** Returns a Task which copies samples as doubles into the given
samer@1 131 * buffer between the given positions. */
samer@1 132 public Task reader(final double [] dbuf, final int off, final int len) {
samer@1 133 setChunkSize(len);
samer@1 134 if (in!=null) setByteBuffer();
samer@1 135
samer@1 136 return new AnonymousTask() {
samer@1 137 public void run() throws Exception {
samer@1 138 int n = 0;
samer@1 139
samer@1 140 synchronized (StreamSource.this) {
samer@1 141 int blen = byte_buf.length;
samer@1 142 while (n < blen) {
samer@1 143 int count = in.read(byte_buf, n, blen - n);
samer@1 144 if (count > 0) n+=count;
samer@1 145 else throw new EOFException();
samer@1 146 }
samer@1 147 }
samer@1 148 if (channelsToMix>0) Util.shortToDoubleMixDown(byte_buf,dbuf,off,len,channelsToMix);
samer@1 149 else Util.shortToDouble(byte_buf,dbuf,off,len);
samer@1 150 }
samer@1 151 };
samer@1 152 }
samer@1 153
samer@1 154 /** Returns a Task which copies samples as floats into the given
samer@1 155 * buffer between the given positions. */
samer@1 156 public Task reader(final float [] dbuf, final int off, final int len) {
samer@1 157 setChunkSize(len);
samer@1 158 if (in!=null) setByteBuffer();
samer@1 159
samer@1 160 return new AnonymousTask() {
samer@1 161 public synchronized void run() throws Exception {
samer@1 162 int n = 0;
samer@1 163
samer@1 164 synchronized (StreamSource.this) {
samer@1 165 int blen = byte_buf.length;
samer@1 166 while (n < blen) {
samer@1 167 int count = in.read(byte_buf, n, blen - n);
samer@1 168 if (count > 0) n+=count;
samer@1 169 else throw new EOFException();
samer@1 170 }
samer@1 171 }
samer@1 172 if (channelsToMix>0) Util.shortToFloatMixDown(byte_buf,dbuf,off,len,channelsToMix);
samer@1 173 else Util.shortToFloat(byte_buf,dbuf,off,len);
samer@1 174 }
samer@1 175 };
samer@1 176 }
samer@1 177
samer@1 178 private static AudioInputStream convertFormat(AudioInputStream sin, AudioFormat fin, AudioFormat fout) throws Exception
samer@1 179 {
samer@1 180 if (fin.equals(fout)) return sin;
samer@1 181 else if (fin.getEncoding()==AudioFormat.Encoding.PCM_SIGNED) {
samer@1 182 try { return AudioSystem.getAudioInputStream(fout,sin); }
samer@1 183 catch (IllegalArgumentException ex) { Shell.trace("Direct conversion failed"); }
samer@1 184
samer@1 185 AudioFormat fint = new AudioFormat( // PCM
samer@1 186 fout.getSampleRate(), fout.getSampleSizeInBits(),
samer@1 187 fin.getChannels(), true, fout.isBigEndian());
samer@1 188 Shell.trace("Trying PCM conversion via "+fint.toString());
samer@1 189 return AudioSystem.getAudioInputStream(fout,AudioSystem.getAudioInputStream(fint,sin));
samer@1 190 } else {
samer@1 191 // First, check for MP3 - if so, cannot convert number of channels
samer@1 192 if (fin.getChannels()==fout.getChannels() && fin.getSampleRate()==fout.getSampleRate()) {
samer@1 193 Shell.trace("Trying direct decoding from "+fin.getEncoding().toString());
samer@1 194 return AudioSystem.getAudioInputStream(fout,sin);
samer@1 195 } else {
samer@1 196 AudioFormat fint = new AudioFormat(
samer@1 197 fin.getSampleRate(), fout.getSampleSizeInBits(),
samer@1 198 fin.getChannels(), true, fout.isBigEndian());
samer@1 199 Shell.trace("Trying conversion via "+fint.toString());
samer@1 200 return convertFormat(AudioSystem.getAudioInputStream(fint,sin),fint,fout);
samer@1 201 }
samer@1 202 }
samer@1 203 }
samer@1 204 }
samer@1 205