DevMaster.net  
[[ Home | Forums | 3D Engines Database | Wiki | Articles/Tutorials | Game Dev Jobs | IRC Chat Network | Contact Us ]]

Sound and Music Programming > OpenAL


Loading OggVorbis Files From Memory          
05/02/2004

Introduction

This tutorial is designed to follow directly from OpenAL Lesson 8: OggVorbis Streaming - Using The Source Queue by Jesse Maurais. For this reason, the code base used is almost identical to the one used in that lesson, the only changes being are in regards to how the file is loaded. Everything else is exactly the same. That's one of the real strengths of the OggVorbis libraries, you can load in the .ogg file however you please, and all the other calls are used in exactly the same way. So, on with the show.

Getting The File Into Memory

The OggVorbis file needs to be loaded into memory before we can start parsing them using the vorbis libraries. The method used in the example program is, in no uncertain terms, a true bodge. Normally, the file would have been preloaded into memory at loading time, maybe extracting it from a pak file or the like. For the sake of this tutorial, just ignore how I've gotten it into main memory.

Getting Ready To Read From Memory

The Vorbis libraries don't actually have any support for loading files from memory, they leave it up to you, only asking that you return the right data as and when they need it (This is a pretty cool method, meaning you can read the .ogg file from just about anywhere and anyhow). For this to work, they require you to provide four callback functions (if you don't know what a callback function is, get reading a book on C programming ;)

The callbacks are passed using the ov_callbacks structure and they are:

Fuction Name Function Description
read_func Function used to read data from memory
close_func Function used to close the file in memory
seek_func Function used to seek to a specific part of the file in memory
tell_func Function used to tell how much we have read so far

These functions are expected to work in exactly the same way as the standard C IO functions (fread(...), ftell(...) etc.), and as such have the same inputs and the same return values.

Once we have declared our callback functions, we store them within the ov_callback structure

vorbisCallbacks.read_func  = VorbisRead;
vorbisCallbacks.close_func = VorbisClose;
vorbisCallbacks.seek_func  = VorbisSeek;
vorbisCallbacks.tell_func  = VorbisTell;

Now that we have told the vorbis libs how we are going to perform all the different IO actions on the data, we need to actually pass the load function a pointer to our data. To do this, we need to define our own struct, as this will allow us to perform all the different IO actions we need.

The struct is as follows:

struct SOggFile
{
    char* dataPtr;    // Pointer to the data in memory
    int   dataSize;   // Size of the data
    int   dataRead;   // How much data we have read so far
};
SOggFile oggMemoryFile;

Once we have the structure we need to initialise it. Pretty simple stuff. We save the pointer to the data in dataPtr, the size of the data in dataSize, and set dataRead to 0 (as we have yet to actually read anything in). Now we just need to set the wheels in motion

    ov_open_callbacks(&oggMemoryFile, &oggStream, NULL, 0, vorbisCallbacks)

This function informs the libraries that we want to open an .off file, but instead of using a FILE, we are going to read in the data ourselves. The only thing here that we haven't seen before is the oggStream. This is just a pointer to a OggVorbis_File, which will be set up for us as we read in the file.

Reading In The Data From Memory

Now all that is left to do is actually read the data from memory. This is all done inside the callback functions that we defined earlier. Lets take a look at each on in turn...

VorbisRead

size_t VorbisRead(void *ptr,           // ptr to the data that the vorbis files need
                  size_t byteSize,     // how big a byte is
                  size_t sizeToRead,   // How much we can read
                  void *datasource)    /* this is a pointer to the data we passed
                                          into ov_open_callbacks (our SOggFile struct)*/
{ 
    ... 
}

This function requires you to read in the data from memory, and place it into the ptr variable (which will be of size byteSize*sizeToRead). All we need to do in this case is to memcopy(...) our data from memory into the ptr, making sure that we do not go beyond the bounds of our data in memory.

A return of 0 means that we have reached the end of the file, and we were unable to read anymore data. Otherwise, we must return the amount of data we have read.

VorbisSeek

int VorbisSeek(void *datasource,   // this is a pointer to the data we passed into ov_open_callbacks (our SOggFile struct)
               ogg_int64_t offset, // offset from the point we wish to seek to
               int whence)         // where we want to seek to
{
    ...
}

This function works in the same way as fseek(...). We are given a point from which to seek (SEEK_SET, SEEK_CUR, SEEK_END), and we must set our data pointer accordingly (again making sure we do not pass past the boundary of our data).

A return of -1 means that this file is not seekable (i.e. you cannot move the pointer, and as such, cannot rewind the file). This is fine if we don't want to loop the sample, but if we do, we need to be able to set the pointer back to the beginning of the file. A return of 0 is a successful call.

VorbisClose

int VorbisClose(void *datasource) // this is a pointer to the data we passed into ov_open_callbacks (our SOggFile struct)
{
    ...
}

This function is called when we call ov_close(...) within our code. The main use of this function is to clean up any allocations made during the opening of the file (of which there should be few, if any). You could, if you wanted, clean up the file in memory, but in most cases I assume the memory would be cleaned up elsewhere, and so this function is left empty.

The return clause is irrelevant. It is assumed this function always succeeds.

VorbisTell

long VorbisTell(void *datasource) // this is a pointer to the data we passed into ov_open_callbacks (our SOggFile struct)
{
    ...
}

This is used to inform the libraries how much of the file we have read so far. Pretty simple stuff, just return the amount we have read. A return of -1 indicated an error, but I can't imagine when this might happen!

Conclusion

There we have it. A few simple functions and the file is loaded from memory. The only thing left to do is clear memory of the file we loaded into memory in the first place, but it needs to be there through the duration of the sample (and beyond if we are looping it, or playing it again)

As you've probably gathered, by using the callbacks we can load a file from absolutely anywhere, not just in memory. By giving the developers a means of controlling the how and when the data is read in, allows us to mess around with the sound as we see fit, allowing us to create simple effect without having to change the actual source sample!

Included with this tutorial is a sample program that demonstrates the implementation (see link below). I hope this tutorial is of use to some of you out there, it would be good to know that my bedroom coding is always going to waste;) If you have any queries or problems, I'm sure you will be able to drop me a line, or post in the forums.

Download the Linux port of this tutorial - (ported by Lee Trager)



Download source code for this article
Discuss this article in the forums
Print article

© 2003-2004 DevMaster.net. All Rights Reserved. Terms of Use & Privacy Policy Want to write for us? Click here