Click here to Skip to main content
15,867,686 members
Articles / Programming Languages / C#
Article

Loading Ogg Vorbis Files from Memory in .NET

Rate me:
Please Sign up or sign in to vote.
4.64/5 (14 votes)
7 Mar 2006CDDL9 min read 81.2K   735   40   16
An article on loading Ogg Vorbis audio data from memory.

Image 1

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:

C#
private void Button1Click(object sender, System.EventArgs e)
{
  OggName = GetOggFileNameToOpen();
  
  if (null != OggName)
  {
    // Demonstrate using the library with a memory stream
    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;// Pointer to the data in memory
  long      dataSize;     // Size of the data
  long      dataRead;     // Bytes read so far
} 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,// A pointer to the data 
                                  // that the vorbis files need
    size_t byteSize,     // Byte size on this particular system
    size_t sizeToRead,   // Maximum number of items to be read
    void* data_src)      // A pointer to the data we passed into 
                         // ov_open_callbacks
{
   POGG_MEMORY_FILE vorbisData = static_cast(data_src);
   if (NULL == vorbisData) return -1;

   // Calculate how much we need to read. 
   // This can be sizeToRead*byteSize 
   // or less depending on how near the 
   // EOF marker we are.
   size_t actualSizeToRead, spaceToEOF = 
       vorbisData->dataSize - vorbisData->dataRead;
   if ((sizeToRead*byteSize) < spaceToEOF)
       actualSizeToRead = (sizeToRead*byteSize);
   else
       actualSizeToRead = spaceToEOF;  
  
   // A copy of the data from memory 
   // to the datastruct that the 
   // Vorbisfile API will use.
   if (actualSizeToRead)
   {
     // Copy the data from the start 
     // of the file PLUS how much 
     // we have already read in.
     memcpy(data_ptr, (char*)vorbisData->dataPtr + 
                       vorbisData->dataRead, actualSizeToRead);

     // Increase by how much we have read by
     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, // A pointer to the data we 
                                // passed to ov_open_callbacks
         ogg_int64_t offset,    // Number of bytes from origin
         int origin)            // Initial position
{
  POGG_MEMORY_FILE vorbisData = static_cast(data_src);
  if (NULL == vorbisData) return -1;

  switch (origin)
  {
    case SEEK_SET: 
    { // Seek to the start of the data 
      // file, make sure we are not 
      // going to the end of the file.
      ogg_int64_t actualOffset; 
      if (vorbisData->dataSize >= offset)
        actualOffset = offset;
      else
        actualOffset = vorbisData->dataSize;

      // Set where we now are
      vorbisData->dataRead = static_cast(actualOffset);
      break;
    }

    case SEEK_CUR: 
    {
      // Seek from where we are, make 
      // sure we don't go past the end
      size_t spaceToEOF = 
         vorbisData->dataSize - vorbisData->dataRead;

      ogg_int64_t actualOffset; 
      if (offset < spaceToEOF)
        actualOffset = offset;
      else
        actualOffset = spaceToEOF;  

      // Seek from our currrent location
      vorbisData->dataRead += 
           static_cast<LONG>(actualOffset);
      break;
    }

    case SEEK_END: 
      // Seek from the end of the file
      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)
{
  // Free the memory that we 
  // created for the stream.
  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;

  // We just want to tell the Vorbisfile 
  // API how much we have read so far
  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)
  {
    // There was an error . . .
  }

  // Copy the memory pointer to the caller
  *vf_out = vf_ptr;
  
  return 0;  // Success!
}

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:

C#
public void PlayOggFile(byte[] data, int playId)
{     
  // Create an event argument class 
  // identified by the playId
  PlayOggFileEventArgs EventArgs = 
                new PlayOggFileEventArgs(playId);

  // Decode the Ogg Vorbis memory 
  // stream in a separate thread
  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.

C#
public void PlayOggFileThreadProc()
{
  // . . .

  // Initialize the file for Ogg Vorbis decoding using
  // data from either a file name or memory stream.
  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:

// Initialization for decoding the 
// given Ogg Vorbis memory stream.
[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.

License

This article, along with any associated source code and files, is licensed under The Common Development and Distribution License (CDDL)


Written By
Founder SpreadTrends.com
United States United States
I've authored many articles that tackle real-world issues to save my peers in the development community valuable time. For example I've written articles that: show how to decode Ogg Vorbis audio files using the .NET Framework; describe best practices for Improving Entity Framework performance; and demonstrate step-by-step how to create a multi-player game.

Comments and Discussions

 
QuestionDynamic MemoryStream?? Pin
hans0117-Mar-09 1:27
hans0117-Mar-09 1:27 
Good article!
Ist there the possibility to play Ogg data from a dynamic memorystream???

I would need the following solution:
I load the data into the stream, at a certain point the audio begins to play and the stream is still filled with data, which will also be played.

Thanx
Hans
QuestionWhat is ogg_decode_one_vorbis_packet used for? Pin
frankmail00727-Aug-08 19:50
frankmail00727-Aug-08 19:50 
Generali no eng Pin
wdqszym5-Jun-08 5:04
wdqszym5-Jun-08 5:04 
QuestionNot working Pin
jacksprat2-Apr-07 4:53
jacksprat2-Apr-07 4:53 
AnswerRe: Not working Pin
Perry Marchant2-Apr-07 6:32
Perry Marchant2-Apr-07 6:32 
GeneralRe: Not working Pin
Rafael Mores2-Apr-07 7:33
Rafael Mores2-Apr-07 7:33 
GeneralRe: Not working [modified] Pin
jacksprat2-Apr-07 18:20
jacksprat2-Apr-07 18:20 
GeneralDOESNT WORK Pin
Rafael Mores23-Feb-07 4:54
Rafael Mores23-Feb-07 4:54 
GeneralRe: DOESNT WORK Pin
Perry Marchant2-Apr-07 6:34
Perry Marchant2-Apr-07 6:34 
QuestionGeneric Stream Player Using DirectX Pin
F8R12-Oct-06 23:38
F8R12-Oct-06 23:38 
QuestionCan you add a pause function and position event Pin
J.Q.C.14-Jul-06 18:17
J.Q.C.14-Jul-06 18:17 
Generalspeex Pin
zapod1-Nov-05 1:18
zapod1-Nov-05 1:18 
GeneralRe: speex Pin
Perry Marchant1-Nov-05 18:43
Perry Marchant1-Nov-05 18:43 
QuestionSound dissapear Pin
eLToBiS25-Oct-05 11:34
eLToBiS25-Oct-05 11:34 
AnswerRe: Sound dissapear Pin
Perry Marchant25-Oct-05 14:25
Perry Marchant25-Oct-05 14:25 
GeneralThanks Pin
ANDYFA10-Oct-05 1:57
ANDYFA10-Oct-05 1:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.