gdx-audio

I just finished off gdx-audio, our newest extension. Features:

  • Decoders for mp3, ogg Vorbis and wav, using Mpg123, Xiph Tremor
  • KissFFT and Java FFT by Damien Di Fede for comparison
  • SoundTouch for pitch shifting, time stretching and playback rate modification

To use the extension add the gdx-audio.jar and gdx-audio-natives.jar to your desktop project. For your android project add the gdx-audio.jar and copy the libgdx-audio.so files to your libs/armeabi and libs/armeabi-v7a folders.

For usage examples see:

  • Mpg123Test, shows how to decode an mp3 with the Mpg123Decoder class
  • VorbisTest, shows how to decode an ogg with the VorbisDecoder class
  • WavTest, shows how to decode an wav with the WavDecoder class
  • SoundTouchTest, shows how to apply pitch shifting to a PCM stream

Caveat: the vorbis and mp3 decoder can only decode files stored on the external storage. I might be able to work around that limitation in the future. For most practical purposes it shouldn’t be to limiting.

  • cyble

    Cool, now let’s make music :)

  • http://therobotequation.blogspot.com/ Manuel

    Great addition! Awesome project!

  • http://therobotequation.blogspot.com/ Manuel

    I thinkg Button should receive a ClickListener in its contructor so we can do:

    Button newGame = new TextButton(“New Game”, Art.skin.getStyle(TextButtonStyle.class)), new ClickListener() {
    @Override
    public void click(Actor actor, float x, float y) {
    onNewGame();
    }
    });

    instead of

    Button newGame = new TextButton(“New Game”, Art.skin.getStyle(TextButtonStyle.class));
    newGame.setClickListener(new ClickListener() {
    @Override
    public void click(Actor actor, float x, float y) {
    onNewGame();
    }
    });

    what do you think?

  • http://www.tachyondev.com TachyonDev

    I am having trouble loading an mp3 from my Galaxy Nexus. There is only internal storage on the device, are you forcing an sdcard path with your external method?

  • http://badlogicgames.com Mario

    While the Nexus has “internal” storage only, there’s actually a distinction between internal files (assets/, res/ folders) and external files. The mpg123 and vorbis decoder can only work with external files. The tests above copy internal files to external files, works just fine on my Galaxy Nexus.

    We are not forcing an SD card path, we use the proper method to query for the external storage absolute path.

    Environment.getExternalStorageDirectory().getAbsolutePath()

  • http://cmbryan.com Chris Bryan

    Thanks for this! Now, does anyone know a quick way that we could use this decoder for URL streams?

  • http://badlogicgames.com Mario

    At the moment it only works file files. Adding a streaming interface you push bytes to would be feasible, but it’s not on my priority list at the moment. Contributions welcome!

  • http://cmbryan.com Chris Bryan

    Thanks, Mario, I may just do that :) Now, another question: I would like to add an inverse method call for KissFFT. I thought it would be pretty simple, but at the moment I’m only getting silence :) Unfortunately I’m not a C programmer, so I’ve probably just missed something obvious!

    For reference, here is the ‘spectrum’ (forward) function:

    ===
    JNIEXPORT void JNICALL Java_com_badlogic_gdx_audio_analysis_KissFFT_spectrum(JNIEnv* env, jclass clazz, jlong handle, jshortArray obj_samples, jfloatArray obj_spectrum) {
    short* samples = (short*)env->GetPrimitiveArrayCritical(obj_samples, 0);
    float* spectrum = (float*)env->GetPrimitiveArrayCritical(obj_spectrum, 0);

    KissFFT* fft = (KissFFT*)handle;
    kiss_fftr( fft->config_forward, (kiss_fft_scalar*)samples, fft->spectrum );

    int len = fft->numSamples / 2 + 1;
    for( int i = 0; i spectrum[i].r) * fft->numSamples;
    float im = scale(fft->spectrum[i].i) * fft->numSamples;

    // edit by Chris Bryan– surely this ‘if’ is unnecessary?
    if( i > 0 )
    spectrum[i] = sqrtf(re*re + im*im);
    else
    spectrum[i] = sqrtf(re*re + im*im);
    }

    env->ReleasePrimitiveArrayCritical(obj_samples, samples, 0);
    env->ReleasePrimitiveArrayCritical(obj_spectrum, spectrum, 0);
    }
    ===

    So shouldn’t this give me back the inverse, if fft->spectrum still has the data from the forward transform? (I’ve used fft->spectrum as the input, rather than the method parameter.) Do I have the scaling wrong?

    ===
    JNIEXPORT void JNICALL Java_com_badlogic_gdx_audio_analysis_KissFFT_inverse(JNIEnv* env, jclass clazz, jlong handle, jshortArray obj_samples, jfloatArray obj_spectrum) {
    short* samples = (short*)env->GetPrimitiveArrayCritical(obj_samples, 0);

    KissFFT* fft = (KissFFT*)handle;

    kiss_fftri( fft->config_inverse, (kiss_fft_cpx*)fft->spectrum, (kiss_fft_scalar*)fft->samples );

    int len = fft->numSamples;
    for( int i = 0; i samples[i].r)/(fft->numSamples);
    }

    env->ReleasePrimitiveArrayCritical(obj_samples, samples, 0);
    env->ReleasePrimitiveArrayCritical(obj_spectrum, spectrum, 0);
    }

  • http://cmbryan.com Chris Bryan

    Sorry, here’s the inverse function again…

    JNIEXPORT void JNICALL Java_com_badlogic_gdx_audio_analysis_KissFFT_inverse(JNIEnv* env, jclass clazz, jlong handle, jshortArray obj_samples, jfloatArray obj_spectrum) {
    short* samples = (short*)env->GetPrimitiveArrayCritical(obj_samples, 0);

    KissFFT* fft = (KissFFT*)handle;

    kiss_fftri( fft->config_inverse, (kiss_fft_cpx*)fft->spectrum, (kiss_fft_scalar*)fft->samples );

    int len = fft->numSamples;
    for( int i = 0; i samples[i].r)/(fft->numSamples);
    }

    env->ReleasePrimitiveArrayCritical(obj_samples, samples, 0);
    env->ReleasePrimitiveArrayCritical(obj_spectrum, spectrum, 0);
    }

  • http://cmbryan.com Chris Bryan

    Ok, why is the ‘for’ loop getting mangled when I hit submit?? Sorry for the multiple posts!!

    ===
    for( int i = 0; i samples[i].r)/(fft->numSamples);
    }
    ===

    Fingers crossed…

  • http://badlogicgames.com Mario

    Seems to be still mangled. Contact me via e-mail (contact at badlogicgames dot com)

  • http://androidblogger.blogspot.com AndroidBlogger

    I think it would be nice to add to this article a hint that the licence are LGPL, so implies that you’re code is open.

  • Greg

    The library ‘gdx-audio-natives.jar’ contains native libraries that will not run on the device. ??

    i have the .so files in there, i linked gdx-audio from teh main project as it said both versions didnt match on when importing it again (there the same file, from the same place)

    fft-android] Native libraries detected in ‘gdx-audio-natives.jar’. See console for more information.
    [2012-06-09 22:28:45 - text-fft-android] The library ‘gdx-audio-natives.jar’ contains native libraries that will not run on the device.
    [2012-06-09 22:28:45 - text-fft-android] The following libraries were found:
    [2012-06-09 22:28:45 - text-fft-android] – libgdx-audio.so
    [2012-06-09 22:28:45 - text-fft-android] – libgdx-audio64.so

  • http://badlogicgames.com Mario

    you took the desktop natives, the android natives are in the armeabi and armeabi-v7a folders. copy both folders to your android project’s libs/ folder.

  • http://cmbryan.com Chris Bryan

    Hi Mario and all, I fell out of the loop for a bit, but now I’m back, and will contribute that inverse FFT to the community now that it’s working :)

    However, I am having a speed issue with mpg123 decoding, in that it’s currently taking up about 60% of realtime speed on android, so I am unable to do much processing before it starts to fall behind. Surely there much be something amiss? I’m just outputting to a short[]. I’ve tried compiling myself as well as using the pro-compiled library.

    (Using ddms I can see 2 or three very short calls to the readSamples function, followed by a massively long call, which is presumably where it’s doing another block of decoding. I suppose I can rule out the java-native bridge, then?)

    Any suggestions would be appreciated!

  • Federico Tanci

    Hi Mario,
    I’m trying to use the kissfft but here something wrong.
    I’ve added the armeabi and armeabi-v7a folders and the gdx-audio.jar to my project.
    Every time I run the code I’ve this error:

    Can you kindly help me to configure the project.

    08-28 14:24:41.894: E/AndroidRuntime(30110): FATAL EXCEPTION: main
    08-28 14:24:41.894: E/AndroidRuntime(30110): java.lang.NoClassDefFoundError: com.badlogic.gdx.audio.analysis.KissFFT
    08-28 14:24:41.894: E/AndroidRuntime(30110): at com.example.simplefft.SimpleFFTActivity.fft(SimpleFFTActivity.java:136)
    08-28 14:24:41.894: E/AndroidRuntime(30110): at com.example.simplefft.SimpleFFTActivity.onCreate(SimpleFFTActivity.java:40)
    08-28 14:24:41.894: E/AndroidRuntime(30110): at android.app.Activity.performCreate(Activity.java:4465)
    08-28 14:24:41.894: E/AndroidRuntime(30110): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1049)
    08-28 14:24:41.894: E/AndroidRuntime(30110): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2033)
    08-28 14:24:41.894: E/AndroidRuntime(30110): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2104)
    08-28 14:24:41.894: E/AndroidRuntime(30110): at android.app.ActivityThread.access$600(ActivityThread.java:132)
    08-28 14:24:41.894: E/AndroidRuntime(30110): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1157)
    08-28 14:24:41.894: E/AndroidRuntime(30110): at android.os.Handler.dispatchMessage(Handler.java:99)
    08-28 14:24:41.894: E/AndroidRuntime(30110): at android.os.Looper.loop(Looper.java:137)
    08-28 14:24:41.894: E/AndroidRuntime(30110): at android.app.ActivityThread.main(ActivityThread.java:4575)
    08-28 14:24:41.894: E/AndroidRuntime(30110): at java.lang.reflect.Method.invokeNative(Native Method)
    08-28 14:24:41.894: E/AndroidRuntime(30110): at java.lang.reflect.Method.invoke(Method.java:511)
    08-28 14:24:41.894: E/AndroidRuntime(30110): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:789)
    08-28 14:24:41.894: E/AndroidRuntime(30110): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:556)
    08-28 14:24:41.894: E/AndroidRuntime(30110): at dalvik.system.NativeStart.main(Native Method)

  • http://badlogicgames.com Mario

    Make sure you export the gdx-audio jar. Go to the properties of your Android Project, Java Build -> Order & Export and make sure gdx-audio is checked. This is a nice new “feature” of ATD 17+. It sucks…

  • RaduB

    Hi,

    I’m trying to run my game on my android device and I keep getting the error:

    05-13 14:55:35.950: E/dalvikvm(27744): dlopen(“/data/app-lib/com.me.serious-1/libgdx-audio.so”) failed: dlopen failed: “/data/app-lib/com.me.serious-1/libgdx-audio.so” has unexpected e_machine: 3

    05-13 14:55:35.985: W/dalvikvm(27744): threadid=11: thread exiting with uncaught exception (group=0x41d0e700)

    05-13 14:55:35.995: E/AndroidRuntime(27744): FATAL EXCEPTION: GLThread 5318

    05-13 14:55:35.995: E/AndroidRuntime(27744): com.badlogic.gdx.utils.GdxRuntimeException: Couldn’t load shared library ‘gdx-audio’ for target: Linux, 32-bit

    05-13 14:55:35.995: E/AndroidRuntime(27744): at com.badlogic.gdx.utils.SharedLibraryLoader.load(SharedLibraryLoader.java:104)

    I have the gdx.audio.jar in the build path and gdx.audio.so and gdx.audio64.so in the armeabi folders.
    can anyone tell me what to do?