001/* Main interface to audio system
002   Copyright (C) 2005, 2012 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<AudioFileReader> i = ServiceFactory.lookupProviders(AudioFileReader.class);
088    while (i.hasNext())
089      {
090        AudioFileReader reader = 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<AudioFileReader> i = ServiceFactory.lookupProviders(AudioFileReader.class);
115    while (i.hasNext())
116      {
117        AudioFileReader reader = 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<AudioFileReader> i = ServiceFactory.lookupProviders(AudioFileReader.class);
142    while (i.hasNext())
143      {
144        AudioFileReader reader = 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<AudioFileWriter> i = ServiceFactory.lookupProviders(AudioFileWriter.class);
166    while (i.hasNext())
167      {
168        AudioFileWriter writer = 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<AudioFileWriter> i = ServiceFactory.lookupProviders(AudioFileWriter.class);
187    while (i.hasNext())
188      {
189        AudioFileWriter writer = 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<FormatConversionProvider> i =
210      ServiceFactory.lookupProviders(FormatConversionProvider.class);
211    while (i.hasNext())
212      {
213        FormatConversionProvider prov = i.next();
214        if (! prov.isConversionSupported(targ, ais.getFormat()))
215          continue;
216        return prov.getAudioInputStream(targ, ais);
217      }
218    throw new IllegalArgumentException("encoding not supported for stream");
219 }
220
221  /**
222   * Given an audio input stream, this will try to create a new audio input
223   * stream whose format matches the given target format.  If no provider
224   * offers this conversion, an exception is thrown.
225   * @param targ the target format
226   * @param ais the original audio stream
227   * @return a new audio stream
228   * @throws IllegalArgumentException if the conversion cannot be made
229   */
230  public static AudioInputStream getAudioInputStream(AudioFormat targ,
231                                                     AudioInputStream ais)
232  {
233    Iterator<FormatConversionProvider> i =
234      ServiceFactory.lookupProviders(FormatConversionProvider.class);
235    while (i.hasNext())
236      {
237        FormatConversionProvider prov = i.next();
238        if (! prov.isConversionSupported(targ, ais.getFormat()))
239          continue;
240        return prov.getAudioInputStream(targ, ais);
241      }
242    throw new IllegalArgumentException("format not supported for stream");
243   }
244
245  /**
246   * Return an audio input stream for the file.
247   * @param f the file to read
248   * @return an audio input stream for the file
249   * @throws UnsupportedAudioFileException if the file's audio format is not
250   * recognized
251   * @throws IOException if there is an error while reading the file
252   */
253  public static AudioInputStream getAudioInputStream(File f)
254    throws UnsupportedAudioFileException, IOException
255  {
256    Iterator<AudioFileReader> i = ServiceFactory.lookupProviders(AudioFileReader.class);
257    while (i.hasNext())
258      {
259        AudioFileReader reader = i.next();
260        try
261          {
262            return reader.getAudioInputStream(f);
263          }
264        catch (UnsupportedAudioFileException _)
265          {
266            // Try the next provider.
267          }
268      }
269    throw new UnsupportedAudioFileException("file type not recognized");
270  }
271
272  /**
273   * Return an audio input stream given an input stream.
274   * @param is the input stream
275   * @return an audio input stream
276   * @throws UnsupportedAudioFileException if the input stream's audio format
277   * is not supported by any of the installed providers
278   * @throws IOException if there is an error while reading the input stream
279   */
280  public static AudioInputStream getAudioInputStream(InputStream is)
281    throws UnsupportedAudioFileException, IOException
282  {
283    Iterator<AudioFileReader> i = ServiceFactory.lookupProviders(AudioFileReader.class);
284    while (i.hasNext())
285      {
286        AudioFileReader reader = i.next();
287        try
288          {
289            return reader.getAudioInputStream(is);
290          }
291        catch (UnsupportedAudioFileException _)
292          {
293            // Try the next provider.
294          }
295      }
296    throw new UnsupportedAudioFileException("input stream type not recognized");
297  }
298
299  /**
300   * Return an audio input stream for the given URL.
301   * @param url the URL
302   * @return an audio input stream
303   * @throws UnsupportedAudioFileException if the URL's audio format is not
304   * supported by any of the installed providers
305   * @throws IOException if there is an error while reading the URL
306   */
307  public static AudioInputStream getAudioInputStream(URL url)
308    throws UnsupportedAudioFileException, IOException
309  {
310    Iterator<AudioFileReader> i = ServiceFactory.lookupProviders(AudioFileReader.class);
311    while (i.hasNext())
312      {
313        AudioFileReader reader = i.next();
314        try
315          {
316            return reader.getAudioInputStream(url);
317          }
318        catch (UnsupportedAudioFileException _)
319          {
320            // Try the next provider.
321          }
322      }
323    throw new UnsupportedAudioFileException("URL type not recognized");
324  }
325
326  /**
327   * Return a new clip which can be used for playing back an audio stream.
328   * @throws LineUnavailableException if a clip is not available for some
329   * reason
330   * @throws SecurityException if a clip cannot be made for security reasons
331   * @since 1.5
332   */
333  public static Clip getClip()
334    throws LineUnavailableException
335  {
336    Mixer.Info[] infos = getMixerInfo();
337    for (int i = 0; i < infos.length; ++i)
338      {
339        Mixer mix = getMixer(infos[i]);
340        Line[] lines = mix.getSourceLines();
341        for (int j = 0; j < lines.length; ++j)
342          {
343            if (lines[j] instanceof Clip)
344              return (Clip) lines[j];
345          }
346      }
347    throw new LineUnavailableException("no Clip available");
348  }
349
350  /**
351   * Return a new clip which can be used for playing back an audio stream.
352   * The clip is obtained from the indicated mixer.
353   * @param info the mixer to use
354   * @throws LineUnavailableException if a clip is not available for some
355   * reason
356   * @throws SecurityException if a clip cannot be made for security reasons
357   * @since 1.5
358   */
359  public static Clip getClip(Mixer.Info info)
360    throws LineUnavailableException
361  {
362    Mixer mix = getMixer(info);
363    Line[] lines = mix.getSourceLines();
364    for (int j = 0; j < lines.length; ++j)
365      {
366        if (lines[j] instanceof Clip)
367          return (Clip) lines[j];
368      }
369    throw new LineUnavailableException("no Clip available");
370  }
371
372  /**
373   * Return a line matching the provided description.  All the providers
374   * on the system are searched for a matching line.
375   * @param info description of the line
376   * @return the matching line
377   * @throws LineUnavailableException if no provider supplies a matching line
378   */
379  public static Line getLine(Line.Info info) throws LineUnavailableException
380  {
381    Mixer.Info[] infos = getMixerInfo();
382    for (int i = 0; i < infos.length; ++i)
383      {
384        Mixer mix = getMixer(infos[i]);
385        try
386        {
387          return mix.getLine(info);
388        }
389        catch (LineUnavailableException _)
390        {
391          // Try the next provider.
392        }
393      }
394    throw new LineUnavailableException("no Clip available");
395  }
396
397  /**
398   * Return a mixer matching the provided description.  All the providers
399   * on the system are searched for a matching mixer.
400   * @param info description of the mixer
401   * @return the matching mixer
402   * @throws IllegalArgumentException if no provider supplies a matching mixer
403   */
404  public static Mixer getMixer(Mixer.Info info)
405  {
406    Iterator<MixerProvider> i = ServiceFactory.lookupProviders(MixerProvider.class);
407    while (i.hasNext())
408      {
409        MixerProvider prov = i.next();
410        if (prov.isMixerSupported(info))
411          return prov.getMixer(info);
412      }
413    throw new IllegalArgumentException("mixer not found");
414  }
415
416  /**
417   * Return an array of descriptions of all the mixers provided on the system.
418   */
419  public static Mixer.Info[] getMixerInfo()
420  {
421    HashSet<Mixer.Info> result = new HashSet<Mixer.Info>();
422    Iterator<MixerProvider> i = ServiceFactory.lookupProviders(MixerProvider.class);
423    while (i.hasNext())
424      {
425        MixerProvider prov = i.next();
426        Mixer.Info[] is = prov.getMixerInfo();
427        for (int j = 0; j < is.length; ++j)
428          result.add(is[j]);
429      }
430    return result.toArray(new Mixer.Info[result.size()]);
431  }
432
433  /**
434   * Return a source data line matching the given audio format.
435   * @param fmt the audio format
436   * @throws LineUnavailableException if no source data line matching
437   * this format is available
438   * @since 1.5
439   */
440  public static SourceDataLine getSourceDataLine(AudioFormat fmt)
441    throws LineUnavailableException
442  {
443    DataLine.Info info = new DataLine.Info(SourceDataLine.class, fmt);
444    Mixer.Info[] mixers = getMixerInfo();
445    for (int i = 0; i < mixers.length; ++i)
446      {
447        Mixer mix = getMixer(mixers[i]);
448        if (mix.isLineSupported(info))
449          return (SourceDataLine) mix.getLine(info);
450      }
451    throw new LineUnavailableException("source data line not found");
452  }
453
454  /**
455   * Return a target data line matching the given audio format.
456   * @param fmt the audio format
457   * @throws LineUnavailableException if no target data line matching
458   * this format is available
459   * @since 1.5
460   */
461  public static SourceDataLine getSourceDataLine(AudioFormat fmt,
462                                                 Mixer.Info mixer)
463    throws LineUnavailableException
464  {
465    DataLine.Info info = new DataLine.Info(SourceDataLine.class, fmt);
466    Mixer mix = getMixer(mixer);
467    if (mix.isLineSupported(info))
468      return (SourceDataLine) mix.getLine(info);
469    throw new LineUnavailableException("source data line not found");
470  }
471
472  /**
473   * Return an array of descriptions of all the source lines matching
474   * the given line description.
475   * @param info description of the lines to match
476   */
477  public static Line.Info[] getSourceLineInfo(Line.Info info)
478  {
479    HashSet<Line.Info> result = new HashSet<Line.Info>();
480    Mixer.Info[] infos = getMixerInfo();
481    for (int i = 0; i < infos.length; ++i)
482      {
483        Mixer mix = getMixer(infos[i]);
484        Line.Info[] srcs = mix.getSourceLineInfo(info);
485        for (int j = 0; j < srcs.length; ++j)
486          result.add(srcs[j]);
487      }
488    return result.toArray(new Line.Info[result.size()]);
489  }
490
491  /**
492   * Find and return a target data line matching the given audio format.
493   * @param fmt the format to match
494   * @throws LineUnavailableException if no matching line was found
495   * @since 1.5
496   */
497  public static TargetDataLine getTargetDataLine(AudioFormat fmt)
498    throws LineUnavailableException
499  {
500    DataLine.Info info = new DataLine.Info(TargetDataLine.class, fmt);
501    Mixer.Info[] mixers = getMixerInfo();
502    for (int i = 0; i < mixers.length; ++i)
503      {
504        Mixer mix = getMixer(mixers[i]);
505        if (mix.isLineSupported(info))
506          return (TargetDataLine) mix.getLine(info);
507      }
508    throw new LineUnavailableException("target data line not found");
509  }
510
511  /**
512   * Return a target data line matching the given audio format and
513   * mixer.
514   * @param fmt the audio format
515   * @param mixer the mixer description
516   * @return a target data line
517   * @throws LineUnavailableException if no matching target data line was
518   * found
519   * @since 1.5
520   */
521  public static TargetDataLine getTargetDataLine(AudioFormat fmt,
522                                                 Mixer.Info mixer)
523    throws LineUnavailableException
524  {
525    DataLine.Info info = new DataLine.Info(TargetDataLine.class, fmt);
526    Mixer mix = getMixer(mixer);
527    if (mix.isLineSupported(info))
528      return (TargetDataLine) mix.getLine(info);
529    throw new LineUnavailableException("target data line not found");
530  }
531
532  /**
533   * Given a source encoding, return an array of all target encodings to which
534   * data in this form can be converted.
535   * @param source the source encoding
536   */
537  public static AudioFormat.Encoding[] getTargetEncodings(AudioFormat.Encoding source)
538  {
539    HashSet<AudioFormat.Encoding> result
540      = new HashSet<AudioFormat.Encoding>();
541    Iterator<FormatConversionProvider> i =
542      ServiceFactory.lookupProviders(FormatConversionProvider.class);
543    while (i.hasNext())
544      {
545        FormatConversionProvider prov = i.next();
546        if (! prov.isSourceEncodingSupported(source))
547          continue;
548        AudioFormat.Encoding[] es = prov.getTargetEncodings();
549        for (int j = 0; j < es.length; ++j)
550          result.add(es[j]);
551      }
552    return result.toArray(new AudioFormat.Encoding[result.size()]);
553  }
554
555  /**
556   * Given a source format, return an array of all the target encodings to
557   * which data in this format can be converted.
558   * @param source the source format
559   */
560  public static AudioFormat.Encoding[] getTargetEncodings(AudioFormat source)
561  {
562    HashSet<AudioFormat.Encoding> result
563      = new HashSet<AudioFormat.Encoding>();
564    Iterator<FormatConversionProvider> i =
565      ServiceFactory.lookupProviders(FormatConversionProvider.class);
566    while (i.hasNext())
567      {
568        FormatConversionProvider prov = i.next();
569        AudioFormat.Encoding[] es = prov.getTargetEncodings(source);
570        for (int j = 0; j < es.length; ++j)
571          result.add(es[j]);
572      }
573    return result.toArray(new AudioFormat.Encoding[result.size()]);
574  }
575
576  /**
577   * Given a target encoding and a source audio format, return an array of all
578   * matching audio formats to which data in this source format can be converted.
579   * @param encoding the target encoding
580   * @param sourceFmt the source format
581   */
582  public static AudioFormat[] getTargetFormats(AudioFormat.Encoding encoding,
583                                               AudioFormat sourceFmt)
584  {
585    HashSet<AudioFormat> result = new HashSet<AudioFormat>();
586    Iterator<FormatConversionProvider> i =
587      ServiceFactory.lookupProviders(FormatConversionProvider.class);
588    while (i.hasNext())
589      {
590        FormatConversionProvider prov = i.next();
591        AudioFormat[] es = prov.getTargetFormats(encoding, sourceFmt);
592        for (int j = 0; j < es.length; ++j)
593          result.add(es[j]);
594      }
595    return result.toArray(new AudioFormat[result.size()]);
596  }
597
598  /**
599   * Given a line description, return an array of descriptions of all
600   * the matching target lines.
601   * @param info the line description
602   */
603  public static Line.Info[] getTargetLineInfo(Line.Info info)
604  {
605    HashSet<Line.Info> result = new HashSet<Line.Info>();
606    Mixer.Info[] infos = getMixerInfo();
607    for (int i = 0; i < infos.length; ++i)
608      {
609        Mixer mix = getMixer(infos[i]);
610        Line.Info[] targs = mix.getTargetLineInfo(info);
611        for (int j = 0; j < targs.length; ++j)
612          result.add(targs[j]);
613      }
614    return result.toArray(new Line.Info[result.size()]);
615  }
616
617  /**
618   * Return true if the currently installed providers are able to
619   * convert data from the given source format to the given target encoding.
620   * @param targ the target encoding
621   * @param source the source format
622   */
623  public static boolean isConversionSupported(AudioFormat.Encoding targ,
624                                              AudioFormat source)
625  {
626    Iterator<FormatConversionProvider> i
627      = ServiceFactory.lookupProviders(FormatConversionProvider.class);
628    while (i.hasNext())
629      {
630        FormatConversionProvider prov = i.next();
631        if (prov.isConversionSupported(targ, source))
632          return true;
633      }
634    return false;
635  }
636
637  /**
638   * Return true if the currently installed providers are able to convert
639   * the given source format to the given target format.
640   * @param targ the target format
641   * @param source the source format
642   */
643  public static boolean isConversionSupported(AudioFormat targ,
644                                              AudioFormat source)
645  {
646    Iterator<FormatConversionProvider> i
647      = ServiceFactory.lookupProviders(FormatConversionProvider.class);
648    while (i.hasNext())
649      {
650        FormatConversionProvider prov = i.next();
651        if (prov.isConversionSupported(targ, source))
652          return true;
653      }
654    return false;
655  }
656
657  private static boolean isFileTypeSupported(AudioFileFormat.Type[] types,
658                                             AudioFileFormat.Type type)
659  {
660    for (int i = 0; i < types.length; ++i)
661      {
662        if (types[i].equals(type))
663          return true;
664      }
665    return false;
666  }
667
668  /**
669   * Return true if the given audio file format is supported by one of
670   * the providers installed on the system.
671   * @param type the audio file format type
672   */
673  public static boolean isFileTypeSupported(AudioFileFormat.Type type)
674  {
675    return isFileTypeSupported(getAudioFileTypes(), type);
676  }
677
678  /**
679   * Return true if the given audio file format is supported for the
680   * given audio input stream by one of the providers installed on the
681   * system.
682   * @param type the audio file format type
683   * @param ais the audio input stream
684   */
685  public static boolean isFileTypeSupported(AudioFileFormat.Type type,
686                                            AudioInputStream ais)
687  {
688    return isFileTypeSupported(getAudioFileTypes(ais), type);
689  }
690
691  /**
692   * Return true if some provider on the system supplies a line
693   * matching the argument.
694   * @param info the line to match
695   */
696  public static boolean isLineSupported(Line.Info info)
697  {
698    Mixer.Info[] infos = getMixerInfo();
699    for (int i = 0; i < infos.length; ++i)
700      {
701        if (getMixer(infos[i]).isLineSupported(info))
702          return true;
703      }
704    return false;
705  }
706
707  /**
708   * Write an audio input stream to the given file, using the specified
709   * audio file format.  All the providers installed on the system will
710   * be searched to find one that supports this operation.
711   * @param ais the audio input stream to write
712   * @param type the desired audio file format type
713   * @param out the file to write to
714   * @return the number of bytes written
715   * @throws IOException if an I/O error occurs while writing
716   * @throws IllegalArgumentException if the file type is not supported
717   */
718  public static int write(AudioInputStream ais, AudioFileFormat.Type type,
719                          File out)
720    throws IOException
721  {
722    Iterator<AudioFileWriter> i = ServiceFactory.lookupProviders(AudioFileWriter.class);
723    while (i.hasNext())
724      {
725        AudioFileWriter w = i.next();
726        if (w.isFileTypeSupported(type, ais))
727          return w.write(ais, type, out);
728      }
729    throw new IllegalArgumentException("file type not supported by system");
730  }
731
732  /**
733   * Write an audio input stream to the given output stream, using the
734   * specified audio file format.  All the providers installed on the
735   * system will be searched to find one that supports this operation.
736   * @param ais the audio input stream to write
737   * @param type the desired audio file format type
738   * @param os the output stream to write to
739   * @return the number of bytes written
740   * @throws IOException if an I/O error occurs while writing
741   * @throws IllegalArgumentException if the file type is not supported
742   */
743  public static int write(AudioInputStream ais, AudioFileFormat.Type type,
744                          OutputStream os)
745    throws IOException
746  {
747    Iterator<AudioFileWriter> i = ServiceFactory.lookupProviders(AudioFileWriter.class);
748    while (i.hasNext())
749      {
750        AudioFileWriter w = i.next();
751        if (w.isFileTypeSupported(type, ais))
752          return w.write(ais, type, os);
753      }
754    throw new IllegalArgumentException("file type not supported by system");
755  }
756}