Using BlueALSA with other ALSA plugins - arkq/bluez-alsa GitHub Wiki

Using BlueALSA with other ALSA Plugins

The purpose of this article is to illustrate how the BlueALSA PCM plugin type can be used with the various plugin types built-in to alsa-lib when writing ALSA configurations. Some of the ALSA plugin types are designed specifically for use with sound cards and will not work with any other I/O device type such as BlueALSA; some others are not relevant because they do not perform any useful function in combination with BlueALSA. However many of them can be used to good effect with BlueALSA devices.

It is recommended to read the BlueALSA plugins manual page and the ALSA PCM plugins documentation before proceeding with the examples here. If you are unfamiliar with the ALSA configuration syntax, it is defined here: ALSA Configuration files.

Plugins that cannot be used with BlueALSA

The following plugin types are designed specifically for use with sound cards and cannot be used with any I/O device type except hw.

  • dmix
  • dshare
  • dsnoop

Plugin types that are not relevant to BlueALSA

  • null
    The null plugin type discards all playback samples, and generates a stream of null samples for capture.
  • mmap_emul
    The mmap_emul plugin coverts the access mode from RW to MMAP.

BlueALSA supports both MMAP and RW access modes, so mmap_emul is not necessary.

Plugins that are automatically invoked by plug

The following are format conversion or channel mapping plugins, and they are invoked as needed automatically by the plug plugin, so it is rare to need to use any of them explicitly with BlueALSA.

  • adpcm
  • alaw
  • copy
  • iec958
  • linear
  • lfloat
  • mulaw
  • rate
  • route

Useful plugins

plug

The plug plugin type performs automatic conversion of audio parameters where required if the "slave" device is not capable of handling the stream parameters used by the client. It does so by inserting one or more of the above conversion plugins into the audio processing chain. The BlueALSA pre-defined PCM, bluealsa, includes plug within its definition, but if you define your own PCM of type bluealsa then the plug must be added explictly if required. For example, to define a PCM called bt-headset that automatically converts an audio stream to the correct parameters then plays it to the Bluetooth A2DP device at address 11:22:33:44:55:66, we can use either of the following equivalent forms:

pcm.bt-headset {
	type plug
	slave.pcm {
		type bluealsa
		device "11:22:33:44:55:66"
		profile "a2dp"
	}
}

or

pcm.bt-headset "bluealsa:DEV=11:22:33:44:55:66,PROFILE=a2dp"

plug will handle mono-to-stereo and stereo-to-mono conversions correctly, but by default will simply drop all channels except front-left and front-right if playing a multi-channel file (e.g. surround-sound 4.0, 5.1, 7.1 etc.) to a stereo device such as a BlueALSA A2DP PCM. To play such files correctly we need to provide additional information to the plug plugin in our PCM definition. For this we need to add a ttable section to the plug. For example, to play a surround 5.1 file, with both the left channels playing to the BlueALSA left channel, both right channels to BlueALSA right, and centre and LFE channels sent to both left and right BlueALSA channels:

pcm.bt-headset51 {
	type plug
	ttable {
		0 { 0 0.3 }
		1 { 1 0.3 }
		2 { 0 0.3 }
		3 { 1 0.3 }
		4 { 0 0.3 }
		4 { 1 0.3 }
		5 { 0 0.1 }
		5 { 1 0.1 }
	}
	slave.pcm {
		type bluealsa
		device "00:00:00:00:00:00"
		profile "a2dp"
	}
}

The decimal values in the above example tell plug to scale the front and rear channel volumes to 30%, center volume to 30% and LFE volume to 10%. So the overall volume scaling for each output channel is 100%. You can vary the values to vary the relative level of each input channel in the mix, but should aim to have a total of 1.0 (i.e. 100%) for each BlueALSA channel to avoid possible clipping of the samples. For example to give equal weight to each channel, including LFE, from a 5.1 stream, use the value 0.25 for each channel. To omit the LFE channel simply leave out the lines for channel number 5 and adjust the relative volume levels of the others accordingly (0.2 for equal weight).

See the vdownmix plugin below for an alternative approach to handling multi-channel streams.

empty

The empty plugin type merely passes all API calls through to its slave. Its main use is to permit binding of arguments, and creation of a new hint description. So for example, to create a BlueALSA PCM specifically for one particular Bluetooth device, say 00:11:22:33:44:55:66, we could use:

pcm.speaker1 {
	type empty
	slave.pcm "bluealsa:DEV=00:11:22:33:44:55:66"
	hint.description "Bluetooth Speaker Number One"
}

asym

The asym plugin type allows to use different devices for capture and playback, accessed by the same PCM id. BlueALSA can be used as capture slave, playback slave, or both. In the following example, playback streams are directed to the default BlueALSA device, and capture streams are taken from the built-in system default source (for example an on-board microphone).

pcm.asym_demo {
	type asym
	playback.pcm "bluealsa"
	capture.pcm "sysdefault"
	hint.description "Playback to Bluetooth, capture from local microphone"
}

Please note that the asym plugin requires both slaves to be available; if no BlueALSA device is connected then it is not possible to use this example PCM to capture even though sysdefault is still available.

file

The file plugin type copies the stream to a file, fifo, or command pipeline. BlueALSA can be used as slave. For example, to save a stream as a wav file while playing or capturing from the default BlueALSA device:

pcm.file_demo {
	type file
	slave.pcm "bluealsa"
	file "file-plugin-demo-%r:%c:%f.wav"
	format "wav"
	hint.description "Copy bluetooth audio stream to file"
}

The audio parameters of the audio recorded by the file plugin (i.e. rate, channels, sample format) are as passed from/to the client. The above example has the file instance called directly by the application, so the recording will have the audio parameters of the application. If we wish to record with the parameters used by the Bluetooth stream we must place the plug plugin as client of the file plugin, and remove plug from the slave of the file plugin (remember that the bluealsa pre-defined PCM has plug included in its definition). So we cannot use the pre-defined bluealsa PCM, and have to define our own slave PCM of type "bluealsa". To specify the "default" BlueALSA device we use the special address 00:00:00:00:00:00.

pcm.file_demo2 {
	type plug
	slave.pcm {
		type file
		file "file-plugin-demo-%r:%c:%f.wav"
		format "wav"
		slave.pcm {
			type bluealsa
			device "00:00:00:00:00:00"
			profile "a2dp"
		}
		hint.description "Copy Bluetooth audio stream to file (Bluetooth audio parameters)"
	}
}

multi

The multi plugin type permits channels from a single client to be split between multiple slaves. So for example it can be used to play from a single application to two Bluetooth headsets at the same time, or to a Bluetooth speaker and wired speakers at the same time.

When using a BlueALSA device in conjunction with a hardware device, the BlueALSA device must be the first slave (or "master slave") because otherwise the BlueALSA event handling code will not be called correctly, causing failure of the multi. This means that other plugins (in particular dmix) which also require to be "master slave" cannot be used with BlueALSA within a multi plugin device. Note that with many sound cards, the ALSA definition of default and sysdefault PCMs includes dmix, making them also incompatible with BlueALSA in a multi.

Because a BlueALSA PCM cannot be used by more than one process at a time, this means that the same restriction applies to any multi that includes a BlueALSA PCM. So the lack of compatibility with dmix is not as restrictive as it may at first appear.

Note that multi requires all its "slave" devices to be available, so if the BlueALSA device disconnects then the whole of the multi will fail.

An example of using the multi plugin to play audio to two Bluetooth headsets simultaneously:

pcm.bluealsa-multi {
	type plug
	slave.pcm {
		type multi
		slaves {
			a {
				pcm "bluealsa:11:22:33:44:55:66"
				channels 2
			}
			b {
				pcm "bluealsa:AA:BB:CC:DD:EE:FF"
				channels 2
			}
		}
		bindings [
			{ slave a channel 0 }
			{ slave a channel 1 }
			{ slave b channel 0 }
			{ slave b channel 1 }
		]
	}
	ttable {
		0.0 1.0
		1.1 1.0
		0.2 1.0
		1.3 1.0
	}
	hint.description "Two Bluetooth Devices Together|IOIDOutput"
}

When sending to a BlueALSA device in combination with a sound card device we need to use BlueALSA as the first "slave". There is likely to be an uncomfortable "echo" effect due to the large latency in Bluetooth audio compared to a local sound card, but we can compensate for this by applying extra delay to the sound card PCM. For this we use the ALSA upmix plugin, which is included in the alsa-plugins package by most distributions (Debian and derivatives call this package libasound2-plugins). upmix adds a delay to channels 2 and 3 (ie rear left and rear right) when upmixing from a stereo source to a 4-channel sink, so we need to re-purpose those channels of the multi for the hardware device, and use channels 0 and 1 for the BlueALSA device. So for example to apply a delay of 200ms:

# Example PCM plays to both BlueALSA device 11:22:33:44:55:66 and the
# first sound card, with a delay of 200 milliseconds to the sound card.

pcm.bt+hw {
	type plug
	slave {
		# tell "plug" to convert any mono source to stereo before passing to
		# upmix. This guarantees that upmix will apply the delay
		channels 2
		pcm {
			type upmix
			channels 4
			delay 200
			slave.pcm {
				type multi
				slaves {
					bt {
						pcm "bluealsa:11:22:33:44:55:66"
						channels 2
					}
					hw {
						# This device must not use "dmix"
						pcm "plughw:0,0"
						channels 2
					}
				}
				bindings [
					{ slave bt channel 0 }
					{ slave bt channel 1 }
					{ slave hw channel 0 }
					{ slave hw channel 1 }
				]
			}
		}
	}
	hint.description "Bluetooth and Wired Speakers Together|IOIDOutput"
}

softvol

The softvol plugin type creates a user volume control for its "slave" PCM; the control is associated with a sound card and appears in the mixer for that sound card. BlueALSA creates its own mixer device with controls for all BlueALSA PCMs, so it is not necessary to use the ALSA softvol in this way. However, there may be some circumstances in which it is desirable to have a BlueALSA control appear in the same mixer as the sound card controls, in which case these examples may be helpful. Note that once created, the control will persist, it will not be removed when the BlueALSA device disconnects.

The ALSA softvol control is distinct from, and not connected to, the BlueALSA volume controls. The volume scaling is applied by alsa-lib within the application before the stream is sent to BlueALSA. Any volume scaling then applied by BlueALSA will be in addition to the scaling already applied by ALSA softvol.

To be useful with BlueALSA the control name must not already exist on the card, otherwise it will control the card and not the BlueALSA PCM. This example creates a volume scale control called "BlueALSA" that controls the default BlueALSA device. The control appears in the mixer for card 0.

Warning user controls are created in kernel memory and are not removed from memory if the softvol definition is removed from the ALSA configuration. Depending on your distribution they will most likely persist even across reboots. If you are using an ALSA release that is older than 1.2.5 you should consult your distribution's documentation regarding removal of softvol controls before trying these examples.

pcm.bluealsa-vol {
	type softvol
	slave.pcm "bluealsa"
	control {
		name "BlueALSA Playback Volume"
		card 0
	}
	hint.description "Bluetooth Playback with Softvol|IOIDOutput"
}

To create a "simple" control that has both volume scale and mute switch it is necessary to add them individually to the PCM, one chained as the slave of the other. For example:

pcm.bluealsa-vol {
	type softvol
	resolution 2
	control {
		name "BlueALSA Playback Switch"
		card 0
	}
	slave.pcm {
		type softvol
		slave.pcm "bluealsa"
		control {
			name "BlueALSA Playback Volume"
			card 0
		}
	}
	hint.description "Bluetooth Playback with Softvol|IOIDOutput"
}

To remove user controls, it is not sufficient to delete the definitions from the ALSA configuration as they will still persist in kernel memory.

With alsa-utils release 1.2.5 and later you can use the ALSA alsactl utility:

alsactl clean 0 "name='BlueALSA Playback Volume'" "name='BlueALSA Playback Switch'"

For earlier releases you should consult your distribution's documentation.

Many ALSA card configurations include a user control called "PCM" in their definition of the default device (for example cards using the Intel HDA codec; some other cards, notably USB cards, do not have this control defined). This control can also be used for bluetooth devices, so that the same volume control is used for both default and BlueALSA devices.

As a simple example, the following creates a PCM called a2dp which sends audio to the most recently connected bluetooth A2DP playback device and has its volume scaled by a control called PCM on card 0. This same control also scales the volume of the default device on card 0 with many ALSA card configurations:

pcm.a2dp {
	type softvol
	slave.pcm "bluealsa:PROFILE=a2dp"
	control.name "PCM Playback Volume"
	control.card 0
	hint.description "Bluetooth A2DP with PCM volume control" 
}

CTL Plugin Types

alsa-lib also includes 2 CTL plugin types that may be used with BlueALSA.

empty

The empty CTL plugin type just redirects the control device to another plugin. As with the "empty" PCM plugin type, its main uses are for binding arguments and adding hint description. Example:

ctl.speaker1 {
	type empty
	child {
		type bluealsa
		device "00:11:22:33:44:55:66"
	}
	hint.description "Bluetooth Speaker Number One"
}

remap

This plugin can create new names for control elements.

So for example to create a mixer for the default BlueALSA PCM in which the "A2DP" playback control is called "Master", we could use:

ctl.bluealsa-default {
	type remap
	child {
		type bluealsa
		device "00:00:00:00:00:00"
	}
	remap {
		'name="A2DP Playback Volume"' 'name="Master Playback Volume"'
		'name="A2DP Playback Switch"' 'name="Master Playback Switch"'
	}
}

Note that the names of the low-level ("primitive") controls must be used, so that to rename the "A2DP" control as "Master" we must rename both the volume component and the switch (mute) component.

External Plugin Types

In addition to the plugin types included within alsa-lib, it is possible to use BlueALSA with other plugin types defined by other libraries. The ALSA project also maintains a library called "alsa-plugins" which contains some filter plugins that may be useful with BlueALSA (Debian and derivatives call this package libasound2-plugins), and there is also a number of third-party plugins available.

The alsa-plugins library contains an assortment of plugin types including I/O plugins, filter plugins, and conversion plugins. Of particular interest are the two mix plugins upmix and vdownmix. Upmix is for use with multi-channel devices, (i.e. devices with more than 2 channels) so is not directly relevant to BlueALSA, but can be useful in combination with the multi plugin. See multi above for details.

vdownmix

The vdownmix plugin converts a multi-channel stream (4, 5 or 6 channels) to stereo, applying a signal processing algorithm to generate a virtual surround-sound experience. Therefore this is an alternative to using the ttable approach described in the plug section above, and may give a more satisfactory result. This plugin accepts only S16 format samples, so it is generally necessary to use the plug plugin. For example, to use the default BlueALSA device:

pcm.!bluealsa_surround40 {
	type plug
	slave {
		channels 4
		pcm {
			type vdownmix
			slave.pcm bluealsa
		}
	}
}

pcm.!bluealsa_surround51 {
	type plug
	slave {
		channels 6
		pcm {
			type vdownmix
			slave.pcm bluealsa
		}
	}
}

alsaequal

The alsaequal PCM plugin type raedwulf/alsaequal passes the PCM stream through a graphic equalizer before passing the result on to BlueALSA. The equalizer is controlled by a CTL plugin type which can be used with applications such as alsamixer etc. This plugin is packaged by many distributions, for example Debian and derivatives include it as package libasound2-plugins-equal.

By default all devices share the same equalizer settings, so to have a different setting for each device it is necessary to define a CTL device for each PCM device. Each CTL is given a unique controls file name, and the corresponding PCM must use that same controls value. For example:

ctl.bt-headset-equal {
	type equal
	controls ".alsaequal-bt-headset.bin"
}

pcm.bt-headset-equal {
	type plug
	slave.pcm {
		type equal
		slave.pcm {
			type plug
			slave.pcm {
				type bluealsa
				device "11:22:33:44:55:66"
				profile "a2dp"
			}
		}
		controls ".alsaequal-bt-headset.bin"
	}
}

alsaequal requires the sample format to be floating-point, so it is usually necessary to use a plug instance on the client side and also on the slave (Bluetooth audio uses signed 16-bit integer samples), as in the above example. This can lead to a noticeable loss of fidelity.