Featured Whitepapers
Newsletter sign-up
View all newsletters

Sign up for our technology specific newsletters.

Enterprise Java
Email Address:

Add MP3 capabilities to Java Sound with SPI

The Service Provider Interface adds functionality to Java Sound without recoding

  • Digg
  • Reddit
  • SlashDot
  • Stumble
  • del.icio.us
  • Technorati
  • dzone

Page 2 of 3

Preparing new services

Service providers add services to the JVM by supplying the class files that perform the service and listing those services in a JAR file's special META-INF/services directory. That directory lists all service providers, and JVM subsystems look for additional services there. With that information in mind, let's take look at how Java Sound's implementation provides audio file readers for the standard sampled audio file types: WAV, AIFF, and AU.

The JRE's important rt.jar file, located in the jre/lib directory of a Java installation, contains most of the JRE's runtime Java classes. If you unzip the rt.jar file, you will find that it contains a META-INF/services directory, inside of which you'll find several files that are named with a javax.sound prefix. One of those files -- javax.sound.sampled.spi.AudioFileReader -- contains a list of classes that provide the reading capability to the Java Sound subsystem. Upon opening that UTF-8-encoded file, you will see:

# Providers for audio file reading
com.sun.media.sound.AuFileReader
com.sun.media.sound.AiffFileReader
com.sun.media.sound.WaveFileReader


The above classes list the service providers that provide audio file read capability to the Java Sound subsystem. The subsystem instantiates those classes, uses them to describe the audio file data format, and gets an AudioInputStream from the file. Similarly, META-INF/services contains other SPI files to enumerate MIDI devices, mixers, sound banks, format converters, and other pieces of the Java Sound subsystem.

The advantage to that architecture: the Java Sound subsystem becomes extensible. To be more specific, other JAR files added to the JRE classpath may contain other service providers that provide additional services. The audio subsystem can query all the service providers and match the appropriate service with the consumer's request. To the consumer, how the services become available and are queried remains completely transparent. Consequently, with the right service providers, older programs can now run with new audio file types -- a big feature.

Let's now move from the theoretical to the concrete by examining how to provide a new service: MP3 audio files.

Implementing the SPI

In this section, we will go step by step through a concrete example of extending the Java Sound audio subsystem using the SPI. To get started, there are two basic classes that link an MP3 decoder to the Java Sound subsystem so that it can play MP3 files:

  • The BasicMP3FileReader (extends AudioFileReader) knows how to read MP3 files
  • The BasicMP3FormatConversionProvider (extends FormatConversionProvider) knows how to convert an MP3 stream to one the Java Sound subsystem can play


The two classes let Java Sound know that MP3 capability is available.

Note: For the purposes of this article, I've kept the classes extremely simple. Many types of encoded MPEG audio exist, but the basic MP3 service provided in this article supports only MPEG versions 1 or 2, layer 3. It does not support multichanneled movie soundtracks. For a full-fledged MPEG decoder, one should investigate the free source Tritonus Java Sound implementation developed by Matthias Pfisterer, available in Resources.

Implementation: Part 1, the BasicMP3FileReader

We begin by implementing the BasicMP3FileReader class, which extends the abstract class javax.sound.sampled.spi.AudioFileReader and requires us to implement the following methods:

  • public abstract AudioFileFormat getAudioFileFormat( InputStream stream ) throws UnsupportedAudioFileException, IOException;
  • public abstract AudioFileFormat getAudioFileFormat( URL url ) throws UnsupportedAudioFileException, IOException;
  • public abstract AudioFileFormat getAudioFileFormat( File file ) throws UnsupportedAudioFileException, IOException;
  • public abstract AudioInputStream getAudioInputStream( InputStream stream ) throws UnsupportedAudioFileException, IOException;
  • public abstract AudioInputStream getAudioInputStream( URL url ) throws UnsupportedAudioFileException, IOException;
  • public abstract AudioInputStream getAudioInputStream( File file ) throws UnsupportedAudioFileException, IOException;


Notice that all the methods throw UnsupportedAudioFileException and IOException, which signal to Java Sound that problems exist with the MP3 file. Those exceptions should be thrown whenever a file is unreadable, bytes do not match, or sample rates or data sizes seem out of whack.

Also notice the two groups of methods to implement. The first group provides an AudioFileFormat object from one of three inputs: InputStream, URL, or File. As its ultimate goal, the getAudioFileFormat() method provides an AudioFileFormat object that describes the encoding, sample rate, sample size, number of channels, and other attributes of the audio stream. While the code contains the details of that conversion, we can summarize by noting that it reads the bytes from the stream, and those bytes are tested to ensure that the stream is, in fact, an MP3 stream, that it describes its sample rate, and that all the necessary fields are present.

Since that SPI code provides support for a new encoding, we have to invent such a class -- BasicMP3Encoding. That simple class contains a static final field to describe the new MP3 encoding in a manner similar to descriptions for existing encodings for PCM, ALAW, and ULAW in the javax.sound.sampled.AudioFormat class.

We also implement the BasicMP3FileFormatType class in a manner similar to javax.sound.sampled.AudioFileFormat, as seen below:

public class BasicMP3Encoding extends AudioFormat.Encoding {
   public static final AudioFormat.Encoding MP3 = new BasicMP3Encoding( "MP3" );
   public BasicMP3Encoding( String encodingName ) {
      super( encodingName );
   }
} 


BasicMP3FileReader's second group of methods provides an AudioInputStream from the same inputs. Since an InputStream can be pulled from a URL or File, we can use the getAudioInputStream() method with the InputStream parameter to implement the other two methods.

This is shown here:

public AudioInputStream getAudioInputStream( URL url )
   throws UnsupportedAudioFileException, IOException {
   InputStream inputStream = url.openStream();
   try {
      return getAudioInputStream( inputStream );
   } catch ( UnsupportedAudioFileException e ) {
      inputStream.close();
      throw e;
   } catch ( IOException e ) {
      inputStream.close();
      throw e;
   }
}


The stream is tested by using the getAudioFileFormat( inputStream ) method to ensure it is an MP3 stream. Then we create a new generic AudioInputStream from the MP3 stream. For further details, read the BasicMP3FileReader.java source file.

Now that we have implemented the AudioFileReader, we are halfway to our goal. Let's look at how to implement the second half of our service provider, the FormatConversionProvider.

Implementation: Part 2, the BasicMP3FormatConversionProvider

Next, we implement BasicMP3FormatConversionProvider, which extends the abstract class javax.sound.sampled.spi.FormatConversionProvider. A format conversion provider converts from a source to a target audio format. To implement BasicMP3FormatConversionProvider, we must implement the following methods:

  • public abstract AudioFormat.Encoding[] getSourceEncodings();
  • public abstract AudioFormat.Encoding[] getTargetEncodings();
  • public abstract AudioFormat.Encoding[] getTargetEncodings( AudioFormat srcFormat );
  • public abstract AudioFormat[] getTargetFormats( AudioFormat.Encoding targetEncoding, AudioFormat sourceFormat );
  • public abstract AudioInputStream getAudioInputStream( AudioFormat.Encoding targetEncoding, AudioInputStream sourceStream );
  • public abstract AudioInputStream getAudioInputStream( AudioFormat targetFormat, AudioInputStream sourceStream );


As you can see, we have three groups of methods. The first group simply enumerates the source and target encodings that the format-conversion provider supports. The BasicMP3FormatConversionProvider class contains some large static arrays that describe the input and output formats supported by the underlying MPEG decoder.

For instance, the source formats are given below. The source encodings simply are derived from those formats when the class instantiates. Whenever someone calls the getSourceEncodings() method, the source encoding array is returned.

protected static final AudioFormat [] SOURCE_FORMATS = {
   // encoding, rate, bits, channels, frameSize, frameRate, big endian
   new AudioFormat( BasicMP3Encoding.MP3,  8000.0F, -1, 1, -1, -1, false ),
   new AudioFormat( BasicMP3Encoding.MP3,  8000.0F, -1, 2, -1, -1, false ),
   new AudioFormat( BasicMP3Encoding.MP3, 11025.0F, -1, 1, -1, -1, false ),
   new AudioFormat( BasicMP3Encoding.MP3, 11025.0F, -1, 2, -1, -1, false ),
   ...


BasicMP3FormatConversionProvider's second group of methods, containing the getTargetFormats() method, proves rather tricky. We want getTargetFormats() to return a target AudioFormat that can be created from the given source AudioFormat. Additionally, the target encoding is given, and the target AudioFormat must be of that encoding. To perform that tricky maneuver, the BasicMP3FormatConversionProvider creates a hashtable to help speed the mapping. The hashtable maps the target format to another hashtable of possible target encodings. The target encodings each point to a set of target audio formats. If you find that difficult to visualize, just remember that the format-conversion provider contains data structures to quickly return a target AudioFormat from a given source AudioFormat.

The third group of methods, two versions of getAudioInputStream(), provides a decoded audio stream from the given input MP3 stream. Simply put, the conversion provider checks that the conversion is supported and, if it does, returns a decoded linear audio-input stream from the given encoded MP3 audio stream. If the conversion is not supported, an IllegalArgumentException is thrown. At that point, our service provider code must actually start decoding the MPEG data stream. As such, it's where the rubber meets the road, as illustrated below:

  • Digg
  • Reddit
  • SlashDot
  • Stumble
  • del.icio.us
  • Technorati
  • dzone
Comments (2)
Login
Forgot your account info?

ThanksBy Anonymous on June 25, 2009, 7:01 amThank you, very clear and useful, I used this example to create a locale service provider for a non official european languange.

Reply | Read entire comment

I need to record mp3 By Anonymous on November 13, 2008, 9:25 amI need to record mp3

Reply | Read entire comment

View all comments

Add comment
Anonymous comments subject to approval. Register here for member benefits.
Have a JavaWorld account? Log in here. Register now for a free account.
Resources
  • Download the complete implementation of the Service Provider Interface in zip format. The zip file includes the MP3.jar archive to add to the Java 2 version 1.3 classpath as well as all the source code to make the jar file
    http://www.javaworld.com/jw-11-2000/mp3/jw-1103-mp3.zip
  • Additionally, to play MP3 files with Java, you will need to download the JavaLayer MP3 decoder package and add it to your classpath
    http://www.javazoom.net/javalayer/javalayer.html
  • Matthias Pfisterer developed the Tritonus Java Sound implementation, released under the GNU Library General Public License. I derived some of the auxiliary files for this article (TCircularBuffer and DecodedMpegAudioInputStream for example) from that code, but they are provided here to help make the absolute smallest implementation of an MP3 service provider. A regular contributor to the Java Sound list group, Matthias has helped advanced the knowledge of the Java Sound community tremendously
    http://tritonus.org/
  • Matthias Pfisterer's Website also boasts an entire tutorial of Java Sound examples, which you'll find at
    http://rupert.informatik.uni-stuttgart.de/~pfistere/jsexamples
  • A full explanation of the Service Provider Interface is contained in Part III, Chapter 13 of the Java 2 version 1.3 "Sound API Programmer's Guide," which can be found online on the Java Sound homepage
    http://java.sun.com/products/java-media/sound
  • A more complete implementation of Java Sound, one that decodes multichannel movie soundtracks as well as MP3 sound files, is on Tritonus, located at
    http://tritonus.org/