Search code examples
javaaudiolwjglopenal

OpenAL renders only one "bump", but there are no errors. What is going on?


I am trying to make a simple sound player in OpenAL as a learning exercise. I've looked through several examples and tutorials and I always got the same result: no sound and no errors. I've read the OpenAL Documentation and it never mentions something like that.

I eventually thought that I may not have OpenAL installed, which funny enough, I didn't. So after installing it, I also had no sound and no errors.

Then I thought that my buffer was filling incorrectly, so I tried to play it with just the Java library. Turns out it plays completely fine!

[After lots of test, EDITED]

Now I have audio, but it's only one "bump" after calling alSourcePlay and sometimes when the application ends. This occurs if I use AL_FORMAT_STEREO8 or AL_FORMAT_MONO8 instead of the 16 bit formats. I suppose this is a problem with the format, but with the otherwise correct format nothing happens; not even a "bump".

I just don't know what could be wrong. I must admit, this is my first time working with OpenAL. There is little information about troubleshooting OpenAL, it seems the big focus is on OpenGL and Vulkan.

Here is the code I've written so far. I am using LWJGL to access OpenAL and Java 8.


import static org.lwjgl.openal.AL10.AL_BUFFER;
import static org.lwjgl.openal.AL10.AL_FORMAT_MONO16;
import static org.lwjgl.openal.AL10.AL_FORMAT_MONO8;
import static org.lwjgl.openal.AL10.AL_FORMAT_STEREO16;
import static org.lwjgl.openal.AL10.AL_FORMAT_STEREO8;
import static org.lwjgl.openal.AL10.AL_GAIN;
import static org.lwjgl.openal.AL10.AL_INVALID_ENUM;
import static org.lwjgl.openal.AL10.AL_INVALID_VALUE;
import static org.lwjgl.openal.AL10.AL_NO_ERROR;
import static org.lwjgl.openal.AL10.AL_OUT_OF_MEMORY;
import static org.lwjgl.openal.AL10.AL_PITCH;
import static org.lwjgl.openal.AL10.AL_POSITION;
import static org.lwjgl.openal.AL10.AL_VELOCITY;
import static org.lwjgl.openal.AL10.alBufferData;
import static org.lwjgl.openal.AL10.alDeleteBuffers;
import static org.lwjgl.openal.AL10.alDeleteSources;
import static org.lwjgl.openal.AL10.alGenBuffers;
import static org.lwjgl.openal.AL10.alGenSources;
import static org.lwjgl.openal.AL10.alGetError;
import static org.lwjgl.openal.AL10.alListener3f;
import static org.lwjgl.openal.AL10.alSource3f;
import static org.lwjgl.openal.AL10.alSourcePlay;
import static org.lwjgl.openal.AL10.alSourcef;
import static org.lwjgl.openal.AL10.alSourcei;
import static org.lwjgl.openal.ALC10.alcCloseDevice;
import static org.lwjgl.openal.ALC10.alcCreateContext;
import static org.lwjgl.openal.ALC10.alcMakeContextCurrent;
import static org.lwjgl.openal.ALC10.alcOpenDevice;

import java.io.BufferedInputStream;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;

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

import org.lwjgl.openal.AL;
import org.lwjgl.openal.ALC;
import org.lwjgl.openal.ALC10;
import org.lwjgl.openal.ALCCapabilities;

import dev.ckitty.engine.kc.io.AutoByteBuffer;
import dev.ckitty.engine.kc.io.FileLoader;
import dev.ckitty.engine.kc.main.Engine;
import dev.ckitty.engine.kc.utils.Util;

public class AudioMaster {

    private long device, context;
    private int buffer, source;

    public AudioMaster() {
        init();
        load();
        source();

        Engine.log("Playing!");
        play();
        Util.waitSec(10);

        deinit();
    }

    public void init() {
        device = alcOpenDevice((ByteBuffer) null);
        if (device == 0)
            throw new IllegalStateException("Failed to open the default device.");

        ALCCapabilities deviceCaps = ALC.createCapabilities(device);

        context = alcCreateContext(device, (IntBuffer) null);
        if (context == 0)
            throw new IllegalStateException("Failed to create an OpenAL context.");

        alcMakeContextCurrent(context);
        Engine.log(erroralc());

        AL.createCapabilities(deviceCaps);
        Engine.log(error());
    }

    public void load() {
        AutoByteBuffer abb = new AutoByteBuffer();
        AudioFormat format = null;

        try {
            InputStream is = FileLoader.Internal.getInputStream("/kc/audios/LFZ - Popsicle.wav");
            BufferedInputStream bis = new BufferedInputStream(is);
            AudioInputStream ais = AudioSystem.getAudioInputStream(bis);
            abb.put(ais);
            format = ais.getFormat();

            ais.close();
            bis.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

        buffer = alGenBuffers();
        int samplerate = (int) format.getSampleRate();
        alBufferData(buffer, AL_FORMAT_STEREO8, abb.flip(), samplerate);
    }

    public void source() {
        source = alGenSources();
        Engine.log(error());
        alSourcef(source, AL_GAIN, 1f);
        alSourcef(source, AL_PITCH, 1f);
        alSource3f(source, AL_POSITION, 0f, 0f, 0f);
        alSourcei(source, AL_BUFFER, buffer);
    }

    public void play() {
        alListener3f(AL_POSITION, 0, 0, 0);
        alListener3f(AL_VELOCITY, 0, 0, 0);



        Engine.log("PLAYING");
        alSourcePlay(source);
    }

    public String erroralc() {
        switch (ALC10.alcGetError(device)) {
          case AL_NO_ERROR: return "ALC_NO_ERROR";
          case AL_INVALID_ENUM: return "ALC_INVALID_ENUM";
          case AL_INVALID_VALUE: return "ALC_INVALID_VALUE";
          case AL_OUT_OF_MEMORY: return "ALC_OUT_OF_MEMORY";
          /* ... */
          default:
            return "Unknown error code";
        }
    }

    public String error() {
        switch (alGetError()) {
          case AL_NO_ERROR: return "AL_NO_ERROR";
          case AL_INVALID_ENUM: return "AL_INVALID_ENUM";
          case AL_INVALID_VALUE: return "AL_INVALID_VALUE";
          case AL_OUT_OF_MEMORY: return "AL_OUT_OF_MEMORY";
          /* ... */
          default:
            return "Unknown error code";
        }
    }

    public void deinit() {
        alDeleteSources(source);
        alDeleteBuffers(buffer);
        alcCloseDevice(device);
    }

}

AutoByteBuffer


import java.io.InputStream;
import java.nio.ByteBuffer;

public class AutoByteBuffer {

    private byte[] array = new byte[0];
    private int index;

    public void put(InputStream is) {
        int n;
        byte[] data = new byte[1024];
        try {
            while ((n = is.read(data)) != -1)
                put(data, 0, n);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void put(byte[] src) {
        put(src, 0, src.length);
    }

    public void put(byte[] src, int off, int length) {
        resize(src.length);
        for (int i = 0; i < length; i++) {
            array[index++] = src[off + i];
        }
    }

    protected void resize(int bytes) {
        Math.addExact(index, bytes);
        if (index + bytes >= array.length) {
            int newsize = (int) ((index + bytes) * 1.5 + 0.5);
            if (newsize < index + bytes)
                throw new RuntimeException("Could not store enough memory!");

            byte[] copy = new byte[newsize];
            System.arraycopy(array, 0, copy, 0, index);
            array = copy;
        }
    }

    public ByteBuffer flip() {
        ByteBuffer buffer = ByteBuffer.allocate(index);
        buffer.put(array, 0, index);
        buffer.flip();
        return buffer;
    }

}

Console Output

 > Starting AudioMaster!
AL lib: (EE) UpdateDeviceParams: Failed to set 44100hz, got 48000hz instead
 > ALC_NO_ERROR
 > AL_NO_ERROR
 > AL_NO_ERROR
 > Playing!
 > PLAYING

Solution

  • I still don't know what happened, but how I followed this tutorial. The dude tells us to use this code to load wav files and it seems to work just fine! My theory is that is works well because it uses LWJGL buffers instead of java ones. Looking at the source of LWJGL it seems it does matters as it's trying to get the memory address of the buffers. And, of course, if LWJGL is the one creating the buffer, it will know exactly where it is.