[SOLVED] LibGDX Loading Audio File

Hi all,

I’m trying to load an audio file and then get the audio data as an array using LibGDX, but it is not able to load the audio file. I’m currently using a wav file (I’d like to be able to use mp3’s as well) which is located in the Android assets folder where I have other files for creating text, buttons, etc. and these all work, so it is only the wav file that isn’t loading.

The wav file format is PCM, 44.1kHz, 16bit, mono, little endian.

Here is all the code I have added so far to see if it would pass the wav file to the AudioInputStream without crashing:

try {
	FileHandle fileHandle = Gdx.files.internal("click.wav");			
	InputStream inputStream = fileHandle.read();
	AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(inputStream);
}
catch (UnsupportedAudioFileException e) { e.printStackTrace(); }
catch (IOException e) { e.printStackTrace(); }

I’ve debugged the program and after the line that should create the FileHandle, the handle’s status is “null”. The Gdx.files.internal() method is what I used to load my files for the graphics and they are working.

When I run the program it crashes on the line:

AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(inputStream);

Here is the error message I get:

java.io.IOException: mark/reset not supported
	at java.io.InputStream.reset(Unknown Source)
	at com.sun.media.sound.SoftMidiAudioFileReader.getAudioInputStream(Unknown Source)
	at javax.sound.sampled.AudioSystem.getAudioInputStream(Unknown Source)
	at com.tekker.audioanimation.SoundFile.readAudioFile(SoundFile.java:61)
	at com.tekker.audioanimation.SoundFile.<init>(SoundFile.java:33)
	at com.tekker.audioanimation.Event.<init>(Event.java:30)
	at com.tekker.audioanimation.AudioAnimation.initArrays(AudioAnimation.java:82)
	at com.tekker.audioanimation.AudioAnimation.create(AudioAnimation.java:67)
	at com.badlogic.gdx.backends.lwjgl.LwjglApplication.mainLoop(LwjglApplication.java:136)
	at com.badlogic.gdx.backends.lwjgl.LwjglApplication$1.run(LwjglApplication.java:114)

Any ideas what I’m doing wrong? Thanks!

I got a sound to play like this:


Sound sound = Gdx.audio.newSound(Gdx.files
				.internal("res/Sound/ButtonClick.wav"));

Then played it using:


sound.play();

Hi kingroka123, thanks for the response.

That method works and it plays the sound file, so the Gdx.files.internal(“click.wav”) method is indeed working. I thought maybe that was the problem since the handle’s status was “null” when I debugged and stepped through the program.

For my program I do need to be able to access the audio data from the wav/mp3 file and store the data in an array. I’ve looked at the LibGDX audio API but didn’t see a way to load an audio file from disk and access the data directly. Currently my program generates its own sounds internally (just simple sine wave tones for testing) and stores them in double[]'s, so now I’m trying to load a file from disk and get it in the same format.

I know I can do that with Java’s AudioInputStream, but it doesn’t seem to work using the LibGDX file handle. Any ideas?

Thanks again!

Just bypass the InputStream step. The InputStream imposes the markability test on the file, and audio files often (usually?) fail this test.

There are two other ways to make an AudioInputStream, using either a File or a URL as the input parameter instead of the InputStream. Neither one of these subjects the audio file to the markability test.

Details can be found at the API for AudioSystem.getAudioInputStream.

A lot of us have run into this very problem, but fortunately the fix is simple.

Thanks philfrei, bypassing the InputStream and getting a File from the LibGDX FileHandle did the trick. :slight_smile:

One last problem I’m having now is when the file plays it just plays a blast of white noise, so I think I’m converting the data from the wav file incorrectly.

The AudioInputStream reads bytes from file and I’m trying to store that into an array of doubles. To do the conversion, I’m storing two bytes into a ByteBuffer and then getting a short back out since a short is 2 bytes, and then I’m casting that to a double to put into the array…it seems like this should work, but maybe this is the wrong way to convert it?

I’ve also tried reversing the order of putting the bytes into the ByteBuffer (in case I had them backwards), but no change.

	private void readAudioFile(){
		FileHandle fileHandle = Gdx.files.internal("click.wav");			
		File file = fileHandle.file();
		int byteLength = (int)fileHandle.length();
		
		byte[] audioFileBytes = new byte[byteLength];
		audioFile = new double[(byteLength/2)]; 
		
		try {
			audioInputStream = AudioSystem.getAudioInputStream(file);
			audioInputStream.read(audioFileBytes);
		} catch (UnsupportedAudioFileException e) { e.printStackTrace(); }
		  catch (IOException e) { e.printStackTrace(); }
		
		int j=0;
		for (int i=0; i<audioFileBytes.length;){
			audioFile[j++] = bytesToDouble(audioFileBytes[i++], audioFileBytes[i++]);
		}
	}
	
	public double bytesToDouble(byte firstByte, byte secondByte){
		ByteBuffer bb = ByteBuffer.allocate(2);
		bb.order(ByteOrder.LITTLE_ENDIAN);
		bb.put(firstByte);
		bb.put(secondByte);
		return (double)bb.getShort(0);
	}

Might want to normalize those shorts :stuck_out_tongue:

short[] audio; // amplitude range: [32767, -32768]
double[] audio; // amplitude range: [1, -1]

Thanks BurntPizza, that was it! :slight_smile: Divided by Short.MAX_VALUE and it is working perfectly now!

Problem officially solved! Thanks everyone, you all are beyond amazing!! :slight_smile:

Just a nitpick:


ShortBuffer shorts = ByteBuffer.wrap(data).asShortBuffer();

More idiomatic (and efficient, no allocations!), as long as endianness is correct.

Niiiiice that is slick! :slight_smile: Thanks BurntPizza, I will give that a go.

Also, it looks like ByteBuffer also has asDoubleBuffer(), so I may even be able to go directly from the byte buffer to the double buffer and bypass the shorts all together:

ByteBuffer.wrap(data).asDoubleBuffer();

Nope, sorry, asDoubleBuffer() knows nothing about audio:

ByteBuffer bb = ByteBuffer.wrap(new byte[] {48,-46,56,2,67,33,50,50});
DoubleBuffer db = bb.asDoubleBuffer();
double[] da = new double[db.limit()];
db.get(da);
		
System.out.println(Arrays.toString(da));
		
ShortBuffer sb = bb.asShortBuffer();
double[] da2 = new double[sb.limit()]; // 16-bit mono
for(int i=0;i<da2.length;i++)
	da2[i] = sb.get() / (double)Short.MAX_VALUE;
		
System.out.println(Arrays.toString(da2));

Oh wow…that’s interesting. Thanks for the heads up BurntPizza, I definitely won’t be using asDoubleBuffer() then. :wink:

Well…after having it work so fantastically on my desktop I tried running it on my Android phone and it crashed and burned!

After some debugging and then some googling I found that Android does not support Java’s AudioInputStream! Grrrrrr! That sucks! ???

I didn’t see any kind of LibGDX equivalent to AudioInputStream, so am I going to have to code this separately for each device (android, iOS, blackberry, ect) using native code or is there something else I can do? Thanks!

musicg is advertised as Android-compatible and can read WAVs: https://code.google.com/p/musicg/

Just a quick google of “java wav decoder” or similar.
Maybe include “android” to be sure.

Thanks BurntPizza!

I’m also hoping to support iOS devices (assuming the LibGDX conversion process works well), so am I safe in assuming that a 3rd party library like musicg wouldn’t be able to be converted to iOS via LibGDX?

If so, then would it be a good idea to just read the bytes straight from the wav file (since wav files aren’t compressed) using a byte reader for files that android also supports (have to look more into this as apparently android doesn’t support java.nio.Files either…go figure! ::))? This gives a good description of the layout inside the wav file:
https://ccrma.stanford.edu/courses/422/projects/WaveFormat/

Just a quick update: I was able to read in all the bytes from the wav file with the LibGDX FileHandle and then maneuver through the header bytes to extract just the audio bytes (using the document from standford I posted before). It works perfectly on both desktop and android, so I can now officially mark this problem solved!

Thanks again for all the help everyone! :slight_smile: