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
|