Adding OGG support to Torque 2D

Prior to being released opensource, Torque 2D supported OGG and WAV files. MP3 support wasn’t included because of licensing issues with the MP3 codec. The opensource version of Torque 2D doesn’t include OGG support. So the only audio support was WAV. This is a problem because WAV is a raw format, so having audio of any length was taking up a lot of disk space. I added OGG support back to the engine and will describe the process in this post. I am not adding it back to the Git project because there are probably open source license issues that caused them to remove OGG in the first place.

To get OGG support back, I started by downloading the latest OGG and vorbis (1.3.4) libraries. I added them as sub-projects in VC2013 and set them to build static libraries with the /MT runtime library. Vorbis depends on OGG, so you will need to add your OGG directory to the include dependency for vorbis.

Once you have the libraries built, you need to setup Torque to use it. The easiest place to add the capability is in audio/audioBuffer. I set it up to look for a .ogg file if the .wav doesn’t exist. That way you can develop your game with .wav files and replace them with .ogg files before delivery.

//Add the following functions to audioBuffer.h
   bool readOgg(ResourceObject *obj);
   long oggRead(struct OggVorbis_File* vf, char *buffer,int length,
		    int bigendianp,int *bitstream);

 

// Add the following functions to audioBuffer.cc
#include "vorbis/vorbisfile.h"
static size_t _ov_read_func( void *ptr, size_t size, size_t nmemb, void *datasource )
{
   Stream *stream = reinterpret_cast<Stream*>( datasource );

   // Stream::read() returns true if any data was
   // read, so we must track the read bytes ourselves.
   U32 startByte = stream->getPosition();
   stream->read( size * nmemb, ptr );
   U32 endByte = stream->getPosition();

   // How many did we actually read?
   U32 readBytes = ( endByte - startByte );
   U32 readItems = readBytes / size;

   return readItems;
}

static int _ov_seek_func( void *datasource, ogg_int64_t offset, int whence )
{
   Stream *stream = reinterpret_cast<Stream*>( datasource );

   U32 newPos = 0;
   if ( whence == SEEK_CUR )
      newPos = stream->getPosition() + (U32)offset;
   else if ( whence == SEEK_END )
      newPos = stream->getStreamSize() - (U32)offset;
   else
      newPos = (U32)offset;

   return stream->setPosition( newPos ) ? 0 : -1;
}

static long _ov_tell_func( void *datasource )
{
   Stream *stream = reinterpret_cast<Stream*>( datasource );
   return stream->getPosition();
}

/*!   The Read an Ogg Vorbis file from the given ResourceObject and initialize
      an alBuffer with it.
*/
bool AudioBuffer::readOgg(ResourceObject *obj)
{
   ALenum  format = AL_FORMAT_MONO16;
   char   *data   = NULL;
   ALsizei size   = 0;
   ALsizei freq   = 22050;
   ALboolean loop = AL_FALSE;
   int current_section = 0;

#if defined(TORQUE_BIG_ENDIAN)
      int endian = 1;
#else
      int endian = 0;
#endif

   int eof = 0;

   Stream *stream = ResourceManager->openStream(obj);
   if (!stream)
      return false;

   OggVorbis_File vf;
   dMemset( &vf, 0, sizeof( OggVorbis_File ) );

   const bool canSeek = stream->hasCapability( Stream::StreamPosition );

   ov_callbacks cb;
   cb.read_func = _ov_read_func;
   cb.seek_func = canSeek ? _ov_seek_func : NULL;
   cb.close_func = NULL;
   cb.tell_func = canSeek ? _ov_tell_func : NULL;

   // Open it.
   int ovResult = ov_open_callbacks( stream, &vf, NULL, 0, cb );
   if( ovResult != 0 )
   {
      ResourceManager->closeStream( stream );
      return false;
   }

   const vorbis_info *vi = ov_info( &vf, -1 );
   freq = vi->rate;

   long samples = (long)ov_pcm_total( &vf, -1 );

   if(vi->channels == 1) {
      format = AL_FORMAT_MONO16;
      size = 2 * samples;
   } else {
      format = AL_FORMAT_STEREO16;
      size = 4 * samples;
   }

   data=new char[ size ];
   if (data)
   {
      long ret = oggRead( &vf, data, size, endian, &current_section );
   }

   /* cleanup */
   ov_clear( &vf );

   ResourceManager->closeStream(stream);
   if( data )
   {
      alBufferData(malBuffer, format, data, size, freq);
      delete [] data;
      return (alGetError() == AL_NO_ERROR);
   }

   return false;
}

// ov_read() only returns a maximum of one page worth of data
// this helper function will repeat the read until buffer is full
long AudioBuffer::oggRead(OggVorbis_File* vf, char *buffer,int length,
          int bigendianp,int *bitstream)
{
   long bytesRead = 0;
   long totalBytes = 0;
   long offset = 0;
   long bytesToRead = 0;
   //while((offset + CHUNKSIZE) < length) {
   while((offset) < length)
   {
      if((length - offset) < CHUNKSIZE)
         bytesToRead = length - offset;
      else
         bytesToRead = CHUNKSIZE;

      bytesRead = ov_read(vf, buffer, bytesToRead, bigendianp, 2, 1, bitstream);
      if(bytesRead <= 0)
         break;
      offset += bytesRead;
      buffer += bytesRead;
   }
   return offset;
}

 

// Finally add the following code to the AudioBuffer::getALBuffer() function right after the
// if(len > 3 && !dStricmp(mFilename + len - 4, ".caf")) block
// The code is basically going through file types. It checks .wav, then .caf and finally
// .ogg
      else if(len > 3 && !dStricmp(mFilename + len - 4, ".ogg"))
      {
#  ifdef LOG_SOUND_LOADS
         Con::printf("Reading Ogg: %s\n", mFilename);
#  endif
         readSuccess = readOgg(obj);
      }

Edit: Thanks to Frogger on the garagegames.com forum for pointing out another code change that should probably be done.

// Add the .ogg extension to the resource manager in the OpenALInitDriver function in audio_ScriptBinding.cc
ResourceManager->registerExtension(".wav", AudioBuffer::construct);
Note: This post was  written 3/23/2014 and retroactively published to 2/17/2014 because that is when I did the work.

Leave a Reply