PulseaudioProfiles

Introduction to PulseAudio profiles and ports

For controlling audio volume and mute, PulseAudio provides an abstraction layer over ALSA. ALSA often exposes a bewildering list of mixer controls, and the end user probably just wants to control the main volume, not several volumes.

However, which volume to control, depend on the currently active output (or input). Some ALSA volume control might be necessary for controlling Headphones volume, and another for Speaker volume. There might be additional controls needed to be set for audio to be output at all.

To describe what mixer controls PulseAudio needs to set for a particular output, PulseAudio uses path files. There is also a profile-set file that describes which path files to look for, and an udev rule to determine which profile-set file will be used for a particular hardware.

The upstream documentation for how to write profile and path files is here. There is also documentation inside these files.

Android also has mixer files

Android devices, at least some of them, have mixer files in an xml format. These can provide very good hints of how to write your paths. Here is how the mixer xml file looks for Nexus7, which will be used as an example in this guide.

Path files

In my example, I have the following paths defined in the Android mixer file:

<path name="speaker"></path>
<path name="headphone"></path>
<path name="main-mic-top"></path>
<path name="main-mic-left"></path>
<path name="headset-mic"></path>

This would typically correspond to similar path files in PulseAudio. I chose to create paths for

  • Speaker
  • Headphone
  • Mic (built-in)

PulseAudio does not support switching port depending on current rotation, and the headset-mic path present is probably bogus, because Nexus7 does not support headset mics (at least so I've been told).

Volumes and mutes

Typically a number of switches need to be set up correctly for audio to be output. If your mixer file looks like this:

  <ctl name="Speaker Playback Switch" value="0" />
  <ctl name="Int Spk Switch" value="0" />
  <path name="speaker">
    <ctl name="Speaker Playback Switch" value="1" />
    <ctl name="Int Spk Switch" value="1" />
  </path>
  <path name="headphone">
  </path>

Then you can figure out that both of "Speaker Playback Switch" and "Int Spk Switch" need to be turned on for audio to be output on the Speaker.

Here's how the corresponding section would look like in PulseAudio's speaker path file:

[Element Int Spk]
switch = mute

[Element Speaker]
switch = mute

And here's how it would look in PulseAudio's headphone path file:

[Element Int Spk]
switch = off

[Element Speaker]
switch = off

In short; "Speaker Playback Switch" is transformed into "Element Speaker", "switch = off" or "switch = mute". If switch is set to "off", it will always be off when that path is active. If it is set to "mute", it will toggle depending on whether audio is currently muted or not.

For volumes, my mixer xml file did not give me any hint. But "Master", "Speaker" are usually safe bets. Try and see if you get the desired results. A "Speaker" playback volume would typically be coded as:

Speaker path:

[Element Speaker]
volume = merge
override-map.1 = all
override-map.2 = all-left,all-right

(Just copy-paste the override-map rows as the above.)

Headphone path:

[Element Speaker]
volume = off

If you have more than one ALSA volume control set to "merge", PulseAudio will merge them in order to create a main volume control with a range that corresponds to all of the ALSA volume controls combined.

Enumerations

Sometimes a mixer control needs to be set to a specific enumeration value, rather than just on/off. E g, if the "DMIC Switch" needs to be set to "DMIC1", this would be specified in the Android mixer file like this:

<ctl name="DMIC Switch" value="DMIC1" />

In the PulseAudio path file, the corresponding section would be:

[Element DMIC]
enumeration = select

[Option DMIC:DMIC1]
name = analog-input-internal-microphone

In this case, you will only have one option per element, so just pick the same name as the entire path for the option's name.

Switching port on device plugin

One problem remains though: When headphones are plugged in, we want the headphones path to be activated (which will, in turn, mute the speaker). On desktop machines, this is done through monitoring read-only mixer controls, and writing corresponding sections in the path files. If you have a mixer control named "Headphone Jack", here's how it would look like:

Speaker path:

[Jack Headphone]
state.unplugged=unknown
state.plugged=no

Headphone path:

[Jack Headphone]
state.unplugged=no
state.plugged=yes

The main point being that headphones are unavailable when they are not plugged in, and speakers are unavailable when headphones are plugged in. For this example, the difference between "unknown" and "yes" can be disregarded.

However; on Android, things might not look like this, as Android usually uses its own extcon/switch interface, for which there is no support in PulseAudio. However, you can write a kernel patch that exposes a mixer control similar to what is done on desktop machines. Here is an example of this being done for Nexus 7, which uses a 3.1 kernel. On 3.3+ kernels, this requires slightly less code as you can use the snd_kctl_jack_new and snd_kctl_jack_report functions instead.

Also note that these jack detection mixer controls are not shown in alsamixer. "amixer contents" (not scontents!) will show them.

Fitting the pieces together

Now that you have some good looking path files, how do you make PulseAudio use them? First, you need a profile-set file that points to the path files. It also tells whether to try opening the device in mono, stereo, 5.1 surround, etc.

Here's how it looks like on the Nexus7:

[General]
auto-profiles = yes

[Mapping analog-stereo]
device-strings = front:%f hw:%f plughw:%f
channel-map = left,right
paths-output = tegra-nexus7-speaker tegra-nexus7-headphone
paths-input = tegra-nexus7-intmic
priority = 10

I'm only interested in stereo input/output, so that's the only mapping section specified. And as you can see, the "paths-output" and "paths-input" points to the paths files.

Second, you need a udev rule to match your hardware. How to write udev rules is beyond this howto, so I'll just quote the Nexus7 example:

SUBSYSTEM!="sound", GOTO="tegra_rt5640_end"
ACTION!="change", GOTO="tegra_rt5640_end"
KERNEL!="card*", GOTO="tegra_rt5640_end"

ATTRS{id}=="tegrart5640", ENV{PULSE_PROFILE_SET}="tegra-nexus7.conf"

LABEL="tegra_rt5640_end"

Here's where to put the files:

  • Udev file: /lib/udev/rules.d/
  • Profile-set file: /usr/share/pulseaudio/alsa-mixer/profile-sets/
  • Path files: /usr/share/pulseaudio/alsa-mixer/paths/

You can also look at existing files in these directories for inspiration.

After updating udev rules, you need to reboot your phone/tablet/computer for changes to take effect. After updating profile-sets or paths, you need to restart PulseAudio (easiest done by executing "pulseaudio -k", then starting a program that requires PulseAudio).

What about UCM?

UCM is another abstraction layer for mixer controls. It was meant to work well with embedded devices, such as phones and tablets, but its implementation in PulseAudio is still immature (as of Ubuntu 13.04), as it does not support, e g, mapping an ALSA mixer control to the main PulseAudio volume. There is also no way to express jack detection (or at least no way that PulseAudio supports).

Resources / Links

Touch/Core/PulseaudioProfiles (last edited 2013-04-16 09:26:57 by diwic)