Skip to content

A new ASIO backend implementation #1299

Description

@LastExceed

I have recently published azo, a re-implementation of ASIO's underlying COM API. As mentioned in #1296, the project's goal is for it to replace asio-sys in cpal's ASIO backend. I'm creating this issue for discussion and tracking purposes.

Motivation

asio-sys is bound to the limitations of Steinberg's ASIO SDK (outlined below), which are unsolvable from the outside.

ASIO SDK Limitations

Licensing Issues

The ASIO SDK has been under a propietary license for most of its life, with GPL being added as an alternative just a few months ago. Neither of them is a great fit for cpal:

  • The propietary license (under which ASIO SDK was introduced into cpal) prohibits any form of redistribution, including even the header files, which requires developers to either
    • use a build script that automatically downloads the SDK from Steinberg's website - which is questionable both legally and security wise
    • manually provide the SDK themselves - which is inconvenient
  • The GPL license is copyleft, which I doubt is 100% compatible with cpal's permissive license either (IANAL, correct me if I'm wrong)

Limited Capability

Most notably, ASIO SDK is incapable of initializing more than 1 driver at a time. This makes using multiple devices in parallel impossible. The spec explicitly calls this out as a limitation of the implementation, rather than a design choice.¹ There are also some other minor limitations, like not supporting strings which exceed certain fixed buffers in length, but these have essentially become part of the standard at this point.

azo

solves both of these problems. It is permissively licensed, and has multi-driver support without limitations. As the author I am of course heavily biased towards it, but I'm willing to do my due diligence in terms of field testing. For that, my first step was to implement cpal's custom host API, and in doing so, I encountered some problems with its design that I would like to discuss here.

cpal Limitations

No channel Mapping

cpal doesn't have a mechanism for channel mapping at the moment, so requesting n channels always means 0..n. Combined with the fact that ASIO channels are always mono (by design), this effectively means that only the first input and output channel of each device can be used. The current implementation works around this by including a mixer, which combines mono channels to stereo using an intermediate buffer. However, I'm very apprehensive of going this route, as it misrepresents the device's topology. The ability to record/playback stereo streams on a device without stereo channels might cause confusion among dependents.

One might point out that other backends feature native mixers out of the box, such as WASAPI's system wide sound server, but this isn't quite comparable, because in those cases, the native mixer IS the origin/destination of the stream, as far as cpal is concerned.

No Bulk Stream Creation

Another problem I faced is that in cpals API, streams are created one at a time, but ASIO synchronizes all channels by design, including their buffer size, sample position, buffer callback, running state, etc. Therefore, it isn't even possible to use the 1 input and 1 output channel that are accessible at the same time, unless there is some serious spaghetti going on under the hood. (edit: the upcoming duplex API solves this)

Overwhelming trait requirements

The device and stream traits impose some requirements that I struggled to fulfill. For instance, devices being Clone was counter intuitive to me. I understand the notion of it being a device identifier, rather than an life handle, but this just raises the question where the actual life handle (driver instance in ASIO's case) should reside. I ended up putting it behind an Arc that all Device and Stream instances hold, but it felt like cheating the system.

The Send bound on Stream was also tricky. ASIO uses apartment threaded COM, and painfully doesn't support marshalling AT ALL, which is very hard to make thread safe. Maybe this more of a criticism of ASIO rather than of cpal, but it is also what we have to work with.

Conclusion

Maybe it is a skill issue, but I am unable to come up with a "correct" design of fitting ASIO into cpal's API to an extent that is practically useful. I don't know how well the other backends fared in this regard, would love to hear some opinions on this.

I'm actually considering to make myself familiar with the other backends before continuing with ASIO, so I can get a bigger picture of why cpal is the way it is, but I fear I may be underestimating the depth of this rabbit hole.


¹ASIO 2.3 Specification, section IV.1:

[...] However, the implementation is currently limited to one active driver. Also, it implies that a driver’s name will have max. 32 characters including the terminating '\0'.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions