blob: 8257b940fdfeae638dba114191738ad6ca9e4f8a [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1999-2003 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package sun.audio;
27
28import java.util.Hashtable;
29import java.util.Vector;
30import java.util.Enumeration;
31import java.io.IOException;
32import java.io.InputStream;
33import java.io.BufferedInputStream;
34import java.io.OutputStream;
35import java.io.ByteArrayInputStream;
36
37import javax.sound.sampled.*;
38import javax.sound.midi.*;
39import com.sun.media.sound.DataPusher;
40import com.sun.media.sound.Toolkit;
41
42/**
43 * This class provides an interface to the Headspace Audio engine through
44 * the Java Sound API.
45 *
46 * This class emulates systems with multiple audio channels, mixing
47 * multiple streams for the workstation's single-channel device.
48 *
49 * @see AudioData
50 * @see AudioDataStream
51 * @see AudioStream
52 * @see AudioStreamSequence
53 * @see ContinuousAudioDataStream
54 * @author David Rivas
55 * @author Kara Kytle
56 * @author Jan Borgersen
57 * @author Florian Bomers
58 */
59
60public class
61 AudioDevice {
62
63 private boolean DEBUG = false /*true*/ ;
64
65 /** Hashtable of audio clips / input streams. */
66 private Hashtable clipStreams;
67
68 private Vector infos;
69
70 /** Are we currently playing audio? */
71 private boolean playing = false;
72
73 /** Handle to the JS audio mixer. */
74 private Mixer mixer = null;
75
76
77
78 /**
79 * The default audio player. This audio player is initialized
80 * automatically.
81 */
82 public static final AudioDevice device = new AudioDevice();
83
84 /**
85 * Create an AudioDevice instance.
86 */
87 private AudioDevice() {
88
89 clipStreams = new Hashtable();
90 infos = new Vector();
91 }
92
93
94 private synchronized void startSampled( AudioInputStream as,
95 InputStream in ) throws UnsupportedAudioFileException,
96 LineUnavailableException {
97
98 Info info = null;
99 DataPusher datapusher = null;
100 DataLine.Info lineinfo = null;
101 SourceDataLine sourcedataline = null;
102
103 // if ALAW or ULAW, we must convert....
104 as = Toolkit.getPCMConvertedAudioInputStream(as);
105
106 if( as==null ) {
107 // could not convert
108 return;
109 }
110
111 lineinfo = new DataLine.Info(SourceDataLine.class,
112 as.getFormat());
113 if( !(AudioSystem.isLineSupported(lineinfo))) {
114 return;
115 }
116 sourcedataline = (SourceDataLine)AudioSystem.getLine(lineinfo);
117 datapusher = new DataPusher(sourcedataline, as);
118
119 info = new Info( null, in, datapusher );
120 infos.addElement( info );
121
122 datapusher.start();
123 }
124
125 private synchronized void startMidi( InputStream bis,
126 InputStream in ) throws InvalidMidiDataException,
127 MidiUnavailableException {
128
129 Sequencer sequencer = null;
130 Info info = null;
131
132 sequencer = MidiSystem.getSequencer( );
133 sequencer.open();
134 try {
135 sequencer.setSequence( bis );
136 } catch( IOException e ) {
137 throw new InvalidMidiDataException( e.getMessage() );
138 }
139
140 info = new Info( sequencer, in, null );
141
142 infos.addElement( info );
143
144 // fix for bug 4302884: Audio device is not released when AudioClip stops
145 sequencer.addMetaEventListener(info);
146
147 sequencer.start();
148
149 }
150
151
152
153 /**
154 * Open an audio channel.
155 */
156 public synchronized void openChannel(InputStream in) {
157
158
159 if(DEBUG) {
160 System.out.println("AudioDevice: openChannel");
161 System.out.println("input stream =" + in);
162 }
163
164 Info info = null;
165
166 // is this already playing? if so, then just return
167 for(int i=0; i<infos.size(); i++) {
168 info = (AudioDevice.Info)infos.elementAt(i);
169 if( info.in == in ) {
170
171 return;
172 }
173 }
174
175
176 AudioInputStream as = null;
177
178 if( in instanceof AudioStream ) {
179
180 if ( ((AudioStream)in).midiformat != null ) {
181
182 // it's a midi file
183 try {
184 startMidi( ((AudioStream)in).stream, in );
185 } catch (Exception e) {
186 return;
187 }
188
189
190 } else if( ((AudioStream)in).ais != null ) {
191
192 // it's sampled audio
193 try {
194 startSampled( ((AudioStream)in).ais, in );
195 } catch (Exception e) {
196 return;
197 }
198
199 }
200 } else if (in instanceof AudioDataStream ) {
201 if (in instanceof ContinuousAudioDataStream) {
202 try {
203 AudioInputStream ais = new AudioInputStream(in,
204 ((AudioDataStream)in).getAudioData().format,
205 AudioSystem.NOT_SPECIFIED);
206 startSampled(ais, in );
207 } catch (Exception e) {
208 return;
209 }
210 }
211 else {
212 try {
213 AudioInputStream ais = new AudioInputStream(in,
214 ((AudioDataStream)in).getAudioData().format,
215 ((AudioDataStream)in).getAudioData().buffer.length);
216 startSampled(ais, in );
217 } catch (Exception e) {
218 return;
219 }
220 }
221 } else {
222 BufferedInputStream bis = new BufferedInputStream( in, 1024 );
223
224 try {
225
226 try {
227 as = AudioSystem.getAudioInputStream(bis);
228 } catch(IOException ioe) {
229 return;
230 }
231
232 startSampled( as, in );
233
234 } catch( UnsupportedAudioFileException e ) {
235
236 try {
237 try {
238 MidiFileFormat mff =
239 MidiSystem.getMidiFileFormat( bis );
240 } catch(IOException ioe1) {
241 return;
242 }
243
244 startMidi( bis, in );
245
246
247 } catch( InvalidMidiDataException e1 ) {
248
249 // $$jb:08.01.99: adding this section to make some of our other
250 // legacy classes work.....
251 // not MIDI either, special case handling for all others
252
253 AudioFormat defformat = new AudioFormat( AudioFormat.Encoding.ULAW,
254 8000, 8, 1, 1, 8000, true );
255 try {
256 AudioInputStream defaif = new AudioInputStream( bis,
257 defformat, AudioSystem.NOT_SPECIFIED);
258 startSampled( defaif, in );
259 } catch (UnsupportedAudioFileException es) {
260 return;
261 } catch (LineUnavailableException es2) {
262 return;
263 }
264
265 } catch( MidiUnavailableException e2 ) {
266
267 // could not open sequence
268 return;
269 }
270
271 } catch( LineUnavailableException e ) {
272
273 return;
274 }
275 }
276
277 // don't forget adjust for a new stream.
278 notify();
279 }
280
281
282 /**
283 * Close an audio channel.
284 */
285 public synchronized void closeChannel(InputStream in) {
286
287 if(DEBUG) {
288 System.out.println("AudioDevice.closeChannel");
289 }
290
291 if (in == null) return; // can't go anywhere here!
292
293 Info info;
294
295 for(int i=0; i<infos.size(); i++) {
296
297 info = (AudioDevice.Info)infos.elementAt(i);
298
299 if( info.in == in ) {
300
301 if( info.sequencer != null ) {
302
303 info.sequencer.stop();
304 //info.sequencer.close();
305 infos.removeElement( info );
306
307 } else if( info.datapusher != null ) {
308
309 info.datapusher.stop();
310 infos.removeElement( info );
311 }
312 }
313 }
314 notify();
315 }
316
317
318 /**
319 * Open the device (done automatically)
320 */
321 public synchronized void open() {
322
323 // $$jb: 06.24.99: This is done on a per-stream
324 // basis using the new JS API now.
325 }
326
327
328 /**
329 * Close the device (done automatically)
330 */
331 public synchronized void close() {
332
333 // $$jb: 06.24.99: This is done on a per-stream
334 // basis using the new JS API now.
335
336 }
337
338
339 /**
340 * Play open audio stream(s)
341 */
342 public void play() {
343
344 // $$jb: 06.24.99: Holdover from old architechture ...
345 // we now open/close the devices as needed on a per-stream
346 // basis using the JavaSound API.
347
348 if (DEBUG) {
349 System.out.println("exiting play()");
350 }
351 }
352
353 /**
354 * Close streams
355 */
356 public synchronized void closeStreams() {
357
358 Info info;
359
360 for(int i=0; i<infos.size(); i++) {
361
362 info = (AudioDevice.Info)infos.elementAt(i);
363
364 if( info.sequencer != null ) {
365
366 info.sequencer.stop();
367 info.sequencer.close();
368 infos.removeElement( info );
369
370 } else if( info.datapusher != null ) {
371
372 info.datapusher.stop();
373 infos.removeElement( info );
374 }
375 }
376
377
378 if (DEBUG) {
379 System.err.println("Audio Device: Streams all closed.");
380 }
381 // Empty the hash table.
382 clipStreams = new Hashtable();
383 infos = new Vector();
384 }
385
386 /**
387 * Number of channels currently open.
388 */
389 public int openChannels() {
390 return infos.size();
391 }
392
393 /**
394 * Make the debug info print out.
395 */
396 void setVerbose(boolean v) {
397 DEBUG = v;
398 }
399
400
401
402
403
404
405 // INFO CLASS
406
407 class Info implements MetaEventListener {
408
409 Sequencer sequencer;
410 InputStream in;
411 DataPusher datapusher;
412
413 Info( Sequencer sequencer, InputStream in, DataPusher datapusher ) {
414
415 this.sequencer = sequencer;
416 this.in = in;
417 this.datapusher = datapusher;
418 }
419
420 public void meta(MetaMessage event) {
421 if (event.getType() == 47 && sequencer != null) {
422 sequencer.close();
423 }
424 }
425 }
426
427
428
429}