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. bool ogg_stream::stream(ALuint buffer) { char data[BUFFER_SIZE]; int size = 0; int section; int result;
while(size < BUFFER_SIZE) { result = ov_read(&oggStream, data + size, BUFFER_SIZE - size, 0, 2, 1, & section);
if(result > 0) size += result; else if(result < 0) throw oggString(result); else break; }
if(size == 0) return false;
alBufferData(buffer, format, data, size, vorbisInfo->rate); check();
return false; }
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. 'ov_read' does exactly what you may be thinking it does; it reads data from the Ogg bitstream. vorbisfile does all the decoding of the bitstream, so we don't have to worry about that. This function takes our 'oggStream' structure, a data buffer where it can write the decoded audio, and the size of the chunk you want to decode. The last 4 arguments you don't really have to worry about but I will explain anyway. Relatively: the first indicates little endian (0) or big endian (1), the second indicates the data size (in bytes) as 8 bit (1) or 16 bit (2), the third indicates whether the data is unsigned (0) or signed (1), and the last gives the number of the current bitstream.
The return value of 'ov_read' indicates several things. If the value of the result is positive then it represents how much data was read. This is important because 'ov_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 'ov_read' over 'BUFFER_SIZE' in any case. If the result of 'ov_read' happens to be negative then it indicates that there was an error in the bitstream. The value of the result is an error code in this case. If the result happens to equal zero then there is nothing left in the file to play.
What makes this code complicated is the while loop. This method was designed to be modular and modifiable. You can change 'BUFFER_SIZE' to whatever you would like and it will still work. But this requires us to make sure that we fill the entire size of the buffer with multiple calls to 'ov_read' and make sure that everything aligns properly. 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 'ov_read'. We employ the 'format' and 'vorbisInfo' data that we set up earlier void ogg_stream::empty() { int queued;
alGetSourcei(source, AL_BUFFERS_QUEUED, &queued);
while(queued--) { ALuint buffer;
alSourceUnqueueBuffers(source, 1, &buffer); check(); } }
This method will will unqueue any buffers that are pending on the source. void ogg_stream::check() { int error = alGetError();
if(error != AL_NO_ERROR) throw string("OpenAL error was raised."); }
This saves us some typing for our error checks. string ogg_stream::errorString(int code) { switch(code) { case OV_EREAD: return string("Read from media."); case OV_ENOTVORBIS: return string("Not Vorbis data."); case OV_EVERSION: return string("Vorbis version mismatch."); case OV_EBADHEADER: return string("Invalid Vorbis header."); case OV_EFAULT: return string("Internal logic fault (bug or heap/stack corruption."); default: return string("Unknown Ogg error."); } }
This will 'stringify' an error message so it makes sense when you read it on a console or MessageBox or whatever. 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. int main(int argc, char* argv[]) { ogg_stream ogg;
alutInit(&argc, argv);
This should be a no-brainer. try { if(argc < 2) throw string("oggplayer *.ogg");
ogg.open(argv[1]);
ogg.display();
Since we are using C++ we will also be using the try/catch/throw keywords for exception handling. You have probably noticed that I've been throwing strings throughout the code in this article.
The first thing I have done here is check to make sure that the user has supplied us with a file path. If there is no arguments to the program then we can't really do anything, so we will simply show the user a little message indicating the Ogg extension. Not very informative but a pretty standard way of handling this in a console application. If there was an argument to the program then we can use it to open a file. We'll also display info on the Ogg file for completeness. if(!ogg.playback()) throw string("Ogg refused to play.");
while(ogg.update()) { if(!ogg.playing()) { if(!ogg.playback()) throw string("Ogg abruptly stopped."); else cout << "Ogg stream was interrupted.\n"; } }
cout << "Program normal termination."; cin.get(); }
I find a programs main loop is always the most fun part to write. We begin by playing the Ogg. An Ogg may refuse to play if there is not enough data to stream through the initial 2 buffers (in other words the Ogg is too small) or if it simply can not read the file.
The program 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. This may seem like it serves the same purpose as 'update', but it will also solve some other issues that have to do with the system. As a simple test I ran this program while also running a lot of other programs at the same time. Eating up as much cpu time as I could to see how oggplayer would react. You may be surprised to find that the interrupt message does get displayed. Streaming can be interrupted by external processes. This does not raise an error however. Keep that in mind.
If nothing else happens then the program will exit normally with a little message to let you know. catch(string error) { cout << error; cin.get(); }
Will catch an error string if one was thrown and display some information on why the program had to terminate. ogg.release();
alutExit();
return 0; }
The end of our main is also a no-brainer. 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. How come my console displays 'oggplayer *.ogg' when I run the example program?
You have to specify a filename. You can simply drag and drop an Ogg file over the program and it should work fine. I did not supply a sample Ogg to go with the example however. Partially due to the legality of using someone else's music and partially to reduce the file size. If someone wants to donate a piece (and I like it I may add it to the example files. But please do not submit copyrighted material or hip-hop .
Download the Dev-C++ source and project file. Download the Visual C++ 6.0 source and project file - (ported by TheCell) Download the Delphi port for this tutorial - (ported by Sascha Willems, updated by Peter Hine) Download the Linux port of this tutorial - (ported by Lee Trager) |