001: /*
002: * Copyright 1999-2003 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package sun.audio;
027:
028: import java.util.Hashtable;
029: import java.util.Vector;
030: import java.util.Enumeration;
031: import java.io.IOException;
032: import java.io.InputStream;
033: import java.io.BufferedInputStream;
034: import java.io.OutputStream;
035: import java.io.ByteArrayInputStream;
036:
037: import javax.sound.sampled.*;
038: import javax.sound.midi.*;
039: import com.sun.media.sound.DataPusher;
040: import com.sun.media.sound.Toolkit;
041:
042: /**
043: * This class provides an interface to the Headspace Audio engine through
044: * the Java Sound API.
045: *
046: * This class emulates systems with multiple audio channels, mixing
047: * multiple streams for the workstation's single-channel device.
048: *
049: * @see AudioData
050: * @see AudioDataStream
051: * @see AudioStream
052: * @see AudioStreamSequence
053: * @see ContinuousAudioDataStream
054: * @author David Rivas
055: * @author Kara Kytle
056: * @author Jan Borgersen
057: * @author Florian Bomers
058: * @version 1.28 07/05/05
059: */
060:
061: public class AudioDevice {
062:
063: private boolean DEBUG = false /*true*/;
064:
065: /** Hashtable of audio clips / input streams. */
066: private Hashtable clipStreams;
067:
068: private Vector infos;
069:
070: /** Are we currently playing audio? */
071: private boolean playing = false;
072:
073: /** Handle to the JS audio mixer. */
074: private Mixer mixer = null;
075:
076: /**
077: * The default audio player. This audio player is initialized
078: * automatically.
079: */
080: public static final AudioDevice device = new AudioDevice();
081:
082: /**
083: * Create an AudioDevice instance.
084: */
085: private AudioDevice() {
086:
087: clipStreams = new Hashtable();
088: infos = new Vector();
089: }
090:
091: private synchronized void startSampled(AudioInputStream as,
092: InputStream in) throws UnsupportedAudioFileException,
093: LineUnavailableException {
094:
095: Info info = null;
096: DataPusher datapusher = null;
097: DataLine.Info lineinfo = null;
098: SourceDataLine sourcedataline = null;
099:
100: // if ALAW or ULAW, we must convert....
101: as = Toolkit.getPCMConvertedAudioInputStream(as);
102:
103: if (as == null) {
104: // could not convert
105: return;
106: }
107:
108: lineinfo = new DataLine.Info(SourceDataLine.class, as
109: .getFormat());
110: if (!(AudioSystem.isLineSupported(lineinfo))) {
111: return;
112: }
113: sourcedataline = (SourceDataLine) AudioSystem.getLine(lineinfo);
114: datapusher = new DataPusher(sourcedataline, as);
115:
116: info = new Info(null, in, datapusher);
117: infos.addElement(info);
118:
119: datapusher.start();
120: }
121:
122: private synchronized void startMidi(InputStream bis, InputStream in)
123: throws InvalidMidiDataException, MidiUnavailableException {
124:
125: Sequencer sequencer = null;
126: Info info = null;
127:
128: sequencer = MidiSystem.getSequencer();
129: sequencer.open();
130: try {
131: sequencer.setSequence(bis);
132: } catch (IOException e) {
133: throw new InvalidMidiDataException(e.getMessage());
134: }
135:
136: info = new Info(sequencer, in, null);
137:
138: infos.addElement(info);
139:
140: // fix for bug 4302884: Audio device is not released when AudioClip stops
141: sequencer.addMetaEventListener(info);
142:
143: sequencer.start();
144:
145: }
146:
147: /**
148: * Open an audio channel.
149: */
150: public synchronized void openChannel(InputStream in) {
151:
152: if (DEBUG) {
153: System.out.println("AudioDevice: openChannel");
154: System.out.println("input stream =" + in);
155: }
156:
157: Info info = null;
158:
159: // is this already playing? if so, then just return
160: for (int i = 0; i < infos.size(); i++) {
161: info = (AudioDevice.Info) infos.elementAt(i);
162: if (info.in == in) {
163:
164: return;
165: }
166: }
167:
168: AudioInputStream as = null;
169:
170: if (in instanceof AudioStream) {
171:
172: if (((AudioStream) in).midiformat != null) {
173:
174: // it's a midi file
175: try {
176: startMidi(((AudioStream) in).stream, in);
177: } catch (Exception e) {
178: return;
179: }
180:
181: } else if (((AudioStream) in).ais != null) {
182:
183: // it's sampled audio
184: try {
185: startSampled(((AudioStream) in).ais, in);
186: } catch (Exception e) {
187: return;
188: }
189:
190: }
191: } else if (in instanceof AudioDataStream) {
192: if (in instanceof ContinuousAudioDataStream) {
193: try {
194: AudioInputStream ais = new AudioInputStream(
195: in,
196: ((AudioDataStream) in).getAudioData().format,
197: AudioSystem.NOT_SPECIFIED);
198: startSampled(ais, in);
199: } catch (Exception e) {
200: return;
201: }
202: } else {
203: try {
204: AudioInputStream ais = new AudioInputStream(
205: in,
206: ((AudioDataStream) in).getAudioData().format,
207: ((AudioDataStream) in).getAudioData().buffer.length);
208: startSampled(ais, in);
209: } catch (Exception e) {
210: return;
211: }
212: }
213: } else {
214: BufferedInputStream bis = new BufferedInputStream(in, 1024);
215:
216: try {
217:
218: try {
219: as = AudioSystem.getAudioInputStream(bis);
220: } catch (IOException ioe) {
221: return;
222: }
223:
224: startSampled(as, in);
225:
226: } catch (UnsupportedAudioFileException e) {
227:
228: try {
229: try {
230: MidiFileFormat mff = MidiSystem
231: .getMidiFileFormat(bis);
232: } catch (IOException ioe1) {
233: return;
234: }
235:
236: startMidi(bis, in);
237:
238: } catch (InvalidMidiDataException e1) {
239:
240: // $$jb:08.01.99: adding this section to make some of our other
241: // legacy classes work.....
242: // not MIDI either, special case handling for all others
243:
244: AudioFormat defformat = new AudioFormat(
245: AudioFormat.Encoding.ULAW, 8000, 8, 1, 1,
246: 8000, true);
247: try {
248: AudioInputStream defaif = new AudioInputStream(
249: bis, defformat,
250: AudioSystem.NOT_SPECIFIED);
251: startSampled(defaif, in);
252: } catch (UnsupportedAudioFileException es) {
253: return;
254: } catch (LineUnavailableException es2) {
255: return;
256: }
257:
258: } catch (MidiUnavailableException e2) {
259:
260: // could not open sequence
261: return;
262: }
263:
264: } catch (LineUnavailableException e) {
265:
266: return;
267: }
268: }
269:
270: // don't forget adjust for a new stream.
271: notify();
272: }
273:
274: /**
275: * Close an audio channel.
276: */
277: public synchronized void closeChannel(InputStream in) {
278:
279: if (DEBUG) {
280: System.out.println("AudioDevice.closeChannel");
281: }
282:
283: if (in == null)
284: return; // can't go anywhere here!
285:
286: Info info;
287:
288: for (int i = 0; i < infos.size(); i++) {
289:
290: info = (AudioDevice.Info) infos.elementAt(i);
291:
292: if (info.in == in) {
293:
294: if (info.sequencer != null) {
295:
296: info.sequencer.stop();
297: //info.sequencer.close();
298: infos.removeElement(info);
299:
300: } else if (info.datapusher != null) {
301:
302: info.datapusher.stop();
303: infos.removeElement(info);
304: }
305: }
306: }
307: notify();
308: }
309:
310: /**
311: * Open the device (done automatically)
312: */
313: public synchronized void open() {
314:
315: // $$jb: 06.24.99: This is done on a per-stream
316: // basis using the new JS API now.
317: }
318:
319: /**
320: * Close the device (done automatically)
321: */
322: public synchronized void close() {
323:
324: // $$jb: 06.24.99: This is done on a per-stream
325: // basis using the new JS API now.
326:
327: }
328:
329: /**
330: * Play open audio stream(s)
331: */
332: public void play() {
333:
334: // $$jb: 06.24.99: Holdover from old architechture ...
335: // we now open/close the devices as needed on a per-stream
336: // basis using the JavaSound API.
337:
338: if (DEBUG) {
339: System.out.println("exiting play()");
340: }
341: }
342:
343: /**
344: * Close streams
345: */
346: public synchronized void closeStreams() {
347:
348: Info info;
349:
350: for (int i = 0; i < infos.size(); i++) {
351:
352: info = (AudioDevice.Info) infos.elementAt(i);
353:
354: if (info.sequencer != null) {
355:
356: info.sequencer.stop();
357: info.sequencer.close();
358: infos.removeElement(info);
359:
360: } else if (info.datapusher != null) {
361:
362: info.datapusher.stop();
363: infos.removeElement(info);
364: }
365: }
366:
367: if (DEBUG) {
368: System.err.println("Audio Device: Streams all closed.");
369: }
370: // Empty the hash table.
371: clipStreams = new Hashtable();
372: infos = new Vector();
373: }
374:
375: /**
376: * Number of channels currently open.
377: */
378: public int openChannels() {
379: return infos.size();
380: }
381:
382: /**
383: * Make the debug info print out.
384: */
385: void setVerbose(boolean v) {
386: DEBUG = v;
387: }
388:
389: // INFO CLASS
390:
391: class Info implements MetaEventListener {
392:
393: Sequencer sequencer;
394: InputStream in;
395: DataPusher datapusher;
396:
397: Info(Sequencer sequencer, InputStream in, DataPusher datapusher) {
398:
399: this .sequencer = sequencer;
400: this .in = in;
401: this .datapusher = datapusher;
402: }
403:
404: public void meta(MetaMessage event) {
405: if (event.getType() == 47 && sequencer != null) {
406: sequencer.close();
407: }
408: }
409: }
410:
411: }
|