Alsa UCM

From postmarketOS

Alsa Use Case Manager describes how to set up the mixer for certain usecases (like "playing audio", "calling"). It also describes how to modify the mixer state to route audio to certain outputs and inputs and how to control those devices.

This basically covers the same things profiles do in Pulseaudio except that UCM files are easier to write and also work without Pulseaudio running. If an UCM configuration is present for a card then pulseaudio will ignore the built-in profiles and generate a profile based on the UCM files.

Official documentation: https://www.alsa-project.org/alsa-doc/alsa-lib/group__ucm__conf.html

Card name

The first thing you need is the card name, this is used as the directory and filename for the UCM main file. You can find it using the aplay command from the alsa-utils package

$ apk add alsa-utils
$ aplay -l
card 0: sun50ia64audio [sun50i-a64-audio], device 0: 1c22c00.dai-sun8i-codec-aif1 sun8i-codec-aif1-0 [1c22c00.dai-sun8i-codec-aif1 sun8i-codec-aif1-0]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

In this case the name you need is the first name in square brackets: sun50i-a64-audio

The first usecase

The next step is creating a file that contains the list of available usecases for the mixer. This file will be named after the mixer name from the section above:

/usr/share/alsa/ucm2/sun50i-a64-audio/sun50i-a64-audio.conf
Syntax 2

SectionUseCase."HiFi" {
	File "HiFi"
	Comment "Play high quality music"
}

For every usecase there will be a SectionUseCase block. The name in quotes directly after that is the "verb", which is the internal name of the profile. This has to be one of the predefined usecases from alsa. The ones that are currently defined are:

  • "Inactive"
  • "HiFi"
  • "HiFi Low Power"
  • "Voice"
  • "Voice Low Power"
  • "Voice Call"
  • "Voice Call IP"
  • "FM Analog Radio"
  • "FM Digital Radio"

The most important profile to create is the HiFi one, that's the main audio playback profile for the card. For the other profiles the actual use of them isn't very well defined by Alsa.

The first setting for the profile is the File line. This defines the filename for the file that contains the exact mixer configuration for that usecase.

The second setting in this example is the Comment. This is used as the display name in the alsaucm tool and in Pulseaudio. This can be freely defined.

The Use case definition

The most complicated part is the actual contents of the HiFi file that needs to be created. This file is basically a collection of scripts to enable/disable the mixer and commands to move from one input/output to another.

This is a minimal example of a usecase file.

/usr/share/alsa/ucm2/sun50i-a64-audio/HiFi
SectionVerb {
	EnableSequence [
		cset "name='Headphone Playback Switch' off"
		cset "name='Headphone Source Playback Route' DAC"
		cset "name='Headphone Playback Volume' 50%"
	]
	DisableSequence [
	]

	Value {
		PlaybackPCM "hw:${CardId},0"
		CapturePCM "hw:${CardId},0"
	}
}

The SectionVerb block defines what commands need to be run to enable or disable this usecase. This is mostly used to bring the whole mixer in a known state on start so it's probably a good idea to set a value for all controls in the mixer here.

The commands in the sections in this section are all cset commands. These commands correspond to commands you'd normally use with the amixer tool from the alsa-utils package. The first command does this when run with amixer:

$ amixer cset name='Headphone Playback Switch' off
numid=36,iface=MIXER,name='Headphone Playback Switch'
  ; type=BOOLEAN,access=rw------,values=2
  ; values=on,on

To get a list of all the controls you can set you can also use the amixer command:

$ amixer controls
numid=35,iface=MIXER,name='Headphone Source Playback Route'
numid=36,iface=MIXER,name='Headphone Playback Switch'
numid=17,iface=MIXER,name='Headphone Playback Volume'
... a lot more lines here ...

These should be fairly easy to correlate with the controls you see in alsamixer. The controls follow a standard naming scheme. The volume sliders for devices on the Playback page of alsamixer are called $name Playback Volume, the same control on the Capture page is called $name Capture Volume. The mute button underneath the playback volume sliders are called $name Playback Switch and the enable switches on the Capture page are called $name Capture Switch. Controls that don't have a volume slider in it but a text selection the name is $name Playback Route

Alsa mixer mapping.png

Debugging UCM configs

Reload UCM configs

alsaucm reload reload the configuration, but if you're using PulseAudio you also need to restart PulseAudio somehow. To both reload alsaucm and PulseAudio conveniently, you can run alsaucm reload && killall -9 pulseaudio.

Monitor ALSA events

alsactl monitor allows you to monitor the ALSA events, especially for jack detection. The event name in the log must match the UCM config JackControl if you want to change ALSA settings when a jack is plugged in or removed.

Using plain ALSA for finding controls

Finding the right controls with ALSA UCM is a pain, to make your life easier:

  1. Disable PulseAudio or PipeWire so ALSA UCM is not active at all.
  2. Write a simple script with amixer -c $CARD cset iface=MIXER,name='$CONTROL' '$VALUE'
  3. Execute the script to set your mixers
  4. Test your sound with aplay -D plughw:$CARD,$DEVICE /usr/share/sounds/alsa/Front_*.wav

Repeat this until you figured out a working list of mixers to play audio to the output device you want. Also make sure that the list of mixers is sufficient to work directly after a reboot. Once you have them figured out, you can transfer to ALSA UCM configs, this way you're sure that the mixers are at least correct without influence from ALSA UCM or PulseAudio/PipeWire.

List of controls

amixer can provide you a lot of information about the available controls with:

  • amixer -c $CARD scontrols
  • amixer -c $CARD scontents

TinyALSA can provide you a more readable version of them. TinyALSA is not in the repos but can be compiled as followed:

  1. git clone https://github.com/tinyalsa/tinyalsa
  2. apk add alpine-sdk linux-headers
  3. make

After compiling TinyALSA, the tools are in utils, you probably might need tinymixer

Checking if a control is actually called

Sometimes you may be wondering if your controls are called you defined in your ALSA UCM sections. ALSA UCM allows executing of arbitrary commands with shell and exec commands. You can add

 shell "/bin/echo 'My Control was called in X' >> /tmp/alsa-ucm.txt"

to debug your sequences. The output will appear in /tmp/alsa-ucm.txt

Keywords

  • ConflictingDevice: indicates conflicts with other devices. For example: earpiece and headphones may be incompatible with each other when they are driven by the same codec.
  • EnableSequence: specifies the amixer commands to enable the device or verb.
  • DisableSequence: specifies the amixer commands to disable the device or verb.
  • TransitionSection."$SECTIONDEVICE": commands to execute to go from a device to an other device.
  • BootSequence: commands to execute only once when the card configuration is not available yet, useful to set the initial volume.
  • FixedBootSequence: commands to execute at each boot, useful to unmute certain things.

Most keywords are documented in the official docs: https://www.alsa-project.org/alsa-doc/alsa-lib/group__ucm__conf.html

Configuration of a card:

 SectionVerb {
   Value {
     // Values to config this card, example for HiFi profile:
     TQ "HiFi"
   }
   EnableSequence [
     // mixer control to be set when the card is initialized
   ]
   DisableSequence [
     // mixer control to be set when the card is destroyed
   ]
 }

Configuration of a device:

 SectionDevice."Speaker" { // The node name.
   Value {
     // Values to config this node
   }
   EnableSequence [
     // mixer control to be set when this node is selected
   ]
   DisableSequence [
     // mixer control to be set when this node is unselected
   ]

}