Page 2 of 3
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.
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:
BasicMP3FileReader (extends AudioFileReader) knows how to read MP3 files
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.
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:
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.
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:
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:
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
Sound programmingBy Anonymous on September 13, 2009, 9:18 amI want to how read MP3 in Java
Reply | Read entire comment
java codes for mp3player projectsBy Anonymous on August 27, 2009, 10:15 amI want code of mp3player with java or appletcodes of mp3player tank
Reply | Read entire comment
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