1

I wish to write a bash script that will create a PulseAudio loopback device between an audio source and a bluetooth device.

(Context - skippable)

The audio source is created with:

load-module module-jackdbus-detect channels=2 sink_name=jack_bt_eq_out sink_client_name="PulseAudio - Bluetooth Sink" source_name=jack_bt_in source_client_name="PulseAudio - Bluetooth Source" connect=false

# (legacy - pre-15.0)
#load-module module-jack-sink sink_name=jack_bt_eq_out client_name="PulseAudio - Bluetooth Sink" channels=2 connect=false
#load-module module-jack-source source_name=jack_bt_in client_name="PulseAudio - Bluetooth Source" channels=2 connect=false

As can be seen from above, an audio sink is created as well. The purpose is to have it pass through an equalizer in Jack, which outputs to the audio source, which in turn I redirect to the actual bluetooth hardware for playback.

screenshot from Carla

So far I have (the relevant part of) my script as:

echo "Device must be connected...";
echo "Choose a device:";
## WIP
# (present device list - 'bluez.alias')
# (wait for input)
# (assign 'sink_name'- without enclosing <>)

pacmd load-module module-loopback source=jack_bt_in sink=[TODO:sink_name] rate=48000 sink_dont_move=true latency_msec=240 max_latency_msec=250;

What I wish to do is to present the user a list of bluetooth audio devices available to PulseAudio to the user, in this form:

  1. AWEI T1L
  2. JBL GO 2
  3. (...)

The list of such devices, as well as their PulseAudio sink names, can be obtained with:

$ pacmd list-sinks | grep "bluez"
    name: <bluez_sink.00_00_00_00_0D_91.a2dp_sink>
    driver: <module-bluez5-device.c>
    card: 8 <bluez_card.00_00_00_00_0D_91>
        device.api = "bluez"
        bluez.path = "/org/bluez/hci0/dev_00_00_00_00_0D_91"
        bluez.class = "0x240404"
        bluez.alias = "AWEI T1L"
    name: <bluez_sink.F8_5C_7D_15_1D_47.a2dp_sink>
    driver: <module-bluez5-device.c>
    card: 9 <bluez_card.F8_5C_7D_15_1D_47>
        device.api = "bluez"
        bluez.path = "/org/bluez/hci0/dev_F8_5C_7D_15_1D_47"
        bluez.class = "0x200414"
        bluez.alias = "JBL GO 2"

Question is: How do I parse the output of the above command, in order to get the values of bluez.alias (for display) and the sink name, stripping the enclosing < and > chars, in order to properly load 'module-loopback'?

So far I can get the list of devices with pacmd list-sinks | grep "bluez.alias" | cut -d \" -f 2, but am not sure of how to present them in an ordered list; and would need a second command to obtain the sink name anyway. This is fragile and not ideal, especially since a device may be disconnected between queries; as such I'd like to get both the device alias and its sink name with a single command, storing it into an array.

1 Answer 1

1

Here's how to do it with bash's built-in regular expressions. You'll just need to replace cat sinks.txt with pacmd list-sinks, and add the pacmd load-module ... command inside the select statement.

#!/bin/bash
set -eu

aliases=()
declare -A mapping

while read -r line; do
    if [[ $line =~ ^name:' <'([^>]*)'>' ]]; then
        name=${BASH_REMATCH[1]}
    elif [[ $line =~ ^bluez.alias' = "'([^\"]*)'"' ]]; then
        aliases+=("${BASH_REMATCH[1]}")
        mapping[${BASH_REMATCH[1]}]=$name
    fi
done < <(cat sinks.txt)

PS3="Select device: "
select alias in "${aliases[@]}"; do
    echo "Connecting sink ${mapping[$alias]}..."
    break
done

The aliases array is not required if device aliases can be presented in random order, but I assumed order is important.

For custom menu formatting, replace the select statement with something like:

while :; do
    for ((i = 0; i < ${#aliases[@]}; ++i)); do
        printf "%s. %s\n" "$((i+1))" "${aliases[i]}"
    done
    read -p 'Select device: '
    [[ $REPLY != *[^0-9]* && $REPLY -gt 0 && $REPLY -le ${#aliases[@]} ]] && break
    echo "Invalid choice."
done
echo "Connecting sink ${mapping[${aliases[REPLY-1]}]}..."

I also added some input validation, so that the menu only accepts valid numbers.

5
  • Thanks, works great. Learned something new with this. Btw, can you enlighten me on how to get an output like 1. [Name] instead of 1) [Name]?
    – Marc.2377
    Commented Apr 9, 2023 at 20:32
  • I don't think you can, unfortunately. At least with the select statement. You'll have to printf the list yourself and then use read to read user input.
    – user469457
    Commented Apr 9, 2023 at 20:39
  • hmm - is that something easy/short/simple enough that you can show me how it's done?
    – Marc.2377
    Commented Apr 10, 2023 at 6:37
  • I've added an example to my answer.
    – user469457
    Commented Apr 10, 2023 at 7:04
  • use pactl command instead of pacmd and sink name is not shown with additional characters e.g. pactl list sinks | grep -E 'Sink|Name|Description
    – nik gnomic
    Commented May 3, 2023 at 10:33

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .