Using bluealsa‐aplay with dsnoop - arkq/bluez-alsa GitHub Wiki

dsnoop: sharing a bluetooth source among multiple capture applications

Introduction

A bluealsa PCM permits only one process to capture from it at a time. For hardware sound cards ALSA provides a plugin, dsnoop, that permits multiple processes to open a single capture PCM simultaneously. Unfortunately no such plugin exists for software PCMs such as bluealsa. We must find an indirect method, introducing a virtual hardware device as a proxy for bluealsa. This is analogous to the dmix playback issue which is discussed in another wiki article here: Using bluealsa with dmix; and we can use that same ALSA Loopback virtual sound card as the proxy for capture. See that article for an introduction to the Loopback card if you are not already familiar with it.

In this article, we describe a method that uses bluealsa-aplay to perform the bluealsa capture and to feed the stream to a Loopback PCM; applications can then capture from the Loopback using dsnoop. You will need to ensure that the snd_aloop kernel module is loaded for the Loopback, again see the Using bluealsa with dmix article for some hints on how to do that.

Method

ALSA configuration

When creating an ALSA configuration for this method, the most important points to consider are:

  • the Loopback device takes its stream parameters from the first process that opens it, no matter if that process opens the capture side or the playback side. All other processes must accept those parameters. Therefore it is essential to force an agreed configuration. We achieve this by pre-defining format, channels and rate and using this within our PCM definitions. In order to avoid unnecessary format conversions it irs recommended to choose values that are used by the bluetooth device (or the majority of your devices if, for example, some use a sample rate of 48000Hz whereas others use 44100Hz).

  • bluealsa-aplay opens a fresh playback connection for each bluetooth device. Therefore we need to mix these streams together when feeding them to the Loopback. For this reason we use a dmix plugin in the Loopback interface to bluealsa-aplay.

defaults.bluealsa_aplay.a2dp {
	format "s16_le"
	rate 48000
	channels 2
	loopback 0
}

defaults.bluealsa_aplay.sco {
	format "s16_le"
	rate 8000
	channels 1
	loopback 1
}

pcm_slave.bluetooth_capture {
	@args [ PROFILE ]
	@args.PROFILE {
		type string
	}
	pcm {
		type dsnoop
		ipc_key 1026
		ipc_perm 0666
		slave {
			pcm {
				type hw
				card Loopback
				device 0
				subdevice {
					@func refer
					name {
						@func concat
						strings [
							"defaults.bluealsa_aplay."
							$PROFILE
							".loopback"
						]
					}
				}
			}
			channels {
				@func refer
				name {
					@func concat
					strings [
						"defaults.bluealsa_aplay."
						$PROFILE
						".channels"
					]
				}
			}
			rate  {
				@func refer
				name {
					@func concat
					strings [
						"defaults.bluealsa_aplay."
						$PROFILE
						".rate"
					]
				}
			}
			format {
				@func refer
				name {
					@func concat
					strings [
						"defaults.bluealsa_aplay."
						$PROFILE
						".format"
					]
				}
			}
		}
	}
}

pcm.bluetooth_a2dp_capture {
	type plug
	slave "bluetooth_capture:PROFILE=a2dp"
	hint {
		show {
			@func refer
			name defaults.namehint.basic
		}
		description "Bluetooth audio A2DP sources"
	}
}

pcm.bluetooth_sco_capture {
	type plug
	slave "bluetooth_capture:PROFILE=sco"
	hint {
		show {
			@func refer
			name defaults.namehint.basic
		}
		description "Bluetooth audio HFP/HSP sources"
	}
}

pcm.bluealsa-aplay {
	@args [ PROFILE ]
	@args.PROFILE {
		type string
		default {
			@func refer
			name defaults.bluealsa.profile
		}
	}
	type plug
	slave.pcm {
		type dmix
		ipc_key 1025
		ipc_perm 0600
		slave {
			pcm {
				type hw
				card Loopback
				device 1
				subdevice {
					@func refer
					name  {
						@func concat
						strings [
							"defaults.bluealsa_aplay."
							$PROFILE
							".loopback"
						]
					}
				}
			}
			channels {
				@func refer
				name {
					@func concat
					strings [
						"defaults.bluealsa_aplay."
						$PROFILE
						".channels"
					]
				}
			}
			rate {
				@func refer
				name  {
					@func concat
					strings [
						"defaults.bluealsa_aplay."
						$PROFILE
						".rate"
					]
				}
			}
			format {
				@func refer
				name  {
					@func concat
					strings [
						"defaults.bluealsa_aplay."
						$PROFILE
						".format"
					]
				}
			}
		}
	}
	hint.show off
}

bluealsa-aplay

An instance of bluealsa-aplay can be used to capture from multiple connected bluealsa A2DP devices and send each stream to a playback device. Similarly, a second bluealsa-aplay instance can do the same for bluealsa SCO capture devices. In this solution we want the playback device to be our virtual Loopback PCM. With the above ALSA configuration we can achieve this simply with:

bluealsa-aplay --profile-a2dp --pcm=bluealsa-aplay:PROFILE=a2dp 00:00:00:00:00:00

Similarly, for HFP/HSP devices we can use:

bluealsa-aplay --profile-sco --pcm=bluealsa-aplay:PROFILE=sco 00:00:00:00:00:00

Using the streams

The A2DP and SCO streams are available to applications as devices bluetooth_a2dp_capture and bluetooth_sco_capture respectively. Each can be captured by multiple applications simultaneously. So for example, it is possible to record using arecord with:

arecord -D bluetooth_a2dp_capture -f dat recording.wav

while at the same time playing the stream to the default playback device with:

alsaloop -C bluetooth_a2dp_capture -P default -t 100000

Notes

  1. To make this configuration available to all users, install the above config file into the alsa configuration directory.

    For alsa versions 1.1.7 and later, this file should be saved as:

    /etc/alsa/conf.d/22-bluealsa-aplay.conf

    and for alsa versions 1.1.6 and earlier:

    /usr/share/alsa/alsa.conf.d/22-bluealsa-aplay.conf

  2. If using this method in combination with the "bmix" playback method described in Using bluealsa with dmix then you must reserve the Loopback substream(s) for use by bluealsa-aplay. In the file /etc/default/bmix include the line

    BMIX_LOOPBACK=2
    

    This will ensure that substreams 0 and 1 are reserved for use by bluealsa-aplay.