Audio Dropouts

Started by Chris Oakley,

Chris Oakley

QuoteAh! That clears the mystery of the multiple stalling mixers then: there was a delay (longer than 250ms) within the processing of one of them, which also held up processing of the others, and the playback buffers became empty in the meantime, resulting in them all stalling. You can avoid a delayed playback channel affecting others by enabling multiple update threads via the BASS_CONFIG_UPDATETHREADS option.

So I do this after I've called BASS_Init?
Bass.BASS_SetConfig(BASSConfig.BASS_CONFIG_UPDATETHREADS, 32)
QuoteNow you just need to find what's causing the delay. Do you have any encoders set on the mixers (or their sources) when the problem happens, and if so, do you have the BASS_ENCODE_QUEUE flag set on them? Also check if you have any callback functions (eg. SYNCPROC/DSPPROC/STREAMPROC) that need to wait for something, eg. a lock/mutex or UI updates.
Just so I know we're talking about the same thing - when you say "delay" you're referring to the other issue I mentioned where the physical output audio will drift and become delayed? If so, not all users have this issue, it seems to be random, but as far as I know it's just because a physical device can clock at a slightly different rate than the No Sound clock and this can cause the consumption of data to be different.

In answer to the part about encoders, we do have the BASS_ENCODE_QUEUE flag in use. We do use DSP_PeakLevelMeter on the mixer that we split from just so we have a way of getting the current level values for a UI level meter.

Ian @ un4seen

Quote from: Chris OakleySo I do this after I've called BASS_Init?
Bass.BASS_SetConfig(BASSConfig.BASS_CONFIG_UPDATETHREADS, 32)

You can set BASS_CONFIG_UPDATETHREADS at any time, before or after BASS_Init. 32 threads is probably unnecessarily high. It would typically be set to the number of CPU cores.

Quote from: Chris OakleyJust so I know we're talking about the same thing - when you say "delay" you're referring to the other issue I mentioned where the physical output audio will drift and become delayed?

No. This delay would be caused by something within a playback channel's processing taking longer than usual. Such a delay could be due to a slow file read operation, but not in this case if it's always playing files from memory. Another possibility is a callback function blocked/waiting for something. As you're using .Net, perhaps that's .Net garbage collection:

    https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/

Are you using any callbacks?

Quote from: Chris OakleyWe do use DSP_PeakLevelMeter on the mixer that we split from just so we have a way of getting the current level values for a UI level meter.

To see if it's related, does removing that prevent the problem? If it does, could you use BASS_ChannelGetLevel(Ex) or BASS_Mixer_ChannelGetLevel(Ex) instead?

Chris Oakley

We only really have this one but the feature that uses this isn't running with the client that's having the issue:
    Private _dupCallback As DSPPROC = Nothing
    Private _buffer As ConcurrentQueue(Of Byte)

    Private Sub DupDSP(handle As Integer, channel As Integer, buffer As IntPtr, length As Integer, user As IntPtr)
        Try
            Dim _bytes(length) As Byte
            Marshal.Copy(buffer, _bytes, 0, length)
            For i As Integer = 0 To length - 1
                _buffer.Enqueue(_bytes(i))
            Next
        Catch ex As Exception
            'Nothing to do
        End Try
    End Sub

When it is enabled it's essentially a ring buffer where it adds the buffer to a stack and we can break it up into precisely sized blocks of data because the system that needs it needs it to be an exact size each time.

As I say I think we can disregard this since it's not operational in this instance, but I imagine it's the sort of thing you were thinking of for GC.

I'll try disabling the DSP peak level meter, but not sure that it will help as we're only reading the level property - I can't see how that could cause a stall.

Chris Oakley

I think we might be getting somewhere now. Overnight we've had one stall, that's all. Only one Bus reported this:
2026-06-30 07:15:00.256 (2026-06-30 03:15:00.256) : AudioNode Bus C Split Stalled: handle = -2147483432 : channel = 800000D6
2026-06-30 07:15:00.256 (2026-06-30 03:15:00.256) : AudioNode Bus C Split Recovered: handle = -2147483432 : channel = 800000D6

What's interesting about this is it was reported at the same time the file on that bus closed:
2026-06-30 07:15:00.279 (2026-06-30 03:15:00.279) : Player "25fa9b08-965f-4e67-a963-6f02871893d7" playing with "138001-007 : XXXXXX XXXX" was closed.
There is a 23ms difference between it showing as stalled and it logging the close, but the routine SoundClose that closes the file would have made a quick update to the SQL DB which would account for this difference as the logging is the last thing that's done after freeing the stream and the SQL.

My suspicion is this happened because it reached the end of the file so the BASS_SYNC_END in this case. Normally a sound is closed when we hit a preset marker that we track ourselves from a timer, but if that preset mark is right at the very end of the file (in this case it was) then our timer might not trigger before the SYNC is triggered - so it's called from the callback.

If we added the BASS_SYNC_THREAD to the sync, would that help because even though we invoke the call to our SoundClose method, I wonder if it's not really working as I expect?
_MySyncId = Bass.BASS_ChannelSetSync(_Stream, BASSSync.BASS_SYNC_END Or BASSSync.BASS_SYNC_ONETIME Or BASSSync.BASS_SYNC_THREAD, 0, _mySync, IntPtr.Zero)

Ian @ un4seen

Quote from: Chris OakleyI'll try disabling the DSP peak level meter, but not sure that it will help as we're only reading the level property - I can't see how that could cause a stall.

I'm not an expert on .Net garbage collection, but I believe it blocks all .Net code in the meantime. DSP_PeakLevelMeter uses a .Net DSPPROC callback function (to get the level of the data in it), so it would probably be affected, ie. it'd have to wait for the garbage collection to finish.

Quote from: Chris OakleyI think we might be getting somewhere now. Overnight we've had one stall, that's all. Only one Bus reported this:
2026-06-30 07:15:00.256 (2026-06-30 03:15:00.256) : AudioNode Bus C Split Stalled: handle = -2147483432 : channel = 800000D6
2026-06-30 07:15:00.256 (2026-06-30 03:15:00.256) : AudioNode Bus C Split Recovered: handle = -2147483432 : channel = 800000D6

Yep. The extra update threads will have prevented that mixer/splitter also delaying and stalling the others.

Quote from: Chris OakleyWhat's interesting about this is it was reported at the same time the file on that bus closed:
2026-06-30 07:15:00.279 (2026-06-30 03:15:00.279) : Player "25fa9b08-965f-4e67-a963-6f02871893d7" playing with "138001-007 : XXXXXX XXXX" was closed.
There is a 23ms difference between it showing as stalled and it logging the close, but the routine SoundClose that closes the file would have made a quick update to the SQL DB which would account for this difference as the logging is the last thing that's done after freeing the stream and the SQL.

My suspicion is this happened because it reached the end of the file so the BASS_SYNC_END in this case. Normally a sound is closed when we hit a preset marker that we track ourselves from a timer, but if that preset mark is right at the very end of the file (in this case it was) then our timer might not trigger before the SYNC is triggered - so it's called from the callback.

If we added the BASS_SYNC_THREAD to the sync, would that help because even though we invoke the call to our SoundClose method, I wonder if it's not really working as I expect?
_MySyncId = Bass.BASS_ChannelSetSync(_Stream, BASSSync.BASS_SYNC_END Or BASSSync.BASS_SYNC_ONETIME Or BASSSync.BASS_SYNC_THREAD, 0, _mySync, IntPtr.Zero)

I guess that SYNCPROC call actually took quite a bit longer than 23ms, as the stall won't have been at the start of it (more like 250ms later because that's the playback buffer length). I would definitely consider the BASS_SYNC_THREAD flag if the SYNCPROC could take anything like that long, as it'll mean the mixer can continue processing while the SYNCPROC runs in a different thread, ie. the mixer won't be delayed by it.

Chris Oakley

Well we had a stall still but there were no DSP levels running, so I'm not sure how it could be responsible. I believe without the updated thread count it would have stalled on all buses again.

We'll do a test with them back in because I can never get the BASS_Mixer_ChannelGetLevel to work. I put the BASS_MIXER_CHAN_BUFFER on when I add a split to a mixer further down the chain, but it never works.

I'm not 100% clear where that flag goes. Should it go on the file stream I open to play audio when I add that to a mixer - or does it go elsewhere? Bear in mind the workflow would be something like:
Audio Stream > Split > Mixer > Split > Mixer

Ian @ un4seen

You would include the BASS_MIXER_CHAN_BUFFER flag in the BASS_Mixer_StreamAddChannel call of the source that you want to be able to call BASS_Mixer_ChannelGetLevel(Ex) on. If you want to get the level of "Audio Stream" in your example, then the first "Split" is what you would set the flag on and use in BASS_Mixer_ChannelGetLevel(Ex) calls. If you want the level of the first "Mixer", then the second "Split" is the one of interest.

Regarding DSP_PeakLevelMeter, I'm not saying it's definitely causing a problem (your logging seems to reveal a SYNCPROC as the cause), but I just mention it in relation to garbage collection.

Chris Oakley

Thanks Ian. I've placed the BASS_MIXER_CHAN_BUFFER flag to the right place, and while it works initially, the reading of the levels becomes more delayed, very quickly, compared to what's playing out of the physical device.

I tried:
Bass.BASS_SetConfig(BASSConfig.BASS_CONFIG_MIXER_BUFFER, 50)but it made no difference.

As for the stalls - we're just monitoring. So far there haven't been any overnight.

Ian @ un4seen

Are the levels definitely behind what's heard from the output device, and if so, by how much? And are the levels taken from the same mixer that's playing on the output device, or might they be from a different mixer, eg. your "No Sound" output mixer? That matters because BASS_Mixer_ChannelGetLevel(Ex) takes account of the output latency when known, so it needs to be the right output for it to be in sync with what's heard.

In your example above, I would expect the 2nd "Split" to be in sync because BASS_Mixer_ChannelGetLevel(Ex) does know the latency (if the 2nd "Mixer" is being played by BASS), and the 1st "Split" to be ahead of the output because the latency isn't known (there's a splitter between the mixer and output). When it doesn't know the output latency, you can tell it what that is via the BASS_ATTRIB_MIXER_LATENCY option. Please see the BASS_Mixer_ChannelGetLevel documentation for details.

Chris Oakley

Ian, with regard to the stalling problem, everything has been fine for days, except this morning we got one brief stall on all mixers. I cannot find anything immediate to account for it. The only thing that occurred was a new file started to play and there was then a stall about 1 second after that in which both playing files had a brief pause before continuing.

So I have a question - if the audio was being played via files on a server, via a UNC path \\192.168.1.10\Files\audio.mp3 and the network data flow was interrupted, could it cause a brief pause on the BASS audio thread or would it immediately trigger the SYNC_END to raise?

Ian @ un4seen

Was the network file(s) in all of the mixers that stalled, either directly or indirectly via submixers? If so, a file read operation taking too long (eg. due to network problem) would indeed cause them all to stall. The file may be ended early (and BASS_SYNC_END triggered) if the read fails, but I think Windows network file reads usually take quite long to timeout/fail.

Chris Oakley

#31
No, the two network files were coming through only two of the mixers at the time. That said, all the mixers would ultimately be getting processed by the main mix engine mixer, so I don't know if that would affect the BASS audio thread.

Ian @ un4seen

Any mixers that ultimately include the files would be affected/delayed by their slow reads, eg. if a slow file is plugged into mixer 1, and mixer 1 is plugged into mixer 2, then both mixer 1 and mixer 2 will be affected. So long as those mixers don't have playback buffering disabled (BASS_ATTRIB_BUFFER=0) and there are other update threads available (via BASS_CONFIG_UPDATETHREADS), then other playback should be unaffected.

Chris Oakley

Well in that case it's a mystery as to why they all stalled.

We did have another stall on the other machine yesterday but it was only one mixer on that which stalled. It was playing the audio from a file located in \\127.0.0.1\ and my understanding of this is that it would still be using the network sub stack despite it being a loopback UNC path.

We have turned memory caching back on so it would play files from memory, so we're just monitoring now to see how things go and see if any more patterns emerge.

There's definitely something very odd happening.

I do want to get to the bottom of the delay on the mixer levels, so I'll start a separate topic for that.