Skip to content

Instantly share code, notes, and snippets.

@nickgravelyn nickgravelyn/Game1.cs
Last active Dec 26, 2017

Embed
What would you like to do?
Wrapper for playing OGG files in XNA using NVorbis and DynamicSoundEffectInstance. Probably has a few bugs but generally works on my machine. :D
using Microsoft.Xna.Framework;
namespace WindowsGame1
{
public class Game1 : Microsoft.Xna.Framework.Game
{
OggSong song;
public Game1()
{
new GraphicsDeviceManager(this);
}
protected override void Dispose(bool disposing)
{
if (song != null)
{
// must dispose due to thread; otherwise the exe never cleanly exits
// probably could fix this in the threading, but I don't mind disposing the song
song.Dispose();
}
base.Dispose(disposing);
}
protected override void Initialize()
{
song = new OggSong("heroic_age.ogg");
song.Play();
}
static void Main(string[] args)
{
using (Game1 game = new Game1())
{
game.Run();
}
}
}
}
using System;
using System.Threading;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using NVorbis;
namespace WindowsGame1
{
class OggSong : IDisposable
{
private VorbisReader reader;
private DynamicSoundEffectInstance effect;
private Thread thread;
private EventWaitHandle threadRunHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
private EventWaitHandle needBufferHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
private byte[] buffer;
private float[] nvBuffer;
public SoundState State
{
get { return effect.State; }
}
public float Volume
{
get { return effect.Volume; }
set { effect.Volume = MathHelper.Clamp(value, 0, 1); }
}
public bool IsLooped { get; set; }
public OggSong(string oggFile)
{
reader = new VorbisReader(oggFile);
effect = new DynamicSoundEffectInstance(reader.SampleRate, (AudioChannels)reader.Channels);
buffer = new byte[effect.GetSampleSizeInBytes(TimeSpan.FromMilliseconds(500))];
nvBuffer = new float[buffer.Length / 2];
// when a buffer is needed, set our handle so the helper thread will read in more data
effect.BufferNeeded += (s, e) => needBufferHandle.Set();
}
~OggSong()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool isDisposing)
{
threadRunHandle.Set();
effect.Dispose();
}
public void Play()
{
Stop();
lock (effect)
{
effect.Play();
}
StartThread();
}
public void Pause()
{
lock (effect)
{
effect.Pause();
}
}
public void Resume()
{
lock (effect)
{
effect.Resume();
}
}
public void Stop()
{
lock (effect)
{
if (!effect.IsDisposed)
{
effect.Stop();
}
}
reader.DecodedTime = TimeSpan.Zero;
if (thread != null)
{
// set the handle to stop our thread
threadRunHandle.Set();
thread = null;
}
}
private void StartThread()
{
if (thread == null)
{
thread = new Thread(StreamThread);
thread.Start();
}
}
private void StreamThread()
{
while (!effect.IsDisposed)
{
// sleep until we need a buffer
while (!effect.IsDisposed && !threadRunHandle.WaitOne(0) && !needBufferHandle.WaitOne(0))
{
Thread.Sleep(50);
}
// if the thread is waiting to exit, leave
if (threadRunHandle.WaitOne(0))
{
break;
}
lock (effect)
{
// ensure the effect isn't disposed
if (effect.IsDisposed) { break; }
}
// read the next chunk of data
int samplesRead = reader.ReadSamples(nvBuffer, 0, nvBuffer.Length);
// out of data and looping? reset the reader and read again
if (samplesRead == 0 && IsLooped)
{
reader.DecodedTime = TimeSpan.Zero;
samplesRead = reader.ReadSamples(nvBuffer, 0, nvBuffer.Length);
}
if (samplesRead > 0)
{
for (int i = 0; i < samplesRead; i++)
{
short sValue = (short)Math.Max(Math.Min(short.MaxValue * nvBuffer[i], short.MaxValue), short.MinValue);
buffer[i * 2] = (byte)(sValue & 0xff);
buffer[i * 2 + 1] = (byte)((sValue >> 8) & 0xff);
}
// submit our buffers
lock (effect)
{
// ensure the effect isn't disposed
if (effect.IsDisposed) { break; }
effect.SubmitBuffer(buffer, 0, samplesRead);
effect.SubmitBuffer(buffer, samplesRead, samplesRead);
}
}
// reset our handle
needBufferHandle.Reset();
}
}
}
}
@ddevault

This comment has been minimized.

Copy link

ddevault commented Oct 5, 2015

License? Can I use this in a project under the MIT license?

@Gert-Jan

This comment has been minimized.

Copy link

Gert-Jan commented Dec 9, 2015

Some very useful code!

Some changes I made were:

Adding a

private int blockAlign;

member variable which is set in the constructor to

blockAlign = reader.Channels * 2; // blockAlign = channels * bitDepth / 8, bitDepth is hardcoded as 16 in DynamicSoundEffectInstance

Than on line 149 I added

// make sure the samples read is block aligned
samplesRead -= samplesRead % blockAlign;

Otherwise it will crash when the full ogg file is not block aligned and the last sample is submitted to the buffer.

Lastly I removed the whole reliability on the BufferNeeded event. There seems to be a rare bug in DynamicSoundEffectInstance which causes the event not to be called. This can be observed by repeatably Stop/Play an ogg file and at some point it will only play the first submitted buffer and than stops firing the event.

Instead I simply replaced the second while loop in StreamThread with this:

// sleep until we need a buffer
while (!effect.IsDisposed && !threadStopHandle.WaitOne(0))
{
    if (effect.PendingBufferCount <= 2)
        break;
    Thread.Sleep(50);
}

PendingBufferCount == 2 is usually the moment when the BufferNeeded event is called.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.