The TgPlayOgg project is a .NET C# library that allows you to play Ogg Vorbis files from your managed code. Decoding a given Ogg Vorbis file into usable sound data is done by TgPlayOgg which makes calls to an unmanaged C++ project TGPlayOgg_vorbisfile. TgPlayOgg also requires managed DirectX for sound output.
At TrayGames, we needed to add support for playing sound files to the multi-player online game development SDK (TGSDK) provided to third party developers. We started out using the MP3 audio format, but we were concerned about licensing issues (fees kick in after you reach a certain level of sales). After comparing alternatives, we chose to use the Ogg Vorbis format. Ogg Vorbis is a completely open, patent-free, professional audio encoding and streaming technology with all the benefits of Open Source.
Using the Code
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. This application demonstrates how to use the library. The steps are as follows:
- Include a reference to the TgPlayOgg project and import the
- Construct an instance of the
OggPlay class (your app only needs one instance no matter how many Ogg Vorbis files are played simultaneously).
- Add your
PlayOggFile event handler to the
PlayOggFileResult delegate. You are now ready to call
PlayOggFile as many times as you want and whenever you want. Note that
PlayOggFile returns immediately after the call, since decoding and playback are done in separate threads. Your event handler will be called when a file is finished playing.
- When you are all done using the instance of the
OggPlay class, you will want to call
Dispose to be sure that the unmanaged resources used by the DirectSound Device object get cleaned up.
Let's take a look at the highlights of the test application. First we see it has a method that does the initialization and handles the
PlayOggFile event. Note that we must call the
OggPlay constructor in a
try block, since it makes calls into DirectSound that may raise exceptions. Then it has another method that allows the user to choose an Ogg Vorbis sound file to open for playback.
private void InitTestOfOggPlayer()
oplay = new OggPlay(this, OggSampleSize.SixteenBits);
oplay.PlayOggFileResult += new PlayOggFileEventHandler(PlayOggFileResult);
textBox1.Text = "Initialization successful.\r\n";
textBox1.Text = "Initialization failed: " + e.Message + "\r\n";
private void Button1Click(object sender, System.EventArgs e)
OggName = GetOggFileNameToOpen();
if (OggName != null)
textBox1.Text = "Playing " + OggName + " Id= " + PlayId.ToString() + "\r\n";
The Ogg Vorbis decoder may have encountered errors while decoding the Ogg Vorbis data. An Ogg Vorbis file may refuse to play if there is not enough data to stream through the initial buffers (the Ogg is too small) or if it simply can not read the file. There are two error counts that are for information purposes only, since if successful the created waveform data was played, but it may not have sounded as intended if either of these two counts are nonzero. The way we handle the
PlayOggFile event is to display a status message indicating success or error (with the two error counts). We'll learn more about what these error counts mean later.
private static void PlayOggFileResult(object sender, PlayOggFileEventArgs e)
MainForm.textBox1.Text += "PlayOggFile(" + e.PlayId + ") succeeded ("
+ "ErrorHoleCount: " + e.ErrorHoleCount + ", ErrorBadLinkCount: "
+ e.ErrorBadLinkCount + ").\r\n";
MainForm.textBox1.Text += "PlayOggFile(" + e.PlayId + ") failed: '"
+ e.ReasonForFailure + "'\r\n";
Note that exiting a calling application does not kill the playback threads if one or more Ogg Vorbis files are still playing. These playback threads keep running although you can no longer hear them playing. The threads will finish playing whatever Ogg Vorbis files they were playing and then quit, unless the threads are specifically told to stop playback. So, when your application exits, it should probably kill any long playing Ogg Vorbis files that are still playing. This is why the test application handles the
Form.Closing event by calling
OggPlay.StopOggFile which you will learn more about later.
protected void Form1_Closing(object sender,
if (PlayId > 0)
if (MessageBox.Show("Ogg files are still playing," +
" are you sure you want to exit?", "TrayGames Ogg Player",
MessageBoxButtons.YesNo) == DialogResult.No)
e.Cancel = true;
while (PlayId > 0)
Other times you might want to kill playback threads in midstream would be if your application has a pause capability, or if you want to reset sounds when the user switches away from your game.
The Ogg Vorbis Wrapper
Ogg Vorbis' high-level API, Vorbisfile, has only two input choices: either a C file pointer or a set of custom callback functions that do the reading of the input Ogg Vorbis data. The better and more portable of these choices is probably custom callbacks, but I wasn't aware that .NET 1.1 gave any control over the calling convention of its methods, and its standard calling convention is
StdCall, while the Vorbisfile dynamic link libraries (DLLs) are compiled with the
Cdecl calling convention. Thus, given C# and .NET 1.1, we decided to write some C/C++ code and compile it into a DLL, and this DLL includes the callbacks that Vorbisfile needs. This is why we created the TGPlayOgg_vorbis wrapper project.
I have since learned that you can use the
DllImportAttribute class to provide the information needed to call a function exported from an unmanaged DLL. So you should be able to modify the source code of this library to eliminate the TGPlayOgg_vorbis wrapper project and make the Vorbisfile API calls directly from the TGPlayOgg project. The .NET Framework Base Class Library (BCL) provides for
ThisCall, and the
WinApi calling conventions.
WinApi selects the correct type automatically based on the platform (Windows or Windows CE). For example, to change the
SomeFunction located in SomeLibrary.dll, you could use the following code:
[DllImport("SomeLibrary.DLL", EntryPoint="SomeFunction", SetLastError=true,
public static extern bool SomeFunction(String param1, String param2);
For now though, the TGPlayOgg_vorbis project makes the calls into the Ogg Vorbis API for us. There are three wrapper functions:
ogg_final_cleanup. You will never call these methods directly, the C# library will make them so it can decode the file. If you want to add the definitions for these methods to your own managed application you would define a
NativeMethods class (use any class name) and add the function prototypes to it. Having a separate class for your unmanaged DLL functions is advisable because consuming DLL functions can be prone to errors. Encapsulating the DLL declarations makes your job easier when it comes time to debug. The "Vorbisapi.cs" file in the TGPlayOgg_vorbis project already has such a class definition containing the declarations:
public unsafe static extern int init_for_ogg_decode(
string fileName, void **vf_out);
public unsafe static extern int ogg_decode_one_vorbis_packet(
void *vf_ptr, void *buf_out, int buf_byte_size,
int bits_per_sample, int *channels_cnt, int *sampling_rate,
int *err_ov_hole_cnt, int *err_ov_ebadlink_cnt);
public unsafe static extern int final_ogg_cleanup(void *vf_ptr);
What allows us to make these calls is the Platform Invoke (PInvoke) service. PInvoke will enable our managed code to call the unmanaged functions implemented in the DLL. It will locate and invoke the exported functions and marshal their parameters across the managed/unmanaged code boundary as needed. Note that PInvoke throws exceptions generated by the unmanaged function to the managed caller. Let's look at our unmanaged functions now.
init_file_for_ogg_decode function will open and initialize the given Ogg Vorbis file for decoding. It sets up all the related decoding structures by calling the
op_open API function. Also, you should be aware that
ov_open, once successful, takes complete possession of the file resource. After you have opened a file using
ov_open, you must close it using
fclose or any other function. Our wrapper functions take care of all of this, here's what our initialize function looks like:
int init_file_for_ogg_decode(wchar_t *filename, void **vf_out)
int ov_ret = ov_open(file_ptr, static_cast<OggVorbis_File*>(vf_ptr), NULL, 0);
if (ov_ret < 0)
*vf_out = vf_ptr;
ogg_decode_one_vorbis_packet function writes PCM (Pulse Code Modulation) data into the given buffer and returns the number of bytes written into that buffer. First it calls the
ov_read which returns up to the specified number of bytes of decoded PCM audio in the requested endianness, signedness, and word size. If the audio is multichannel, the channels are interleaved in the output buffer. This function is used to decode a Vorbis file within a loop. Our C# application, which we'll see later, will be doing just that.
Next it calls
ov_info which returns the
vorbis_info struct for the specified bitstream. This allows us to return the number of channels in the bitstream, and the sampling rate of the bitstream to our C# application. There are basically two errors that can occur:
OV_HOLE which indicates there was an interruption in the data, and
OV_EBADLINK which indicates that an invalid stream section was supplied, or the requested link is corrupt.
The Ogg Vorbis format allows for multiple logical bitstreams to be combined (with restrictions) into a single physical bitstream. Note that the Vorbisfile API could more or less hide the multiple logical bitstream nature of chaining from an application but, when reading audio back, the application must be aware that multiple bitstream sections do not necessarily use the same number of channels or sampling rate. The Ogg Vorbis documentation provides more information on Ogg logical bitstream framing.
int ogg_decode_one_vorbis_packet(void *vf_ptr,
void *buf_out, int buf_byte_size,
int *channels_cnt, int *sampling_rate,
int *err_ov_hole_cnt, int *err_ov_ebadlink_cnt)
for (bytes_put_in_buf = 0;;)
long ov_ret = ov_read(static_cast<OggVorbis_File*>(vf_ptr),
static_cast<char*>(buf_out), buf_byte_size, 0,
word_size, want_signed, &bitstream);
if (ov_ret == 0)
else if (ov_ret < 0)
if (ov_ret == OV_HOLE)
else if (ov_ret == OV_EBADLINK)
assert(ov_ret <= buf_byte_size);
vorbis_info* vi_ptr = ov_info(static_cast<OggVorbis_File*>(vf_ptr),
if (vi_ptr != NULL)
*channels_cnt = vi_ptr->channels;
*sampling_rate = vi_ptr->rate;
bytes_put_in_buf = ov_ret;
After a bitstream has been opened using
ov_open and decoding is complete, an application must call
ov_clear to clear the decoder's buffers and close the file. The
ogg_final_cleanup function does this by calling this function, it also frees the memory pointed to by
vf_out. You can take a look at the Vorbisfile API documentation for more information on any of these functions.
int ogg_final_cleanup(void *vf_ptr)
int ret = 0;
if (vf_ptr != NULL)
ret = ov_clear(static_cast<OggVorbis_File*>(vf_ptr));
The .NET Ogg Vorbis Library
The Microsoft .NET 1.1 Framework has no sound playing classes, so to play the waveform data constructed from the decoded Ogg Vorbis file data, there are basically two choices. The first is to write the waveform data out as a WAV file, and then use quartz.dll (on Win98 and later) to play that WAV file. The disadvantage of this choice is that WAV files can be very large (e.g. a 5.5 MB Ogg Vorbis file was tested and resulted in a 67 MB WAV file), and playback can't begin until after the entire WAV file has been written out (e.g. decoding that 5.5 MB Ogg Vorbis file and writing out a WAV file takes more than 20 seconds on a 1.6 GHz P4 PC). The other choice is to use methods in managed DirectX which means there's no need to write out any WAV file, and we can play the waveform data as it is generated, so playback can begin much quicker than the first approach. The TrayGames client already ensures that the managed DirectX APIs are installed on target computers so this was not an issue for us and it's the choice we went with.
OggPlay class is the main class that your application will be using. Its constructor creates a new DirectX Sound device, sets the cooperative level and sample size of the Ogg Vorbis file.
public OggPlay(Control owner, OggSampleSize wantedOggSampleSize)
DirectSoundDevice = new Device();
OggFileSampleSize = wantedOggSampleSize;
owner parameter is used by the DirectSound
SetCooperativeLevel method, which defines its
owner parameter as "The
System.Windows.Forms.Control of the application that is using the Device object". This should probably be your application's main window. The
wantedOggSampleSize parameter is either 8 bits or 16 bits. 8-bit sample size has lower quality but is faster and takes less memory than 16-bit sample size. If your application's Ogg Vorbis files are encoded with 8-bit sample size, then choose 8 (you can also choose 16, but it's wasteful and gains nothing if the Ogg Vorbis sources are only 8-bit). If your application's Ogg Vorbis files are encoded with 16-bit sample size, then choose 16 to get the full sound quality during playback, or choose 8, or give the user the option of choosing 8, if you want to minimize playback resource requirements. If your application's Ogg Vorbis files are a mixture (some are encoded with 8-bit sample size and others are encoded with 16-bit sample size), then choose whichever you think is best (either setting, 8 or 16 bits, will play all the Ogg Vorbis files).
The TgPlayOgg library declares two events with delegates and an event argument class (which defines data for both events) for playing and stopping Ogg Vorbis files. The
PlayOggFileResult event (
PlayOggFileEventHandler delegate) is used for event notification when the
PlayOggFile method completes, while the
StopOggFileNow event (
StopOggFileEventHandler delegate) is used when the client wishes to interrupt playback prematurely. Here's a look at the data members of the event argument class.
public sealed class PlayOggFileEventArgs : EventArgs
private bool success;
private string reasonForFailure;
private int playId;
public int ErrorHoleCount,
OggPlay provides two simple methods
PlayOggFile 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. This event is raised by
PlayOggFileThreadProc. In your event-handler code, you can use the returned
playID to know which specific
PlayOggFile call resulted in that handled event. This is why your application should attach to the
public void PlayOggFile(string fileName, int playId)
PlayOggFileEventArgs EventArgs = new PlayOggFileEventArgs(playId);
PlayOggFileThreadInfo pofInfo = new PlayOggFileThreadInfo(
OggFileSampleSize == OggSampleSize.EightBits ? 8 : 16,
Thread PlaybackThread = new Thread(new
StopOggFile raises the
StopOggFileNow event. This event will be handled by the
PlayOggFileThreadProc method. Your application does not need to attach to the
StopOggFileEventHandler delegate, but
PlayOggFileThreadProc does of course.
public void StopOggFile(int playId)
PlayOggFileEventArgs EventArgs = new PlayOggFileEventArgs(playId);
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. In a way, this class sits between the managed and unmanaged environments. It does work on behalf of
OggPlay by making calls into the unmanaged Ogg Vorbis wrapper described above. The main method in this class is
PlayOggFileThreadProc and we will look at some of the parts of this method now.
The first thing that
PlayOggFileThreadProc does is initialize the Ogg Vorbis file for decoding by calling into the Ogg Vorbis wrapper. If an error is encountered during initialization, it's returned via the
PlayOggFileEventHandler (see below). Note that the filename, sample rate, and DirectSound device are all passed to this class through its constructor. The constructor also registers the class'
InterruptOggFilePlayback method to handle the
int ErrorCode = NativeMethods.init_file_for_ogg_decode(FileName, &vf);
if (ErrorCode != 0)
PlayOggFileThreadProc creates the PCM byte array and passes it to the
ogg_decode_one_vorbis_packet function. This function will pass back the first chunk of decoded Ogg Vorbis data and its size.
fixed(byte *buf = &PcmBuffer)
fixed(int *HoleCount = &EventArgs.ErrorHoleCount)
fixed(int *BadLinkCount = &EventArgs.ErrorBadLinkCount)
PcmBytes = NativeMethods.ogg_decode_one_vorbis_packet(
vf, buf, PcmBuffer.Length,
The first time we return from the
ogg_decode_one_vorbis_packet function, we create DirectSound
WaveFormat is used to hold the format of the waveform audio data after it's been decoded.
BufferDescription will describe the characteristics of the new buffer object, including the
SecondaryBuffer has methods and properties used to manage the sound buffer.
Notify allows us to set up notification triggers at different points during playback.
int HoldThisManySamples =
(int)(SamplingRate * SecBufHoldThisManySeconds);
MyWaveFormat.AverageBytesPerSecond = AverageBytesPerSecond;
MyWaveFormat.BitsPerSample = (short)BitsPerSample;
MyWaveFormat.BlockAlign = (short)BlockAlign;
MyWaveFormat.Channels = (short)ChannelsCount;
MyWaveFormat.SamplesPerSecond = SamplingRate;
MyWaveFormat.FormatTag = WaveFormatTag.Pcm;
MyDescription = new BufferDescription();
MyDescription.Format = MyWaveFormat;
SecBufByteSize = HoldThisManySamples * BlockAlign;
MyDescription.CanGetCurrentPosition = true;
MyDescription.ControlPositionNotify = true;
SecBuf = new SecondaryBuffer(MyDescription, DirectSoundDevice);
MyNotify = new Notify(SecBuf);
BufferPositionNotify MyBufferPositions = new BufferPositionNotify;
MyBufferPositions.Offset = 0;
(HoldThisManySamples / 3) * BlockAlign;
((HoldThisManySamples * 2) / 3) * BlockAlign;
After these objects are prepared, we load the decoded PCM data into a
MemoryStream object. This stream is written into the DirectSound buffer object and then played using the asynchronous
Play method. This process is repeated until we reach the end of the Ogg Vorbis file. We must be aware that multiple bitstream sections do not necessarily use the same number of channels or sampling rate (we refer to this as its format). While we can handle a different format at the start of a new Ogg Vorbis file, we can't handle a format change during the playback of a file. Besides reaching the end of file, or an error, this is another reason the library will stop playback.
PcmStream.Write(PcmBuffer, 0, PcmBytes);
PcmStream.Position = 0;
PcmStreamNextConsumPcmPosition = 0;
int WriteCount = (int)Math.Min(
SecBufByteSize - SecBufNextWritePosition);
if (WriteCount > 0)
SecBufNextWritePosition += WriteCount;
PcmStreamNextConsumPcmPosition += WriteCount;
if (SecBufByteSize == SecBufNextWritePosition)
SecBufInitialLoad = false;
SecBufNextWritePosition = 0;
Points of Interest
Those are pretty much the highlights of the sample, TgPlayOgg, and TgPlayOgg_vorbisfile projects. These projects are interesting if you want to learn about decoding Ogg Vorbis audio files or as an example of how to call unmanaged code from the managed .NET environment. 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 and the many tools for manipulating it.
- 02 April 2007
Updated this library to support Visual Studio .NET 2005 and made several bug fixes. The updated library is also available in the TGSDK, downloadable from the TrayGames Developer web site.
- 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.
- 22 August 2005
Added more details on the Vorbisfile API functions that this library calls to decode an Ogg Vorbis sound file.
- 11 August 2005
Fixed some minor defects in the source code, updated the test application and fixed some grammatical errors in the article.
- 18 July 2005
Initial revision. Based on some welcomed feedback, I've updated the Ogg Vorbis Wrapper section of this article to talk about the .NET Framework Base Class Library
DllImportAttribute class which can be used to call a function exported from an unmanaged DLL.