Reading mp3's from executable jar file

Hey folks,

I’m trying to test my project as a runnable jar file instead of running it from the Eclipse environment, and I’m having trouble getting it to recognize .mp3 assets.

I’m using the the following .jar files to enable the native audio library to run compressed formats:
jl1.0.1.jar
mp3spi.1.9.5.jar
tritonus_share.jar

Here are the constructors for my sound class:

private SoundClip(String filename) {
		String path = "sfx/" + filename;
		try {
			
			InputStream src = getClass().getClassLoader().getResourceAsStream(path);
			InputStream bufferedIn = new BufferedInputStream(src);
			
			input = AudioSystem.getAudioInputStream(bufferedIn);

			AudioFormat baseFormat = input.getFormat();
			AudioFormat decodeFormat = new AudioFormat(
					AudioFormat.Encoding.PCM_SIGNED,
					baseFormat.getSampleRate(),
					16,
					baseFormat.getChannels(),
					baseFormat.getChannels() * 2,
					baseFormat.getSampleRate(),
					false
			);
			AudioInputStream decodedInput = AudioSystem.getAudioInputStream(decodeFormat, input);
			clip = AudioSystem.getClip();
			clip.open(decodedInput);
			
			gainControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
		
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
private SoundClip(String filename, float gain){
		this(filename);
		setGain(gain);
	}

I can load and play mp3’s just fine from the Eclipse environment, but when I try to run the jar as a standalone, it throws this:

javax.sound.sampled.UnsupportedAudioFileException: could not get audio input stream from input stream
at javax.sound.sampled.AudioSystem.getAudioInputStream(Unknown Source)
at com.noah.breakit.assets.SoundClip.(SoundClip.java:37)
at com.noah.breakit.assets.SoundClip.(SoundClip.java:14)
at com.noah.breakit.game.Game.(Game.java:71)
at com.noah.breakit.game.Game.main(Game.java:159)
javax.sound.sampled.UnsupportedAudioFileException: could not get audio input stream from input stream
at javax.sound.sampled.AudioSystem.getAudioInputStream(Unknown Source)
at com.noah.breakit.assets.SoundClip.(SoundClip.java:37)
at com.noah.breakit.assets.SoundClip.(SoundClip.java:15)
at com.noah.breakit.game.Game.(Game.java:71)
at com.noah.breakit.game.Game.main(Game.java:159)
javax.sound.sampled.UnsupportedAudioFileException: could not get audio input stream from input stream
at javax.sound.sampled.AudioSystem.getAudioInputStream(Unknown Source)
at com.noah.breakit.assets.SoundClip.(SoundClip.java:37)
at com.noah.breakit.assets.SoundClip.(SoundClip.java:16)
at com.noah.breakit.game.Game.(Game.java:71)
at com.noah.breakit.game.Game.main(Game.java:159)
javax.sound.sampled.UnsupportedAudioFileException: could not get audio input stream from input stream
at javax.sound.sampled.AudioSystem.getAudioInputStream(Unknown Source)
at com.noah.breakit.assets.SoundClip.(SoundClip.java:37)
at com.noah.breakit.assets.SoundClip.(SoundClip.java:17)
at com.noah.breakit.game.Game.(Game.java:71)
at com.noah.breakit.game.Game.main(Game.java:159)
javax.sound.sampled.UnsupportedAudioFileException: could not get audio input stream from input stream
at javax.sound.sampled.AudioSystem.getAudioInputStream(Unknown Source)
at com.noah.breakit.assets.SoundClip.(SoundClip.java:37)
at com.noah.breakit.assets.SoundClip.(SoundClip.java:18)
at com.noah.breakit.game.Game.(Game.java:71)
at com.noah.breakit.game.Game.main(Game.java:159)
javax.sound.sampled.UnsupportedAudioFileException: could not get audio input stream from input stream
at javax.sound.sampled.AudioSystem.getAudioInputStream(Unknown Source)
at com.noah.breakit.assets.SoundClip.(SoundClip.java:37)
at com.noah.breakit.assets.SoundClip.(SoundClip.java:19)
at com.noah.breakit.game.Game.(Game.java:71)
at com.noah.breakit.game.Game.main(Game.java:159)
javax.sound.sampled.UnsupportedAudioFileException: could not get audio input stream from input stream
at javax.sound.sampled.AudioSystem.getAudioInputStream(Unknown Source)
at com.noah.breakit.assets.SoundClip.(SoundClip.java:37)
at com.noah.breakit.assets.SoundClip.(SoundClip.java:20)
at com.noah.breakit.game.Game.(Game.java:71)
at com.noah.breakit.game.Game.main(Game.java:159)
javax.sound.sampled.UnsupportedAudioFileException: could not get audio input stream from input stream
at javax.sound.sampled.AudioSystem.getAudioInputStream(Unknown Source)
at com.noah.breakit.assets.SoundClip.(SoundClip.java:37)
at com.noah.breakit.assets.SoundClip.(SoundClip.java:21)
at com.noah.breakit.game.Game.(Game.java:71)
at com.noah.breakit.game.Game.main(Game.java:159)
java.io.IOException: Stream closed
at java.io.BufferedInputStream.getInIfOpen(Unknown Source)
at java.io.BufferedInputStream.fill(Unknown Source)
at java.io.BufferedInputStream.read(Unknown Source)
at com.sun.media.sound.RIFFReader.read(Unknown Source)
at com.sun.media.sound.RIFFReader.(Unknown Source)
at com.sun.media.sound.WaveFloatFileReader.internal_getAudioFileFormat(Unknown Source)
at com.sun.media.sound.WaveFloatFileReader.getAudioFileFormat(Unknown Source)
at com.sun.media.sound.WaveFloatFileReader.getAudioInputStream(Unknown Source)
at javax.sound.sampled.AudioSystem.getAudioInputStream(Unknown Source)
at com.noah.breakit.assets.SoundClip.(SoundClip.java:37)
at com.noah.breakit.assets.SoundClip.(SoundClip.java:61)
at com.noah.breakit.assets.SoundClip.(SoundClip.java:23)
at com.noah.breakit.game.Game.(Game.java:71)
at com.noah.breakit.game.Game.main(Game.java:159)
Exception in thread “main” java.lang.ExceptionInInitializerError
at com.noah.breakit.game.Game.(Game.java:71)
at com.noah.breakit.game.Game.main(Game.java:159)
Caused by: java.lang.NullPointerException
at com.noah.breakit.assets.SoundClip.setGain(SoundClip.java:94)
at com.noah.breakit.assets.SoundClip.(SoundClip.java:62)
at com.noah.breakit.assets.SoundClip.(SoundClip.java:23)
… 2 more

If anybody has any ideas about where the error is, I’m all ears!

When something works in Eclipse but not in a jar, it is usually because the file system cannot address locations that were within the file system in Eclipse but are now within the jar.

A URL can read from a jar location. I recommend building a URL and using that to get your AudioInputStream:


    URL url = this.getClass().getResource("sfx/" + filename)
    AudioInputStream ais = AudioSystem.getAudioInputStream(url);
    DataLine.Info info = new DataLine.Info(Clip.class, ais.getFormat());  // you might want to specify your own format
    Clip clip = (Clip) AudioSystem.getLine(info);
    clip.open(ais);

Another problem used to come up in that InputStream is assumed to support mark and reset, but audio data files often don’t support this. This is another reason to get your AudioInputStream using a URL rather than an InputStream, so as to avoid the intermediate step where an exception might be thrown due to lack of mark and reset. You can compare the API’s to see what I’m talking about:
http://docs.oracle.com/javase/7/docs/api/javax/sound/sampled/AudioSystem.html#getAudioInputStream(java.net.URL)
http://docs.oracle.com/javase/7/docs/api/javax/sound/sampled/AudioSystem.html#getAudioInputStream(java.io.InputStream)

I’m not seeing where you are decoding the mp3 and I didn’t include that step in my example as I’ve not done that. (Been sticking with ogg/vorbis when using compression.)

Yes I had already run into the problem with lack of mark/reset support and had implemented a BufferedInputStream as a workaround. However, that didn’t solve the problem of not finding the resources in the filepath. Sounds like using a URL kills both birds with one stone. I’m working on implementing URLs now and will let you know how it goes. Thanks for the help!

I implemented the URL system. It worked fine when running the project in the Eclipse environment. I exported the jar, and it threw this exception:

javax.sound.sampled.UnsupportedAudioFileException: could not get audio input stream from input URL

As far as decoding the mp3 goes, I’m pretty sure this is the code block that does that:

AudioFormat baseFormat = input.getFormat();
         AudioFormat decodeFormat = new AudioFormat(
               AudioFormat.Encoding.PCM_SIGNED,
               baseFormat.getSampleRate(),
               16,
               baseFormat.getChannels(),
               baseFormat.getChannels() * 2,
               baseFormat.getSampleRate(),
               false
         );
         AudioInputStream decodedInput = AudioSystem.getAudioInputStream(decodeFormat, input);
         clip = AudioSystem.getClip();
         clip.open(decodedInput);

It works just fine in eclipse, but the jar file doesn’t seem to recognize the format even with the jar files that provide mp3 support. I really want to use a compressed format because wav has proven to be slow and laggy. You mentioned using ogg/vorbis for compression-- what API are you using to do that?

If I am reading this post correctly:


perhaps the mp3spi jar is not included in your export?

It seems to me there are some extra steps sometimes required when exporting a project with jars. I’m not sure how to check for this. Maybe rename your jar to .zip and step inside and look and see if the jars are there.

This might be a clue:

The tool for Ogg/Vorbis is here:
http://www.jcraft.com/jorbis/
I went through some major headaches getting it to work, but that is because I’m not using it in a normal way. I import and decode the file into raw PCM and store it in a normalized float array, for use with some other custom tools I made. This required some tinkering with their example code at the edges of my skill level. The work path that resulted is not very friendly.

You might consider using TinySound. It supports ogg/vorbis, and functions as a way to mix multiple sound sources and play them back at the same time. On the plus side, ogg/vorbis sounds just as good, if not better than mp3, and doesn’t trigger licensing requirements from Fraunhofer.

OK I solved it-- it was a matter of configuring the project build path properly.

My workspace assets are organised like this:

res/sfx

res/sfx/songs

where the sound effects are under sfx, and the music is under songs.

For some reason, I couln’t include the whole res folder in the Java Build Path, I had to expand that folder and include sfx, then go back in the code and remove references to sfx in the file paths.

so instead of “sfx/” and “sfx/songs/” I am now using just “songs/”

It really doesn’t make much sense to me why I can’t just include res in the build path, but for some reason when it exports to jar, it removes the res folder entirely and puts the sfx folder into the top level of the JAR archive.

I’m still going to look into TinySound because I’m not very impressed with the performance of mp3’s or wav’s in java. It seems to skip a lot, and large mp3 files like songs take an eternity to load into ram, which doesn’t seem right at all. :frowning:

Glad to hear you are getting this solved.

You probably already know this, but it is not clear from your post or the code fragments:

A long file like a song should probably be played directly as a SourceDataLine, not loaded into memory as a Clip. Usually, no more than one “decoding” stream is played at one time since decoding adds to the cost of playback. But having one decoding stream going is usually fine. If there are dropouts, it might be due to the buffer being too small (the buffer used in the SourceDataLine operations). The SourceDataLine’s buffer can be larger since for background song playback, a little extra latency is not a significant issue.

A Clip should be preloaded. If you combine open() and start(), the Clip will not commence until the entire file has first been loaded into memory, creating considerable latencey. Often first-time users of Java sound reload the Clip data with each start(), which repeats the costly loading over and over again. A Clip, once loaded can be reset so that it can be played again without reloading. Once loaded and set to the starting position, the start() method should commence virtually immediately. If it doesn’t, something probably needs debugging.

With these limits in mind, it should be possible to use javax.sound.sampled libraries and get things to work. But TinySound can make handling all this easier and adds some additional functionality.

Thanks. I knew the part about preloading the Clips but I didn’t know the part about the SourceDataLine! I’m going to experiment with that, because I do dropouts, and most annoyingly, i get this effect where the clips don’t play in the frame they are supposed to, then all playback at once, like a “rubber banding” effect (I don’t really know the proper term for what I’m trying to describe). It’s like in a laggy multiplayer game, where the whole game freezes, then all of a sudden a bunch of things happen at once that were supposed to happen in previous frames.

It wasn’t clear from my previous posts, but I do have the clips preloaded. Here is my entire sound class to show exactly how I have things set up. (sorry for the wall of text!)

package com.noah.breakit.assets;

import java.net.URL;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.FloatControl;

import com.noah.breakit.util.Util;

public class SoundClip {

	//sfx credits to little robot sound factory
	public static final SoundClip death = new SoundClip("death.mp3");
	public static final SoundClip explode = new SoundClip("explode.mp3");
	public static final SoundClip gameover = new SoundClip("gameover.mp3");
	public static final SoundClip hiping = new SoundClip("hiping.mp3");
	public static final SoundClip launch = new SoundClip("launch.mp3");
	public static final SoundClip levelbeat = new SoundClip("levelbeat.mp3");
	public static final SoundClip loping = new SoundClip("loping.mp3");
	public static final SoundClip oneup = new SoundClip("oneup.mp3");
	public static final SoundClip select = new SoundClip("select.mp3");
	public static final SoundClip voidsound = new SoundClip("voidsound.mp3");

	// music credit to sketchylogic
	public static final SoundClip titlesong = new SoundClip("songs/titlesong.mp3");
	public static final SoundClip playfieldsong = new SoundClip("songs/playfieldsong.mp3");
	public static final SoundClip instructionsong = new SoundClip("songs/instructionsong.mp3");
	public static final SoundClip gameoversong = new SoundClip("songs/gameoversong.mp3");

	private AudioInputStream input;
	private Clip clip;

	private FloatControl gainControl;

	private SoundClip(String filename) {

		try {
			URL url = this.getClass().getClassLoader().getResource(filename);

			input = AudioSystem.getAudioInputStream(url);

			AudioFormat baseFormat = input.getFormat();
			AudioFormat decodeFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, baseFormat.getSampleRate(), 16,
					baseFormat.getChannels(), baseFormat.getChannels() * 2, baseFormat.getSampleRate(), false);
			AudioInputStream decodedInput = AudioSystem.getAudioInputStream(decodeFormat, input);
			clip = AudioSystem.getClip();
			clip.open(decodedInput);

			gainControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private SoundClip(String filename, float gain) {
		this(filename);
		setGain(gain);
	}

	private synchronized void play() {
		new Thread(new Runnable() {
			public void run() {
				try {
					clip.stop();
					clip.setFramePosition(0);
					clip.start();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}).start();
	}

	private synchronized void loop(int count) {
		new Thread(new Runnable() {
			public void run() {
				try {
					clip.stop();
					clip.setFramePosition(0);
					clip.loop(count);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}).start();
	}
	
	public void playSFX(){
		play();
	}
	
	public void playSong(){
		loop(Clip.LOOP_CONTINUOUSLY);
	}
	
	public void playSong(float gain){
		gainControl.setValue(gain);
		loop(Clip.LOOP_CONTINUOUSLY);
	}
	
	public void setGain(float gain) {
		gainControl.setValue(gain);
	}

	public void adjustGain(float gain) {
		float value = Util.min((gainControl.getValue() + gain), gainControl.getMinimum());
		gainControl.setValue(value);
	}
	
	public boolean atMin(){
		return gainControl.getValue() == gainControl.getMinimum();
	}
}

I have the assets preloaded and then I make static calls to them.

Looks solid. I could see where it might take a few seconds to decode and load all these cues. That could happen on a background thread, perhaps. Depending upon their length, some of the “songs” might be better off as SourceDataLines. Unlike a Clip, though, you can only use one once before having to close it and open a new one.

Since each playback requires a new Thread, you might experiment with using an ExecutiveService.

public class SoundThreadPool {

	private static ExecutorService exec;
	
	public SoundThreadPool(int nThreads)
	{
		exec = Executors.newFixedThreadPool(nThreads);
	}
	
	public static void shutdown()
	{
		exec.shutdown();
	}
	
	public static void execute(Runnable command)
	{
		exec.execute(command);
	}
}

Initialize it to something that covers your maximum use case (how many concurrent sounds–then add a couple more for safety) Then, code something like the following in your play and loop methods:

     SoundThreadPool.execute(new Runnable() {
         public void run() {
         ...

Making and destroying Threads has more than the usual overhead for most Objects and so it often benefits from pooling. Note: the Executor will need to be shut down as part of exiting the app.

I think my pool class above is reasonably efficient. I haven’t exposed it to scrutiny before. It is pretty simple and bare bones. I’m finding it has improved the performance of my soundscapes, and it has also made coding the individual cues a bit easier. Maybe this will alleviate some of the latencies (if they are related to the overhead of making new Threads).

Hey, this is great! I have some questions about how to properly implement this. Since exec is static, it would be senseless to have more than one instance of SoundThreadPool, so it would be instantiated somewhere like the main Game class, correct?

Also, I have no idea how to shut down the service. I understand that you would call shutdown() or shutdownNow(), but I’m not really getting where and how to properly invoke this. I looked at the javadocs and they gave an example where a network service throws and IOException in its constructor, which is then caught by the class’s run() method, but I don’t really get it:

 class NetworkService implements Runnable {
   private final ServerSocket serverSocket;
   private final ExecutorService pool;

   public NetworkService(int port, int poolSize)
       throws IOException {
     serverSocket = new ServerSocket(port);
     pool = Executors.newFixedThreadPool(poolSize);
   }

   public void run() { // run the service
     try {
       for (;;) {
         pool.execute(new Handler(serverSocket.accept()));
       }
     } catch (IOException ex) {
       pool.shutdown();
     }
   }
 }

 class Handler implements Runnable {
   private final Socket socket;
   Handler(Socket socket) { this.socket = socket; }
   public void run() {
     // read and service request on socket
   }
 }

Since the exception is thrown in the constructor, I don’t see that working within the main Game class. Is there an event listener that detects a program shutdown, like clicking to close the window? ???

I got it. I made a shutdown thread and registered it:

Runtime.getRuntime().addShutdownHook(new ShutdownThread());

The thread is just this:

public class ShutdownThread extends Thread{

	public void run() {
		System.out.println("SoundThreadPool shutting down...");
		SoundThreadPool.shutdown();
		System.out.println("SoundThreadPool shutdown complete!");
	}
}

I haven’t implemented SourceDataLine yet, but even so, it is already running much smoother! Thanks dude! 8)

I haven’t looked closely at your solution. If it works, great! Very glad to hear it is all running more smoothly. Java’s sound classes are much maligned (mostly due to difficult-to-assimilate documentation), but they are capable of performing quite well, imho.

[quote]I have some questions about how to properly implement this. Since exec is static, it would be senseless to have more than one instance of SoundThreadPool, so it would be instantiated somewhere like the main Game class, correct?
[/quote]
What I do is to make a class called SoundHandler or GameSound or something like that and make it the top organizational point for all game sound. The executor is called in the SoundHandler constructor:

    new SoundThreadPool(10);

That is all that is needed.

The SoundHandler has (among other things) two methods, a start() and a stop(). I put the shutdown code in the stop() method:

    SoundThreadPool.shutdown();

[quote] Is there an event listener that detects a program shutdown, like clicking to close the window?
[/quote]
I’ve only made GUIs with JavaFX since coming up with this scheme. The main JavaFX GUI element is type Stage. It has the following method for detecting closes:

    stage.setOnCloseRequest( e -> cleanShutdown() );

The method cleanShutdown() is where I put soundHandler.stop() which in turn calls SoundThreadPool.shutdown().

I haven’t tried this with Swing yet. I did a search and found this which looks like just the way to go:
http://docs.oracle.com/javase/tutorial/uiswing/events/windowlistener.html

Working with the Runtime thread looks interesting! I haven’t delved into that area yet, and know little about it’s mysteries or capabilities. Just goes to show, though, that there’s almost always more than one way to accomplish something.