From 49b57bace3b45f87b152d8ce0d63750f078f6eec Mon Sep 17 00:00:00 2001 From: krishna_gadepalli Date: Sat, 3 Feb 2007 01:54:03 +0000 Subject: Added a new tutorial page for lesson8 (Ogg/Vorbis streaming) Added a title to all other pages git-svn-id: file:///home/mbien/NetBeansProjects/JOGAMP/joal-sync/svn-server-sync-demos/joal-demos/trunk@56 235fdd13-0e8c-4fed-b5ee-0a390d04b286 --- www/devmaster/lesson1.html | 2 +- www/devmaster/lesson2.html | 4 +- www/devmaster/lesson3.html | 4 +- www/devmaster/lesson4.html | 4 +- www/devmaster/lesson5.html | 2 +- www/devmaster/lesson6.html | 2 +- www/devmaster/lesson7.html | 2 +- www/devmaster/lesson8.html | 427 +++++++++++++++++++++++++++++++++++++++++++++ www/index.html | 2 + 9 files changed, 439 insertions(+), 10 deletions(-) create mode 100644 www/devmaster/lesson8.html (limited to 'www') diff --git a/www/devmaster/lesson1.html b/www/devmaster/lesson1.html index e3be401..ffa1eff 100644 --- a/www/devmaster/lesson1.html +++ b/www/devmaster/lesson1.html @@ -1,7 +1,7 @@ -Untitled Document +Lesson 1: Single Static Source diff --git a/www/devmaster/lesson2.html b/www/devmaster/lesson2.html index 089205d..88a8756 100644 --- a/www/devmaster/lesson2.html +++ b/www/devmaster/lesson2.html @@ -2,7 +2,7 @@ -DevMaster.net - OpenAL Tutorials: Lesson 2 +Lesson 2: Looping and Fade-away @@ -171,4 +171,4 @@ real quick and easy tutorial. It won't get too much more complicated at this poi - \ No newline at end of file + diff --git a/www/devmaster/lesson3.html b/www/devmaster/lesson3.html index 05f0a5d..17ae9ba 100644 --- a/www/devmaster/lesson3.html +++ b/www/devmaster/lesson3.html @@ -1,7 +1,7 @@ - DevMaster.net Game Development + Lesson 3: Multiple Sources
@@ -295,4 +295,4 @@ OpenAL Tutorials from DevMaster.net. Reprinted with Permission.
- \ No newline at end of file + diff --git a/www/devmaster/lesson4.html b/www/devmaster/lesson4.html index 5084d2c..f3cf6a8 100644 --- a/www/devmaster/lesson4.html +++ b/www/devmaster/lesson4.html @@ -1,7 +1,7 @@ - DevMaster.net Game Development + Lesson 4: A Closer Look at ALC
@@ -163,4 +163,4 @@ alc.alcCloseDevice(device);

 

- \ No newline at end of file + diff --git a/www/devmaster/lesson5.html b/www/devmaster/lesson5.html index 66c3524..ad4d98b 100644 --- a/www/devmaster/lesson5.html +++ b/www/devmaster/lesson5.html @@ -1,7 +1,7 @@ -Untitled Document +Lesson 5: Sources Sharing Buffers diff --git a/www/devmaster/lesson6.html b/www/devmaster/lesson6.html index 4d557ea..0f85423 100644 --- a/www/devmaster/lesson6.html +++ b/www/devmaster/lesson6.html @@ -1,7 +1,7 @@ -Untitled Document +Lesson 6: Advanced Loading and Error Handles diff --git a/www/devmaster/lesson7.html b/www/devmaster/lesson7.html index 9b3dfd0..ff3a5ba 100644 --- a/www/devmaster/lesson7.html +++ b/www/devmaster/lesson7.html @@ -1,7 +1,7 @@ -Untitled Document +Lesson 7: The Doppler Effect diff --git a/www/devmaster/lesson8.html b/www/devmaster/lesson8.html new file mode 100644 index 0000000..4d68908 --- /dev/null +++ b/www/devmaster/lesson8.html @@ -0,0 +1,427 @@ + + + + Lesson 8: Ogg/Vorbis Streaming + + + + + +
+
+ + +Projects +Wiki +Weblogs +Forums +JavaGames Home +Java.net + + + +
+ + +
+ + +OpenAL Tutorials from DevMaster.net. Reprinted with Permission.
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + +
+ +

OpenAL + Tutorials

+ +
+ +

DevMaster.net

+ +
+ + +

Ogg/Vorbis Streaming +
+ + +Lesson 8

+ + + +

Author: Jesse + Maurais
+ + +
Adapted for Java by: Krishna + Gadepalli

+ + +

+ + + +

Launch the Demo via Java Web Start (not working yet)

+ + + +

This is a translation of +OpenAL Lesson 8: OggVorbis Streaming Using The Source Queue +tutorial from DevMaster.net to JOAL. + +

+ +

+"This software is based on or using the J-Ogg library available + from http://www.j-ogg.de and copyrighted by Tor-Einar Jarnbjo." + +

+ +

An Introduction to OggVorbis

+ + + +

Ever heard of Ogg? There's more to it than a funny sounding name. It's the +biggest thing to happen for audio compression since mp3's (also typically used +for music). Hopefully, one day, it will replace mp3's as the mainstream standard +for compressed audio. Is it better than mp3? That is a question that is a little +more difficult to answer. It's a pretty strong debate in some crowds. There are +various arguments about compression ratio vs. sound quality which can sometimes +get really cumbersome to read through. I personally don't have any opinion on +which is "better". I feel the evidence in either case is arguable and not worth +mentioning. But for me the fact that Ogg is royalty free (which mp3 is not) wins +the argument hands down. The mp3 licensing fee is by no means steep for +developers with deep pockets, but as an independent working on a project in your +spare time and on minimal resources, shelling out a few grand in fees is not an +option. Ogg may be the answer to your prayers.

+ + + +

Designing Your OggVorbis Streaming API

+ + +

Without further ado let's get to some code.

+ + +

This tutorial will be written in Java and has two main classes: +OggDecoder and +OggStreamer. The +OggDecoder class is wrapper on the J-Ogg library to decode an Ogg/Vorbis stream and will not be described in this +tutorial. OggStreamer which is the main class doing most of the working for streaming using OpenAL is explained below. + +

+ +
    // The size of a chunk from the stream that we want to read for each update.
private static int BUFFER_SIZE = 4096*8;
+ + + +

'BUFFER_SIZE' defines how big a chunk we want +to read from the stream on each update. You will find (with a little +experimentation) that larger buffers usually produce better sound quality since +they don't update as often, and will generally avoid any abrupt pauses or sound +distortions. Of course making your buffer too big will also eat up more memory. +Making a stream redundant. I beleive 4096 is the minimum buffer size one can +have. I don't recommend using one that small. I tried, and it caused many +clicks.

+ + +

So why should we even bother with streaming? Why not load the whole file into +a buffer and then play it? Well, that is a good question. The quick answer is +that there is too much audio data. Even though the actual Ogg file size is quite +small (usually around 1-3 MB) you must remember that is compressed audio data. +You cannot play the compressed form of the data. It must be decompressed and +formatted into a form OpenAL recognizes before it can be used in a buffer. That +is why we stream the file.

+ + + +
    /**
* The main loop to initialize and play the entire stream
*/
public boolean playstream() { ... }

/**
* Open the Ogg/Vorbis stream and initialize OpenAL based
* on the stream properties
*/
public boolean open() { ... }

/**
* OpenAL cleanup
*/
public void release() { ... }

/**
* Play the Ogg stream
*/
public boolean playback() { ... }

/**
* Check if the source is playing
*/
public boolean playing() { ... }

/**
* Update the stream if necessary
*/
public boolean update() { ... }

/**
* Reloads a buffer (reads in the next chunk)
*/
public boolean stream(int buffer) { ... }

/**
* Empties the queue
*/
protected void empty() { ... }
+ + + +

This will be the base of our Ogg streaming api. The public methods are +everything that one needs to actually get the Ogg to play. Protected methods are +more internal procedures. I won't go over each function +just yet. I believe my comments should give you an idea of what they're for.

+ + + +
    // Buffers hold sound data. There are two of them (front/back)
private int[] buffers = new int[2];

// Sources are points emitting sound.
private int[] source = new int[1];

private int format; // OpenAL data format
private int rate; // sample rate
+ + + +

First thing that I want to point out is that we have 2 buffers dedicated to +the stream rather than the 1 we have always used for wav files. This is +important. To understand this I want you to think about how double buffering +works in OpenGL/DirectX. There is a front buffer that is "on screen" at any +given second, while a back buffer is being drawn to. Then they are swapped. The +back buffer becomes the front and vice versa. Pretty much the same principle is +applied here. There is a buffer being played and one waiting to be played. When +the buffer being played has finished the next one starts. While the next buffer +is being played, the first one is refilled with a new chunk of data from the +stream and is set to play once the one playing is finished. Confused yet? I'll +explain this in more detail later on.

+ + + +
    public boolean open() {
oggDecoder = new OggDecoder(url);

if (!oggDecoder.initialize()) {
System.err.println("Error initializing ogg stream...");
return false;
}

if (oggDecoder.numChannels() == 1)
format = AL.AL_FORMAT_MONO16;
else
format = AL.AL_FORMAT_STEREO16;

rate = oggDecoder.sampleRate();

...
}
+ + + +

This creates a decoder for Ogg file, initializes it and grabs some information on the file. We extract the OpenAL format +enumerator based on how many channels are in the Ogg and then make a not of the sample rate.

+ + + +
    public boolean open() {
...

al.alGenBuffers(2, buffers, 0); check();
al.alGenSources(1, source, 0); check();

al.alSourcefv(source[0], AL.AL_POSITION , sourcePos, 0);
al.alSourcefv(source[0], AL.AL_VELOCITY , sourceVel, 0);
al.alSourcefv(source[0], AL.AL_DIRECTION, sourceDir, 0);

al.alSourcef(source[0], AL.AL_ROLLOFF_FACTOR, 0.0f );
al.alSourcei(source[0], AL.AL_SOURCE_RELATIVE, AL.AL_TRUE);

...
}
+ + + +

You've seen most of this before. We set a bunch of default values, position, +velocity, direction... But what is rolloff factor? This has to do with +attenuation. I will cover attenuation in a later article so I won't go too +in-depth, but I will explain it basically. Rolloff factor judges the strength of +attenuation over distance. By setting it to 0 we will have turned it off. This +means that no matter how far away the Listener is to the source of the Ogg they +will still hear it. The same idea applies to source relativity.

+ + + +
    public void release() {
al.alSourceStop(source[0]);
empty();

al.alDeleteSources(1, source, 0); check();
al.alDeleteBuffers(2, buffers, 0); check();
}
+ + +

We can clean up after ourselves using this. We stop the source, empty out any +buffers that are still in the queue, and destroy our objects. + +

+ +
    public boolean playback() {
if (playing())
return true;

if (!stream(buffers[0]))
return false;

if(!stream(buffers[1]))
return false;

al.alSourceQueueBuffers(source[0], 2, buffers, 0);
al.alSourcePlay(source[0]);

return true;
}
+ + + +

This will start playing the Ogg. If the Ogg is already playing then there is +no reason to do it again. We must also initialize the buffers with their first +data set. We then queue them and tell the source to play them. This is the first +time we have used 'alSourceQueueBuffers'. What it does basically is give the +source multiple buffers. These buffers will be played sequentially. I will +explain more on this along with the source queue momentarily. One thing to make +a note of though: if you are using a source for streaming never bind a buffer to +it using 'alSourcei'. Always use 'alSourceQueueBuffers' consistently.

+ + + +
    public boolean playing() {
int[] state = new int[1];

al.alGetSourcei(source[0], AL.AL_SOURCE_STATE, state, 0);

return (state[0] == AL.AL_PLAYING);
}
+ + +

This simplifies the task of checking the state of the source.

+ + + +
    public boolean update() {
int[] processed = new int[1];
boolean active = true;

al.alGetSourcei(source[0], AL.AL_BUFFERS_PROCESSED, processed, 0);

while (processed[0] > 0)
{
int[] buffer = new int[1];

al.alSourceUnqueueBuffers(source[0], 1, buffer, 0); check();

active = stream(buffer[0]);

al.alSourceQueueBuffers(source[0], 1, buffer, 0); check();

processed[0]--;
}

return active;
}
+ + +

Here is how the queue works in a nutshell: There is a 'list' of buffers. When +you unqueue a buffer it gets popped off of the front. When you queue a buffer it +gets pushed to the back. That's it. Simple enough?

+ + +

This is 1 of the 2 most important methods in the class. What we do in this +bit of code is check if any buffers have already been played. If there is then +we start popping each of them off the back of the queue, we refill the buffers +with data from the stream, and then we push them back onto the queue so that +they can be played. Hopefully the Listener will have no idea that we have done +this. It should sound like one long continuous chain of music. The 'stream' +function also tells us if the stream is finished playing. This flag is reported +back when the function returns.

+ + + +
    public boolean stream(int buffer) {
byte[] pcm = new byte[BUFFER_SIZE];
int size = 0;

try {
if ((size = oggDecoder.read(pcm)) <= 0)
return false;
} catch (Exception e) {
e.printStackTrace();
return false;
}

ByteBuffer data = ByteBuffer.wrap(pcm, 0, size);
al.alBufferData(buffer, format, data, size, rate);
check();

return true;
}
+ + +

This is another important method of the class. This part fills the buffers +with data from the Ogg bitstream. It's a little harder to get a grip on because +it's not explainable in a top down manner. 'oggDecoder.read' does exactly what you may +be thinking it does; it reads data from the Ogg bitstream. The j-ogg library does all +the decoding of the bitstream, so we don't have to worry about that. This +function takes a byte array as an argument and will decode atmost the capacity of +this array.

+ + +

The return value of 'oggDecoder.read' indicates several things. If the value of the +result is positive then it represents how much data was read. This is important +because 'read' may not be able to read the entire size requested (usually +because it's at the end of the file and there's nothing left to read). Use the +result of 'read' over 'BUFFER_SIZE' in any case. If the result of 'read' +happens to be negative then it indicates that there was an error in the +bitstream. If the result happens to equal zero then there is nothing left in the file to play.

+ + + +

The last part of this method is the call to 'alBufferData' +which fills the buffer id with the data that we streamed from the Ogg using 'read'. +We employ the 'format' and 'rate' values that we set up earlier

+ + + +
    protected void empty() {
int[] queued = new int[1];

al.alGetSourcei(source[0], AL.AL_BUFFERS_QUEUED, queued, 0);

while (queued[0] > 0)
{
int[] buffer = new int[1];

al.alSourceUnqueueBuffers(source[0], 1, buffer, 0);
check();

queued[0]--;
}

oggDecoder = null;
}
+ + +

This method will will unqueue any buffers that are pending on the source.

+ + + +
    protected void check() {
if (al.alGetError() != AL.AL_NO_ERROR)
throw new ALException("OpenAL error raised...");
}
+ + +

This saves us some typing for our error checks.

+ + + +

Making Your Own OggVorbis Player

+ + +

If you're with me so far then you must be pretty serious about getting this +to work for you. Don't worry! We are almost done. All that we need do now is use +our newly designed class to play an Ogg file. It should be a relatively simple +process from here on in. We have done the hardest part. I won't assume that you +will be using this in a game loop, but I'll keep it in mind when designing the +loop.

+ +

This should be a no-brainer.

+ + +
+    public boolean playstream() {
+        if (!open())
+            return false;
+        
+        oggDecoder.dump();
+        
+        if (!playback())
+            return false;
+        
+        while (update()) {
+            if (playing()) continue;
+            
+            if (!playback())
+                return false;
+        }
+        
+        return true;
+    }
+
+ +

The program opens the stream, dumps some stream information and then +will continually loop as long as the 'update' method continues to +return true, and it will continue to return true as long as it can successfully +read and play the audio stream. Within the loop we will make sure that the Ogg +is playing.

+ +

Answers To Questions You May Be Asking

+ + +

Can I use more than one buffer for the stream?

+ + +

In short, yes. There can be any number of buffers queued on the source at a +time. Doing this may actually give you better results too. As I said earlier, +with just 2 buffers in the queue at any time and with the cpu being clocked out +(or if the system hangs), the source may actually finish playing before the +stream has decoded another chunk. Having 3 or even 4 buffers in the queue will +keep you a little further ahead in case you miss the update.

+ + +

How often should I call ogg.update?

+ + +

This is going to vary depending on several things. If you want a quick answer +I'll tell you to update as often as you can, but that is not really necessary. +As long as you update before the source finishes playing to the end of the +queue. The biggest factors that are going to affect this are the buffer size and +the number of buffers dedicated to the queue. Obviously if you have more data +ready to play to begin with less updates will be necessary.

+ + +

Is it safe to stream more than one Ogg at a time?

+ + +

It should be fine. I haven't performed any extreme testing but I don't see +why not. Generally you will not have that many streams anyway. You may have one +to play some background music, and the occasional character dialog for a game, +but most sound effects are too short to bother with streaming. Most of your +sources will only ever have one buffer attached to them.

+ + +

So what is with the name?

+ + +

"Ogg" is the name of Xiph.org's container format for audio, video, and +metadata. "Vorbis" is the name of a specific audio compression scheme that's +designed to be contained in Ogg. As for the specific meanings of the words... +well, that's a little harder to tell. I think they involve some strange +relationship to Terry Pratchett novels. Here is a little page that goes into the +details.

+ + + + + + + + + + + + + + + + + + + + +
+ +

© 2003 DevMaster.net. + All rights reserved.

+ +
+ +

+ Contact us if you + want to write for us or for any comments, suggestions, or feedback.

+ +
+ + +

 

+ + + + diff --git a/www/index.html b/www/index.html index 8cf0203..b74be79 100644 --- a/www/index.html +++ b/www/index.html @@ -114,6 +114,8 @@ Loading and Error Handles.
  • lesson 7: The Doppler Effect
  • +
  • lesson 8: + Ogg/Vorbis Streaming
  • source for lessons 1,2,3 & 5
  • -- cgit v1.2.3