WASAPI and ASIO questions.

Started by AndyMK, 15 Feb '25 - 17:51

AndyMK

Hi Ian, hope you are well.

Regarding WASAPI. Is it possible to add an IAudioClient3 shared mode? I quickly made a test app using this API and it really performs well. 5ms glitch free on one on my USB interfaces and less than 3ms on my Motherboard audio. Would be nice to integrate this in to BASS.

Regarding ASIO. I have an interface (SSL12, the driver is the same for all of their devices) that does not notify on buffer size changes. I have an old ASIO client host i implemented years ago and i can confirm i get a reset event when i change the buffer size on that interface.

Thanks.
Regards.

Ian @ un4seen

Quote from: AndyMK on 15 Feb '25 - 17:51Regarding WASAPI. Is it possible to add an IAudioClient3 shared mode? I quickly made a test app using this API and it really performs well. 5ms glitch free on one on my USB interfaces and less than 3ms on my Motherboard audio. Would be nice to integrate this in to BASS.

That should already be supported. This is the relevant part from the BASS_WASAPI_Init docs:

QuoteShared mode usually has a fixed period of 10ms, but Windows 10 supports shorter periods, which allows smaller buffers and lower latency. A shorter period (and buffer) can be requested by setting buffer to 0 and period to the length wanted. If the requested period is lower than the device (or Windows) supports, then it will be automatically raised to the minimum supported. It will also be rounded up if it does not match the device's granularity. The actual period in use can be determined from the (minimum) amount of data that gets requested from the WASAPIPROC callback function. The shared mode period is a system-wide setting that affects all users of the device, particular those using event-driven buffering; they will be asked to provide data at the new period. If another process is already using a non-default period, then it will not be possible to set a different period until they finish; the existing period will have to be used in the meantime.

So basically set "buffer" to 0 and "period" to the wanted period. Let me know if it doesn't work for you.

Quote from: AndyMK on 15 Feb '25 - 17:51Regarding ASIO. I have an interface (SSL12, the driver is the same for all of their devices) that does not notify on buffer size changes. I have an old ASIO client host i implemented years ago and i can confirm i get a reset event when i change the buffer size on that interface.

To confirm... When using your own ASIO client, your asioMessage function is called with kAsioResetRequest when you change the buffer size? But when using BASSASIO and BASS_ASIO_SetNotify, your ASIONOTIFYPROC function isn't called with BASS_ASIO_NOTIFY_RESET when you change the buffer size? That's strange if so, as the ASIONOTIFYPROC function is called by BASSASIO's asioMessage function. I'd probably need to send you a debug version for more info in that case.

AndyMK

QuoteTo confirm... When using your own ASIO client, your asioMessage function is called with kAsioResetRequest when you change the buffer size? But when using BASSASIO and BASS_ASIO_SetNotify, your ASIONOTIFYPROC function isn't called with BASS_ASIO_NOTIFY_RESET when you change the buffer size? That's strange if so, as the ASIONOTIFYPROC function is called by BASSASIO's asioMessage function. I'd probably need to send you a debug version for more info in that case.

I just tried it on another PC with the same interface and it works fine so there must be something wrong with the driver installation or version. Is it normal for the callback to fire several times per action?

With regards to the WASAP question, i'm sure it was returning a 480 sample buffer no matter what i set but i'll test it again on the new PC and let you know. Thanks.

Ian @ un4seen

Quote from: AndyMK on 17 Feb '25 - 16:21Is it normal for the callback to fire several times per action?

I don't think so generally, but it would be driver-dependent. BASSASIO basically just forwards the callbacks from the ASIO driver (BASS_ASIO_NOTIFY_RESET=asioMessages/kAsioResetRequest, BASS_ASIO_NOTIFY_RATE=sampleRateChanged), so you should see the same callbacks as with your own ASIO client. Are you seeing a difference between them?

AndyMK

Checked with my implementation and it has the same behavior.

AndyMK

Hi Ian, i had a chance to check IAudioClient3 today. After Activation, i ran GetCurrentSharedModeEnginePeriod() and it returned 480 samples at 48000hz.
I then ran GetSharedModeEnginePeriod() and got:

pDefaultPeriodInFrames =         480
pFundamentalPeriodInFrames = 4
pMinPeriodInFrames =              96
pMaxPeriodInFrames =             480

after running InitializeSharedAudioStream() with a period of 256, it initializes fine. GetCurrentSharedModeEnginePeriod() returns 256 and the GetBuffer() in the callback is returning 256 for the numFramesAvailable. All works fine.

BASS_WASAPI_Init(-1, 48000, 2, #BASS_WASAPI_EVENT, 0, 256, @callback3(), 0) gives me a solid 480 samples available to write to in the callback

Ian @ un4seen

The BASS_WASAPI_Init "period" (and "buffer") parameter is in seconds by default, but it can be in samples instead with the BASS_WASAPI_SAMPLES flag. So please try adding that flag to your call and see what result you get then. BASS_WASAPI_Init also uses GetSharedModeEnginePeriod to validate/restrict the requested period, so the callback amounts should be the same as you saw when using your own WASAPI code.

AndyMK

Quote from: Ian @ un4seen on 20 Feb '25 - 15:00The BASS_WASAPI_Init "period" (and "buffer") parameter is in seconds by default, but it can be in samples instead with the BASS_WASAPI_SAMPLES flag. So please try adding that flag to your call and see what result you get then. BASS_WASAPI_Init also uses GetSharedModeEnginePeriod to validate/restrict the requested period, so the callback amounts should be the same as you saw when using your own WASAPI code.

I feel stupid for not seeing such a mistake.
BASS_WASAPI_Init(-1, 48000, 2, #BASS_WASAPI_EVENT | #BASS_WASAPI_SAMPLES, 0, 256, @callback3(), 0)Fails with -1. If i change the period to 480, it works.

AndyMK

This is strange. My default device is currently a Universal Audio Apollo Twin X Quad (thunderbolt) The WDM driver is terrible on this device. If i use a period of anything other than 480 with BAS WASAPI, it fails with -1. My other interfaces including onboard audio are fine but they all default to 480 samples no matter what i choose. The Universal Audio Device also has an Exclusive mode problem where it does not respond to events(timeout) but that also happens with my own WASAPI implementation.

Ian @ un4seen

Quote from: AndyMK on 20 Feb '25 - 15:35BASS_WASAPI_Init(-1, 48000, 2, #BASS_WASAPI_EVENT | #BASS_WASAPI_SAMPLES, 0, 256, @callback3(), 0)Fails with -1. If i change the period to 480, it works.

Ah, I think I'm able to reproduce that too. It looks like resampling isn't supported by IAudioClient3::InitializeSharedAudioStream. BASS_WASAPI_Init currently requests resampling support when the BASS_WASAPI_AUTOFORMAT flag isn't specified. Does the call still fail if you add BASS_WASAPI_AUTOFORMAT to it?

Quote from: AndyMK on 21 Feb '25 - 06:56My other interfaces including onboard audio are fine but they all default to 480 samples no matter what i choose.

Is GetSharedModeEnginePeriod returning non-480 values for those devices? It seems some devices have a fixed period, ie. pMinPeriodInFrames = pMaxPeriodInFrames.

AndyMK

#10
BASS_WASAPI_AUTOFORMAT fixed the problem with the Universal Audio Interface and it reports a 256 sample buffer. The other interface reports a minperiod of 144 samples but is stuck at 480 samples. i'll retest this with my implementation.

AndyMK

Hi Ian. In my Implementation, the device that only supports 480 samples with IAudioClient3 gives me:

pDefaultPeriodInFrames = 480
pFundamentalPeriodInFrames = 480
pMinPeriodInFrames = 480
pMaxPeriodInFrames = 480

when calling GetSharedModeEnginePeriod() which seems correct.

AndyMK

Hi Ian. Please check BASS_WASAPI_Free() in the new Beta.

BASS_WASAPI_SetDevice(#BASS_DEVICE_PROCESS | pid)
BASS_WASAPI_Free()

I have check pid is a valid processId. The application hangs at BASS_WASAPI_Free() indefinitely without returning or crashing.

AndyMK

Quote from: AndyMK on 23 Feb '25 - 16:47Hi Ian. Please check BASS_WASAPI_Free() in the new Beta.

BASS_WASAPI_SetDevice(#BASS_DEVICE_PROCESS | pid)
BASS_WASAPI_Free()

I have check pid is a valid processId. The application hangs at BASS_WASAPI_Free() indefinitely without returning or crashing.

Ignore this for now, it has something to do with the thread i'm calling it from.

Ian @ un4seen

Quote from: AndyMK on 21 Feb '25 - 16:54BASS_WASAPI_AUTOFORMAT fixed the problem with the Universal Audio Interface and it reports a 256 sample buffer.

Good to hear BASS_WASAPI_AUTOFORMAT fixed the problem. Here's an update that shouldn't require that flag if the specified format matches the mix format, and should also fail with BASS_ERROR_FORMAT (instead of BASS_ERROR_UNKNOWN) when it doesn't match:

   www.un4seen.com/stuff/basswasapi.zip

Quote from: AndyMK on 21 Feb '25 - 16:54The other interface reports a minperiod of 144 samples but is stuck at 480 samples.

I guess that minperiod=144 value is based on the BASS_WASAPI_DEVICEINFO minperiod value? If so, that applies to exclusive mode (it's from IAudioClient::GetDevicePeriod), and it looks like it doesn't necessarily also apply to shared-mode. I'll add a note of this in the BASS_WASAPI_DEVICEINFO documentation.

Quote from: AndyMK on 23 Feb '25 - 16:51
Quote from: AndyMK on 23 Feb '25 - 16:47BASS_WASAPI_SetDevice(#BASS_DEVICE_PROCESS | pid)
BASS_WASAPI_Free()

I have check pid is a valid processId. The application hangs at BASS_WASAPI_Free() indefinitely without returning or crashing.

Ignore this for now, it has something to do with the thread i'm calling it from.

I tried creating a new thread just for the BASS_WASAPI_Free call, and that seemed to work. Is there something special about the thread you're calling it from, eg. perhaps a callback thread? If the problem persists, please provide a dump file from it (eg. using Task Manager's "Create dump file" option) to have a look at.

You probably already know this, but a thread-related thing to note about the BASS_DEVICE_PROCESS option is that the BASS_WASAPI_Init call should be in the main thread then, because ActivateAudioInterfaceAsync is used internally by it:

   https://learn.microsoft.com/en-us/windows/win32/api/mmdeviceapi/nf-mmdeviceapi-activateaudiointerfaceasync

AndyMK

Hi Ian. The update now works without the BASS_WASAPI_AUTOFORMAT. Regarding the BASS_WASAPI_DEVICEINFO returned, is it possible for you to return the values based on the IAudioClient version initialized and if IAudioClient3, get the values using GetSharedModeEnginePeriod() ? Just thinking of a way to get the most reliable available latencies for end user selection.

Ian @ un4seen

BASS_WASAPI_GetDeviceInfo can be (and probably most often is) called on an uninitialized device, so I'm not sure that solution would be good. I think a more consistent solution would be a new BASS_WASAPI_GetDeviceInfo flag (eg. 0x40000000) to use GetSharedModeEnginePeriod in setting the "minperiod" value, or separate BASS_WASAPI_GetDeviceInfoEx and BASS_WASAPI_DEVICEINFOEX including a new "minsharedperiod" (for example) value. Let me know if you can think of any other solutions.

AndyMK

Hi Ian, sorry for the late reply. Is it feasible to initialize a device when calling BASS_WASAPI_GetDeviceInfo and then uninitialize it when done? Not sure what the overhead is or if it will affect other streams. You could activate IAudioClient3 and use GetSharedModeEnginePeriod. If IAudioClient3 is unavailable, fallback to using IAudioClient method for getting period values. Just thinking out aloud.

I also wanted to ask, Is there any way to prevent recording streams from being ducked by apps like WhatsApp? I already disabled ducking in the windows audio control panel communication tab ("Do Nothing") but it seems to have no effect. The App i'm working on is capturing audio from multiple processes and sending to an ASIO device via a BASS_Mixer. I assume its the windows mixer ducking the Loopback streams before they get to my App because ASIO should not be affected by windows audio ducking.

Ian @ un4seen

Quote from: AndyMK on 18 Mar '25 - 13:38Is it feasible to initialize a device when calling BASS_WASAPI_GetDeviceInfo and then uninitialize it when done? Not sure what the overhead is or if it will affect other streams. You could activate IAudioClient3 and use GetSharedModeEnginePeriod. If IAudioClient3 is unavailable, fallback to using IAudioClient method for getting period values. Just thinking out aloud.

Yes, that shouldn't be a problem, as BASS_WASAPI_GetDeviceInfo already gets an IAudioClient for the device's info and it would just need to additionally query the IAudioClient3 interface. The question is how to tell when to do so.

Quote from: AndyMK on 18 Mar '25 - 13:38I also wanted to ask, Is there any way to prevent recording streams from being ducked by apps like WhatsApp? I already disabled ducking in the windows audio control panel communication tab ("Do Nothing") but it seems to have no effect. The App i'm working on is capturing audio from multiple processes and sending to an ASIO device via a BASS_Mixer. I assume its the windows mixer ducking the Loopback streams before they get to my App because ASIO should not be affected by windows audio ducking.

Yeah, I would guess the issue is that the ducking has already been applied before the data gets to the loopback. I presume per-device loopbacks are also affected, not only per-process loopbacks? If you haven't already done so, please also check loopback recording with other software for comparison, including the pre-compiled RECTEST.EXE example from in the BASSWASAPI package (C\BIN folder).

AndyMK

The only way i can get around this is to put WhatsApp and any other apps that apply ducking on their own Virtual audio cable.