Page MenuHomeFreeBSD

sound: Implement AFMT_FLOAT support
Needs ReviewPublic

Authored by christos on Nov 16 2024, 6:15 PM.
Tags
None
Referenced Files
F107365061: D47638.id148936.diff
Mon, Jan 13, 2:39 AM
Unknown Object (File)
Wed, Jan 8, 9:11 PM
Unknown Object (File)
Wed, Jan 8, 8:57 PM
Unknown Object (File)
Wed, Jan 8, 8:38 PM
Unknown Object (File)
Sat, Jan 4, 7:36 PM
Unknown Object (File)
Tue, Dec 17, 3:08 PM
Unknown Object (File)
Dec 13 2024, 1:56 PM
Unknown Object (File)
Nov 29 2024, 7:13 AM

Details

Summary

Even though the OSS manual [1] advises against using AFMT_FLOAT, there
are applications that expect the sound driver to support it, and might
not work properly without it.

This patch adds AFMT_F32_LE|BE (as well as AFMT_FLOAT for OSS
compatibility) in sys/soundcard.h and implements AFMT_F32_LE|BE <->
AFMT_S32_LE|BE conversion functions. As a result, applications can
write/read floats to/from sound(4), but internally, because sound(4)
works with integers, we convert floating point samples to integer ones,
before doing any processing.

[1] http://manuals.opensound.com/developer/AFMT_FLOAT.html

PR: 157050, 184380, 264973, 280612, 281390
Sponsored by: The FreeBSD Foundation
MFC after: 1 week

Test Plan

Apply D47639 to test sound.


Below is a benchmark for AFMT_FLOAT operations in pcm_sample_read() and pcm_sample_write(). The results are in cycles, and are measured by adding 2 rdtscp() calls in pcm_sample_read() and pcm_sample_write() respectively - one at the start and one at the end, and calculating the difference.

The test program is a simple loopback program that reads and writes zeroes 512 times:

#include <sys/soundcard.h>

#include <err.h>
#include <fcntl.h>
#include <unistd.h>

int
main(int argc, char *argv[])
{
        uint32_t sample;
        int fd, fmt, chans, rate, n;

        if ((fd = open(argv[1], O_RDWR)) < 0)
                err(1, "open(%s)", argv[1]);

        chans = 1;
        if (ioctl(fd, SNDCTL_DSP_CHANNELS, &chans) < 0)
                err(1, "ioctl(SNDCTL_DSP_CHANNELS)");

        fmt = AFMT_FLOAT;
        if (ioctl(fd, SNDCTL_DSP_SETFMT, &fmt) < 0)
                err(1, "ioctl(SNDCTL_DSP_SETFMT)");

        rate = 48000;
        if (ioctl(fd, SNDCTL_DSP_SPEED, &rate) < 0)
                err(1, "ioctl(SNDCTL_DSP_SPEED)");

        for (n = 512; n--;) {
                sample = 0;
                if (read(fd, &sample, sizeof(sample)) < 0)
                        err(1, "read");
                if (write(fd, &sample, sizeof(sample)) < 0)
                        err(1, "write");
        }

        close(fd);
}

The sound card used is a Focusrite Scarlett Solo (snd_uaudio(4)), with VCHANs disabled to simplify the test.

functioncycles
pcm_sample_read(AFMT_S32_LE)330.806
pcm_sample_write(AFMT_S32_LE)183.298
pcm_sample_read(AFMT_F32_LE)3246.6
pcm_sample_write(AFMT_F32_LE)1893.8

The AFMT_FLOAT operations seem to be ~10x more expensive.

Diff Detail

Repository
rG FreeBSD src repository
Lint
Lint Skipped
Unit
Tests Skipped
Build Status
Buildable 61084
Build 57968: arc lint + arc unit

Event Timeline

Hello,

i can confirm so far that it works fine with emulators/wine.

I did took 2 FMOD games for testing (Way of the Hunter and Roboquest) that required AFMT_FLOAT.

Thank you for your work!

sys/dev/sound/pcm/feeder_format.c
58

I'm a bit worried about a possible performance impact, and we might need to hoist the fpu_kern_enter / fpu_kern_leaave up a level, but we can profile it and see what the impact actually is.

Very welcome, thanks for tackling this @christos. Please note that there are sound cards supporting float format, and I intend to experiment with that some time in the future (probably needs bitperfect for now).

sys/dev/sound/pcm/feeder_format.c
58

I'm a bit worried about a possible performance impact, and we might need to hoist the fpu_kern_enter / fpu_kern_leaave up a level, but we can profile it and see what the impact actually is.

+1. Also the way this function is called through a function pointer for every sample seems quite inefficient, at least I don't see how it could be inlined there. Means a restructuring could help all format conversions, not just float.

Although I see moving format conversions out to userland as the better option in the middle to long term, anyway.

sys/dev/sound/pcm/feeder_format.c
58

I will second Florian's idea about optimizing the conversion framework in general, as long as we keep doing this in the kernel and not userland.

For the fpu_kern(9) functions specifically, I think they could be moved around the do-while in feed_format_feed(), as Ed suggested in private, although I haven't tested this yet. This will make the code a bit uglier, because we'll have to check for AFMT_FLOAT in feed_format_feed() so that we can call fpu_kern_enter()/fpu_kern_leave(), but it will most likely give us a good performance boost.

sys/dev/sound/pcm/feeder_format.c
58

Also I am wondering whether we need to care about endianness.

sys/dev/sound/pcm/feeder_format.c
58

Also I am wondering whether we need to care about endianness.

Good point. In principle there's AFMT_FLOAT_BE and AFMT_FLOAT_LE, and what we currently support here as AFMT_FLOAT is actually AFMT_FLOAT_NE. I don't think it matters with audio data from userland, that should always be native endian. But it would matter when going to a sound card, e.g. a RME HDSPe card in a PowerPC big endian machine.

I suppose we should have a closer look. For now we could at least define the different endianness formats for floats, similar to the integer formats.

58

I will second Florian's idea about optimizing the conversion framework in general, as long as we keep doing this in the kernel and not userland.

The remark on doing conversion in userland was meant as a perspective on how much effort we should invest here, not as a concrete suggestion - sorry for the confusion.

For the fpu_kern(9) functions specifically, I think they could be moved around the do-while in feed_format_feed(), as Ed suggested in private, although I haven't tested this yet. This will make the code a bit uglier, because we'll have to check for AFMT_FLOAT in feed_format_feed() so that we can call fpu_kern_enter()/fpu_kern_leave(), but it will most likely give us a good performance boost.

If my assumptions about the code are correct, we do two function calls in feed_format_feed() (read and write) for each sample, with no inlining at all. That would be terribly inefficient, for all formats. Ideally we could specialize the whole read-write-loop instead of just the read / write functions, thereby speed up all format conversions and include the fpu_kern_enter()/fpu_kern_leave() in the float specializations. Unfortunately, this creates a whole lot of specializations for all format combinations.

Does that make sense? What is a good way to measure this in the kernel?

  • Depend on D47932.
  • Implement little and big endian support.
  • Make AFMT_FLOAT usable from dev.pcm.X.play|rec.vchanformat.
christos added inline comments.
sys/dev/sound/pcm/feeder_format.c
58

I think we can move the discussion to D47932. I cleaned up the framework quite a lot IMHO so we can work on it easier now.

Remove early return in pcm_sample_read().

Fix shifting error I accidentally introduced while rewriting some parts.

Use __bswap32() instead of doing the LE <-> BE conversion manually.

As per kevans@ and imp@ on IRC, since v is an int32_t, it shouldn't be a
problem that __bswap32() expects and returns a uint32_t, because the return
value is implicitly casted.

sys/dev/sound/pcm/feeder_format.c
141

Why not just bswap32?

258

Bswap32?

300

Bswap32?

I have a hard time to apply these interdependent patches cleanly to my tree. @christos, do you update all affected patches in the series when doing a rebase?

sys/dev/sound/pcm/feeder_format.c
141

There's discussion relevant to this in D47932.

christos marked 8 inline comments as done.
  • Make implementation alignment-agnostic.
  • Add support in feeder_chain.c, feeder_rate.c and feeder_volume.c.
  • Rebase to work with D47932.
  • Define AFMT_F32_OE.
  • Guard fpu_kern(9) calls with ifdef _KERNEL so that the unit test can run.

I think the handling in pcm_sample_read() is quite ugly. Maybe it can be
simplified a bit.

Also, according to the benchmarks I provided, it seems that the fpu_kern(9)
calls do introduce a significant overhead, but I am not sure whether that's an amount worth caring about. Perhaps someone can provide more feedback.
It might be worth trying to do these operations using only integers.

I will also add tests to the unit test implemented in D48330.