source: trunk/src_apollo/org/apollo/audio/RecordManager.java@ 332

Last change on this file since 332 was 332, checked in by bjn8, 16 years ago

metronome improvements

File size: 7.3 KB
Line 
1package org.apollo.audio;
2
3import java.io.IOException;
4import java.io.PipedInputStream;
5import java.io.PipedOutputStream;
6
7import javax.sound.sampled.AudioFormat;
8import javax.sound.sampled.DataLine;
9import javax.sound.sampled.LineUnavailableException;
10import javax.sound.sampled.TargetDataLine;
11
12import org.apollo.mvc.AbstractSubject;
13import org.apollo.mvc.SubjectChangedEvent;
14
15/**
16 * Provides sampled recording services.
17 *
18 * @author Brook Novak
19 *
20 */
21public class RecordManager extends AbstractSubject {
22
23 private AudioCaptureThread captureThread = null;
24
25 private static RecordManager instance = new RecordManager(); // single design pattern
26
27 /**
28 * @return The singleton instance of the SampledAudioManager
29 */
30 public static RecordManager getInstance() { // single design pattern
31 return instance;
32 }
33
34 private RecordManager() { // singleton design pattern
35 }
36
37 /**
38 * Stops all captures... kills thread. Halts until killed.
39 */
40 public void releaseResources() {
41
42 // Quickly stop playback thread
43 if (isCapturing())
44 stopCapturing(); // halts
45 }
46
47 /**
48 * @return True if audio is currently being captured.
49 */
50 public boolean isCapturing() {
51 return (captureThread != null && captureThread.isAlive());
52 }
53
54 /**
55 * Stops capturing audio if any is being captured. Waits until capture proccess
56 * stops.
57 *
58 * A CAPTURE_STOPPED event will be raised by invoking this method if there was
59 * audio being captured prior to invoking this method.
60 */
61 public void stopCapturing() {
62 // Tell the capture thread to stop
63 if (captureThread != null) {
64 captureThread.stopCapturing();
65 captureThread = null;
66 }
67 }
68
69 /**
70 * Begins capturing audio from the current input mixture.
71 *
72 * If audio is currently being captured, that capture process is stopped
73 * (hence a CAPTURE_STOPPED event will be raised).
74 *
75 * A CAPTURE_STARTED event will be raised by invoking this method. Once the record thread has started
76 *
77 * @param state
78 * An object for optional state info - this is included in the audio event.
79 * The intention being that observers can ID whether the start/stop events are
80 * relevelent to them or not...
81 *
82 * @return
83 * A AudioCapturePipe which the recorded bytes will be written to.
84 *
85 * @throws LineUnavailableException
86 * If a line from the current input mixer is not available.
87 * (could be used by another proccess/application)
88 *
89 * @throws IOException
90 * If failed to create a pipe.
91 */
92 public synchronized AudioCapturePipe captureAudio(Object state)
93 throws LineUnavailableException, IOException {
94
95 // Check to see if input mixer is available
96 if (SampledAudioManager.getInstance().getCurrentInputMixure() == null) {
97 throw new LineUnavailableException("No input mixer avialable");
98 }
99
100 if (SampledAudioManager.getInstance().getDefaultCaptureFormat() == null) {
101 throw new LineUnavailableException("The current input mixer does not support audio capture in a valid format");
102 }
103
104
105 // Stop capturing any audio
106 stopCapturing();
107
108 // Create pipe
109 PipedOutputStream pout = new PipedOutputStream();
110 PipedInputStream pin = new PipedInputStream(pout);
111
112 // Begin capturing
113 assert (captureThread == null || !captureThread.isAlive());
114 captureThread = new AudioCaptureThread(pout, state);
115 captureThread.start();
116
117 return new AudioCapturePipe(pin, captureThread.format, captureThread.bufferSize);
118 }
119
120
121 /**
122 * This inner class is used for asynchronously read data from a target data line
123 * to a stream; i.e. audio capture.
124 *
125 * Once started, the audio from a TargetDataLine which is linked ot the input mixer
126 * until either stopCapturing is invoked or the output stream is closed.
127 *
128 * Fires Capture start/stop events once the thread starts / finishes respectively
129 *
130 * @author Brook Novak
131 */
132 class AudioCaptureThread extends Thread {
133
134 private boolean isCancelled = false;
135
136 private TargetDataLine tdl;
137
138 private Object state;
139
140 private PipedOutputStream pout;
141
142 private AudioFormat format;
143
144 private int bufferSize;
145
146 /**
147 * Constructor.
148 * The input mixer must not be null.
149 *
150 * @param state
151 * An object for optional state info - this is included in the audio event.
152 * The intention being that observers can ID whether the start/stop events are
153 * relevelent to them or not...
154 *
155 * @param pout
156 * The output stream to write to.
157 *
158 * @throws LineUnavailableException
159 * If a line from the current mixer is not available.
160 * (could be used by another proccess/application)
161 *
162 */
163 AudioCaptureThread(PipedOutputStream pout, Object state)
164 throws LineUnavailableException {
165
166 assert(pout != null);
167 assert(SampledAudioManager.getInstance().getCurrentInputMixure() != null);
168 assert(SampledAudioManager.getInstance().getDefaultCaptureFormat() != null);
169
170 this.state = state;
171 this.pout = pout;
172
173 this.format = SampledAudioManager.getInstance().getDefaultCaptureFormat();
174
175 // Select a target data line
176 DataLine.Info dlInfo = new DataLine.Info(TargetDataLine.class, format);
177
178 // The current input mixer should always support the defaultCaptureFormat
179 assert(SampledAudioManager.getInstance().getCurrentInputMixure().isLineSupported(dlInfo));
180
181 tdl = (TargetDataLine) SampledAudioManager.getInstance().getCurrentInputMixure().getLine(dlInfo);
182
183 tdl.open(format); // Throws LineUnavailableException
184
185 // Ensure a multiple of frame size
186 bufferSize = tdl.getBufferSize();
187 bufferSize -= (bufferSize % format.getFrameSize());
188 if (bufferSize <= 0) bufferSize = format.getFrameSize();
189 }
190
191 /**
192 * Stops capturing the stream. Waits until thread stopped
193 * Can return without the thread actually stopping (yet) if the calling thread
194 * is interupted during the wait.
195 */
196 public void stopCapturing() {
197 isCancelled = true;
198 if (isAlive()) {
199 tdl.stop(); // blocks (wait time can vary depending on formats..some may take awhile!)
200 tdl.drain();
201 tdl.close();
202 try {
203 join();
204 } catch (InterruptedException e) { /* Consume */
205 }
206 }
207 }
208
209 @Override
210 public void run() {
211
212 // Notify observers
213 fireSubjectChangedLaterOnSwingThread(new SubjectChangedEvent(
214 ApolloSubjectChangedEvent.CAPTURE_STARTED, state));
215
216 byte buffer[] = new byte[bufferSize];
217
218 try {
219 // Start capturing
220 tdl.start();
221
222 // Dump bytes into pipe
223 while (!isCancelled) {
224
225 int count = tdl.read(buffer, 0, buffer.length);
226
227 if (count > 0) {
228 pout.write(buffer, 0, count);
229 }
230 }
231
232 } catch (IOException e) { // Failed to write to pipe
233 e.printStackTrace();
234
235 } finally {
236
237 // If thread execution stopped in a way other than the TDL closing ... ensure that
238 // it is actually closed.
239 if (tdl.isOpen()) {
240 tdl.close();
241 }
242
243 // Close off pipe - will release reader from blocking and notify them that finished
244 try {
245 pout.close();
246 } catch (IOException e) { /* Consume */ }
247
248 if (Metronome.getInstance().isEnabled() &&
249 Metronome.getInstance().isPlaying()) {
250
251 }
252
253 // Notify observers
254 fireSubjectChangedLaterOnSwingThread(new SubjectChangedEvent(
255 ApolloSubjectChangedEvent.CAPTURE_STOPPED,
256 state));
257 }
258
259 }
260
261 }
262
263}
Note: See TracBrowser for help on using the repository browser.