cpal icon indicating copy to clipboard operation
cpal copied to clipboard

Proposal: Duplex API

Open ollpu opened this issue 3 years ago • 3 comments

We're working on an audio editor / DAW, and full duplex audio IO is a fairly important feature for such software that is currently missing from CPAL.

For reference, duplex (or more accurately, full duplex) means that both input and output are processed at the same time, on the same stream, implicitly synchronized. Hosts have different levels of support for this: some provide it natively (JACK, CoreAudio), while on others this API merely allows input and output to run on the same loop to simplify synchronization. Without it, a CPAL user needs to handle synchronization manually and buffer an indeterminate amount to avoid over/underruns, as is done in the feedback example. This proposal does not address combining input and output streams from separate devices since in general that opens up further responsibilities such as clock drift compensation.

The API I have laid out in this PR is based on the one previously discussed here, though some things in CPAL have since changed. It is mostly a mirror of the existing input / output APIs, forming a third type of stream called "duplex". In particular,

  • input/output_devices -> duplex_devices, default_input/output_device -> default_duplex_device
  • supported_input/output_configs -> supported_duplex_configs, default_input/output_config -> default_duplex_config
  • build_input/output_stream -> build_duplex_stream
    • The data callback for duplex has the signature FnMut(&Data, &mut Data, &DuplexCallbackInfo). In general the order of parameters should always be input, then output.

The largest difference is with how channels are configured. I determined it doesn't make sense to list out every possible combination of the amount of input and output channels as separate SupportedDuplexStreamConfigRange instances for the user to iterate over. Therefore I instead incorporate ranges of possible channel configurations (min, max, default) for both input and output in the SupportedDuplexStreamConfigRange structs. Hopefully this doesn't make the API too illogical. I wrote up a more in-depth rationale in this commit.

In addition, it should be straightforward to add a conversion layer to convert input/output SupportedStreamConfig into SupportedDuplexStreamConfig with zero channels of the other type. This would allow users of CPAL to only write one type of callback handler, but have their code still work even if duplex devices aren't available. Internally, build_duplex_stream would delegate to the respective raw methods so the host implementation doesn't have to be aware of it or try to open devices with zero channels.

Feedback and improvement ideas are welcome. If this is deemed the way to go, I and anyone interested can start implementing support for it in more hosts.

Host implementation status

  • [x] JACK, part of this PR.

ollpu avatar Mar 16 '21 23:03 ollpu

Initially done, and it works (try the duplex example!).

Why is JACK support in a separate branch (https://github.com/rustydaw/cpal/tree/duplex-jack) not included in this PR? Right now, this PR doesn't compile when I enable the JACK feature.

nyanpasu64 avatar Jan 03 '22 19:01 nyanpasu64

Why is JACK support in a separate branch (https://github.com/rustydaw/cpal/tree/duplex-jack) not included in this PR?

My thought was that it wouldn't make sense to collect all host implementations into this PR, so it's easier to review the design. Having one implementation as an example isn't a bad idea so I've incorporated it here now.

Apologies for leaving this PR hanging for so long – I was sort of hoping for some comments before continuing work on it. It should now be good to merge as-is, and other host implementations can be added in later.

Edit: Looks like the default heuristic comparator still needs an implementation.

ollpu avatar Jan 04 '22 16:01 ollpu

(copied from Discord) When running under pipewire-jack, cargo run --example duplex --features jack -- -j has the minimum possible latency (equal to the current pipewire quantum) as measured by jack_iodelay, so it seems to work:

Screenshot_2022-01-04_15-58-46

Latency is unaffected when I chain 3 cpal jack duplex clients:

Screenshot_2022-01-04_16-03-05

I assume latency should be deterministic, since the JACK server calls clients with input, and waits for it to return output synchronously.

Startup seems glitch-free most of the time?

nyanpasu64 avatar Jan 09 '22 10:01 nyanpasu64