Auto switch audio sinks on a wireless headset power-on (with USB dongle)

Hi. I have this fantastic (because very cheap) wireless headset:

❯ lsusb | grep YZ
Bus 005 Device 011: ID 7545:0897 YZ Technology Corporation 2.4G Wireless headset

Unfortunately, it comes with a USB dongle, which means it’s always connected. Dongle itself has blue and red LEDs that indicate “idle”, “connected”, and for a short period after disconnecting “searching”. However neither built-in pipewire-pulse auto switching works, nor the auto switching module (I confirmed it loaded correctly, and it worked with devices that I plugged/unplugged).

I could just write a little daemon that checks a state of, well, something, every few seconds and switches sinks “manually”. The issues with that is I couldn’t find anything that changes.

I tried comparing outputs of:

  • cat /sys/bus/usb/devices/5-2.2/* (and */* and */*/*)
  • pactl list cards
  • pactl list sinks
  • lspci
  • lsusb
  • dmesg -w
  • journalctl -ef

…and there’s zero difference between the outputs before and after powering the headset on or off (checked with diff).

I’m looking for suggestions on where to look for that device state, or alternatively, how to detect activity on a specific microphone (because it’s very hard to find information about it when every search result revolves around the mic not getting detected at all).

I think the issue here is that these 2.4GHz “dongles” are nothing more than transceivers with some sort of simple keying system for the device they came with; in other words, if it’s plugged in and detected the OS doesn’t know if the “paired device” is working or not — unlike with Bluetooth, where things are configured to detect whether a “paired” device is actually connected.

1 Like

I absolutely agree. Maybe someone will come up with some way of determining when the dongle consumes more power or something like that (for the LEDs or because it’s actually receiving the signal)… and for now I guess I decided to focus on detecting the headset’s microphone levels.

I failed, though, because I assumed I’d be able to detect activity with something like:

arecord -D hw:3,0 -f S16_LE -t raw -d 1 -r 44100  -N

but it returns and error:

arecord: main:850: audio open error: Device or resource busy

…which makes sense because audio devices are managed by Pipewire / Wireplumber. So I guess I have to use some API…? The way e.g. pavucontrol does when showing sound levels on different input and output devices. I’m going to check its source, but that’s a goal for tomorrow, it’s 4:00am.

It’s worth noting that when i had both machines running PulseAudio, my Doqaus Care 1 headset, which is supposed to disconnect after a period of silence, wasn’t doing so on this machine but was on the other.

Since both machines now use Pipewire, the headset disconnects from the other machine, but only sometimes from this one.

I wonder if it’s just the “signal noise floor” level being too high and keeping it active.

Note: Mine is a BT headset (no dongle) but I do have an issue with the WiFi/BT card in this machine. This could well be adding “noise”.

Edited to add:
There could be a source of interference from a nearby device which keeps the “audio profile” active, as it’s receiving “something” albeit just noise.

I also guess the profile switches if you pull the dongle?

My solution below, hopefully someone finds it useful. Works fine and doesn’t seem to have any impact on sound - neither while recording in OBS, nor while watching videos with mpv - or on performance.

#!/bin/sh

buffer=/dev/shm/switch-to-headset.wav      # in-memory file for mic audio
headset_source=alsa_input.usb-YZ_Technology_Corporation_2.4G_Wireless_headset-00.mono-fallback
headset_sink=alsa_output.usb-YZ_Technology_Corporation_2.4G_Wireless_headset-00.analog-stereo
default_source=$(pactl get-default-source) # if default devices change on reboot, just
default_sink=$(pactl get-default-sink)     # replace these with literal device names

get_headset_mic_level() {
    ffmpeg -y -loglevel panic -f pulse -i $headset_source -t 0.2 $buffer    # record 200ms of mic into buffer
    sox -t .wav $buffer -n stat 2>&1 | grep 'Maximum amplitude' | cut -c24- # analyse buffer and cut amplitude
}

current_source=$default_source

while true; do
    mic_level=$(get_headset_mic_level)

    if [[ "$mic_level" == "0.000000" && "$current_source" != $default_source ]]; then
        pactl set-default-sink $default_sink
        pactl set-default-source $default_source
        current_source=$default_source
    elif [[ "$mic_level" != "0.000000" && "$current_source" != $headset_source ]]; then
        pactl set-default-sink $headset_sink
        pactl set-default-source $headset_source
        current_source=$headset_source
    fi

    # echo $mic_level $current_source # uncomment to see current amplitude and source

    sleep 5    # don't need to repeat this often, probably even 10 seconds would be OK
done
1 Like

If ALSA device is busy it is probably connected to Pipewire. To confirm:

sudo fuser -av /dev/snd/pcmC*c

To record direct from an ALSA source, stop pipewire audio services:

systemctl --user stop pipewire-pulse.socket pipewire-pulse.service wireplumber.service

To record audio from default ALSA input (pulseaudio or pipewire-pulse)

arecord -D default

I know all of this, but it defeats the purpose. I even said so in the same post that mentioned the error. :man_shrugging:

Other forum users may not know this additional information

2 Likes

This topic was automatically closed 3 days after the last reply. New replies are no longer allowed.