Your Audio Setup - Desktop Customization & Workflow
Users browsing this thread: 1 Guest(s)
|
|||
Hello nixers,
I searched the forums and couldn't actually find anything related to audio. I recently wrote a summary of the audio stack on open source Unix-like systems. But I'd like to know your opinion and your actual local setup. Do you prefer the audio processing to happen in user-land or kernel? Do you have a use for audio pipeline? Or for volume per-application? What do you think of PipeWire, are you using it? What's your preferred audio stack currently? Do you generate music on your machine? Otherwise, let's talk anything audio. The thing I'm most amazed is that it's the first time that I actually understand ALSA's configuration format. Now it just clicked: it's a big dictionary of different profiles/devices that get merged. |
|||
|
|||
(08-02-2021, 03:37 AM)venam Wrote: I recently wrote a summaryA “summary”! 😂 That’s an awesome in-depth article that could show up in a book, my guy. I use PulseAudio and Jack. PA has features that are really hard to do with plain ALSA, for example I can easily plug in an equalizer (or any LADSPA module!), I can set volume per-application (yes, I do that), I can easily move programs between sound cards (I have at least two of them, plus maybe HDMI). It’s also awesome to have those “Monitor” devices, where you can just record what’s playing on a certain device. They also allow me to use rtspeccy as a generic visualizer for everything, which is great. I admit that I skipped the first few years of PulseAudio where it might have been very buggy – no idea. It’s solid today and I actually like it. pulsemixer is the frontend that I use. It’s a very tidy UI. Sometimes I get the impression that people are just confused by tools like pavucontrol and then blame PA for it, but that’s pure speculation, of course. Jack is unavoidable for some software like Guitarix (which saved me a ton of money) or Ardour. My external audio interface (a Focusrite Scarlett Solo) is blacklisted in PulseAudio, so I can run Jack and PA at the same time, where Jack only uses the Scarlett. That said, Jack (or the applications using it) has more bugs than PA. For example, LMMS gets kicked from Jack all the time, which is very annoying. Ardour sometimes screws up everything, all the routing is gone. This is a major PITA, it really feels a bit fragile. Could be a bug in Ardour or Jack, I don’t know. I used to have a hand-written ~/.asoundrc and used plain ALSA, but come on, this is so much work … Haven’t used PipeWire yet. PA just became a really stable and solid platform for me, so I’m a bit reluctant to change again. (I have zero use for all that Flatpak stuff and containerized apps – yet.) My desktop environment doesn’t use sound events. Well, GTK sometimes does, but not on purpose. Some of my scripts sometimes run beep, but that’s it. Sound events feel like a relic from the Windows 95 era to me. %) |
|||
|
|||
I'm also currently using pulseaudio.
(09-02-2021, 01:06 PM)vain Wrote: pulsemixer is the frontend that I useSimilarly, I use pulsemixer, it's simple and neat. I got paprefs installed in case I want to set simultaneous output, when I have multiple headsets connected like when watching a movie with friends each can have their own audio without bothering the rest of the house. I use the GUI instead of loading the module manually - It's just so much easier. One thing I'm testing is having different virtual sinks for different cases: voip, media, notification. So far, I have these in my default.pa Code: load-sample-lazy x11-bell /usr/share/sounds/freedesktop/stereo/bell.oga NB: The module-x11-bell can't be rerouted using pulseaudio restoration database so you have to specify the sink at module load time. Other than that this gives me 3 virtual sinks called "voip", "notification", and "media". Then I need pulseaudio to remember the routing of streams. It does that automatically normally when you manually move streams (according to different criteria) and saves it in the restoration database. So far I haven't found any user-interface that allows editing it easily, so I started writing one today. |
|||
|
|||
(09-02-2021, 01:30 PM)venam Wrote: load-module module-virtual-sinkDo you know if there’s a comprehensive documentation on this module? Or the standard PA modules in general? I know of this page: https://www.freedesktop.org/wiki/Softwar...r/Modules/ But either I’m blind or module-virtual-sink is not listed here. Of course, I can try to deduce stuff from reading the source code, but meh. |
|||
|
|||
(10-02-2021, 11:33 AM)vain Wrote: Do you know if there’s a comprehensive documentation on this module? Or the standard PA modules in general? You can always do this: Code: pacmd describe-module module-virtual-sink There are a bunch of default modules that comes with PulseAudio in the source directory you mentioned. Not all of them are documented on that page you sent. You can find a bigger list here. Another way would be to look in your local lib folder, somewhere like /usr/lib/pulse-<version>/modules. That's really one of the drawback of PulseAudio and even more with PipeWire, the documentation is lacking, and the toolset could be much more advanced and easy if people were aware of the features. EDIT: You can dump all installed modules like this: Code: pulseaudio --dump-modules --verbose |
|||
|
|||
Let's update this thread with my new setup as I recently dived bit more into PipeWire.
I was able to reproduce my previous setup with PipeWire since my last issue with the restoration stream was fixed. I even tested if the target-node, which is what PipeWire called the node a stream remembers to attach to when seen, keeps the right target in edge-cases like when it fallsback to another node when the current node the stream is attached to disappears. It still keeps the right expected target node in that case, which is what we want. My setup consists of 3 nodes that are virtual path connected to my headset by default: - Multimedia Node - Voip Node - Notification Node ![]() The idea is that I rely on the restoration mechanism to have streams automatically remember which type of path they should attach. The use-case is that if I am in a Voip call, I can listen to it through the Voip Node path, and meanwhile, I can redirect the Multimedia Node audio towards the call if I need to without the person hearing a loopback of their own voice. Additionally, as a bonus, I should be able to plug and unplug my headset without loosing the streams that are currently being played as they are attached to these virtual nodes. I've had issues in the past with this on PulseAudio with this specific setup that I mentioned above. Now to achieve this there are two steps that need to be done: 1. Create the virtual nodes 2. Have a mechanism to attach them automatically to my headset The first step is pretty simple, you can rely on the `context.objects` section of one of the service you're starting, be it pipewire, pipewire-pulse, or pipewire-media-session, they all allow this. It doesn't make much difference which one, as I noticed, so I just added them in my ~/.config/pipewire/pipewire.conf It looks like this: Code: { factory = adapter The node.description is what appears in pulseaudio tools, media.class dictates how ports are created, adding "Virtual" to it hides it from most tools, Duplex makes the output port name be called "capture_*" instead of "monitor_*". Monitor would the equivalent of the automated capture streams created in PulseAudio language. The part that was the hardest to discover was the monitor.channel-volumes, which if set to false would mean that the audio is pass-through, so if you change the volume in one of the virtual stream it wouldn't affect it. That's not what I want so I opted to set this to true. The second step is the one that gave me issues. I initially wanted to rely on WirePlumber implementation of the concept of Endpoints, which is similar to what I want to implement, but I wasn't able to rely on the restoration mechanism and nor did the Endpoint appear in PulseAudio tools, so it wasn't really what I wanted. I couldn't also create the links between the virtual nodes and my headset directly from the config file, even when reproducing some configs I've seen online. They set either "target.node" or "node.target", which I'm not sure what is the difference (it seems the first one is in the metadata and the other in the node properties), and that's suppose to give a hint to the session manager, similar to what is in the restoration db, to connect the nodes. Yet, it's not respected. Maybe it's related to the fact that if I issue the following I don't see my nodes but only my headset: Code: pw-cat -p --list-targets I then tried to find another way to do this natively and stumbled upon this: Code: pw_metadata <id> target.node <target-id> Spa:Id The best thing I've found when it comes to this was to rely on jack toolkits. I personally used something called jack-plumbing, but I've heard of similar tools such as jack_connect, and possibly others like aj-snapshot when running in daemon mode. What these tools do is listen for events in the graph and decide how to connect nodes according to specific rules. I know that's supposed to be the role of the session manager, but in this case neither pipewire-media-session nor WirePlumber offers an easy way to do that. Here are the rules I've set in my ~/.jack-plumbing: Code: (connect "Voip Node Monitor:capture_FL" "LifeChat LX-3000 Headset Digital Stereo \(IEC958\):playback_FL") What's nice with jack-plumbing (which you should run as pw-jack jack-plumbing), is that on any event, even when links change for a reason or another, it will go through the rules and re-create the links if needed. That's useful if the headset gets disconnected and reconnected. The next issue is to run pipewire, pipewire-media-session, pipewire-pulse, and jack-plumbing as systemd services. Fortunately, Arch comes with service and socket units for all of them, so I just had to enable them… except for my custom jack-plumbing. Another thing to beware, is that socket units should be the first units started in the chain. This is what I ended up having as a jack-plumbing.service in my ~/.config/systemd/user/jack-plumbing.service Code: [Unit] The next thing I'll try to add, which I've tested but haven't really seen the benefits of yet, is having rnnoise ldaspa module filter for echo cancelation on the microphone. I had to install a package called "noise-suppression-for-voice" on ArchLinux to get the ladspa library in /usr/lib/ladspa/librnnoise_ladspa.so, there wasn't so much documentation on that. It could also be used to add another level of indirection, avoiding issues when real devices disconnect. In the pipewire configuration you can then create a node that will use that filter for you. Another way to test this is to create a separate pipewire configuration file, with the bare minimum in it and put only this module. Code: { name = libpipewire-module-filter-chain Now as for the utilities that can be used to interact with PipeWire stuff, it's not missing, you can rely on PulseAudio, on Jack, or on native tools. I personally use the most pulsemixer for volume and qjackctl to move streams. I was curious to see all the ways I coul set the volume for the default node. The easiest way is to rely on the ALSA pcm that PipeWire creates: Code: amixer -D pipewire sset Master 5%- # decrease Code: amixer -D pulse sset Master 5%+ We can rely on pactl Code: pactl set-sink-volume @DEFAULT_SINK@ +5% So far, so good, now the hardest way to set the volume is to do it natively, there's no direct tool to do that right now. So here's as a bonus how to do it. 1. Find the current default sink 2. Find the current volume 3. Set the volume To find the current default sink, we have to rely on pw-metadata, as its an information that's only useful for the session manager. Here's what I got (NB: I rely on the jq tool to parse json): Code: # the metadata only contains the name of the default sink To find the current volume isn't easy either, you have to interrogate the params of the node and parse them, converting the volume using cube root function, here's what I got: Code: current_volume=$(pw-cli enum-params $default_sink_id 'Props' | grep -A 2 'Spa:Pod:Object:Param:Props:channelVolumes' | awk '/Float / {gsub(/.*Float\s/," "); print $1^(1/3) }') Now the final step is to set the volume, we can do that using pw-cli, but we also need to calculate how much change we want. Here we simulate an increment of 0.1. Code: change=0.1 This works fine, yet, the PulseAudio tools don't seem to show the volume as updated when you do this maneuver. Here's the final script: Code: # the metadata only contains the name of the default sink Another way to set the volume would be to rely on wpctl from WirePlumber tools (but doesn't actually need WirePlumber running), it also doesn't need all the juggling with cube transformation and updates PulseAudio tools: Code: wpctl set-volume $default_sink_id $new_volume So to conclude, PipeWire is sort of the Perl of media, or the shell pipeline of media, mechanism not policy, somehwat the Tim Toady There's more than one way to do it. I've had issues in the past with the PulseAudio setup I mentioned above but it seems to work fine on PipeWire. The native tooling is still missing things but you get around. Same for the stuff which I had to rely on jack-plumbing. But it's nice to have everything backward compatible. NB: After further usage, I'm still hitting edge-cases and bugs, so be sure to know it's still not the most stable software yet. Update: I'm testing with the following setup so that the media stream is automatically connected and merge through an intermediary node called "Record Node", that is the default source node for recording. Here's what it looks like when the headset is in (using helvum instead of qjackctl): ![]() And when I remove my headset: ![]() I also forgot to set the rtkit permission for pipewire, I did that in polkit, it's helping it being more responsive. |
|||
|
|||
Usually I simply reply on NetBSD's audio(4) system, which at the same time provides in-kernel hardware support, an in-kernel mixer able to manage multiple buffers at the same time, and a usersace sound API. The latter includes native OSS emulation for legacy programs, provides a command-line frontend, some sysctl hw.audio.* tunable parameters, and a virtual interface (/dev/audio) for libraries like SDL, OpenAL and portaudio, as well programs capable of streaming directly to it (patches for firefox, seamonkey, mpv, mpd, ffmpeg have been sent upstream to allow this).
You can think of it as alsa-driver + alsa-lib + alsa-utils + alsa-plugins. Mixing at hardware level doesn't result in the same cpu overload as that experienced in Linux using dmix, so using it directly without a sound server is still a viable option in this day and age. There's also a curses front-end for the mixer available in base, which is called aiomixer. On Linux (Slackware), I transitioned from a pure ALSA system (with plugins) to pulseaudio a couple of years a go, due to the fact most applications were dropping alsa-lib support. It's not like I wasn't using pulseaudio in light of some philosophical stance, it was just habit. Also, I have a good amount of legacy hardware and will happily use i686 machines daily, where the few more megabytes of volatile memory taken by pulseaudio may actually matter. I experimented with sndio a bit on Void Linux, but found application support to be too limited. I haven't looked into pipewire yet as of today. I do not generate music on *nix, but like to play with MIDIs on FreeDOS, using Voyetra Sequencer Plus GOLD, in conjunction with MPXPLAY, which, unlike most DOS software, supports AC'97 besides classic ISA sound cards. |
|||
|
|||
My audio system is built on jack and pulseaudio with ALSA, but it extends beyond just a single computer. It's a near complete integration of everything I own, and by that I mean that any device can use audio from anywhere. This part is built on Audinate's proprietary Dante network audio devices. The main linux computer is running a Digigram LX-Dante PCI-e card (supports 128 channels in/out but I'm using a fraction of those). This card is the main system device for jack. For example I usually have a stereo output that goes to HDMI but also to the Dante network. I have alsa_out running like this:
Code: /usr/bin/alsa_out -c 2 -j tv_speakers -d hdmi:CARD=NVidia,DEV=0 That handles the TV speakers. But I'm not just running that command every time I start my system; it's of course managed with systemd user units. This is where it starts to get unusual. Code: systemctl --user list-dependencies tv_speakers.target Yeah… so in order to run my tv speakers, I'm using a systemd target called tv_speakers.target, which starts alsa_out@nvidia.service to bind to the HDMI ALSA device. But it's also managing the connections to its jack client (called tv_speakers). There are two helper scripts, written in python: one to enforce connections/disconnections between two jack clients and their ports, and one to check that a client exists. There are absolutely no sleep or jack_connect shell commands in use here because I'm using jackdbus and watching for clients/ports and reacting to them as soon as something changes on the graph state. And then I went and added systemd template services for those clients and connections so I can declaratively script the state of my jack clients and connections. This opens up some interesting possibilities. I can now compose systemd services and targets that rely on jack connections or clients just as easily as I manage any other dependencies in user-level systemd services. The list-dependencies output from earlier should start to make sense now. I've got tv_speakers.target and it's starting the ALSA/jack device, but also pulling in an ecasound effects processor that uses LADSPA. The speaker_effects one is for extra dynamic range compression beyond what jack_effects.target does, which is nearly identical in purpose. But each step of the signal chain is set up with systemd so that it actually starts only what it needs, in the correct order. The systemd journal logs for the jack python scripts use custom fields to add context about the jack client in a way that I'll be able to filter on things like a jack client name or port later. The next thing I'm going to do is to make transient clients scripted in the same way using the .wants folder possibly. So I could start something like mednafen with a particular rom file and have its jack connections connected automatically (rather than what it does now blindly auto connecting to system ports 1 and 2). Most things are in systemd user units for me by now. I can start discord.target and it pulls in a jack_client@discord.service which pulls in the connections to at least have output. But I'm using the systemd targets to declare intent—what I want the system to set itself up to do. I either do this by starting targets/services directly or clicking buttons on home assistant on my phone. A week ago I played with making AI agent start/stop units from a prompt. The one I'm usually using is audio-full.target: Code: Requires = dante_pcie_in_notify.service I'm in the middle of refactoring from having the connections all defined and managed by some node.js scripts that used to handle watching dbus for jack clients and ports, and a few of these are still in use. And although I've been using it for years reliably, it's still a work in progress. There's also a multi-channel recording service that runs a fixed instance of ecasound, which is kind of lazy but because of it I have years worth of conversations recorded where my audio is isolated from others. I anticipated things like whisper (audio to text transcription with AI) years ago and have the raw data to mine these recordings eventually. I'm not using a USB headset. It's a full rack of studio grade equipment, including dedicated AD/DA into the Dante network, a managed switch, compressors, a hardware mixer, a wireless microphone receiver, wireless transmitter for IEMs. Each device is either always on or conditionally enabled using SNMP commands to a networked PDU. It powers on only what it needs to complete the signal chain for whatever I'm doing, and it's systemd targets and services all the way down. If I only want the microphone signal chain set up, I can just start microphone.target. The devices power on and get connected in a specific order using AFTER/BEFORE rules in the units to avoid any pops or static. Anything that can be started simultaneously will do that, everything else waits for dependencies. It checks that the devices are actually reachable on the network, or uses known delays for fully analog devices, before considering the device started. I'm using sd_notify in my scripts to know what a service is doing now, not just whether it started successfully or not. Code: systemctl --user list-dependencies microphone.target --plain Everything is in Dante, which is of course unusual for a home user. All of my other computers have an Audinate AVIO USB adapter for two channels in/out. Software-only solutions exist for network audio (I used NetJack in the past), but I wanted pro level reliability so I went with these. Any computer can use any microphone, all output from every source is mixed into my IEMs, which I then hear wirelessly on a Shure P10R+ bodypack. I'm still mad I didn't go with Lectrosonics for this, because then my IEMs would be encrypted. I have it set up so that I can switch between two separate final signal chains, but I rarely make use of it. For example, two people playing separate games in the same room could hear each other on a microphone but are hearing different game audio (with no speakers). The only problem with Dante (besides the cost, obviously), is their licensed API and MacOS/Windows-only GUI client for managing the devices and the connections. I did just enough reverse-engineering on their control protocol (wireshark while doing stuff on the GUI) to make a CLI tool to cover the basics: mDNS service discovery, basic device configuration, routing. Code: netaudio device list Code: netaudio subscription list | grep -iEv '(own signal|unresolved|no subscription)' Code: jack_lsp -c All the information and capabilities are there already, but I haven't made it into a proper daemon that can react to changes in the routing or device configuration. My Dante routing setup is pretty static right now because of it, but that's fine. But eventually I want this to be set up with systemd so that I can know for sure that my entire signal chain (hardware, jack, Dante) is actually correct. I've been at this step for a while and just haven't finished it. The microphone is a Shure SM7B on a Yellowtec M!ka mount. I have spares for both. I also have a wireless Shure lav mic (Shure AD4D receiver with an AD1 bodypack transmitter). I have a Bluetooth AVIO adapter to get audio in and out of my phone and steam deck, but I hate using Bluetooth for audio, obviously. The full signal chain is SM7B -> Rupert Neve Shelford Channel (channel strip) -> Ferrofish A32 Dante (AD/DA) -> (stuff in/out of Dante from everywhere and the AD4D wireless mic receiver) -> Ferrofish DA -> Neve Satellite 5059 (analog mixing to two stereo pairs output) -> Neve Portico 5043 (compressor; a single half-rack unit does stereo, I have two of these) -> Shure P10T for the final mix to be transmitted wirelessly to my Shure P10R+ for my Shure SE846 IEMs. The final effect is that every device uses both microphones and I can hear everything and still talk moving around my house wirelessly. Low latency. For HDMI I've got an HDFury VRROOM to extract audio from HDMI 2.1 sources (game consoles, macbooks, gaming pc, whatever). This device has IP control, which I've scripted so I can switch video inputs on systemd/home assistant. It has TOSLINK output into a splitter. One goes into the Ferrofish A32 Dante (as a backup, I don't want this device to be a hard requirement for this) and a Hosa TOSLINK to AES3 adapter into an Audinate AVIO AES3 adapter. The AVIO adapter is always on, so I don't need to start the Ferrofish unless the other device fails (which it has, I'm on the second one). So yeah, like $1000 to get HDMI 2.1 audio into Dante. There's more, but that's enough of an overview. More recently, I put a lot of effort into core and IRQ isolation to get the jack xrun count lower (only really became an issue when I started having lots of network traffic and a 10+ day BTRFS balance, which was enough to make it absolutely necessary to finally do). I even found a bug in pulseaudio that leads to an infinite loop. I tried to do setcap on it so it could renice itself but it kept trying to drop privileges thinking it was root because of it. I last wrote about this here: https://www.avsforum.com/threads/im-usin...e.3287663/ which has some photos before I moved and a bunch of hyperlinks to the gear involved. |
|||