ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Extend JavaSound to Play MP3, Ogg Vorbis, and More
Pages: 1, 2

From the SPI User Side

Thanks to JavaSound plugin architecture, using the MP3 SPI in your application is as easy as using the JavaSound API.



To get MP3 information (such as channels, sampling rate, and other metadata), you need to call the AudioSystem.getAudioFileFormat(file) static method from AudioSystem. It will return an instance of MpegAudioFileFormat, from which you can get audio properties. Note that the AudioSystem class acts as the entry point to the sampled-audio system resources.

File file = new File("filename.mp3");
AudioFileFormat baseFileFormat = null;
AudioFormat baseFormat = null;
baseFileFormat = AudioSystem.getAudioFileFormat(file);
baseFormat = baseFileFormat.getFormat();
// Audio type such as MPEG1 Layer3, or Layer 2, or ...
AudioFileFormat.Type type = baseFileFormat.getType();
// Sample rate in Hz (e.g. 44100).
float frequency = baseFormat.getSampleRate();

To play MP3, you need first to call AudioSystem.getAudioInputStream(file) to get an AudioInputStream from an MP3 file, select the target format (i.e., PCM) according to input MP3 channels and sampling rate, and finally get an AudioInputStream with the target format. If JavaSound doesn't find a matching SPI implementation supporting the MP3-to-PCM conversion, then it will throw an exception.

File file = new File("filename.mp3");
AudioInputStream in= AudioSystem.getAudioInputStream(file);
AudioInputStream din = null;
AudioFormat baseFormat = in.getFormat();
AudioFormat decodedFormat =
    new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
                    baseFormat.getSampleRate(),
                    16,
                    baseFormat.getChannels(),
                    baseFormat.getChannels() * 2,
                    baseFormat.getSampleRate(),
                    false);
din = AudioSystem.getAudioInputStream(decodedFormat, in);
// Play now.
rawplay(decodedFormat, din);
in.close();

Second, you have to send the decoded PCM data to a SourceDataLine. This means you have to load PCM data from the decoded AudioInputStream into the SourceDataLine buffer until the end of file is reached. JavaSound will send this data to the sound card. Once the file is exhausted, the line resources must be closed.


private void rawplay(AudioFormat targetFormat,
                     AudioInputStream din)
   throws IOException, LineUnavailableException
{
  byte[] data = new byte[4096];
  SourceDataLine line = getLine(targetFormat);
  if (line != null)
  {
    // Start
    line.start();
    int nBytesRead = 0, nBytesWritten = 0;
    while (nBytesRead != -1)
    {
        nBytesRead = din.read(data, 0, data.length);
        if (nBytesRead != -1)
            nBytesWritten = line.write(data, 0, nBytesRead);
    }
    // Stop
    line.drain();
    line.stop();
    line.close();
    din.close();
  }
}

private SourceDataLine getLine(AudioFormat audioFormat)
    throws LineUnavailableException
{
  SourceDataLine res = null;
  DataLine.Info info =
    new DataLine.Info(SourceDataLine.class, audioFormat);
  res = (SourceDataLine) AudioSystem.getLine(info);
  res.open(audioFormat);
  return res;
}

If you're familiar with JavaSound API, you will notice that source code for playing MP3 is similar to the what you'd use to play a WAV file. The source code sample above has no dependencies upon the MP3 SPI implementation. It's transparent for the developer.

Notice that if the file to play was stored on a web server, we would have used:

URL url = new URL("http://www.myserver.com/filename.mp3");
AudioInputStream in= AudioSystem.getAudioInputStream(url);

instead of:

File file = new File("filename.mp3");
AudioInputStream in= AudioSystem.getAudioInputStream(file);

Metadata

Most audio formats include metadata such as title, album, comments, compression quality, encoding, and copyright. ID3 tags, used for MP3, are the best-known metadata format. Depending on ID3 version (v1 or v2), they can be found either at the end or at the beginning of an MP3 file. They include information such as duration, title, album, artist, track number, date, genre, copyright, etc. They can even include lyrics and pictures. The famous (and free) SHOUTcast streaming MP3 server, from Nullsoft, uses a different scheme in order to provide additional metadata such as title streaming, which allows a player to display the current song being played from the online radio stream. All of these metadata items need to be parsed and exposed through the SPI implementation. As of J2SE 1.5, the JavaSound API standardizes the passing of metadata parameters through an immutable java.util.Map:


File file = new File("filename.mp3");
AudioFileFormat baseFileFormat =
    AudioSystem.getAudioFileFormat(file);
Map properties = baseFileFormat.properties();
String key_author = "author";
String author = (String) properties.get(key_author);
String key_duration = "duration";
Long duration = (Long) properties.get(key_duration);

All metadata keys and types should be provided in the SPI documentation. However, common properties include:

  • "duration" (Long): Playback duration of file, in microseconds
  • "author" (String): Name of the author of the file
  • "title" (String): Title of the file
  • "copyright" (String): Copyright message
  • "comment" (String): Arbitrary text

Using Multiple SPIs in an Application

Adding MP3 audio capabilities to the Java platform means adding JAR files containing the MP3 SPI implementation to the runtime CLASSPATH. Adding Ogg Vorbis, Speex, Flac, or Monkey's Audio support would be similar, but could generate conflicts that make other SPI implementations fail. The following situation could occur:

  1. Your runtime application CLASSPATH includes both MP3 and Ogg Vorbis SPIs.
  2. Your application tries to play an MP3 file.
  3. JavaSound's AudioSystem tries Ogg Vorbis SPI first.
  4. The Ogg Vorbis SPI implementation doesn't detect that incoming file isn't an Ogg-Vorbis-compliant stream, so it doesn't throw any exception.
  5. Your application tries to play an MP3 with the Ogg Vorbis SPI. At best you will get a runtime exception (NullPointerException, ArrayIndexOutOfBoundException), and in the worst case, you will hear weird noises or just deadlock.

In the example above, it's true that the problem comes from the Ogg Vorbis SPI implementation, but it's not easy for the SPI provider to have reliable controls (just think about streaming). Thus, each SPI provider has to pay attention to the others. That's the main practical drawback of the JavaSound plugin architecture. So don't be surprised if you have problems making multiple SPIs work together in your application.

Differences with JMF

JMF stands for Java Media Framework. It's an optional J2SE packages that adds multimedia support to the Java platform. It includes audio (GSM, QuickTime, etc.), video (AVI, QuickTime, H.263, etc.) and RTP streaming features. JMF provides a plugin architecture, but it is not compliant with that of JavaSound. In fact, MP3 support was previously included in JMF, but it was removed in 2002 because of licensing issues.

Conclusion

JavaSound rocks. It provides a plugin architecture allowing any third-party provider to add custom audio format support, such as for MP3 files. API is flexible enough to plug most heterogeneous (lossy, lossless) audio formats, whatever their parameters and metadata, to the Java platform -- "Write once, play anywhere."

References and Resources

The JavaZOOM Team are the authors of the open source projects JLayer and jlGui.


Return to ONJava.com