Occasional Issues With GatherNextPage/FindNextPageHeader

Thanks for your help over at NAudio. I'm finding that sometimes if I manage to load an ogg file and play it too quickly after, or if I try to load the same ogg file twice then it throws an exception for not being able find the next page.

I need to be able to load the same file multiple times so that I can play it back as many times laid over each other as required (gunshots, etc). Am I going about it the wrong way?

It boils down to one of the crc checks failing and the method FindNextPageHeader() returning null because of this. It never appears to be the first CRC check either.

Here's the real kicker, it appears that in Debug it just stutters the track endlessly, and only in Release does it throw the exception. The error is thrown by GatherNextPage because hdr == null. Error is "Could not find next page."

Right, ok, it appears to only happen when I'm loading an OGG on a different thread at the same time.

Here's my set up currently:

I have a thread that I tell "this is your next track", and it loads that ogg then fades it in while fading the current one out.

Also:

At the same time, pretty much straight after telling the music thread to play the battle music, I play the character battle cry as a test. Then I get the error I've posted above.

Is there something not thread safe about the loading process? Should I be handling it differently?

Thanks,

Steve

P.S. I would like to apologise for my misleading, non-threading related analysis previously. It seems entirely down to my threaded audio. Tbh, I'm a great high-level games programmer, but when it gets to the low level stuff I'm still a bit of a newbie lol.

I don't immediately see where the threading issue is, but you might try moving line 47 in AudioFileReader.Initialize(string) to the getter for Length. That way your loading function doesn't read through the entire file before returning, and the length
is only read when actually needed. This will also speed up file loading tremendously.

I've seen an issue with threading Length while decoding, but it should have been fixed by the multi-threaded stream wrapper (Ogg.ThreadSafeStream)...

Well I've noticed that sometimes when it crashes out, this method isn't in the call stack:
NAudio.OggVorbis.ContainerReader.PageReaderLock pageLock) Line 351 + 0xe bytes C#

I don't know whether that's pertinent to the issue or not.

That's a great suggestion about the length. Currently the AudioFileReader sets its internal length for later use, I'll shift it around so that AudioFileReader only gets length the first time its asked for.

I'll have a nose around and if need be, I'm sure I can be smarter with my threading so that Audio is all handled by one thread.

Cool. I'd like to know the cause of this threading issue, but I think it would be better in your case to change the way your audio engine works...

My recommendations:

1) Decode sound effects into a cache. You may consider decoding on a separate thread for each file requested.
2) Decode background music into a WaveBuffer on a dedicated thread. Include all logic for changing tracks here.
3) Put your mixing and your sound effect playback logic in your implementation of IWaveProvider.Read(...).
4) Add a "command queue" between your game engine and the audio engine. If using a global counter for your engine, put a field in the commands to mark when the audio engine should start the command (vs. the global counter).
5) Have the command queue immediately trigger #1 when the cache does not have a sound effect decoded yet.
6) Have the background music thread watch the command queue and execute the commands as needed.
7) Have the sound effect playback logic watch the command queue and start playback of sound effects at the correct time (from the cache).
8) Don't let the cache get too big... Include pruning logic somewhere. I'd probably put it in #5.

For #4, you might even consider two command queues: One for the music (and a "stop everything" command), and one for the sound effects.

As far as I can see, the NAudio+NVorbis combination just doesn't enjoy being on a separate thread. Even implementing the most basic of a threaded audio system causes it to either immediately or eventually start to stutter/lock up a sound effect/music track.

If you can excuse the code dump, this is my threaded audio class (only if you're interested), I've strimmed out the error checking stuff for ease of viewing:

Thanks for the explanation. I get where you're coming from. Just so I'm clear, decoding is the stage where the file is initialised? Can I get away with decoding on multiple threads and playing on one single separate thread or should I be decoding all on
one thread and playing all on another?

I appreciate all your help, definitely gives me an understanding that I wouldnt have otherwise have had :)

Even if no new instances of audio are decoded during the running of the threaded playback occasionally it starts to stutter. I don't suppose you have a working, proven example of threaded OGG audio that I could adapt? I feel like I must be doing something inherently
wrong (beyond not decoding on a separate thread) for nobody else to have encountered the issues that I have.

Decoding in NVorbis happens as needed, specifically in the call to ReadSamples(...). Initialization just finds the stream headers and unpacks the decode parameters (no audio data, though).

I would highly recommend that the playback thread only have to deal with previously-decoded data. It is very latency-sensitive, so there's probably not time to wait for the decoder in most cases. Separate threads makes this much simpler. Decoding on multiple
threads will work great; Just make sure you manage your threads correctly (normal multi-threading rules / guidelines apply here).

The stuttering is likely related to the above point: Playback is very latency-sensitive and really can't afford to wait for the decoder. It "can" be done on a fast enough machine, but I wouldn't even bother. Also, be aware that NVorbis will have performance
issues if the disk takes too long to return data. I have some ideas for making it less sensitive, but won't be able to mess with them for a while.

Ah yes, of course. That makes great sense thanks. I'll develop it to have the decoding work as fast as it can to buffer a decent amount on separate threads, then have the playing of that buffered data be on the main audio thread, then use the main gameplay
thread for everything else.

I appreciate all of the depth you've gone into with me here, its really helped me understand your library.