naudio / NAudio

Audio and MIDI library for .NET

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

`MixingSampleProvider` removes inputs that return incomplete reads

lostmsu opened this issue · comments

It is an unexpected behavior that affects real-time processing.

In my ISampleProvider I return from Read as soon as any data becomes available - this is how regular Read calls behave (e.g. NetworkStream.Read). So the returned value is often less than the requested count, but allows to forward samples with the lowest latency.

Unfortunately when I tried to use it with the MixingSampleProvider it removed my provider from inputs collection pretty quickly because of this line:

Generally speaking the only condition when the input can be deemed ended is when Read returns 0 or (for streams) or throws EndOfStreamException, and this is what I'd expect from MixingSampleProvider.

To repro:
Just make a ISampleProvider that returns silence but always only returns half the requested samples and try to use it with MixingSampleProvider

The design of sample providers is that they should always return count from the Read method unless they have reached the end. That's because it's often being called to fill in a playback buffer for audio playback, and returning less will result in dropouts. Things like BufferedWaveProvider can fill in the gaps with silence if you can't always provide the requested number of samples

Adding a BufferedWaveProvider or even just waiting for the source to provide exactly count data introduces latency. If the source is only ready to provide half the data it might be better to process that half and forward the result to the next step without buffering.

For the same reason this design assumption and the latency consequence apply to all sample providers, which also seems to be a problem to me.

Well you could make your own custom MixingSampleProvider that coped with sources returning < Read, but things start to get complicated pretty quickly. Let's say I asked for 1000 samples, and my mixer has 2 inputs. The first returns 1000 samples, but the second returns only 500. Now I've either got to pad the second one's output with 500ms of silence, or I've got to store the extra 500 samples from the first input somewhere else ready for the next call to Read.

This is exactly what I ended up doing with a circular buffer per input.