Saving and restoring ALSA mixer state

  • I understand that the intended way of restoring a custom mixer state for ASLA sound cards on boot, is to do something like

    Code
    alsactl store -f /storage/.config/sound.conf

    which would then restore the state of the mixers of all devices to that stored when the above command was executed. This does not quite work for me.

    Running

    Code
    alsactl restore-f $HOME/.config/sound.conf

    manually at any point via SSH works fine, but it doesn't work on boot, even though the soundconfig script does run (i.e. I do see "Setting up sound card" in the journal logs).

    There seem to be various interesting things about that setup. First, the udev rule in charge of running this script, is

    Code
      KERNEL=="controlC[0-9]*",  NAME="snd/%k", ACTION=="add", RUN+="soundconfig %k"

    Now that NAME="snd/%k" part, which presumably is meant to say something like "match only events having to do with snd/controlC*", actually is an action saying "rename this network interface to that". As this is not a network inteface, the journal has lines like

    Quote

    (udev-worker)[402]: controlC1: /usr/lib/udev/rules.d/90-alsa-restore.rules:5 Only network interfaces can be renamed, ignoring NAME="snd/%k".

    and that action has no effect. The system-shipped rule on my PC additionally has a KERNELS!="card*" rule. I'm not quite clear on what it's there for, but if I'm interpreting the intent of that NAME action correctly, I suspect it's doing something similar.

    On another note, that KERNEL=="controlC[0-9]*" looks like its meant to be a regular expression, saying "controlC, potentially followed by numbers", but these are actually glob patterns, so it means "controlC, followed by a number and whatever else". If that is the case, it doesn't hurt, but the rule on my PC has just KERNEL=="controlC*", which is probably enough.

    Moving on to the soundconfig script, the relevant portion in it, reads:

    Code
    if [ -f $HOME/.config/sound.conf ]; then
     alsactl restore -f $HOME/.config/sound.conf
    else
    ...

    One issue with this, is that it gets run when each card gets added to the system, and when there's no saved state, it does initialize that specific card. When there is a saved state though, it attempts to reset the state of all cards each time a new card gets added. That probably doesn't break anything but it should result in some wasted work. The restore command does take an optional card argument, so that can be improved.

    Anyway, all of that doesn't seem to have anything to do with my problem. My problem seems to be more insidious and I suspect there's some sort of race going on. I've tried various changes and got it to work, but I'm not sure as to the how and why. It seems that after attaching something like > /tmp/log 2>&1 to the end of the alsactl command (which I did just to get any error messages) it did work. If I then removed it, it kept working for some reason. (I should say here that for testing purposes, I installed a copy of the system-wide udev rule inside /etc/udev/rules.d, running a copy of soundconfig, which I could then edit and retry, by removing and reloading my sound card's module to get the device to disappear and reappear.)

    I took a guess and removed that final & in the soundconfig script, backgrounding the whole subshell and it now works correctly. I'm not sure why, but I have the vague notion that the main script's shell exits before the subshell has finished and something tricky happens there.

    So, in conclusion:

    1. Does this work for you?

    2. Is there some specific reason for backgrounding the subshell?

    3. I have a patch for most of the changes discussed here. Would there be interest in trying them out and potentially applying them? Should I open a PR?

  • TBH I never tried that but one thing for sure: the whole soundcard/soundconfig handling in LE is an utter mess dating back to ancient OpenELEC days.

    Completely nuking it and replacing it with the current ALSA default config/state handling (like any current linux distro does) has been on my TODO list for quite a while now but unfortunately I didn't find the spare time to actually do that.

    So any help in that area is highly appreciated, just open a PR.

    so long,

    Hias

  • Ah, I think I've found the issue. According to the udev(8) man page, regading the RUN action:

    Quote

    Starting daemons or other long-running processes is not allowed; the forked processes, detached or not, will be unconditionally killed after the event handling has finished.

    Debian's system-shipped rule does call alsactl via RUN though, so I suppose that's fine.

    Great, I'll look into it a bit more and open a PR then. So, to your knowledge, there may be no reason (any more) not to use the system-shipped rules? If not, I could try to restore those, since the existing setup does seem to be a bit ad-hoc. My only concern is with testing, as LE runs on a lot of disparate setups and I can only test it on one of those.

  • My only concern is with testing, as LE runs on a lot of disparate setups and I can only test it on one of those.

    It would be good to have something submitted that can be smoke-tested and critiqued by staff first, then after LE12 ships (so early in the LE13 cycle) we can merge the change to nightlies. If there are minor problems we work on fixing them, and if there are major ones we can revert the changes and rethink.

    In short, as long as you're patient and understanding (this is a large change, we'll be cautious about it) .. all will be fine :thumbup:

  • I'm trying to restore the default alsa-utils functionality regarding card state handling and it's relatively straightforward so far, except for one point. By default alsactl attempts to store state to /var/lib/alsa/asound.state but /var/lib/alsa/ does not exist, because it is not created. So when the alsa-restore service runs, trying to save the state at shutdown, it fails.

    As far as I can see, various package-created directories are explicitly removed by the build scritps, and this includes /var/* directories, so I suppose this is all intended and I wonder what the preferred way of handling this would be. Should we:

    1. Try to change the default ALSA state file to something else, like .config/sound.conf. I don't think so since this is always overwrriten by the system at shutdown, saving the current state.

    2. Recreate the /var/lib/alsa/ directory post-install? If so, what would be the canonical way to do that?

    3. Do omething else?

  • If it's possible to use a compile time option to change/specify the state location we can modify the relevant alsa package.mk to set that to a /run/alsa/ or /storage/.cache/alsa location at compile time. If there's none in current code we probably need to find where it's defined in code and patch the location (or patch making it configurable and send the patch upstream).

  • That should be possible; I'll look into it. If we do change the state directory though, to either of these choices, the directory still won't exist. How should it be created?

    Also, out of curiosity, why change the default setting? It is the appropriate location, at least per the standards. Quoting from the Filesystem Hierarchy Standard, on the purpose of /run:

    Quote

    This directory contains system information data describing the system since it was booted. Files under this directory must be cleared (removed or truncated as appropriate) at the beginning of the boot process.

    On the other hand, about /var/lib:

    Quote

    This hierarchy holds state information pertaining to an application or the system. State information is data that programs modify while they run, and that pertains to one specific host.

  • I realize I've probably not been very clear. The system-shipped functionality I'm trying to restore, is this. So the service saves the state on shutdown by default, but can be configured to run as a daemon, saving the state as it is changed. The end result is the same: the user can make desired changes and these are persisted across reboots. This is essentially the same as the existing approach, only there's no need on behalf of the user to save the state of the sound cards; just to set it.

  • You cannot store things directly in /var/lib/alsa because /var resides inside a read-only squashfs file that's decompressed on boot to create a non-persistent virtual filesystem.

    You can put symlinks inside the read-only squashfs file that relocate the directory to locations like /storage/.cache/alsa which can persist data on a read-write volume. You can have systemd create directories on first-boot as needed; see packages/network/bluez/tmpfiles.d/z_05_bluez.conf for an example.

  • You cannot store things directly in /var/lib/alsa because /var resides inside a read-only squashfs file that's decompressed on boot to create a non-persistent virtual filesystem.

    Ah, yes. I lazily just quickly tested /var for writability and didn't look much deeper ^^

    So, I used systemd-tmpfiles to create /storage/.cache/alsa/ plus a symlink /var/lib/alsa -> /storage/.cache/alsa. Seems to work fine, is simple to do and has the extra benefit of retaining the "usual" setup as much as possible.

    I think I have enough for a first PR now, at least as a basis for further disucssion. I'll review it again and probably open it tomorrow.

  • Just for people looking for a temporary solution until the PR is merged: I simply added an 'amixer' command to the 'autostart.sh' file in '.config' - that works like a charm and sets my volume upon each reboot.