cpal icon indicating copy to clipboard operation
cpal copied to clipboard

PipeWire implementation

Open midoriiro opened this issue 11 months ago • 9 comments

PipeWire implementation based on pipewire/pipewire-rs/doc and others draft PRs in this repository (thanks to them).

There is two additional crates, one for the PW client and one for SPA utils (used for some POD deserialization).

Client capabilities:

  • Retrieve all Audio/(Sink/Source) nodes
    • Thats include default audio nodes (defined by your session manager)
    • Format information (sample rate, sample format, channels)
  • Retrieve metadata for global settings and default audio nodes (provided by session manager, AKA WirePlumber)
  • Create and manage stream
  • Create virtual node

I updated some CPAL examples (enumerate and beep).

TODO (just some ideas):

  • Duplex audio node:
    • Can be useful as a "loopback" device. In CPAL that mean input stream can be retrieved, updated and injected in output stream. Need a POC, I'm just supposing.
  • Updating Port/Link of an audio node: Use case, a virtual node is a DSP (EQ, Reverb, ...), clients nodes (Spotify, browser, ...), audio node (sound card playback). DSP node will be inserted between clients nodes and audio node, with all ports/links remapped correctly.
    • Or simple use case: create a virtual node and plug it to an audio node monitor ports
  • Subscribe to node/stream events/listeners: stream format or parameters changed, same for node.
  • Improve test coverage and create some integration test
    • Use TestContainer to test different cases with embed PipeWire and session manager installed in a container image
  • Improve documentation (README.md to explain client initialization flow, post initialization flow, how to handle PW object creation)
  • Improve client API: currently PipewireClient is just a struct with a bunch of function that send requests. I would like to create different structs to separate concern and improve readability/maintainability (struct API for node, struct API for stream, etc)
  • Improve quality code in general, clean some code, keep coherence in different parts on code base

I'm posting this PR as draft for now, if you have any suggestion/feedback or particular use cases, I'm glad to discuss it.

Note: You will need to install PipeWire development lib from your package manager. Having WirePlumber or PipeWire Media Session running. If your PW socket is located somewhere else you can define env vars:

  • (socket name): PIPEWIRE_REMOTE
  • (socket location): PIPEWIRE_RUNTIME_DIR or XDG_RUNTIME_DIR

midoriiro avatar Jan 15 '25 14:01 midoriiro

image I guess I'll move this client to a separate repository, this PR became prettry huge héhé.

midoriiro avatar Jan 28 '25 19:01 midoriiro

Hi thanks for your feedback, indeed this a huge PR. I'm still working on it but on my own repository at this moment, I was planning to return to this PR to include only the PipeWire integration into CPAL.

I don't mind if you merge it now, that could be cool to have feedbacks. But I was thinking if this Pipewire client implementation could have it own repository under RustAudio organization. What do you think ? Either way, I would be glad to maintain it.

midoriiro avatar Feb 26 '25 15:02 midoriiro

I'll just note here that my PR #957 has some overlap with this, since PipeWire has first-class support for the PulseAudio protocol. I don't have a preference for how that overlap gets resolved.

(I didn't see this until after I opened #957.)

colinmarc avatar Feb 27 '25 15:02 colinmarc

I'll just note here that my PR #957 has some overlap with this, since PipeWire has first-class support for the PulseAudio protocol. I don't have a preference for how that overlap gets resolved.

(I didn't see this until after I opened #957.)

Good question, if by overlap you mean about merge conflicts that will depend on which PR will be merged first. I need to do some rework before this PR will be merged.

If you mean overlap between PulseAudio integration and PipeWire integration. I guess if you have a PipeWire server running on your system, you could use CPAL PulseAudio integration to connect to the PipeWire server through their pipewire-pulse daemon.

I don't have any knowledge about how PA is working but to integrate PW to CPAL I needed to create an init phase to retrieve default configuration (sample rate, etc) and default devices configured by the session manager (WirePlumber). That could be interesting to know how your PA implementation behave when connected to pipewire-pulse daemon.

If think the two implementations can coexists. If tomorrow I'm going to switch to Archlinux and setup a PA server, I'll be glad to find a PA integration into CPAL.

midoriiro avatar Mar 03 '25 14:03 midoriiro

New collaborator here and doing backlog grooming, when I came across this very nice and pretty sizable :wink: addition!

From what I read this PR was 'bout good to merge but then it wasn't. When we agree that PulseAudio and PipeWire implementations can co-exist, and are both maintained, then what'd be the right next step? In any case it'd be great if you could resolve the conflicts - shouldn't be much - and get the CI to pass.

To the point of a code review I am going to trigger a Copilot review in any case, hoping that will address major points, if any. Then based on the maturity of where we are, there's all sorts of directions we could take:

  • merge this
  • merge this behind an experimental feature flag
  • create a feature branch to mature it
  • something else?

roderickvd avatar Aug 05 '25 21:08 roderickvd

Please don't make this depend on tokio. It should be runtime agnostic. Many projects use alternative async runtimes.

narodnik avatar Aug 22 '25 11:08 narodnik

Please don't make this depend on tokio. It should be runtime agnostic. Many projects use alternative async runtimes.

Is there any reason to introduce async at all in CPAL?

dheijl avatar Aug 22 '25 15:08 dheijl

@roderickvd: I'll check the review this week-end.

This feature already has a feature flag. I also moved the client implementation code to a separate repository to keep the current PR short.

We could merge this PR and keep the PipeWire integration marked as experimental, as you suggested, until the client implementation is mature enough. That way we can collect feedback from users and developers, improve the implementation design, and iterate over time.

If there is other suggestions, I'll be happy to hear your thoughts.

@dheijl / @narodnik:

I'll try to give you some context on why I used Tokio/async.

PipeWire is designed as a client/server architecture. As a client, you need to use an event loop to catch events sent from the server, and that event loop runs on a separate thread. The implemented client is fully synchronous, but to consider the client initialized we need to wait for particular data (device nodes and properties, global settings) and for WirePlumber to initialize. This process is not linear or deterministic.

The main idea was to use a short-lived thread, through Tokio, to wait for certain conditions during the initialization phase. The intent was only for internal purposes.

I agree this was not the best design decision and it needs some refactoring.

I hope this answers your question. Please let me know if you have any other questions I can clarify.

midoriiro avatar Aug 22 '25 17:08 midoriiro

I agree this was not the best design decision and it needs some refactoring.

I certainly don’t question your design decisions, except for introducing async which means introducing overhead and code bloat with questionable benifits in the uses I can see for the CPAL library. I have no issues with async when used where needed, I just don't see the need here, but maybe I'm wrong, and there are uses for CPAL in situations where async is beneficial?

dheijl avatar Aug 22 '25 18:08 dheijl