Improving EasyOgg

Well ogg is used in a lot of things so I’m sure someone must no how to fix the clicking problem.
Though fading would be good if you could add that. Though not sure if it would solve my problem unless the fade time was short or able to set the fade time.
Would make my music a lot nicer when stopping since its just a instant stop.

Heh, I use OGG, just not EasyOGG :slight_smile: I’m mostly pushing the OGGs to OpenAL rather than to Java sound.

Kev

Think I got what you were talking about.

http://www.cokeandcode.com/downloads/EasyOgg-0.6.zip

Kev

Ok cool.
Hopefully someone can help with the 2 horrible clicking problems, I been playing around trying to stop it with no luck.

I thank you for your source code. I think I’m going to study it because I have to rewrite the sound system of my game because of problems with too often full garbage collections.

There is something wrong with EasyOgg. It is impossible to play 2 sounds simultaneously. I tried to use 2 OggClip instances at the same time and I heard only the first one. Do you know how to solve this problem? I think that each clip uses the same SourceDataLine instance ???

It was originally implemented just so someone could play ogg music through JavaSound, so no mixer was required. It’d need mixer support to get multiple sound fx working I think. Assuming your oggs have the same rate/channels the same line might get picked by AudioSystem currently I guess (?), though here it works with multiple clips it seems.

Kev

Why does it work with multiple clips in your case? Do they have different rates?

My clips do have different rates, but I’m not sure thats the reason it works. It could be the JavaSound implementation also. I’m on Windows here.

Kev

Lol, my problem is reproducible under Linux and Windows XP :wink: I think that the default mixer selected by the audio system supports only a very few lines on some machines. I’m going to select the mixer by myself.

Some controls are not supported by the default mixer. Instead of using a lot of try/catch clause, I prefer using the method Mixer.isControlSupported(Type control).

I was right, now it works:


/**
 * Try to find a mixer that both supports this line and supports
 * as much line as possible
 */
private static final Mixer getBestFittedMixer(DataLine.Info info){
    Mixer currentMixer=null;
    Mixer.Info[] mi=AudioSystem.getMixerInfo();
    Mixer bestMixer=null;
    for(int i=0;i<mi.length;i++)
        {currentMixer=AudioSystem.getMixer(mi[i]);
         if(currentMixer.isLineSupported(info))
             if(bestMixer==null||bestMixer.getMaxLines(info)<currentMixer.getMaxLines(info))
                 bestMixer=currentMixer;	                     
        }
    //The best mixer cannot be null as AudioSystem.isLineSupported returned true
    return(bestMixer);
}

I’m now improving the gain system. I will submit my modifications on the SVN repository of TUER, I encourage you to watch the source code. As there was no license, I had to put one (to avoid some legal problems and because TUER is considered as a 100% open source project), I hope it is not problematic, sorry.

I’ve tried to use EasyOgg but found it buggy when playing the same clip more than once. Test it by playing 10 clips in a loop:


for (int i=0; i<10; i++) {
  ogg.play();
}

I get a bunch of the following exception:


java.lang.NullPointerException
        at org.newdawn.easyogg.OggClip.stop(OggClip.java:301)
        at org.newdawn.easyogg.OggClip.play(OggClip.java:228)
        ...

I’ve looked at the code and I think it is caused by threading bugs. A new thread is started every time play is invoked. This is done without waiting for the previous thread to stop. This causes bugs as the threads change the same shared member variables. I’ll try to fix this if/when I get the time and send the changes to kev. Until then I’ve made a wrapper that creates a new OggClip every time play is invoked. That way OggClip.play() is never invoked more than once. Here it is:


package org.newdawn.easyogg;

import java.io.IOException;
import java.net.URL;

/**
 * Wraps an OggClip to avoid the threading bugs in OggClip. The wrapper can be
 * played more than once at a time without problem.
 */
public class OggClipWrapper {
    
    private OggClip clip;
    private URL resource;
    private float gain;
    private boolean isLooping = false;
    
    public OggClipWrapper(URL resource, float gain) {
        this.resource = resource;
        this.gain = gain;
    }
    
    public void play() {
        if (clip != null && isLooping) {
            clip.stop();
        }
        isLooping = false;

        try {
            clip = new OggClip(resource.openStream());
            clip.setGain(gain);
            clip.play();
        } catch (IOException e) {
            e.printStackTrace();
            clip = null;
        }
    }
    
    public void stop() {
        if (clip != null) {
            isLooping = false;
            clip.stop();
            clip = null;
        }
    }
    
    public void loop() {
        if (clip != null && isLooping) {
            clip.stop();
        }        
        
        try {
            clip = new OggClip(resource.openStream());
            clip.setGain(gain);
            clip.loop();
            isLooping = true;
        } catch (IOException e) {
            e.printStackTrace();
            clip = null;
        }
    }
    
    public void pause() {
        if (clip != null) {
            clip.pause();
        }
    }
    
    public void resume() {
        if (clip != null) {
            clip.resume();
        }
    }
}


This has the added “benifit” of being able to play the same clip more than once at the same time.

On my view, having to create several clips to play the same sound several times even though one of the clips is still been played is not a bug but what happens when you play a clip that is still been played is a bug.

I think it is a bug. If you want to play the same OggClip twice you have to do something like this:


ogg.stop();
while(!ogg.stopped()) {
  Thread.yield()
}
ogg.play();

…not how the api was inteded to be used.

Thanks. I will update my source code to handle this case, you’re right.

There is another bug. When you play a sound whose length is less than the buffer size, it doesn’t work.

I think I’m going to rewrite something to play ogg samples because EasyOgg samples are streamed at real time whereas I would like to load data prior to playback in order to get better performance, more accurate control (better stop mechanism) and no more need of creating lots of threads. Maybe Pulpcore has a better support of ogg files.

I thought about trying to fix the stopping bug, but found out that it would be easier to start from scratch. One of the problems is that the constructor takes an InputStream. I only need it to take an URL which makes things much easier. I can just open up a new stream from the URL instead of synchronizing it with the previous player thread. It is trivial to add a constructor that takes a byte array containing the ogg file. So it is easily possible to extend it so the sound can be streamed from memory, without passing an InputStream to the constructor. Also if you use an URL the whole ogg file will not be loaded into memory like EasyOgg does.

I’ve got a working version that has almost the same api as EasyOgg. I’ve replaced the setGain with setVolume that converts the normalised value between 0-1 to a gain dB using the following formula:


float gain = (float) (Math.log(volume) / Math.log(10.0) * 20.0);

No need for a -1 “default” gain value.

I also use OggInputStream to decode the file. It is easier to use and it has some bug fixes in it. It will play sounds whose length is less than the buffer size.

Also added a fade duration that will fade the sound in and out on pause, resume and stop. I use it because it sounds better, but I assume it can also be used to remove clicking.

I plan to release the code, when it is a bit more polished.

I still had a lot problems when I used easyOgg myself, it just never really worked well for me.
So wouldn’t mind seeing what you come up with tom.

Don’t forget that EasyOgg calls the method drain(), it is a blocking method :frowning: It is not well fitted for gaming. I think EasyOgg is excellent for a small media player but not for a true game.

I fixed this bug but I use a different approach, I use sounds (Clip) that are loaded prior to playback to avoid input/output during the game. I will submit my modifications on my SVN repository in some days. Nevertheless, I have modified a little bit the API to fit into my needs and I’ve been writing a sound manager to be able to play several sounds together but by avoiding any blocking call and by minimizing the use of sound resource, it is very important to prevent the playback from decreasing the frame rate.

About licensing, as EasyOgg is inspired of JOrbisPlayer (under GPL license), TUER is under GPL license and my classes reuses a piece of code written by Vincent Stahl extracted from d3caster (under GPL license), obviously my classes are under GPL license, I hope it is not a problem (don’t forget the viral clause).