001/* Main interface to audio system
002   Copyright (C) 2005 Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.sound.sampled;
040
041import gnu.classpath.ServiceFactory;
042
043import java.io.File;
044import java.io.IOException;
045import java.io.InputStream;
046import java.io.OutputStream;
047import java.net.URL;
048import java.util.HashSet;
049import java.util.Iterator;
050
051import javax.sound.sampled.spi.AudioFileReader;
052import javax.sound.sampled.spi.AudioFileWriter;
053import javax.sound.sampled.spi.FormatConversionProvider;
054import javax.sound.sampled.spi.MixerProvider;
055
056/**
057 * This clas is the primary interface to the audio system.  It contains
058 * a number of static methods which can be used to access this package's
059 * functionality.
060 * 
061 * @since 1.3
062 */
063public class AudioSystem
064{
065  /**
066   * A constant which can be passed to a number of methods in this package,
067   * to indicate an unspecified value.
068   */
069  public static final int NOT_SPECIFIED = -1;
070
071  // This class is not instantiable.
072  private AudioSystem()
073  {
074  }
075
076  /**
077   * Return the file format of a given File.
078   * @param f the file to check
079   * @return the format of the file
080   * @throws UnsupportedAudioFileException if the file's format is not 
081   * recognized
082   * @throws IOException if there is an I/O error reading the file
083   */
084  public static AudioFileFormat getAudioFileFormat(File f)
085    throws UnsupportedAudioFileException, IOException
086  {
087    Iterator i = ServiceFactory.lookupProviders(AudioFileReader.class);
088    while (i.hasNext())
089      {
090        AudioFileReader reader = (AudioFileReader) i.next();
091        try
092          {
093            return reader.getAudioFileFormat(f);
094          }
095        catch (UnsupportedAudioFileException _)
096          {
097            // Try the next provider.
098          }
099      }
100    throw new UnsupportedAudioFileException("file type not recognized");
101  }
102
103  /**
104   * Return the file format of a given input stream.
105   * @param is the input stream to check
106   * @return the format of the stream
107   * @throws UnsupportedAudioFileException if the stream's format is not 
108   * recognized
109   * @throws IOException if there is an I/O error reading the stream
110   */
111  public static AudioFileFormat getAudioFileFormat(InputStream is)
112    throws UnsupportedAudioFileException, IOException
113  {
114    Iterator i = ServiceFactory.lookupProviders(AudioFileReader.class);
115    while (i.hasNext())
116      {
117        AudioFileReader reader = (AudioFileReader) i.next();
118        try
119          {
120            return reader.getAudioFileFormat(is);
121          }
122        catch (UnsupportedAudioFileException _)
123          {
124            // Try the next provider.
125          }
126      }
127    throw new UnsupportedAudioFileException("input stream type not recognized");
128  }
129
130  /**
131   * Return the file format of a given URL.
132   * @param url the URL to check
133   * @return the format of the URL
134   * @throws UnsupportedAudioFileException if the URL's format is not 
135   * recognized
136   * @throws IOException if there is an I/O error reading the URL
137   */
138  public static AudioFileFormat getAudioFileFormat(URL url)
139    throws UnsupportedAudioFileException, IOException
140  {
141    Iterator i = ServiceFactory.lookupProviders(AudioFileReader.class);
142    while (i.hasNext())
143      {
144        AudioFileReader reader = (AudioFileReader) i.next();
145        try
146          {
147            return reader.getAudioFileFormat(url);
148          }
149        catch (UnsupportedAudioFileException _)
150          {
151            // Try the next provider.
152          }
153      }
154    throw new UnsupportedAudioFileException("URL type not recognized");
155  }
156
157  /**
158   * Return an array of all the supported AudioFileFormat types.
159   * @return an array of unique types
160   */
161  public static AudioFileFormat.Type[] getAudioFileTypes()
162  {
163    HashSet<AudioFileFormat.Type> result
164      = new HashSet<AudioFileFormat.Type>();
165    Iterator i = ServiceFactory.lookupProviders(AudioFileWriter.class);
166    while (i.hasNext())
167      {
168        AudioFileWriter writer = (AudioFileWriter) i.next();
169        AudioFileFormat.Type[] types = writer.getAudioFileTypes();
170        for (int j = 0; j < types.length; ++j)
171          result.add(types[j]);
172      }
173    return result.toArray(new AudioFileFormat.Type[result.size()]);
174  }
175
176  /**
177   * Return an array of all the supported AudioFileFormat types which match the
178   * given audio input stream
179   * @param ais the audio input stream
180   * @return an array of unique types
181   */
182  public static AudioFileFormat.Type[] getAudioFileTypes(AudioInputStream ais)
183  {
184    HashSet<AudioFileFormat.Type> result
185      = new HashSet<AudioFileFormat.Type>();
186    Iterator i = ServiceFactory.lookupProviders(AudioFileWriter.class);
187    while (i.hasNext())
188      {
189        AudioFileWriter writer = (AudioFileWriter) i.next();
190        AudioFileFormat.Type[] types = writer.getAudioFileTypes(ais);
191        for (int j = 0; j < types.length; ++j)
192          result.add(types[j]);
193      }
194    return result.toArray(new AudioFileFormat.Type[result.size()]);
195  }
196
197  /**
198   * Given an audio input stream, this will try to create a new audio input
199   * stream whose encoding matches the given target encoding.  If no provider
200   * offers this conversion, an exception is thrown. 
201   * @param targ the target encoding
202   * @param ais the original audio stream
203   * @return a new audio stream
204   * @throws IllegalArgumentException if the conversion cannot be made
205   */
206  public static AudioInputStream getAudioInputStream(AudioFormat.Encoding targ,
207                                                     AudioInputStream ais)
208  {
209    Iterator i = ServiceFactory.lookupProviders(FormatConversionProvider.class);
210    while (i.hasNext())
211      {
212        FormatConversionProvider prov = (FormatConversionProvider) i.next();
213        if (! prov.isConversionSupported(targ, ais.getFormat()))
214          continue;
215        return prov.getAudioInputStream(targ, ais);
216      }
217    throw new IllegalArgumentException("encoding not supported for stream");
218 }
219
220  /**
221   * Given an audio input stream, this will try to create a new audio input
222   * stream whose format matches the given target format.  If no provider
223   * offers this conversion, an exception is thrown. 
224   * @param targ the target format
225   * @param ais the original audio stream
226   * @return a new audio stream
227   * @throws IllegalArgumentException if the conversion cannot be made
228   */
229  public static AudioInputStream getAudioInputStream(AudioFormat targ,
230                                                     AudioInputStream ais)
231  {
232    Iterator i = ServiceFactory.lookupProviders(FormatConversionProvider.class);
233    while (i.hasNext())
234      {
235        FormatConversionProvider prov = (FormatConversionProvider) i.next();
236        if (! prov.isConversionSupported(targ, ais.getFormat()))
237          continue;
238        return prov.getAudioInputStream(targ, ais);
239      }
240    throw new IllegalArgumentException("format not supported for stream");
241   }
242
243  /**
244   * Return an audio input stream for the file.
245   * @param f the file to read
246   * @return an audio input stream for the file
247   * @throws UnsupportedAudioFileException if the file's audio format is not
248   * recognized
249   * @throws IOException if there is an error while reading the file
250   */
251  public static AudioInputStream getAudioInputStream(File f)
252    throws UnsupportedAudioFileException, IOException
253  {
254    Iterator i = ServiceFactory.lookupProviders(AudioFileReader.class);
255    while (i.hasNext())
256      {
257        AudioFileReader reader = (AudioFileReader) i.next();
258        try
259          {
260            return reader.getAudioInputStream(f);
261          }
262        catch (UnsupportedAudioFileException _)
263          {
264            // Try the next provider.
265          }
266      }
267    throw new UnsupportedAudioFileException("file type not recognized");
268  }
269
270  /**
271   * Return an audio input stream given an input stream.
272   * @param is the input stream
273   * @return an audio input stream
274   * @throws UnsupportedAudioFileException if the input stream's audio format
275   * is not supported by any of the installed providers
276   * @throws IOException if there is an error while reading the input stream
277   */
278  public static AudioInputStream getAudioInputStream(InputStream is)
279    throws UnsupportedAudioFileException, IOException
280  {
281    Iterator i = ServiceFactory.lookupProviders(AudioFileReader.class);
282    while (i.hasNext())
283      {
284        AudioFileReader reader = (AudioFileReader) i.next();
285        try
286          {
287            return reader.getAudioInputStream(is);
288          }
289        catch (UnsupportedAudioFileException _)
290          {
291            // Try the next provider.
292          }
293      }
294    throw new UnsupportedAudioFileException("input stream type not recognized");
295  }
296
297  /**
298   * Return an audio input stream for the given URL.
299   * @param url the URL
300   * @return an audio input stream
301   * @throws UnsupportedAudioFileException if the URL's audio format is not
302   * supported by any of the installed providers
303   * @throws IOException if there is an error while reading the URL
304   */
305  public static AudioInputStream getAudioInputStream(URL url)
306    throws UnsupportedAudioFileException, IOException
307  {
308    Iterator i = ServiceFactory.lookupProviders(AudioFileReader.class);
309    while (i.hasNext())
310      {
311        AudioFileReader reader = (AudioFileReader) i.next();
312        try
313          {
314            return reader.getAudioInputStream(url);
315          }
316        catch (UnsupportedAudioFileException _)
317          {
318            // Try the next provider.
319          }
320      }
321    throw new UnsupportedAudioFileException("URL type not recognized");
322  }
323
324  /**
325   * Return a new clip which can be used for playing back an audio stream.
326   * @throws LineUnavailableException if a clip is not available for some
327   * reason
328   * @throws SecurityException if a clip cannot be made for security reasons
329   * @since 1.5
330   */
331  public static Clip getClip()
332    throws LineUnavailableException
333  {
334    Mixer.Info[] infos = getMixerInfo();
335    for (int i = 0; i < infos.length; ++i)
336      {
337        Mixer mix = getMixer(infos[i]);
338        Line[] lines = mix.getSourceLines();
339        for (int j = 0; j < lines.length; ++j)
340          {
341            if (lines[j] instanceof Clip)
342              return (Clip) lines[j];
343          }
344      }
345    throw new LineUnavailableException("no Clip available");
346  }
347
348  /**
349   * Return a new clip which can be used for playing back an audio stream.
350   * The clip is obtained from the indicated mixer.
351   * @param info the mixer to use
352   * @throws LineUnavailableException if a clip is not available for some
353   * reason
354   * @throws SecurityException if a clip cannot be made for security reasons
355   * @since 1.5
356   */
357  public static Clip getClip(Mixer.Info info)
358    throws LineUnavailableException
359  {
360    Mixer mix = getMixer(info);
361    Line[] lines = mix.getSourceLines();
362    for (int j = 0; j < lines.length; ++j)
363      {
364        if (lines[j] instanceof Clip)
365          return (Clip) lines[j];
366      }
367    throw new LineUnavailableException("no Clip available");
368  }
369
370  /**
371   * Return a line matching the provided description.  All the providers
372   * on the system are searched for a matching line.
373   * @param info description of the line
374   * @return the matching line
375   * @throws LineUnavailableException if no provider supplies a matching line
376   */
377  public static Line getLine(Line.Info info) throws LineUnavailableException
378  {
379    Mixer.Info[] infos = getMixerInfo();
380    for (int i = 0; i < infos.length; ++i)
381      {
382        Mixer mix = getMixer(infos[i]);
383        try
384        {
385          return mix.getLine(info);
386        }
387        catch (LineUnavailableException _)
388        {
389          // Try the next provider.
390        }
391      }
392    throw new LineUnavailableException("no Clip available");
393  }
394
395  /**
396   * Return a mixer matching the provided description.  All the providers
397   * on the system are searched for a matching mixer.
398   * @param info description of the mixer
399   * @return the matching mixer
400   * @throws IllegalArgumentException if no provider supplies a matching mixer
401   */
402  public static Mixer getMixer(Mixer.Info info)
403  {
404    Iterator i = ServiceFactory.lookupProviders(MixerProvider.class);
405    while (i.hasNext())
406      {
407        MixerProvider prov = (MixerProvider) i.next();
408        if (prov.isMixerSupported(info))
409          return prov.getMixer(info);
410      }
411    throw new IllegalArgumentException("mixer not found");
412  }
413
414  /**
415   * Return an array of descriptions of all the mixers provided on the system.
416   */
417  public static Mixer.Info[] getMixerInfo()
418  {
419    HashSet<Mixer.Info> result = new HashSet<Mixer.Info>();
420    Iterator i = ServiceFactory.lookupProviders(MixerProvider.class);
421    while (i.hasNext())
422      {
423        MixerProvider prov = (MixerProvider) i.next();
424        Mixer.Info[] is = prov.getMixerInfo();
425        for (int j = 0; j < is.length; ++j)
426          result.add(is[j]);
427      }
428    return result.toArray(new Mixer.Info[result.size()]);
429  }
430
431  /**
432   * Return a source data line matching the given audio format.
433   * @param fmt the audio format
434   * @throws LineUnavailableException if no source data line matching
435   * this format is available
436   * @since 1.5
437   */
438  public static SourceDataLine getSourceDataLine(AudioFormat fmt)
439    throws LineUnavailableException
440  {
441    DataLine.Info info = new DataLine.Info(SourceDataLine.class, fmt);
442    Mixer.Info[] mixers = getMixerInfo();
443    for (int i = 0; i < mixers.length; ++i)
444      {
445        Mixer mix = getMixer(mixers[i]);
446        if (mix.isLineSupported(info))
447          return (SourceDataLine) mix.getLine(info);
448      }
449    throw new LineUnavailableException("source data line not found");
450  }
451
452  /**
453   * Return a target data line matching the given audio format.
454   * @param fmt the audio format
455   * @throws LineUnavailableException if no target data line matching
456   * this format is available
457   * @since 1.5
458   */
459  public static SourceDataLine getSourceDataLine(AudioFormat fmt,
460                                                 Mixer.Info mixer)
461    throws LineUnavailableException
462  {
463    DataLine.Info info = new DataLine.Info(SourceDataLine.class, fmt);
464    Mixer mix = getMixer(mixer);
465    if (mix.isLineSupported(info))
466      return (SourceDataLine) mix.getLine(info);
467    throw new LineUnavailableException("source data line not found");
468  }
469
470  /**
471   * Return an array of descriptions of all the source lines matching
472   * the given line description.
473   * @param info description of the lines to match
474   */
475  public static Line.Info[] getSourceLineInfo(Line.Info info)
476  {
477    HashSet<Line.Info> result = new HashSet<Line.Info>();
478    Mixer.Info[] infos = getMixerInfo();
479    for (int i = 0; i < infos.length; ++i)
480      {
481        Mixer mix = getMixer(infos[i]);
482        Line.Info[] srcs = mix.getSourceLineInfo(info);
483        for (int j = 0; j < srcs.length; ++j)
484          result.add(srcs[j]);
485      }
486    return result.toArray(new Line.Info[result.size()]);
487  }
488
489  /**
490   * Find and return a target data line matching the given audio format.
491   * @param fmt the format to match
492   * @throws LineUnavailableException if no matching line was found 
493   * @since 1.5
494   */
495  public static TargetDataLine getTargetDataLine(AudioFormat fmt)
496    throws LineUnavailableException
497  {
498    DataLine.Info info = new DataLine.Info(TargetDataLine.class, fmt);
499    Mixer.Info[] mixers = getMixerInfo();
500    for (int i = 0; i < mixers.length; ++i)
501      {
502        Mixer mix = getMixer(mixers[i]);
503        if (mix.isLineSupported(info))
504          return (TargetDataLine) mix.getLine(info);
505      }
506    throw new LineUnavailableException("target data line not found");
507  }
508
509  /**
510   * Return a target data line matching the given audio format and
511   * mixer.
512   * @param fmt the audio format
513   * @param mixer the mixer description
514   * @return a target data line
515   * @throws LineUnavailableException if no matching target data line was
516   * found
517   * @since 1.5
518   */
519  public static TargetDataLine getTargetDataLine(AudioFormat fmt,
520                                                 Mixer.Info mixer)
521    throws LineUnavailableException
522  {
523    DataLine.Info info = new DataLine.Info(TargetDataLine.class, fmt);
524    Mixer mix = getMixer(mixer);
525    if (mix.isLineSupported(info))
526      return (TargetDataLine) mix.getLine(info);
527    throw new LineUnavailableException("target data line not found");
528  }
529
530  /**
531   * Given a source encoding, return an array of all target encodings to which
532   * data in this form can be converted.
533   * @param source the source encoding
534   */
535  public static AudioFormat.Encoding[] getTargetEncodings(AudioFormat.Encoding source)
536  {
537    HashSet<AudioFormat.Encoding> result
538      = new HashSet<AudioFormat.Encoding>();
539    Iterator i = ServiceFactory.lookupProviders(FormatConversionProvider.class);
540    while (i.hasNext())
541      {
542        FormatConversionProvider prov = (FormatConversionProvider) i.next();
543        if (! prov.isSourceEncodingSupported(source))
544          continue;
545        AudioFormat.Encoding[] es = prov.getTargetEncodings();
546        for (int j = 0; j < es.length; ++j)
547          result.add(es[j]);
548      }
549    return result.toArray(new AudioFormat.Encoding[result.size()]);
550  }
551
552  /**
553   * Given a source format, return an array of all the target encodings to
554   * which data in this format can be converted.
555   * @param source the source format
556   */
557  public static AudioFormat.Encoding[] getTargetEncodings(AudioFormat source)
558  {
559    HashSet<AudioFormat.Encoding> result
560      = new HashSet<AudioFormat.Encoding>();
561    Iterator i = ServiceFactory.lookupProviders(FormatConversionProvider.class);
562    while (i.hasNext())
563      {
564        FormatConversionProvider prov = (FormatConversionProvider) i.next();
565        AudioFormat.Encoding[] es = prov.getTargetEncodings(source);
566        for (int j = 0; j < es.length; ++j)
567          result.add(es[j]);
568      }
569    return result.toArray(new AudioFormat.Encoding[result.size()]);
570  }
571
572  /**
573   * Given a target encoding and a source audio format, return an array of all
574   * matching audio formats to which data in this source format can be converted. 
575   * @param encoding the target encoding
576   * @param sourceFmt the source format
577   */
578  public static AudioFormat[] getTargetFormats(AudioFormat.Encoding encoding,
579                                               AudioFormat sourceFmt)
580  {
581    HashSet<AudioFormat> result = new HashSet<AudioFormat>();
582    Iterator i = ServiceFactory.lookupProviders(FormatConversionProvider.class);
583    while (i.hasNext())
584      {
585        FormatConversionProvider prov = (FormatConversionProvider) i.next();
586        AudioFormat[] es = prov.getTargetFormats(encoding, sourceFmt);
587        for (int j = 0; j < es.length; ++j)
588          result.add(es[j]);
589      }
590    return result.toArray(new AudioFormat[result.size()]);
591  }
592
593  /**
594   * Given a line description, return an array of descriptions of all
595   * the matching target lines.
596   * @param info the line description
597   */
598  public static Line.Info[] getTargetLineInfo(Line.Info info)
599  {
600    HashSet<Line.Info> result = new HashSet<Line.Info>();
601    Mixer.Info[] infos = getMixerInfo();
602    for (int i = 0; i < infos.length; ++i)
603      {
604        Mixer mix = getMixer(infos[i]);
605        Line.Info[] targs = mix.getTargetLineInfo(info);
606        for (int j = 0; j < targs.length; ++j)
607          result.add(targs[j]);
608      }
609    return result.toArray(new Line.Info[result.size()]);
610  }
611
612  /**
613   * Return true if the currently installed providers are able to
614   * convert data from the given source format to the given target encoding.
615   * @param targ the target encoding
616   * @param source the source format
617   */
618  public static boolean isConversionSupported(AudioFormat.Encoding targ,
619                                              AudioFormat source)
620  {
621    Iterator i 
622      = ServiceFactory.lookupProviders(FormatConversionProvider.class);
623    while (i.hasNext())
624      {
625        FormatConversionProvider prov = (FormatConversionProvider) i.next();
626        if (prov.isConversionSupported(targ, source))
627          return true;
628      }
629    return false;
630  }
631
632  /**
633   * Return true if the currently installed providers are able to convert
634   * the given source format to the given target format.
635   * @param targ the target format
636   * @param source the source format
637   */
638  public static boolean isConversionSupported(AudioFormat targ,
639                                              AudioFormat source)
640  {
641    Iterator i 
642      = ServiceFactory.lookupProviders(FormatConversionProvider.class);
643    while (i.hasNext())
644      {
645        FormatConversionProvider prov = (FormatConversionProvider) i.next();
646        if (prov.isConversionSupported(targ, source))
647          return true;
648      }
649    return false;
650  }
651
652  private static boolean isFileTypeSupported(AudioFileFormat.Type[] types,
653                                             AudioFileFormat.Type type)
654  {
655    for (int i = 0; i < types.length; ++i)
656      {
657        if (types[i].equals(type))
658          return true;
659      }
660    return false;
661  }
662
663  /**
664   * Return true if the given audio file format is supported by one of
665   * the providers installed on the system.
666   * @param type the audio file format type
667   */
668  public static boolean isFileTypeSupported(AudioFileFormat.Type type)
669  {
670    return isFileTypeSupported(getAudioFileTypes(), type);
671  }
672
673  /**
674   * Return true if the given audio file format is supported for the
675   * given audio input stream by one of the providers installed on the 
676   * system.
677   * @param type the audio file format type
678   * @param ais the audio input stream
679   */
680  public static boolean isFileTypeSupported(AudioFileFormat.Type type,
681                                            AudioInputStream ais)
682  {
683    return isFileTypeSupported(getAudioFileTypes(ais), type);
684  }
685
686  /**
687   * Return true if some provider on the system supplies a line
688   * matching the argument. 
689   * @param info the line to match
690   */
691  public static boolean isLineSupported(Line.Info info)
692  {
693    Mixer.Info[] infos = getMixerInfo();
694    for (int i = 0; i < infos.length; ++i)
695      {
696        if (getMixer(infos[i]).isLineSupported(info))
697          return true;
698      }
699    return false;
700  }
701
702  /**
703   * Write an audio input stream to the given file, using the specified
704   * audio file format.  All the providers installed on the system will
705   * be searched to find one that supports this operation.
706   * @param ais the audio input stream to write
707   * @param type the desired audio file format type
708   * @param out the file to write to
709   * @return the number of bytes written
710   * @throws IOException if an I/O error occurs while writing
711   * @throws IllegalArgumentException if the file type is not supported
712   */
713  public static int write(AudioInputStream ais, AudioFileFormat.Type type,
714                          File out)
715    throws IOException
716  {
717    Iterator i = ServiceFactory.lookupProviders(AudioFileWriter.class);
718    while (i.hasNext())
719      {
720        AudioFileWriter w = (AudioFileWriter) i.next();
721        if (w.isFileTypeSupported(type, ais))
722          return w.write(ais, type, out);
723      }
724    throw new IllegalArgumentException("file type not supported by system");
725  }
726
727  /**
728   * Write an audio input stream to the given output stream, using the
729   * specified audio file format.  All the providers installed on the
730   * system will be searched to find one that supports this operation.
731   * @param ais the audio input stream to write
732   * @param type the desired audio file format type
733   * @param os the output stream to write to
734   * @return the number of bytes written
735   * @throws IOException if an I/O error occurs while writing
736   * @throws IllegalArgumentException if the file type is not supported
737   */
738  public static int write(AudioInputStream ais, AudioFileFormat.Type type,
739                          OutputStream os)
740    throws IOException
741  {
742    Iterator i = ServiceFactory.lookupProviders(AudioFileWriter.class);
743    while (i.hasNext())
744      {
745        AudioFileWriter w = (AudioFileWriter) i.next();
746        if (w.isFileTypeSupported(type, ais))
747          return w.write(ais, type, os);
748      }
749    throw new IllegalArgumentException("file type not supported by system");
750  }
751}