Score:1

Storing pactl name to variable by using alsa.name

cn flag
PV3

I'm having an issue where my RPI using ubuntu 20.04 sink cards change card # between boots. I was thinking to remedy this, I can create a bash script to get each card Name and it’s associated also.name, then store the name that uses alsa.name “bcm2835 Headphones” into a variable which I can use to set the default using systemd. I'm not able to get the alsa.name and cant figure out what's throwing it off.

My pactl list sink output is:

Sink #0
        State: SUSPENDED
        Name: alsa_output.platform-bcm2835_audio.stereo-fallback
        Description: Built-in Audio Stereo
        Driver: module-alsa-card.c
        Sample Specification: s16le 2ch 44100Hz
        Channel Map: front-left,front-right
        Owner Module: 7
        Mute: no
        Volume: front-left: 65536 / 100% / 0.00 dB,   front-right: 65536 / 100% / 0.00 dB
                balance 0.00
        Base Volume: 65536 / 100% / 0.00 dB
        Monitor Source: alsa_output.platform-bcm2835_audio.stereo-fallback.monitor
        Latency: 0 usec, configured 0 usec
        Flags: HARDWARE DECIBEL_VOLUME LATENCY 
        Properties:
                alsa.resolution_bits = "16"
                device.api = "alsa"
                device.class = "sound"
                alsa.class = "generic"
                alsa.subclass = "generic-mix"
                alsa.name = "bcm2835 HDMI 1"
                alsa.id = "bcm2835 HDMI 1"
                alsa.subdevice = "0"
                alsa.subdevice_name = "subdevice #0"
                alsa.device = "0"
                alsa.card = "0"
                alsa.card_name = "bcm2835 HDMI 1"
                alsa.long_card_name = "bcm2835 HDMI 1"
                alsa.driver_name = "snd_bcm2835"
                device.bus_path = "platform-bcm2835_audio"
                sysfs.path = "/devices/platform/soc/fe00b840.mailbox/bcm2835_audio/sound/card0"
                device.form_factor = "internal"
                device.string = "hw:0"
                device.buffering.buffer_size = "131072"
                device.buffering.fragment_size = "131072"
                device.access_mode = "mmap+timer"
                device.profile.name = "stereo-fallback"
                device.profile.description = "Stereo"
                device.description = "Built-in Audio Stereo"
                module-udev-detect.discovered = "1"
                device.icon_name = "audio-card"
        Ports:
                analog-output: Analog Output (priority: 9900)
        Active Port: analog-output
        Formats:
                pcm

Sink #1
        State: RUNNING
        Name: alsa_output.platform-bcm2835_audio.stereo-fallback.2
        Description: Built-in Audio Stereo
        Driver: module-alsa-card.c
        Sample Specification: s16le 2ch 44100Hz
        Channel Map: front-left,front-right
        Owner Module: 8
        Mute: no
        Volume: front-left: 32770 /  50% / -18.06 dB,   front-right: 32770 /  50% / -18.06 dB
                balance 0.00
        Base Volume: 56210 /  86% / -4.00 dB
        Monitor Source: alsa_output.platform-bcm2835_audio.stereo-fallback.2.monitor
        Latency: 28157 usec, configured 36000 usec
        Flags: HARDWARE HW_MUTE_CTRL HW_VOLUME_CTRL DECIBEL_VOLUME LATENCY 
        Properties:
                alsa.resolution_bits = "16"
                device.api = "alsa"
                device.class = "sound"
                alsa.class = "generic"
                alsa.subclass = "generic-mix"
                alsa.name = "bcm2835 Headphones"
                alsa.id = "bcm2835 Headphones"
                alsa.subdevice = "0"
                alsa.subdevice_name = "subdevice #0"
                alsa.device = "0"
                alsa.card = "1"
                alsa.card_name = "bcm2835 Headphones"
                alsa.long_card_name = "bcm2835 Headphones"
                alsa.driver_name = "snd_bcm2835"
                device.bus_path = "platform-bcm2835_audio"
                sysfs.path = "/devices/platform/soc/fe00b840.mailbox/bcm2835_audio/sound/card1"
                device.form_factor = "internal"
                device.string = "hw:1"
                device.buffering.buffer_size = "131072"
                device.buffering.fragment_size = "131072"
                device.access_mode = "mmap+timer"
                device.profile.name = "stereo-fallback"
                device.profile.description = "Stereo"
                device.description = "Built-in Audio Stereo"
                module-udev-detect.discovered = "1"
                device.icon_name = "audio-card"
        Ports:
                analog-output-headphones: Headphones (priority: 9900)
        Active Port: analog-output-headphones
        Formats:
                pcm

My current script is

#!/bin/bash

# Get the output of pactl list sinks
output=$(pactl list sinks)

# Split the output into an array of lines
lines=($output)

# Initialize variables
card_num=0
name=""
also_name=""

# Iterate through the lines of the output
for line in "${lines[@]}"; do
  # Check if the line starts with "Sink #" (indicating a new sink)
  if [[ $line =~ ^Sink\ # ]]; then
    # If we have previously found a sink, print the information for it
    if [[ $card_num -ne 0 ]]; then
      echo "Card #: $card_num"
      echo "Name: $name"
      echo "also.name: $also_name"
      echo ""
    fi
    # Reset the variables for the new sink
    card_num=0
    name=""
    also_name=""
  fi
  # Extract the card number from the "Card:" line
  if [[ $line =~ ^Card: ]]; then
    card_num=$(echo $line | cut -d ' ' -f 2)
  fi
  # Extract the name from the "Name:" line
  if [[ $line =~ ^Name: ]]; then
    name=$(echo $line | cut -d ' ' -f 2-)
  fi
  # Extract the also.name from the "also.name:" line
  if [[ $line =~ ^also.name: ]]; then
    also_name=$(echo $line | cut -d ' ' -f 2-)
  fi
done

# Print the information for the last sink
echo "Card #: $card_num"
echo "Name: $name"
echo "also.name: $also_name"

Thanks M. Becerra, that's embarrassing.

I made some progress today. Made one service that get's and sets the default sink and outputs it to a txt file.

sink=$(pactl list sinks)

#parse for the value of "Name:" to get sink names and store each seperated by a comma   

sink_names=$(echo "$sink" | grep -oP '(?<=Name: ).*' | tr '\n' ',' | sed 's/,$//')

#parse for the value of "alsa.name =" to get alsa names and store each alsa name seperated by a comma

alsa_names=$(echo "$sink" | grep -oP '(?<=alsa.name = ").*(?=")' | tr '\n' ',' | sed 's/,$//')

#Intialize sink1 and add the first sink name and first alsa name to it seperated by a comma

sink1=$(echo "$sink_names" | cut -d ',' -f 1),$(echo "$alsa_names" | cut -d ',' -f 1)

#Intialize sink2 and add the second sink name and second alsa name to it seperated by a comma

sink2=$(echo "$sink_names" | cut -d ',' -f 2),$(echo "$alsa_names" | cut -d ',' -f 2)

#Check if the second valye in sink1 or sink2 matches "bcm2835 Headphones", if yes, then store the value of the sink name into a new variable, defualt_sink

if [[ $(echo "$sink1" | cut -d ',' -f 2) == "bcm2835 Headphones" ]]; then

    default_sink=$(echo "$sink1" | cut -d ',' -f 1)

elif [[ $(echo "$sink2" | cut -d ',' -f 2) == "bcm2835 Headphones" ]]; then

    default_sink=$(echo "$sink2" | cut -d ',' -f 1)

fi

echo $default_sink  > default_sink.txt

#set the default sink to the value of default_sink using pactl set-default-sink

pactl set-default-sink "$default_sink"

#unmute the default sink

pactl set-sink-mute "$default_sink" 0

#set the volume of the default sink to 40%

pactl set-sink-volume "$default_sink" 40%

To add a little more color, I'm using a PIR sensor to turn off the monitor and mute volume during inactivity.

import os
import time
from datetime import datetime
from gpiozero import MotionSensor
import logging
import subprocess

# Read the default sink from the default_sink.txt file
with open('default_sink.txt', 'r') as f:
    default_sink = f.read().strip()

os.environ['DISPLAY'] = ':0'
os.environ['PATH'] += ':/usr/bin'

# Create a MotionSensor object on pin 17 (or whatever pin the PIR sensor is connected to)
pir = MotionSensor(4)

# Initialize the monitor state to "off" and audio state to "unmuted"
monitor_on = False
audio_unmuted = False

# Create a timer variable
timer = 0

# Set up logging
logging.basicConfig(filename='motion.log', level=logging.INFO)

while True:**strong text**
    # If the PIR sensor is triggered
    if pir.motion_detected:
        # If the monitor is off, turn it on and unmute the audio
        if not monitor_on:
            os.system("xset dpms force on")
            # Unmute the audio using pactl
            result = subprocess.run(["/usr/bin/pactl", "set-sink-mute", default_sink, "0"])
            # Log the default sink and the exit code of the command
            logging.info("{} - Default sink: {}, pactl set-sink-mute exit code: {}".format(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), default_sink, result))
            monitor_on = True
            audio_unmuted = True
            # Restart the timer
            timer = time.time()
    # If the PIR sensor has been inactive for 1 minute
    elif time.time() - timer > 60:
        # If the monitor is on, turn it off and mute the audio
        if monitor_on:
            os.system("xset dpms force off")
            # Mute the audio using pactl
            result = subprocess.run(["/usr/bin/pactl", "set-sink-mute", default_sink, "1"])
            # Log the default sink and the exit code of the command
            logging.info("{} - Default sink: {}, pactl set-sink-mute exit code: {}".format(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), default_sink, result))
            monitor_on = False
            audio_unmuted = False

    # Sleep for a short period to prevent excessive CPU usage
    time.sleep(0.1)

Now i'm faced with an issue of pactl commands not working within the script

INFO:root:2023-01-10 16:05:01 - Default sink: alsa_output.platform-bcm2835_audio.stereo-fallback.2, pactl set-sink-mute exit code: CompletedProcess(args=['/usr/bin/pactl', 'set-sink-mute', 'alsa_output.platform-bcm2835_audio.stereo-fallback.2', '1'], returncode=1)

I have an idea which is that it's being ran as root and not the user. The systemd service has a user and group property set, not sure why pactl would be running root if it indeed is.

M. Becerra avatar
gg flag
Hi, a few things from a quick glance. Alsa is mistyped as `also` in several places. In the line `if [[ $line =~ ^Sink\ # ]]; then`, everything after the # is commented out, so it's not matching as you want it to. Other than that, it's great you've already given it a try and shared it, additionally, I'd recommend posting the expected output as well, to make it easier to understand what you want to achieve.
I sit in a Tesla and translated this thread with Ai:

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.