Realtime Audio + Parametric Equalization + Bitperfect Mode in pure OSS in non-root userspace

Searching for answers on how to achieve parametric equalization on bitperfect OSS audio without having to use any middleware like Jack, Pipewire or Pulseaudio, I did not really find a clear answer so, I decided to make a Howto in case other people have the same desire and want a full guide in order to achieve it.

Before starting I would like to point out some software which needs to be installed on your FreeBSD OS:
audio/musicpd the MPD daemon
audio/ncmpcpp the MPD client or another MPD client (see point 3.2 Console)
multimedia/ffmpeg for parametric equalization filter
sysutils/mac_rtprio for executing rtprio without root rights (pkg name: mac_rtprio-kmod) - new policy module available since FreeBSD 13-1 (Thank you angry_vincent for pointing that out)
For this guide the used MPD client will be ncmpcpp, but I think the settings applied to ncmpcpp should be transferable with some minor tweaks to other MPD clients as well.

Let us start then.

First of all add the kernel module mac_rtprio to your /etc/rc.conf file.
First of all add the kernel module mac_priority to your /etc/rc.conf file.
This step can be done in the following ways:
1) Open the file as root through your favourite text editor, and append mac_priority to your kld_list.
OR
2) As root issue the following command sysrc kld_list+="mac_priority".
AND
3) Add your user to the realtime group by executing as root pw groupmod realtime -m $USERNAME

The next step is to edit your /etc/sysctl.conf file.
These parameters should be set:
Code:
# Set and enable gid for rtprio so that users in that group can use rtprio (prior to FreeBSD 13.1)
security.mac.rtprio.gid=your-group-id (prior to FreeBSD 13.1)
security.mac.rtprio.enabled=1 (prior to FreeBSD 13.1)

# Increase the precision of the software clocks used to synchronise process wake-up
# Minimize the number of times the CPU is forced to perform a relatively expensive operation to enter and exit idle states
kern.timecounter.alloweddeviation=0

# Set the lowest available buffering latency
hw.snd.latency=0

# Enable bitperfect mode
dev.pcm.%d.bitperfect=1

# Set VCHAN number for play and rec channel
dev.pcm.%d.play.vchans=0
dev.pcm.%d.rec.vchans=0

You can find your group id by issuing the following command:
Code:
cat /etc/group | grep $USER (prior to FreeBSD 13.1)
your-username:*:your-group-id: (prior to FreeBSD 13.1)
%d revers to your device number which you can look up through the following command cat /dev/sndstat.
If you have PCMs then it will be the number appended to a PCM.
I recommend you to reboot your device before you proceed to the next step.

The next step explains how to set up the MPD daemon including its configuration.
Check whether the mpd directory exists in your $HOME/.config directory.
If not, create one with mkdir -p $HOME/.config/mpd.

Create the following files inside the $HOME/.config/mpd directory.
Code:
touch $HOME/.config/mpd/{database,log,musicpd.conf,socket,state,sticker.sql}

Open the $HOME/.config/mpd/musicpd.conf file with your text editor of choice.
Copy in the following content and adjust some variables.
Code:
# Read the user manual for documentation: http://www.musicpd.org/doc/user/

# VARIABLES ###################################################################
#
# Set variables which are needed for musicpd to work correctly
music_directory    "/path-to-your-album-directory"
playlist_directory "/path-to-your-playlist-directory"
db_file            "/home/username/.config/mpd/database"
log_file           "/home/username/.config/mpd/log"
pid_file           "/home/username/.config/mpd/pid"
state_file         "/home/username/.config/mpd/state"
sticker_file       "/home/username/.config/mpd/sticker.sql"
user               "username"
group              "groupname"
bind_to_address    "/home/username/.config/mpd/socket"

# Input #######################################################################
#
input {
        plugin "curl" # Manage the connection to musicpd
#       proxy "proxy.isp.com:8080"
#       proxy_user "user"
#       proxy_password "password"
}

# Filter ######################################################################
#
# Apply ffmpegs high-order parametric multiband equalizer (anequalizer) for the Focal Utopia 2022 revision
# 10 band parametric equalization will be used
# Format: "anequalizer=cchn f=cf w=w g=g t=f | ..."
# Each equalizer band is separated by ’|’
# Settings to set for each band of the Focal Utopia 2022 revision version 1:
# band 1:  c0 f=105 w=40 g=9.1 t=0
# band 2:  c0 f=65 w=1162 g=-5.4 t=0
# band 3:  c0 f=1227 w=8773 g=-3.1 t=0
# band 4:  c0 f=10000 w=8020 g=6.3 t=0
# band 5:  c0 f=1980 w=8020 g=2.9 t=0
# band 6:  c0 f=10000 w=4982 g=-3.1 t=0
# band 7:  c0 f=5018 w=954 g=2.9 t=0
# band 8:  c0 f=5972 w=2972 g=-3.2 t=0
# band 9:  c0 f=3000 w=578 g=-1.7 t=0
# band 10: c0 f=3578 w=3578 g=1.5 t=0
#
filter {
plugin  "ffmpeg"
name "utopia-2022-eq-version-1"
graph "anequalizer=c0 f=105 w=40 g=9.1 t=0|c0 f=65 w=1162 g=-5.4 t=0|c0 f=1227 w=8773 g=-3.1 t=0|c0 f=10000 w=8020 g=6.3 t=0|c0 f=1980 w=8020 g=2.9 t=0|c0 f=10000 w=4982 g=-3.1 t=0|c0 f=5018 w=954 g=2.9 t=0|c0 f=5972 w=2972 g=-3.2 t=0|c0 f=3000 w=578 g=-1.7 t=0|c0 f=3578 w=3578 g=1.5 t=0"
}
#
# Settings to set for each band of the Focal Utopia 2022 revision version 2:
# band 1:  c0 f=80 w=25 g=-2.6 t=0
# band 2:  c0 f=105 w=105 g=5.5 t=0
# band 3:  c0 f=210 w=1060 g=-2.0 t=0
# band 4:  c0 f=1270 w=730 g=-3.3 t=0
# band 5:  c0 f=2000 w=900 g=3.3 t=0
# band 6:  c0 f=2900 w=3100 g=-2.3 t=0
# band 7:  c0 f=6000 w=1700 g=-5.0 t=0
# band 8:  c0 f=7700 w=1800 g=-2.0 t=0
# band 9:  c0 f=9500 w=3500 g=6.5 t=0
# band 10: c0 f=13000 w=13000 g=-3.0 t=0
#
filter {
plugin  "ffmpeg"
name "utopia-2022-eq-version-2"
graph "anequalizer=c0 f=80 w=25 g=-2.6 t=0|c0 f=105 w=105 g=5.5 t=0|c0 f=210 w=1060 g=-2.0 t=0|c0 f=1270 w=730 g=-3.3 t=0|c0 f=2000 w=900 g=3.3 t=0|c0 f=2900 w=3100 g=-2.3 t=0|c0 f=6000 w=1700 g=-5.0 t=0|c0 f=7700 w=1800 g=-2.0 t=0|c0 f=9500 w=3500 g=6.5 t=0|c0 f=13000 w=13000 g=-3.0 t=0"
}

# Audio Output ################################################################
#
# MPD supports various audio output types, as well as playing through multiple
# audio outputs at the same time, through multiple audio_output settings
# blocks. Setting this block is optional, though the server will only attempt
# autodetection for one sound card.
#
# OSS audio output through headphones filtered
#
audio_output {
type "oss" # Sound Driver where the filtered output goes through
name "OSS Headphones" # Decive name
device "/dev/dspN" # Headphones device
mixer_type "hardware" # Preferred mixer method (Another alternative is "software" as mixer_type)
mixer_device "/dev/mixerN" # Mixer used for headphones
# mixer_control "vol" # I will set my volume per audio output as needed

# Toggle filters as needed
#filters "utopia-2022-eq-version-1" # Filter in use for the headphones
filters "utopia-2022-eq-version-2" # Filter in use for the headphones
}
Replace username and groupname with your user and his group (usually the same as username).
The N appended to /dev/dsp and /dev/mixer is the number of your device you want to use with MPD + MPD client.

The filter works as it is, but you eventually need to adjust the number of bands and variables f=band-frequency, w=bandwith-between-two-frequencies-in-Hz, and g=gain-in-db to your needs if you use a different device.
For adjusting f and g, there are two lists available which I can recommend you.
These are lists with values for all sorts of audio devices (List1, List2).

Regarding the variable w, I could only find bandwith measured in octaves so, I needed to do manual calculations for each band to get the bandwith in Hz.
The formula is from electric engineering and looks like this one (f_max - f_min).
Now what does it exactly mean...
Suppose you have 5 bands.
The bandwith is the width bewteen two bands which are close to each other so, in the case of 5 bands you would start to compare band 1 and band 2, and look which one has the higher frequency.
The one with the higher frequency will be f_max, the other one f_min so, w for band 1 will be the result of this band 1, and band 2 frequency (f) subtraction.
Going next you want to compare band 2 and band 3, and choosing which f you put into f_max and f_min.
The result is going to be stored in w of band 2.
Coming to band 5, I think since there is no band afterward w will equal f of band 5, because there is no f_max nor f_min, and setting w=0 degraded my audio quality.

The final step now is to configure ncmpcpp.
Create a ncmpcpp directory in $HOME/.config through mkdir -p $HOME/.config/ncmpcpp.
Create the config file with touch touch $HOME/.config/ncmpcpp/config and paste the following content in the file:
Code:
#[mpd]
mpd_music_dir = "~/library/audio/playlists"
mpd_host = "~/.config/mpd/socket"

Ok, I think we are done with the configuration step.
Now let us see whether everything works.

Start the MPD server in realtime:
rtprio 0 musicpd --verbose $HOME/.config/mpd/musicpd.conf
ncmpcpp

Stop the MPD server if not needed anymore:
service musicpd stop

These were the steps to make everything described in this Howto to work for me.
If you have any questions, problems during setup or suggestions do not hesitate to reply to this Howto.
 
New policy module available in FreeBSD-13.1 - mac_priority
Simply add it to
/etc/rc.conf

kld_list+="mac_priority"

The add you user to realtime group:

# pw groupmod realtime -m $USERNAME

And then after user logged in:

rtprio 0 musicpd /usr/local/etc/musicpd.conf

Or whatever file is used for musicpd config ( such as ~/.mpdconf )
To verify that musicpd is running in realtime mode:

ps -o rtprio -axl | grep [m]usicpd

Output should look like this:

real:0 1001 1981 1 6 -52 0 229744 41996 select I<s - 0:06,21 musicpd /usr/local/etc/musicpd.conf

mac_priority()
rtprio()
This way there is no need to use external port kernel module
sysutils/mac_rtprio
 
New policy module available in FreeBSD-13.1 - mac_priority
Simply add it to
/etc/rc.conf

kld_list+="mac_priority"

The add you user to realtime group:

# pw groupmod realtime -m $USERNAME

And then after user logged in:

rtprio 0 musicpd /usr/local/etc/musicpd.conf

Or whatever file is used for musicpd config ( such as ~/.mpdconf )
To verify that musicpd is running in realtime mode:

ps -o rtprio -axl | grep [m]usicpd

Output should look like this:

real:0 1001 1981 1 6 -52 0 229744 41996 select I<s - 0:06,21 musicpd /usr/local/etc/musicpd.conf

mac_priority()
rtprio()
This way there is no need to use external port kernel module
sysutils/mac_rtprio
Thank you.
I have edited the howto to reflect your suggestions.
:)
 
Back
Top