Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pulse Audio support #259

Open
knappador opened this issue Mar 21, 2019 · 23 comments
Open

Pulse Audio support #259

knappador opened this issue Mar 21, 2019 · 23 comments

Comments

@knappador
Copy link

Tagging #250 regarding the sibling Linux sound server. I'm about to release a program under LGPL3 to which I own the full copyright (for now), but I don't want to discourage anyone from harvesting any bits and pieces in the name of getting all sound server API's under one roof, so I released an MIT licensed gist.

The main value of this gist for consumption is having a bit more abstract example than the documentation libpulse. Likely I'm going to overhaul it in the owning repo within the month.

My goal was not to get down to the rust libpulse bindings sys crate or talk directly to the server, but must mention that I quickly developed strong antipathy for the mainloop threaded API, which is inherited from the C client. Possibly the client-server interaction scheme has a deep pathological will to make pseudo-synchronous operations rely on libpulse's behavior and polling loop. Attempting to circumvent libpulse may involve worse hazards than managing them.

Playing directly to ALSA is a bad idea on Linux as it takes exclusive access to devices, and it's a bit weird to create monitors to listen to what other programs play.

@knappador
Copy link
Author

Mainloop not being thread safe and including an RC is quite a pain. Implementation in the case that there are several possible reader threads (I have this use case) will require some form of thread parking or possibly scoped threads to hold a mainloop for any length of time. Multiple contexts seems bad style. Basically to make non-blocking calls from an application, you have to poll or implement callbacks into your own event loop mechanism and still pseudo-serialize the calls to pulse or implement a state tracking API around the API 🐚 🐚 🐚

@MOZGIII
Copy link

MOZGIII commented Mar 31, 2019

Technically, when cpal is using ALSA on the properly configured system with PulseAudio installed and running, it's most likely passing the data through to the pulse via the plugin.
See https://wiki.debian.org/PulseAudio (Dynamically enable/disable section).

That said, I definetly see the value of talking to Pulse directly without going through asla-lib, becuase it adds just another layer of bugs and latency.

@knappador
Copy link
Author

it's most likely passing the data through to the pulse via the plugin

Is "it" Cpal or ALSA?

I read the wiki but did not come away understanding an obviously related set of implications. It seems implications could exist both for extending CPAL or configuring PA & ALSA so that CPAL uses PA without libpulse.

On my config, playback is exclusive to either ALSA & cpal or Pulse & all other programs can play at one time. I'm interested in exploring and documenting the topic of how the playback device, ALSA, and PA can interact particular to my use case, but would like to verify what end states I should be trying to reach.

@MOZGIII
Copy link

MOZGIII commented Apr 5, 2019

it is ALSA.

Here's the source of the plugin for more context: https://github.com/alsa-project/alsa-plugins/tree/master/pulse

This is how it works in Ubuntu 18.04, and probably what you'd like to setup to test:
cpal -> libasound2 (ALSA) -> libasound_module_pcm_pulse (ALSA plugin) -> libpulse (PulseAudio).

Obviously, using pulse directly is less bug-prone:
cpal -> libpulse (PulseAudio).

That that's not supported by cpal yet.

Currently, we're suffering from a weird bug that occurs at Ubuntu 18.04 - see #215 - and it seems it's due to that long chain. For me it blocks shipping of the app for linux users, but I weren't able to find a solution or workaround so far.

@eugene2k
Copy link

eugene2k commented Apr 7, 2019

Wouldn't using ALSA instead of Pulse be preferable, though? Systems with PulseAudio are typically configured to use the alsa plugin, while systems without PulseAudio only have alsa, so software using alsa for output would have the benefit of working on both kinds of systems.

@MOZGIII
Copy link

MOZGIII commented Apr 7, 2019

It is reasonable to assume that - after all it's less maintenance that way.
However:

  • as mentioned above, in practice the existing "pulse audio via ALSA" implementation is broken and simply doesn't work on one of the major platforms
    This is a practical blocker for shipping the code relying solely on cpal - as it's not going to work too.

  • when doing low-latency stuff (which is often the case in audio) it's often preferable to have as few layers of abstraction over the hardware as possible
    This is because even if the code is quick it's often doing some extra work that's not really necessary, but adds a bit of latency to the processing. Obviously, this is, in theory, an easy cut for cpal.

That said, implementing pulse backend also has it's downsides:

  • hard to implement - pulse audio brings in it's own event-loop, and it may be non-trivial to implement the cpal API over that; the alternative - i.e using two threads - one for pulse's main loop and one for cpal even loop - does not look like a solution with good properties to me;
  • additional maintenance cost - another backend implementation takes effort to support.

In my view it's worth it though because:

  • bugs, bugs are everywhere, but in this case we'll at least have more control and directly talk to pulse;
  • with that, I might be able to finally move to shipping my app for linux users; right now I'm stuck with this bug.

@MOZGIII
Copy link

MOZGIII commented Apr 7, 2019

@Ralith
Copy link
Contributor

Ralith commented May 20, 2019

The beep demo experiences erratic popping artifacts on my system unless I manually turn up pulse's latency. Other software on my system, generally using pulse natively, doesn't seem to have this problem, so native pulse support seems like it would have good chances of fixing it.

e: Actually, setting the latency did not solve the problem; the artifacts are very erratic so it's hard to tell.

@mitchmindtree
Copy link
Member

@Ralith is there any chance that there were any applications streaming audio to/from Pulse Audio while you ran the beep example? I believe the default ALSA API (without any plugins) expects the user to have exclusive access to the device, so it's common to get glitches if there is some other stream open accessing the same device via Pulse Audio for example. It can be incredibly difficult to test this as Pulse Audio is really good at magically starting again any time some application wants to play a noise (e.g. even desktop or browser notifications).

Pulse Audio support would be great and is pretty essential to get CPAL used in day-to-day Linux applications, but we must implement something along the lines of #204 to allow the user to select between multiple available APIs on a single system (e.g. ALSA/Pulse/Jack). The ASIO PR #221 makes a start at this in order to allow for selecting between WDM or ASIO on Windows.

@Ralith
Copy link
Contributor

Ralith commented May 20, 2019

is there any chance that there were any applications streaming audio to/from Pulse Audio while you ran the beep example?

I don't think so, though it's hard to be certain. #272 indicates that the issue exists on other platforms, too, so perhaps it's not anything to do with pulse/alsa.

@mitchmindtree
Copy link
Member

I would be surprised if both those cases were the same problem as almost everything other than the API calls are implemented differently for each backend (e.g. I don't believe there is any looping or timing code shared by the backends).

I don't think so, though it's hard to be certain.

Yeah, I'm using GNOME and in the short time I've had to test I haven't yet worked out a way to confidently and consistently disable pulseaudio without some random background application or notification turning it on again, I think the only way to test for sure is to log out of the DE entirely. I'm not sure if it is the same case for other DEs.

@Ralith
Copy link
Contributor

Ralith commented May 20, 2019

I successfully reproduced the popping with pasystray reporting no clients other than the demo.

@mitchmindtree
Copy link
Member

Are you able to test ALSA without using pulseaudio at all - e.g. completely disabling it? And making sure that the CPAL application is the only application accessing the audio device? I'm still not convinced it isn't some conflict between pulseaudio access to the device and some (perhaps incorrect) access to the device from the CPAL implementation.

@Ralith
Copy link
Contributor

Ralith commented May 20, 2019

Reproduced the popping with pulse disabled (ps aux |rg pulse displayed nothing while sound was playing). Still hard to tell whether anything else was accessing the device; alsa empirically doesn't seem to prevent it, as I was able to intentionally play other sounds simultaneously.

mitchmindtree added a commit to mitchmindtree/cpal that referenced this issue Jun 24, 2019
This is an implementation of the API described at RustAudio#204. Please see that
issue for more details on the motivation.

-----

A **Host** provides access to the available audio devices on the system.
Some platforms have more than one host available, e.g.
wasapi/asio/dsound on windows, alsa/pulse/jack on linux and so on. As a
result, some audio devices are only available on certain hosts, while
others are only available on other hosts. Every platform supported by
CPAL has at least one **DefaultHost** that is guaranteed to be available
(alsa, wasapi and coreaudio). Currently, the default hosts are the only
hosts supported by CPAL, however this will change as of landing RustAudio#221 (cc
@freesig). These changes should also accommodate support for other hosts
such as jack RustAudio#250 (cc @derekdreery) and pulseaudio (cc @knappador) RustAudio#259.

This introduces a suite of traits allowing for both compile time and
runtime dispatch of different hosts and their uniquely associated device
and event loop types.

A new private **host** module has been added containing the individual
host implementations, each in their own submodule gated to the platforms
on which they are available.

A new **platform** module has been added containing platform-specific
items, including a dynamically dispatched host type that allows for
easily switching between hosts at runtime.

The **ALL_HOSTS** slice contains a **HostId** for each host supported on
the current platform. The **available_hosts** function produces a
**HostId** for each host that is currently *available* on the platform.
The **host_from_id** function allows for initialising a host from its
associated ID, failing with a **HostUnavailable** error. The
**default_host** function returns the default host and should never
fail.

Please see the examples for a demonstration of the change in usage. For
the most part, things look the same at the surface level, however the
role of device enumeration and creating the event loop have been moved
from global functions to host methods. The enumerate.rs example has been
updated to enumerate all devices for each host, not just the default.

**TODO**

- [x] Add the new **Host** API
- [x] Update examples for the new API.
- [x] ALSA host
- [ ] WASAPI host
- [ ] CoreAudio host
- [ ] Emscripten host **Follow-up PR**
- [ ] ASIO host RustAudio#221

cc @ishitatsuyuki more to review for you if you're interested, but it
might be easier after RustAudio#288 lands and this gets rebased.
mitchmindtree added a commit to mitchmindtree/cpal that referenced this issue Jun 24, 2019
This is an implementation of the API described at RustAudio#204. Please see that
issue for more details on the motivation.

-----

A **Host** provides access to the available audio devices on the system.
Some platforms have more than one host available, e.g.
wasapi/asio/dsound on windows, alsa/pulse/jack on linux and so on. As a
result, some audio devices are only available on certain hosts, while
others are only available on other hosts. Every platform supported by
CPAL has at least one **DefaultHost** that is guaranteed to be available
(alsa, wasapi and coreaudio). Currently, the default hosts are the only
hosts supported by CPAL, however this will change as of landing RustAudio#221 (cc
@freesig). These changes should also accommodate support for other hosts
such as jack RustAudio#250 (cc @derekdreery) and pulseaudio (cc @knappador) RustAudio#259.

This introduces a suite of traits allowing for both compile time and
runtime dispatch of different hosts and their uniquely associated device
and event loop types.

A new private **host** module has been added containing the individual
host implementations, each in their own submodule gated to the platforms
on which they are available.

A new **platform** module has been added containing platform-specific
items, including a dynamically dispatched host type that allows for
easily switching between hosts at runtime.

The **ALL_HOSTS** slice contains a **HostId** for each host supported on
the current platform. The **available_hosts** function produces a
**HostId** for each host that is currently *available* on the platform.
The **host_from_id** function allows for initialising a host from its
associated ID, failing with a **HostUnavailable** error. The
**default_host** function returns the default host and should never
fail.

Please see the examples for a demonstration of the change in usage. For
the most part, things look the same at the surface level, however the
role of device enumeration and creating the event loop have been moved
from global functions to host methods. The enumerate.rs example has been
updated to enumerate all devices for each host, not just the default.

**TODO**

- [x] Add the new **Host** API
- [x] Update examples for the new API.
- [x] ALSA host
- [ ] WASAPI host
- [ ] CoreAudio host
- [ ] Emscripten host **Follow-up PR**
- [ ] ASIO host RustAudio#221

cc @ishitatsuyuki more to review for you if you're interested, but it
might be easier after RustAudio#288 lands and this gets rebased.
mitchmindtree added a commit to mitchmindtree/cpal that referenced this issue Jun 24, 2019
This is an implementation of the API described at RustAudio#204. Please see that
issue for more details on the motivation.

-----

A **Host** provides access to the available audio devices on the system.
Some platforms have more than one host available, e.g.
wasapi/asio/dsound on windows, alsa/pulse/jack on linux and so on. As a
result, some audio devices are only available on certain hosts, while
others are only available on other hosts. Every platform supported by
CPAL has at least one **DefaultHost** that is guaranteed to be available
(alsa, wasapi and coreaudio). Currently, the default hosts are the only
hosts supported by CPAL, however this will change as of landing RustAudio#221 (cc
@freesig). These changes should also accommodate support for other hosts
such as jack RustAudio#250 (cc @derekdreery) and pulseaudio (cc @knappador) RustAudio#259.

This introduces a suite of traits allowing for both compile time and
runtime dispatch of different hosts and their uniquely associated device
and event loop types.

A new private **host** module has been added containing the individual
host implementations, each in their own submodule gated to the platforms
on which they are available.

A new **platform** module has been added containing platform-specific
items, including a dynamically dispatched host type that allows for
easily switching between hosts at runtime.

The **ALL_HOSTS** slice contains a **HostId** for each host supported on
the current platform. The **available_hosts** function produces a
**HostId** for each host that is currently *available* on the platform.
The **host_from_id** function allows for initialising a host from its
associated ID, failing with a **HostUnavailable** error. The
**default_host** function returns the default host and should never
fail.

Please see the examples for a demonstration of the change in usage. For
the most part, things look the same at the surface level, however the
role of device enumeration and creating the event loop have been moved
from global functions to host methods. The enumerate.rs example has been
updated to enumerate all devices for each host, not just the default.

**TODO**

- [x] Add the new **Host** API
- [x] Update examples for the new API.
- [x] ALSA host
- [ ] WASAPI host
- [ ] CoreAudio host
- [ ] Emscripten host **Follow-up PR**
- [ ] ASIO host RustAudio#221

cc @ishitatsuyuki more to review for you if you're interested, but it
might be easier after RustAudio#288 lands and this gets rebased.
@mitchmindtree mitchmindtree mentioned this issue Jun 24, 2019
8 tasks
@Be-ing
Copy link
Contributor

Be-ing commented Mar 18, 2021

This will be obsolete soon. I recommend to invest the effort into PipeWire instead (#554).

@Be-ing
Copy link
Contributor

Be-ing commented Mar 18, 2021

Considering the ugly technical challenges with PulseAudio's design (which is why it is being deprecated) discussed above and that PulseAudio will be obsolete soon, I suggest to close this issue in favor of PipeWire #554.

@minecrawler
Copy link

I am not sure if that's a good idea, since not everyone will jump on Pipewire from day one. The question is, what kind of software era this lib wants to support, and if it is anything before "tomorrow" and "cutting edge", then PulseAudio is a must-have in my book.

@Be-ing
Copy link
Contributor

Be-ing commented Mar 18, 2021

If there were unlimited labor available, sure, support the legacy API. But there is not, so let's focus on PipeWire. By the time that is done and robust, PulseAudio will be on the way out.

Also, the end result will be much better with PipeWire than PulseAudio and it will probably be easier to implement PipeWire with CPAL's architecture.

@Ralith
Copy link
Contributor

Ralith commented Mar 18, 2021

Bear in mind that the existing generally-functional alsa backend works decently on top of pulse. The question is whether we need to go to the effort of developing and maintaining a specialized backend in addition to that.

@Be-ing
Copy link
Contributor

Be-ing commented Mar 19, 2021

That depends on the application. A simple music player application that only needs to output a stereo signal to whatever is configured as the default output in PulseAudio can make do with that. But creative applications that require user control of multichannel routing to arbitrary destinations cannot choose the devices used with the pulse virtual ALSA device. It is the same situation with the PipeWire ALSA virtual device.

@peterix
Copy link

peterix commented Jun 2, 2022

This issue is about PulseAudio, not PipeWire.

As a user of this library, I would want this to work with Pulse now AND PipeWire in the future. They are separate things.

(As an aside, I have zero interest in using PipeWire in the next 10 years.)

@colinmarc
Copy link

Hi, I've implemented the PulseAudio protocol in rust, continuing a previous abandoned effort: https://github.com/colinmarc/pulseaudio-rs/

Playback is working - here's an example: https://github.com/colinmarc/pulseaudio-rs/blob/main/examples/playback.rs

If there's interest from the maintainers, I'd be willing to contribute an adapter to integrate it into cpal.

As far as the pulse/pw question goes, I think the Pulse API is actually much simpler and easier to use, at least for simple playback, and PipeWire has an embedded pulse server (my library has integration tests that run against both the actual PulseAudio daemon and pipewire-pulse). A native rust implementation of the Pulse protocol will work on both systems, and is probably the easiest option to maintain in the long run.

Please let me know your thoughts!

@Ralith
Copy link
Contributor

Ralith commented Jan 5, 2024

I think pipewire considers pulse clients first-class, so that's a fine approach for the foreseeable future, and surely an improvement over the status quo regardless. Thanks for taking this up, and I hope we can see it integrated!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants