OpenAL + LWJGL + jME2 problem when using extensively MemoryPlayer + StreamPlayer

For jcrpg I've started to use jmex audio and constantly run into smaller annoying problems.

Anyway, I've done some changes in jmex audio, and all these points are solved though not perfectly.


  1. StreamingAudioPlayer can't play short sounds (I've modified in an ugly way to check for file size and modify buffer size according to the extreme small file size (for longer samples still using the regular buffer sizes) , so this part i won't propose as patch)
  2. MemoryPlayer can't fire events like track stop (I've added a PlayerThread similar to StreamingPlayer's that polls for stop and fires event, this is not so ugly I think.)
  3. Worst: Using both (Memory and Streaming) for multiple sounds for longer durations and multiple sounds repeatedly (so openAL sources are reused) openAL sources got stuck, throwing openAL API errors/exceptions upon reusing (getNextFreeSource returns a source but it doesnt play, with exception coming from openAL) (I've separated a source pool for StreamingPlayer, and one for MemoryPlayer, this seems to work okay.)



    (Under Linux 32-bit)



    I'm asking if you have run into such problems?





    If there's need I will try to create some polished patches and share it for people to test.

I'm experiencing the same problem.

Short ogg file doesn't played. Actually It is played, but stopped at once.

Can you share the patch?

It'll help a lot.

Thanks in advance.  :slight_smile:

Are you on Linux mulova?



(if I remember correctly the openAL stuff can have issues in Linux…)

basixs said:

Are you on Linux mulova?

(if I remember correctly the openAL stuff can have issues in Linux...)

No, It is reproduced in Windows. I'll do a close look at the code once more.
timong said:

1. StreamingAudioPlayer can't play short sounds (I've modified in an ugly way to check for file size and modify buffer size according to the extreme small file size (for longer samples still using the regular buffer sizes) , so this part i won't propose as patch)

For some reason, audio track was stoped as soon as started. Fixed now. (rev 4342)

timong said:

3. Worst: Using both (Memory and Streaming) for multiple sounds for longer durations and multiple sounds repeatedly (so openAL sources are reused) openAL sources got stuck, throwing openAL API errors/exceptions upon reusing (getNextFreeSource returns a source but it doesnt play, with exception coming from openAL) (I've separated a source pool for StreamingPlayer, and one for MemoryPlayer, this seems to work okay.)

It's because OpenAL Source was used for both memory buffer and stream buffer.
Sources are split into two part. Now fixed. (rev 4342)

Nice fix there! Much nicer than my version was actually. :slight_smile: I'll test it now , updating SVN right now! Been off for a while… Thanks for your work!

One thing I'm still missing in this nice fix is the event firing stop for short sounds played with openalmemoryplayer.

Here's what I have as an ugly patch right now to make me able to follow when sounds are stopped:



/*
 * Copyright (c) 2003-2009 jMonkeyEngine
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.jmex.audio.openal;

import java.nio.IntBuffer;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.lwjgl.openal.AL10;

import com.jme.math.Vector3f;
import com.jme.util.geom.BufferUtils;
import com.jmex.audio.AudioBuffer;
import com.jmex.audio.AudioSystem;
import com.jmex.audio.AudioTrack;
import com.jmex.audio.player.MemoryAudioPlayer;

/**
 * @see MemoryAudioPlayer
 * @author Joshua Slack
 * @version $Id: OpenALMemoryAudioPlayer.java 4342 2009-05-13 00:45:51Z mulova $
 */
public class OpenALMemoryAudioPlayer extends MemoryAudioPlayer {
    private static final Logger logger = Logger.getLogger(OpenALMemoryAudioPlayer.class.getName());
   
    private OpenALSource source;

    private boolean isPaused = false;

    public OpenALMemoryAudioPlayer(AudioBuffer buffer, AudioTrack parent) {
        super(buffer, parent);
    }
   
    @Override
    public void init() {
    }
    private IntBuffer idBuffer = BufferUtils.createIntBuffer(1);
    @Override
    public void cleanup() {
        synchronized (this) {
            if (source == null)
                return;
           idBuffer.clear();
           idBuffer.put(((OpenALAudioBuffer)getBuffer()).getId());
           AL10.alSourceUnqueueBuffers(source.getId(), idBuffer);
            AL10.alSourceRewind(source.getId());
            AL10.alSourceStop(source.getId());
            source = null;
        }

    }

    @Override
    public boolean isPlaying() {
        return source != null && source.getState() == AL10.AL_PLAYING;
    }

    @Override
    public boolean isActive() {
        return source != null && (source.getState() == AL10.AL_PLAYING || source.getState() == AL10.AL_PAUSED);
    }

    @Override
    public boolean isStopped() {
        return source != null && source.getState() == AL10.AL_STOPPED;
    }

    @Override
    public void pause() {
       spentTime += System.currentTimeMillis() - lastPlayStartTime;
        isPaused = true;
        if (source!=null) AL10.alSourcePause(source.getId());
        setPauseTime(System.currentTimeMillis());
    }
    long lastPlayStartTime = 0;
    long spentTime = 0;
    long length = 0;

    @Override
    public void play() {
        synchronized (this) {
            if (isPaused) {
                isPaused = false;
                AL10.alSourcePlay(source.getId());
                setStartTime(getStartTime()+System.currentTimeMillis()-getPauseTime());
                return;
            }
   
            source = ((OpenALSystem) AudioSystem.getSystem()).getNextFreeMemorySource();
            if (source == null) return;
            source.setTrack(getTrack());
            applyTrackProperties();
           
            AL10.alSource3f(source.getId(), AL10.AL_POSITION, 0, 0, 0);
            AL10.alSource3f(source.getId(), AL10.AL_VELOCITY, 0, 0, 0);
            AL10.alSource3f(source.getId(), AL10.AL_DIRECTION, 0, 0, 0);
            AL10.alSourcei(source.getId(), AL10.AL_SOURCE_RELATIVE, getTrack().isRelative() ? AL10.AL_TRUE : AL10.AL_FALSE);
           
            AL10.alSourcei(source.getId(), AL10.AL_BUFFER, ((OpenALAudioBuffer)getBuffer()).getId());
            AL10.alSourcePlay(source.getId());
            setStartTime(System.currentTimeMillis());
            lastPlayStartTime = System.currentTimeMillis();
            spentTime = 0;
            length = (long)(getBuffer().getLength()*1000);
            trackStopInNewThread(300,source);
        }
    }
   
    /**
     * The thread that updates the sound.
     * XXX: I am considering abolishing these one-per-sound threads.
     */
    class PlayerThread extends Thread {
        // at what interval update is called.
        long interval;
        OpenALSource source = null;

        /** Creates the PlayerThread */
        PlayerThread(long interval, OpenALSource source) {
            this.interval = interval;
            this.source = source;
            setDaemon(true);
        }

        /** Calls update at an interval */
        public void run() {
            try {
               // TODO this isn't working on high load
               sleep(interval);
               
                while (spentTime<length || isActive()) {
                    sleep(interval);
                    if (!isPaused)
                    {
                       spentTime+=System.currentTimeMillis()-lastPlayStartTime;
                       lastPlayStartTime = System.currentTimeMillis();
                    }
                }
                sleep(interval);
                getTrack().stop();
            } catch (Exception e) {
//                e.printStackTrace();
            }
        }
        public boolean isActive() {
            return source != null && (source.getState() == AL10.AL_PLAYING || source.getState() == AL10.AL_PAUSED);
        }

    }

    public void trackStopInNewThread(long updateIntervalMillis, OpenALSource source) {
        try {
                PlayerThread playerThread = new PlayerThread(updateIntervalMillis,source);
                playerThread.start();
           
        } catch (Exception e) {
            logger.log(Level.SEVERE, "Audio Error!", e);
        }

    }
   
   
    @Override
    public void applyTrackProperties() {
        OpenALPropertyTool.applyProperties(this, source);
        if (source != null)
            AL10.alSourcei(source.getId(), AL10.AL_LOOPING, isLoop() ? AL10.AL_TRUE : AL10.AL_FALSE);
    }

    @Override
    public void stop() {
        synchronized (this) {
            if (source == null)
                return;
            AL10.alSourceStop(source.getId());
            source = null;
        }
    }

    /**
     * checks OpenAL error state
     */
    protected void check() {
        int error = AL10.alGetError();
        if (error != AL10.AL_NO_ERROR) {
            logger.log(Level.INFO, "OpenAL error was raised. errorCode={0}", error);
        }
    }

    @Override
    public void loop(boolean shouldLoop) {
        super.loop(shouldLoop);
        if (source != null)
            AL10.alSourcei(source.getId(), AL10.AL_LOOPING, shouldLoop ? AL10.AL_TRUE : AL10.AL_FALSE);
    }
   
    @Override
    public void updateTrackPlacement() {
        Vector3f pos = getTrack().getWorldPosition();
        Vector3f vel = getTrack().getCurrVelocity();

        AL10.alSource3f(source.getId(), AL10.AL_POSITION, pos.x, pos.y, pos.z);
        AL10.alSource3f(source.getId(), AL10.AL_VELOCITY, vel.x, vel.y, vel.z);
    }

    @Override
    public void setVolume(float volume) {
        super.setVolume(volume);
        OpenALPropertyTool.applyChannelVolume(source, volume);
    }
   
    @Override
    public void setPitch(float pitch) {
        if (pitch > 0f && pitch <= 2.0f) {
            super.setPitch(pitch);
            OpenALPropertyTool.applyChannelPitch(source, getPitch());
        } else
            logger.warning("Pitch must be > 0 and <= 2.0f");
    }

    @Override
    public void setMaxAudibleDistance(float maxDistance) {
        super.setMaxAudibleDistance(maxDistance);
        OpenALPropertyTool.applyChannelMaxAudibleDistance(source, maxDistance);
    }

    @Override
    public void setMaxVolume(float maxVolume) {
        super.setMaxVolume(maxVolume);
        OpenALPropertyTool.applyChannelMaxVolume(source, maxVolume);
    }

    @Override
    public void setMinVolume(float minVolume) {
        super.setMinVolume(minVolume);
        OpenALPropertyTool.applyChannelMinVolume(source, minVolume);
    }

    @Override
    public void setReferenceDistance(float refDistance) {
        super.setReferenceDistance(refDistance);
        OpenALPropertyTool.applyChannelReferenceDistance(source, refDistance);
    }

    @Override
    public void setRolloff(float rolloff) {
        super.setRolloff(rolloff);
        OpenALPropertyTool.applyChannelRolloff(source, rolloff);
    }

    @Override
    public int getBitRate() {
        return getBuffer().getBitRate();
    }

    @Override
    public int getChannels() {
        return getBuffer().getChannels();
    }

    @Override
    public int getDepth() {
        return getBuffer().getDepth();
    }

    @Override
    public float getLength() {
        return getBuffer().getLength();
    }
}



There is this playtime thing which is quite ugly, but probably you can see what i'm aiming at.