Pipewire + pipewire-pulse as main audio server

Pipewire is a really solid technology and I've been using it for a while on Linux, works great for routing, works great for pretty much everything. I've since tried out FreeBSD on a dual boot partition and I noticed that you guys do not use it! - using, instead, either OSS, Sndio, or something else. In order to get screencasting support on Niri, the wayland compositor i'm using, it requires pipewire to be running on the background (and the GNOME xdg-portal, too). I just thought that like, if I already have pipewire running, why not just use it instead of sndio? Would that be possible? I've just noticed no sinks really showed up on Helvum (even when running with wireplumber), apparently it just loads dummy interfaces instead of binding to anything.

My system if anyone needs that https://bsd-hardware.info/?probe=e4e1bf6fa2
 
Not all FreeBSD ports and packages support pulse. Some have options to compile in pulseaudio support.

There is also packages that demand jack. The jack interface to pipewire works by replacing the shared library, an approach that I would have to try to trust it.
 
Sort-of a guess and vague, but while other sound servers/tech exist, FreeBSD defaults to OSS packaged up well-enough at the desktop. PA and/or PW can be installed, but need wired-up to the desktop at a high-enough level to present themselves to apps.

I don't think sound servers can override each other since they're higher-level and communicate with low-level audio stuff transparent to apps (ALSA on Linux), so I don't see why PW couldn't be hooked up and running as the primary sound system to desktop apps. So installing PW, making the server auto-start (rc?), and having it wired up to low-level OS audio stuff (/dev/dsp0) I'd imagine is all that's needed.

I'm not exactly sure how OSS or sndio fit into all that. ALSA as I understand is the low-level hardware audio system on Linux for communicating directly with hardware, and things can either use it directly (via ALSA's lib), or another sound layer to do something like: Game -> PipeWire (or PulseAudio) -> ALSA (hw). With Wine on FreeBSD it still used ALSA, but I'm thinking ALSA is the lib there and it's going to something low-level transparently (OSS?).

As for sound server priority; while that could probably be set somewhere, environment variables might also be needed or make that easier (like SDL_AUDIODRIVER=pipewire); as long as the sound server is running and the app knows what to do with such an env it should just use it.
 
great stuff

i noticed pipewire didnt have a oss sink when i tried it a while ago
which as noise pointed out meant it wasnt much use

one use case that springs to mind is using pipewire-spa-oss with obs studio
 
We now have https://www.freshports.org/audio/pipewire-spa-oss. (Written by me, obviously.) It's a quick and dirty implementation that was barely tested, so YMMV. If you build this from source don't forget to update PipeWire as well.

Appreciated. I had an OSS backend for pipewire on my mind, but I'm quite busy now with job-hunting and other stuff. Had a quick glance at the code when I saw it in ports, at least the OSS part seems a bit simplistic yet. Feel free to contact me if you'd like to discuss that, as I have some experience from writing (and rewriting) the OSS backend of Jack.
 
Had a quick glance at the code when I saw it in ports, at least the OSS part seems a bit simplistic yet. Feel free to contact me if you'd like to discuss that, as I have some experience from writing (and rewriting) the OSS backend of Jack.
Any particular concerns?
 
Any particular concerns?

From the project description:
The author is yet to figure out how buffering works,which means there are occassional crackling sounds.
If that's still the case I may be able to help. IIRC the modus operandi of pipewire is different from what other sound servers do, probably needs some non-standard buffer handling strategy to make it robust.
 
I was kind of expecting "seems a bit simplistic" having something to do with my code. Everything about buffers is out of control of SPA plugins, so I'm not sure what could be adjusted here. We have some control over the timer code that initiates processing, but that's a very nominal thing as well (PipeWire dictates how often the timer should fire).
 
I was kind of expecting "seems a bit simplistic" having something to do with my code. Everything about buffers is out of control of SPA plugins, so I'm not sure what could be adjusted here. We have some control over the timer code that initiates processing, but that's a very nominal thing as well (PipeWire dictates how often the timer should fire).

If all the timing is constrained by pipewire, there's still some wiggle room how you decouple the pipewire pace from the OSS processing - non-blocking IO, resizing the OSS buffers (that's a PITA though) and setting the low water marks for select() / poll(), stuff like that. I haven't seen your code prefill the playback buffer? Also you want to have separate file descriptors for read and write, since not all OSS ioctls work in duplex.

But I really should have a closer look first before I can give some meaningful advice.
 
I'll note here at the moment most of my crackling issues are with Wine (winepulse) and really small buffers (~5 ms of sound) that PipeWire seems to choose for it, while the timer accuracy is somewhere around 10 ms (on my machine, with a bit of extra load).

non-blocking IO,
For source nodes I'm using blocking IO with select --> SNDCTL_DSP_GETISPACE --> read as the docs seem to suggest, for sink nodes it's just a single write call because extra syscalls annoy me (also select seems unnecessary when we already have the timer) and I'm trying to see whether I can get away with it :) In theory non-blocking IO should be better, but it doesn't seem to behave as nicely.

resizing the OSS buffers (that's a PITA though)
OSS does good enough job with that.

I haven't seen your code prefill the playback buffer?
Should I?

Also you want to have separate file descriptors for read and write, since not all OSS ioctls work in duplex.
We don't need duplex operation at all. Reading and writing is done separately in PipeWire.
 
I'll note here at the moment most of my crackling issues are with Wine (winepulse) and really small buffers (~5 ms of sound) that PipeWire seems to choose for it, while the timer accuracy is somewhere around 10 ms (on my machine, with a bit of extra load).

For source nodes I'm using blocking IO with select --> SNDCTL_DSP_GETISPACE --> read as the docs seem to suggest, for sink nodes it's just a single write call because extra syscalls annoy me (also select seems unnecessary when we already have the timer) and I'm trying to see whether I can get away with it :) In theory non-blocking IO should be better, but it doesn't seem to behave as nicely.

Blocking IO is ok if you have separate threads for that. If you always "emulate" non-blocking IO for source nodes you could just open the file descriptor in non-blocking mode and get rid of SNDCTL_DSP_GETISPACE. The read() call will then tell you how much audio data is available.

Non-blocking IO behaves well if you understand how it works. If you want to block with a timeout, there's select() or poll(). But you may have to adjust the low water mark to the amount of audio data you want to process.

Should I?

Probably. If you start with playback buffer empty, and pipewire feeds you 5ms of audio data every 5ms, the OSS buffer will be almost empty at each end of the 5ms period. Good chance this cures the crackles. Rule of thumb would be to prefill with one pipewire period (e.g. 5ms) of silence, unless that would fill more than the half of the OSS buffer.

There's a similar argument for the recording direction, if you want to finish the pipewire source requests in a timely manner.

We don't need duplex operation at all. Reading and writing is done separately in PipeWire.

Sometimes you have to set different formats / channel numbers / other properties, in which case you need separate file descriptors for playback and recording - the OSS API is not very consistent in that department.

Maybe we should discuss this somewhere else, unless there's public interest.
Is there a way to do nested quotes in this forum, BTW?
 
Let me reiterate: there is no clever code that can help with inaccurate timers. Larger buffers would (at the cost of latency), but I have no idea on how to decide the minimal size.

Non-blocking IO behaves well if you understand how it works.
I understand how it works, but not how to inform PipeWire about its buffer not being fully written. Blocking processing for a few ms does not really break normal use cases, it's more of a scalability issue. (That is, one would need a slow CPU + multiple sinks + a bunch of audio processing plugins to see the difference.)

Probably. If you start with playback buffer empty, and pipewire feeds you 5ms of audio data every 5ms, the OSS buffer will be almost empty at each end of the 5ms period. Good chance this cures the crackles. Rule of thumb would be to prefill with one pipewire period (e.g. 5ms) of silence, unless that would fill more than the half of the OSS buffer.
Well, nobody writes bytes directly into the OSS buffer (in the non-mmap mode at least), it can and will put zeroes there as necessary. Or do you mean that helps OSS to determine the buffer size?

Sometimes you have to set different formats / channel numbers / other properties, in which case you need separate file descriptors for playback and recording - the OSS API is not very consistent in that department.
They are separate.

Maybe we should discuss this somewhere else, unless there's public interest.
I don't see the issue, it's not like we have anything more interesting to discuss.
 
Let me reiterate: there is no clever code that can help with inaccurate timers. Larger buffers would (at the cost of latency), but I have no idea on how to decide the minimal size.

For playback the amount of surplus audio data in the OSS buffer determines how late your timers can be, without producing an underrun (crackle). That's what the prefill is for.

Well, nobody writes bytes directly into the OSS buffer (in the non-mmap mode at least), it can and will put zeroes there as necessary. Or do you mean that helps OSS to determine the buffer size?

If you write() to the dsp device, you write directly into the (software side) OSS buffer. It works like a queue and does not magically fill up with zeroes. The driver will consume what you've written, and underrun if there's nothing left. A prefill with zeroes (silence) will give you some slack in case the timer is late.

I don't see the issue, it's not like we have anything more interesting to discuss.

Depends on your interest to fix your implementation.
 
For playback the amount of surplus audio data in the OSS buffer determines how late your timers can be, without producing an underrun (crackle). That's what the prefill is for.
IMO, that's just masking the issue for a while. The timer is not just a bit late, it's regularly x2-x3 interval late.

If you write() to the dsp device, you write directly into the (software side) OSS buffer. It works like a queue and does not magically fill up with zeroes.
From http://manuals.opensound.com/developer/SNDCTL_DSP_GETODELAY.html: "If the application stops writing to the device it will sooner or later cause a buffer underrun. OSS will recover from this situation by appending some silent samples to the buffer." You don't need to talk to me like I'm 3 year old.
 
No reason to act like in defiance phase. Your implementation doesn't even handle being a fraction of an interval late, so it's broken, independent of the timer problem. Adding 16ms of latency is still better than crackles and irregular latency. Also the OSSv4 manual is not a documentation for FreeBSD, the OSS buffer stays empty on underrun. But the OSSv4 manual does correctly advise to prefill the buffer in your use case.

If you want to keep it in unusable state that's fine, just tell people upfront then.
 
FWIW, just ignore him. It's the only person I blocked on this Forum and just looking at your last comment (I don't see his responses, obviously) I was right.
 
Back
Top