Skip to content

Conversation

@pkviet
Copy link
Member

@pkviet pkviet commented Oct 15, 2025

Description

ASIO is a driver standard developed by Steinberg for low-latency audio. It is commonly used in pro audio.
It was unfortunately not possible for a long time to write a native ASIO host in obs because of license issues.
This has changed thanks to a partnership between the project and Yamaha Steinberg:
https://www.steinberg.net/press/2025/obs-collaboration/
The ASIO sdk is now released under dual licensing, including GPL v3 which is compatible with obs-studio.

Historical aside. When I got involved with obs coding, after working on surround sound support, my first plugin
endeavor was to write an asio source, which was developed eventually in collaboration with Andersama. It was a great learning experience. The ASIO sdk is notoriously tricky with lots of pitfalls for hosts and drivers alike. It was so daunting that at that time (early 2018) we relied on third-party libraries (RTAudio, Bassasio, portaudio) and now JUCE. The last incarnation of our asio plugin for obs (based on JUCE) was mostly coded by Andersama. It has been superseded in many ways by another third-party plugin based also on JUCE, atkaudio which has lot of capabilities but again relies on a third-party library. And anyway our ultimate goal had always been to add support for ASIO into obs.

This PR brings ASIO support in obs-studio natively: no third-party library is used but Steinberg SDK is leveraged directly.
For the host implementation, we have taken cues from JUCE ASIO host which documents a lot of pitfalls.

Our ASIO host provides:

  • an asio source where inputs from asio devices can be captured with also channel selection.
  • an asio output.

The asio output in turn allows:

  • track monitoring with channel selection too,
  • a special monitoring track which automatically mixes all monitored sources and can be selected in the asio output.

Technical note

The asio callback processes both audio from host to device and from device to host at the same time. The main difference with wasapi, is that any monitored audio sent to the device is not automatically mixed; it is the responsibility of the host to do the mixing. That is why asio can not be seamlessly integrated into libobs/audio-monitoring and that we resorted to creating an obs-output for asio.
This output is controlled through a frontend plugin which instead of adding an entry into the tool menu (as is customary) is callable from Settings > Audio alongside monitoring devices (wasapi on windows).

Screenshots:

ASIO source
obs64_2025-09-02_17-25-32

ASIO source with channel selection
obs64_2025-09-02_17-25-51

ASIO source with device selection
obs64_2025-09-02_17-25-58

ASIO monitoring integrated to audio settings
obs64_2025-10-15_20-46-35

ASIO monitoring panel
obs64_2025-10-15_20-52-18

ASIO monitoring panel with track channel selection. Not the special monitoring track
obs64_2025-10-15_20-52-40

Motivation and Context

ASIO brings the following advantages:

  • low-latency capture,
  • low-latency monitoring (the wasapi monitoring has long been a source of user complaints),
  • capability to do complex channel routing.

How Has This Been Tested?

Tested on windows 11 pro with several asio devices:

  • Steinberg IXO 22
  • Steinberg UR22C
  • ESI U22XT
  • Behringer XR18
  • Tascam US-122 MKII
  • virtual drivers such as Rearoute from Reaper, Dante virtual card

Testers are welcome to report their results with their interface and I'll update the list.

Types of changes

  • New feature (non-breaking change which adds functionality)

Checklist:

  • My code has been run through clang-format.
  • I have read the contributing document.
  • My code is not on the master branch.
  • The code has been tested.
  • All commit messages are properly formatted and commits squashed where appropriate.
  • I have included updates to all appropriate documentation.

@pkviet pkviet force-pushed the asio_host branch 3 times, most recently from b408fbf to 763a7f3 Compare October 15, 2025 20:07
@Fenrirthviti Fenrirthviti added the Seeking Testers Build artifacts on CI label Oct 15, 2025
@pkviet pkviet added the New Feature New feature or plugin label Oct 15, 2025
@pkviet pkviet added this to the OBS Studio 32.0 milestone Oct 15, 2025
@pkviet pkviet force-pushed the asio_host branch 5 times, most recently from 4c9de0e to aa3d977 Compare October 17, 2025 19:30
@pkviet
Copy link
Member Author

pkviet commented Oct 17, 2025

Update

  • Fixed a lot of bugs; thanks to @prgmitchell , indrit (on discord), @WizardCM
  • On @WizardCM suggestion, i added a message when a driver fails to load (usually because the device is disconnected,
    or just because of flaky drivers - there's a lot of them in ASIO landscape , or because the ASIO driver is being used by another host.)
obs64_2025-10-17_21-01-58

@pkviet pkviet force-pushed the asio_host branch 5 times, most recently from 44c4228 to 3c02361 Compare October 19, 2025 13:19
@pkviet
Copy link
Member Author

pkviet commented Oct 19, 2025

Update 10-19-25
Fixed some bugs with output.
Factored a lot of the input and output functions.
Fixed a bug with drivers which set a different default sample rate than that from obs

Copy link
Member

@PatTheMav PatTheMav left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like I need to see the code "in action" to better gauge its correctness. I there something like an "ASIO virtual device" or something I can install in my VM and then use this plugin with it?

Comment on lines +1 to +7
/* Copyright (c) 2022-2025 pkv <[email protected]>
*
* This file is part of win-asio.
*
* This file and all modifications by pkv <[email protected]> are licensed under
* the GNU General Public License, version 3 or later.
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RytoEX Should this use the default GPL license header instead?

Comment on lines +25 to +28
void buffer_switch_0(long, long);
ASIOTime *buffer_switch_time_info_0(ASIOTime *, long, long);
long asio_message_callback_0(long, long, void *, double *);
void sample_rate_changed_callback_0(ASIOSampleRate);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do these need to be hardcoded? This and the use of DEFINE_CALLBACK_SET are unfortunate to me, as we should not use function-like macros at all, particularly not at that complexity.

Copy link
Member Author

@pkviet pkviet Oct 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The context is that the asio sdk was written while having in mind a host loading a single driver. As in daws.
But of course we need the host to be multidriver. Unfortunately the sdk doesn't pass the 'this' pointer from driver to host.
So it is impossible to know which driver is calling any given callback.
In order to allow for up to 16 devices, I hard coded the 16 sets of callbacks.
If there is a more elegant solution in plain C, I am interested !

bool is_open;
bool is_started;
bool driver_failure;
volatile bool need_to_reset;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the purpose of volatile here? Note that volatile doesn't protect against anything, it merely suppresses optimisation by the compiler.

The purpose of the storage modifier was to enable memory-mapped I/O originally, but this doesn't seem to be the case here (as those are just state variables).

If these can be set by multiple threads, then they need to be atomic or need to use a spin lock, mutex, semaphore, etc. instead and then be used without volatile.

See also: https://www.kernel.org/doc/html/v4.17/process/volatile-considered-harmful.html

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The need_to_reset bool does use our atomics functions (os_atomics_set_bool and os_atomics_load_bool) since it is across threads. The signature of these functions require volatile bools.
See:
https://docs.obsproject.com/reference-libobs-util-threading

My understanding is that this usage is because we use c89 or c99 which don't offer atomics.
An inspection of how these atomics were written will probably reveal they require volatile bool to avoid over aggressive optimizations ?
You can easily check that my usage is consistent with what we do in the rest of the code base, for instance obs-ffmpeg-output.c, etc, where our home-bred atomics always use volatile bools.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is incorrect. A function signature like

static inline bool os_atomic_load_bool(const volatile bool *ptr)

does not mean that the pointer passed into has to be volatile qualified, it merely means that the function may accept such a pointer.

If a non-qualified pointer is passed into it, it will automatically be upgraded with the qualification and thus the code pessimisation (no reordering of accesses, explicit loads, no register-based caching) will apply to the function body.

As Linus has pointed out:

the only possible meaning for "volatile" is a purely single-CPU meaning. And if you only have a single CPU involved in the process, the "volatile" is by definition pointless (because even without a volatile, the compiler is required to make the C code appear consistent as far as a single CPU is concerned)

And as far as I can tell the only reason why that function signature carries the volatile is because the functions used on Windows also carry the qualifier:

CHAR InterlockedExchange8(
  [in, out] CHAR volatile *Target,
  [in]      CHAR          Value
);

So the actual question that has to be answered here is:

Is need_to_reset changed directly by a hardware I/O register, an external signal or interrupt handler, or between calls to setjmp and longjmp, and consequently also read directly?

Because if neither is the case and all read/write accesses from any thread use the atomic functions, there is no benefit to qualify it as volatile, and instead code pessimisation takes place.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok i wasn't aware that the volatile qualifier was not compulsory and would be automatically upgraded.
Currently the bool is used only in our atomics functions in ui thread and in the driver thread, so I assume it is then safe to drop the qualifier.
But the question is then whether it leads to adverse consequences to keep it?
Your observation would then extend to other places to the code base.
I merely followed the current usage without questioning it due to lack of litteracy that i will readily confess, but it seems i am not alone in that respect.
Is the question you ask of great import or just nitpicking ? Honest question.

This adds:
- an ASIO Input source, allowing audio capture of devices using
Steinberg ASIO audio SDK on windows. This allows low latency capture.
- an ASIO output, which will allow routing audio from OBS to any ASIO
device. A per channel routing is provided.
The UI to setup the ASIO output is provided in a later commit.

Signed-off-by: pkv <[email protected]>
This adds an ASIO output entry in Tools Menu allowing to setup audio
output from obs to an ASIO device.

Signed-off-by: pkv <[email protected]>
On windows, this adds an additional audio track (Track 7) which is used
to output a mix of monitored sources. Currently the mixing is left to
WASAPI on windows for WASAPI monitoring devices.
But ASIO SDK doesn't allow multiple clients (although individual drivers
 might allow it) and therefore can not mix them. So we have to do the
mixing on obs side.

Signed-off-by: pkv <[email protected]>
This enables a monitoring track for output.

Signed-off-by: pkv <[email protected]>
This adds a QToolButton to the Audio Settings on Windows.
This triggers in turn a dedicated settings for the ASIO monitoring
output.
If no ASIO driver is detected in the system, the panel displays an
explanatory message.
The panel allows to:
- select a monitoring device;
- for each output channel of the ASIO device, one can select any channel
from any of the 6 tracks or from the monitoring mix.

Signed-off-by: pkv <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

New Feature New feature or plugin Seeking Testers Build artifacts on CI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants