Re: RFC - Work on FreeBSD's Audio Stack

From: Florian Walpen <dev_at_submerge.ch>
Date: Sun, 17 Dec 2023 18:30:53 UTC
Hello Christos,

On Saturday, December 16, 2023 5:06:12 PM CET Christos Margiolis wrote:
> 
> This is something I have been thinking about even before the proposal,
> but replacing OSS with an existing sound system (sndio maybe?), or
> rolling our own one, is probably too large of an endeavour, considering
> the amount of infrastructure built around OSS.
> 
> That being said, I am also in favour of replacing OSS at some point, and
> very much willing to work on this, but it might be wiser, for now, to
> improve what we currently have and provide a better user-experience than
> leave it as-is until we decide whether we'll get rid of OSS or not.

No need to replace OSS right now, I just think we should keep that in mind 
when we decide where to put our efforts. And yes, sndio is a candidate, but is 
missing mmap() and passthrough support AFAIK.

> > > snd uaudio(4) fixes
> > > 
> > > The project will also address bugs in the USB audio driver, snd
> > > uaudio(4),
> > > which I have been able to reproduce using my Focusrite Scarlett USB
> > > sound
> > > card, with the most prominent and consistent one being noise produced
> > > during the first 12 seconds of playback and when moving along a
> > > track/video. If the user tries to move forward multiple times in a short
> > > time window, the audio device most of the time becomes unusable (i.e no
> > > audio) and it has to be replugged. Though this issue is largely bypassed
> > > if
> > > audio is routed to the USB device through virtual oss, this is still a
> > > bug
> > > that needs to be addressed.
> > 
> > From the description here this sounds more like an issue with the player
> > or
> > the vchan feeders, given that virtual_oss doesn't produce noise. Did you
> > file a bug report?
> 
> I really doubt it's the player, as this happens with pretty much any
> player (mpv, ncmpcpp, firefox, ...) I have tried. virtual_oss does
> produce noise, but only when the device is opened.

Ok, maybe these players recklessly stop and restart playback when seeking in 
the track, and virtual_oss wouldn't communicate that further to the underlying 
dsp device. Doesn't explain to me why these players would read(2) at all...
Anyway, this is better discussed in a bug report with proper logs.

> > > oss(3)
> > 
> > What's the scope of this library? Main PCM devices, virtual PCM
> > devices, hardware devices? Settings and state only, or also playback
> > and recording operation?
> > 
> > Beware that there's a great variety of operation strategies for different
> > use cases. With a simple poll() based read() / write() at one end of the
> > spectrum, and something like my new Jack OSS backend on the other end.
> > It's timer based, preferably mmap()ed, uses its own buffer management and
> > strives to keep latency reproducible within +/- 1ms. The backend code is
> > here:
> > 
> > https://github.com/0EVSG/sosso
> 
> It's going to have both mmap'd and non-mmap'd buffer-management
> (reading, writing, splitting, merging), as well as state setting. Goran
> Mekić had brought up your library when we discussed this during
> EuroBSDCon 2023, and I think oss(3) could re-use some of the ideas
> implemented there, especially the low-latency mechanisms.

This is gonna be difficult to get right. Feel free to contact me when you're 
down to concrete design decisions, hopefully I can remember some of the 
pitfalls I ran into.

> > > Hot-swapping
> > 
> > What you outline here is a complete sound server, but it doesn't tell how
> > you want to implement that. Extend the vchan infrastructure, autostart
> > virtual_oss on all PCM devices, or something else?
> > 
> > In many use cases this just duplicates the sound server that sits on top
> > of
> > the dsp device. At worst it compromises quality and latency because of
> > conversions and routing buffers. As a provocative question, wouldn't the
> > average user be served better if we just implemented full pipewire
> > support,
> > going forward?
> 
> Not really. We currently change the default unit by either doing
> 
> 	$ mixer -d<new_unit>
> 
> Or
> 	# sysctl hw.snd.default_unit=<new_unit>
> 
> Which requires a track restart for the change to take effect. All that
> would change with my proposed solution is simply detect if virtual_oss
> is present, and in that case, change both the default unit through the
> sysctl AND switch to the wanted unit through the virtual_oss IOCTL
> interface so that the change takes effect immediately if we have
> virtual_oss running.

No objection, this use case is a lot less invasive than I thought. Make sure 
to sync the selected default unit between your tool and virtual_oss.

> > > Testing
> > > 
> > > The audio driver will be tested by writing a test program to go through
> > > most of the IOCTLs provided to by the driver, to both confirm that the
> > > information returned is correct, and also to make sure that users cannot
> > > pass values that would break the driver. Exact cases will be considered
> > > further down the project.
> > 
> > You mean IOCTLs provided by the dsp devices? On a dummy driver? Because
> > the
> > hardware drivers are usually well separated in kernel modules, which means
> > they can be tested separately.
> 
> Honestly, I am still thinking about this, so I am not exactly sure yet.
> Do you think there it's possible to create a reliable test case using a
> virtual dummy device?

Depends on what you want to test. For IOCTLs (the dsp device) and the vchan 
feeder stuff you could load a dummy audio driver kernel module, where you can 
check the outcome. If you want to test hardware drivers, I'm not sure they can 
be tested meaningfully without listening to sound output.
For automated tests of your oss(3) library you could strip down virtual_oss, 
catching the IOCTLs on its cuse(3) devices. It has no mmap() though.

> > Again, OSSv4 compatibility won't help much, it's basically abandoned by
> > audio software developers. Apart from open bug reports there's also
> > missing kevent support (instead of poll() being the only option), or the
> > buffer / blocksize based latency setting which is conceptually broken. I
> > can give more details on these topics if there's interest.
> 
> More details would be appreciated. :)

What kqueue / kevent support means is probably clear? Currently poll() is the 
only method an application can use to wait for the sound buffer to reach its 
low water mark.

The latency / OSS buffer fragment / blocksize stuff is one of the bigger 
blunders I know in our audio stack, and one that can be fixed without 
compromising OSS API compatibility too much.

There's an abstract latency setting through SNDCTL_DSP_POLICY ioctl, the 
hw.snd.latency_profile and hw.snd.latency sysctl. It translates to a certain 
buffer and fragment size, at 48kHz 16bit stereo. Different ones for recording 
and playback. These sizes are then scaled according to the actual sample rate 
and frame size, and rounded to a power of two. Which can be already far off 
from the intended latency per fragment.

These buffer and fragment sizes are then further processed, limited and rounded 
(power of two) again. They end up as the fragment size of the internal buffers. 
Funny enough, the fragment size is almost meaningless, apart from defining the 
default low water mark. Less funny that it is also communicated as blocksize 
to the driver, to set a certain latency. At this point it's a bit of a lottery 
because the driver has its own rounding requirements (e.g. milliseconds), and 
it depends on whether recording or playback blocksize has the final say. 
Because drivers typically need the same blocksize for recording and playback.

Some of the drivers have dedicated tunables to get predictable blocksizes. 
Yes, that's what hw.usb.uaudio.buffer_ms is for.

So at least it would be nice to get a consistent latency hint at the driver 
level. The effective fragment size for the front side buffers should be set 
according to what the driver uses, not the other way round.
Bonus points if we find a way to set the buffer size from an application, 
without the obsessive power-of-two-rounding.

For a first glance, have a look at chn_resizebuf() and chn_calclatency() in
sys/dev/sound/pcm/channel.c

Hope this helps.

Florian