
Introduction
This article describes how to load an Ogg Vorbis file from a memory stream and then decode that memory stream into usable sound data in a managed environment. This article requires that you read my first article which described how to implement an Ogg Vorbis player in .NET, because I will add code to the libraries used in that article and I will assume you know the basic architecture of those libraries. This version of the TgOggPlay library contains all of the functionality of the original version.
Background
At TrayGames, we needed to add support for playing Ogg Vorbis files to the multi-player online game development SDK (TGSDK) provided to third party developers. We developed a library that allowed you to play these files from the managed .NET environment, but the library needed the file name of the file that you wanted to decode. Some developers requested that the library also work by taking a chunk of memory that contained encoded Ogg Vorbis data instead of a file name. This is useful when you don't have individual sound files but a single resource file containing many sound files embedded within it. Such is the case with a TrayGames resource PAK file created for use with our skinning library.
Using the code
The code base used in this article is essentially the same as what we used in the first article, but some code was added to support the new way to load the data. A method that takes a byte
array instead of a file name string as a parameter has been added to the TgPlayOgg project, but the rest of the classes in that project remain basically the same. A new load function has been added to the unmanaged TgPlayOgg_vorbisfile wrapper project but the rest of the functions remain the same. A new file containing the callback functions required by the Ogg Vorbis API for decoding Ogg Vorbis data from memory has been added to the unmanaged wrapper project, these are all new.
Some changes were made to the test application to demonstrate the new functionality. The "Play Ogg File..." button now exercises the new methods that use a memory stream. The method used in the test application loads a file into memory for demonstration purposes and to keep the test application simple. Normally the memory stream containing the data would have come from data extracted from a PAK file or something similar. The "Repeat Last Ogg" button exercises the original way to play back an Ogg Vorbis file, using a file name string. Overall some other changes were made across all projects to clean up the source code, provide better comments, and fix a few bugs that have been found since the release of the original article.
If you download the source, there is an "OggPlayer.sln" solution file under the "OggPlayer Sample" folder that will build all of the projects mentioned in this article. A sample test application has been provided in the "Test App" folder under the TgPlayOgg project. The test application is the same application provided in my first article, with a few small changes. Let's take a look at the changes to this test application. We've changed the Button1Click
method (which handles the "Play Ogg File..." button) to read in the contents of a chosen Ogg Vorbis file into a flat byte
array. We then pass the byte
array to a new method in the TgPlayOgg object oplay
to start playback of the file. The new method looks like this:
private void Button1Click(object sender, System.EventArgs e)
{
OggName = GetOggFileNameToOpen();
if (null != OggName)
{
using (FileStream fs = new FileStream(OggName,
FileMode.Open, FileAccess.Read))
{
byte[] OggData = new byte[fs.Length];
BinaryReader br = new BinaryReader(fs);
br.Read(OggData, 0, (int)fs.Length);
br.Close();
oplay.PlayOggFile(OggData, ++PlayId);
}
textBox1.Text +=
"Playing '" + OggName + "' Id= " + PlayId.ToString() + "\r\n";
StillPlaying++;
}
}
We are essentially still loading the Ogg Vorbis file from a memory, at least to start, but this is only to keep the demonstration test application simple. Again, normally your memory stream containing the data would have come from the data extracted from a PAK file or something similar.
The Ogg Vorbis wrapper
The Ogg Vorbis API does not include functions to load a file from memory; instead they allow you to provide four callback functions that they will use to load the file. The advantage of this is that our Ogg Vorbis sound data can be anywhere, the disadvantage is there is some work involved for us. In my first article the TgPlayOgg_vorbisfile wrapper project provided a init_for_ogg_decode
method. This method used the Ogg Vorbis ov_info
API function to open and initialize the given Ogg Vorbis file for decoding. That API function took a file pointer as a parameter. Since we want to load the data from memory, not a file, we can no longer use the ov_info
function. Instead we will now fill out an ov_callbacks
structure and provide it as a parameter to the ov_open_callbacks
API function.
In order to fill out the ov_callbacks
structure we needed to create four new functions that will be used as callbacks to read, seek, tell and close the data in memory. These functions are expected to work in the same way as the standard C runtime IO functions, having the same parameters and the same return values. All four of our functions will use the following custom structure that contains the pointer to our Ogg Vorbis file in memory, plus some extra information:
typedef struct _OggMemoryFile
{
unsigned char* dataPtr;
long dataSize;
long dataRead;
} OGG_MEMORY_FILE, *POGG_MEMORY_FILE;
Our vorbis_read
function reads up to sizeToRead
items of size byteSize
from the input stream and stores them in the output buffer data_src
. It returns the number of full items actually read which may be less than sizeToRead
if an error occurs or if the end of the file is encountered before reaching the requested amount of items. A return value of zero means that we have reached the end of the file and we were unable to read anymore data. A return value of -1 means there was an error:
size_t vorbis_read(void* data_ptr,
size_t byteSize,
size_t sizeToRead,
void* data_src)
{
POGG_MEMORY_FILE vorbisData = static_cast(data_src);
if (NULL == vorbisData) return -1;
size_t actualSizeToRead, spaceToEOF =
vorbisData->dataSize - vorbisData->dataRead;
if ((sizeToRead*byteSize) < spaceToEOF)
actualSizeToRead = (sizeToRead*byteSize);
else
actualSizeToRead = spaceToEOF;
if (actualSizeToRead)
{
memcpy(data_ptr, (char*)vorbisData->dataPtr +
vorbisData->dataRead, actualSizeToRead);
vorbisData->dataRead += actualSizeToRead;
}
return actualSizeToRead;
}
Our vorbis_seek
moves the pointer associated with stream to a new location that is offset
bytes from origin
. You can use this function to reposition the data pointer anywhere in the stream. The origin
parameter is a point from which to seek (SEEK_SET
, SEEK_CUR
, SEEK_END
), and the data pointer is moved accordingly making sure not to pass past the boundary of the data. A return of -1 means that this file is not seek-able while a return value of 0 indicates success:
int vorbis_seek(void* data_src,
ogg_int64_t offset,
int origin)
{
POGG_MEMORY_FILE vorbisData = static_cast(data_src);
if (NULL == vorbisData) return -1;
switch (origin)
{
case SEEK_SET:
{
ogg_int64_t actualOffset;
if (vorbisData->dataSize >= offset)
actualOffset = offset;
else
actualOffset = vorbisData->dataSize;
vorbisData->dataRead = static_cast(actualOffset);
break;
}
case SEEK_CUR:
{
size_t spaceToEOF =
vorbisData->dataSize - vorbisData->dataRead;
ogg_int64_t actualOffset;
if (offset < spaceToEOF)
actualOffset = offset;
else
actualOffset = spaceToEOF;
vorbisData->dataRead +=
static_cast<LONG>(actualOffset);
break;
}
case SEEK_END:
vorbisData->dataRead = vorbisData->dataSize+1;
break;
default:
_ASSERT(false && "The 'origin' argument must be " +
"one of the following constants, defined in STDIO.H!\n");
break;
};
return 0;
}
Our vorbis_close
function closes the stream and frees the memory that we allocated for the Ogg Vorbis data and the structure that holds the data. This function turns out to be very useful because it allows us to allocate the memory for the Ogg Vorbis data, hand the structure off to the ov_open_callbacks
API function, and forget about it. The fact that this function is called gives us the opportunity to free that memory without having to keep a copy of the original structure:
int vorbis_close(void* data_src)
{
POGG_MEMORY_FILE oggStream = static_cast(data_src);
if (NULL != oggStream)
{
if (NULL != oggStream->dataPtr)
{
delete[] oggStream->dataPtr;
oggStream->dataPtr = NULL;
}
delete oggStream;
return 0;
}
_ASSERT(false && "The 'data_src' argument (set by " +
"ov_open_callbacks) was NULL so memory was not cleaned up!\n");
return EOF;
}
Our vorbis_tell
gets the current position of the pointer associated with stream. The position is expressed as an offset relative to the beginning of the stream.
long vorbis_tell(void* data_src)
{
POGG_MEMORY_FILE vorbisData = static_cast(data_src);
if (NULL == vorbisData) return -1L;
return vorbisData->dataRead;
}
Once we have the callback functions written we are ready to open the file from memory. I've added a new method to the original wrapper project memory_stream_for_ogg_decode
which does all of the work required. This is the method that our .NET library will call instead of the init_for_ogg_decode
method. The new method allocates the memory for a OggVorbis_File
structure (this is the output buffer for the API call). Next it saves the data in the given memory stream in the OGG_MEMORY_FILE
structure. Once we have our file in memory, we need to let the Vorbis libraries how to read it. To do this, we provide the callback functions that enable us to do the reading. Last we need to pass ov_open_callbacks
a pointer to our data (OGG_MEMORY_FILE
structure), a pointer to our OggVorbis_File
output buffer (which the Vorbis libraries will fill for us), and our callbacks structure:
int memory_stream_for_ogg_decode(unsigned char* stream,
int sizeOfStream, void** vf_out)
{
void *vf_ptr = malloc(sizeof(OggVorbis_File));
if (NULL == vf_ptr)
return ifod_err_malloc_failed;
POGG_MEMORY_FILE oggStream = new OGG_MEMORY_FILE;
oggStream->dataRead = 0;
oggStream->dataSize = sizeOfStream;
oggStream->dataPtr = new unsigned char[sizeOfStream];
for (int i=0; i < sizeOfStream; i++, stream++)
oggStream->dataPtr[i] = *stream;
oggCallbacks.read_func = vorbis_read;
oggCallbacks.close_func = vorbis_close;
oggCallbacks.seek_func = vorbis_seek;
oggCallbacks.tell_func = vorbis_tell;
int ov_ret = ov_open_callbacks(oggStream,
static_cast<OGGVORBIS_FILE *>(vf_ptr),
NULL, 0, oggCallbacks);
if (0 > ov_ret)
{
}
*vf_out = vf_ptr;
return 0;
}
Note that oggCallbacks
is a global variable of type ov_callbacks
. Those are all the changes we needed to make to the wrapper project for loading Ogg Vorbis files from memory. All of the other calls to the Ogg Vorbis API remain exactly the same as before. We're almost finished; we just need to make a few adjustments to our .NET library.
The .NET Ogg Vorbis library
The original TgPlayOgg library provided an OggPlay
class which had a PlayOggFile
method. This method plays the Ogg Vorbis file specified by the fileName
parameter. The playId
parameter is an arbitrary value determined by the user, and it is returned in the raised PlayOggFileResult
event. We've overloaded this method to take a data
parameter. This parameter is the flat byte
array containing the Ogg Vorbis sound data that you want to decode. Other than that the method is the same as the other overloaded version:
public void PlayOggFile(byte[] data, int playId)
{
PlayOggFileEventArgs EventArgs =
new PlayOggFileEventArgs(playId);
PlayOggFileThreadInfo pofInfo = new PlayOggFileThreadInfo(
EventArgs, null, data,
OggFileSampleSize == OggSampleSize.EightBits ? 8 : 16,
DirectSoundDevice, this);
Thread PlaybackThread = new Thread(
new ThreadStart(pofInfo.PlayOggFileThreadProc));
PlaybackThread.Start();
}
The OggPlay
class contains the PlayOggFileThreadInfo
class which is used as the thread class for the playback thread created in the PlayOggFile
method of the OggPlay
class. We need to make a small change to this class so that the PlayOggFileThreadProc
method can determine weather to call the init_for_ogg_decode
wrapper method (if we are using a file), or the new memory_stream_for_ogg_decode
method (if we are using a memory stream). The rest of the method remains the same. We also need to add a new data member byte[] memFile
, and a new parameter to the class constructor to set this member.
public void PlayOggFileThreadProc()
{
int ErrorCode = 0;
if (null != FileName)
ErrorCode =
NativeMethods.init_for_ogg_decode(FileName, &vf);
else if (null != MemFile)
ErrorCode =
NativeMethods.memory_stream_for_ogg_decode(
MemFile, MemFile.Length, &vf);
}
Finally we had to add the prototype for the new method in our unmanaged wrapper project to our NativeMethods
class:
[DllImport("TgPlayOgg_vorbisfile.dll",
CharSet=CharSet.Unicode,
CallingConvention=CallingConvention.Cdecl)]
public unsafe static extern int memory_stream_for_ogg_decode(
byte[] stream, int sizeOfStream, void** vf_out);
Those are all the changes we needed to make to the .NET library project for loading Ogg Vorbis audio files from memory. All of the other methods in this project remain exactly the same as before.
Points of interest
Those are pretty much the highlights of playing Ogg Vorbis encoded sound data from a memory stream, or anywhere else you want. The callbacks can load a file from anywhere, not just memory, which gives developers a lot of control and flexibility over the sound sample. These projects are interesting if you want to learn about the different ways you can load an Ogg Vorbis audio file, or as a starting point for creating simple effects without changing the original sound sample source. If you are interested in checking out the full TGSDK for producing your own multi-player online games, you can get it at the TrayGames web site. You may also want to check out the Ogg Vorbis web site to learn more about their encoding format.
Revision history
- 07 March 2006
Updated this library to support .NET 2.0, added a WaitForAllOggFiles
method that will block until all outstanding Ogg files are finished playing, and made several bug fixes. The updated library is only available in the TGSDK, downloadable from the TrayGames web site.
- 21 September 2005
Initial revision.