Skip to content

Instantly share code, notes, and snippets.

@OndraZizka
Last active March 25, 2024 12:58
Show Gist options
  • Star 55 You must be signed in to star a gist
  • Fork 9 You must be signed in to fork a gist
  • Save OndraZizka/2724d353f695dacd73a50883dfdf0fc6 to your computer and use it in GitHub Desktop.
Save OndraZizka/2724d353f695dacd73a50883dfdf0fc6 to your computer and use it in GitHub Desktop.
Bluetooth headset - switch between quality sound + no mic (A2DP) and crappy sound and mic (HSP/HFP)
#!/bin/bash
#### Restart Bluetooth
if [ "$1" == "resetBT" ] ; then
sudo rfkill block bluetooth && sleep 0.1 && sudo rfkill unblock bluetooth;
exit;
fi;
#### Toggle listen/speak
if [ "$1" == "" -o "$1" == "toggle" ] ; then
LINE=`pacmd list-sinks | grep '\(name:\|alias\)' | grep -B1 F5A | head -1`
if [ "$LINE" == "" ] ; then echo "F5A headset not found."; $0 reset; sleep 2; exit; fi
SINK_NAME="bluez_sink.00_19_5D_25_6F_6C.a2dp_sink"
if $(echo "$LINE" | grep $SINK_NAME &> /dev/null) ; then
echo "Detected quality sound output, that means we can't speak; switch that."
$0 speak;
else
echo "Quality sound not found, switch to the good sound."
$0 listen;
fi
fi
#### Change the output to F5A
if [ "$1" == "listen" ] ; then
LINE=`pacmd list-sinks | grep '\(name:\|alias\)' | grep -B1 F5A | head -1`
if [ "$LINE" == "" ] ; then echo "F5A phones not found."; $0 reset; sleep 2; exit; fi
# name: <bluez_sink.00_19_5D_25_6F_6C.headset_head_unit>
## Get what's between <...>
SINK_NAME=`echo "$LINE" | tr '>' '<' | cut -d'<' -f2`;
## The above gives an ID according to the active profile.
## To set manually:
#SINK_NAME="bluez_sink.00_19_5D_25_6F_6C.headset_head_unit"
#SINK_NAME="bluez_sink.00_19_5D_25_6F_6C.a2dp_sink"
## Switch the output to that.
echo "Switching audio output to $SINK_NAME";
pacmd set-default-sink "$SINK_NAME"
#### Change profile to quality output + no mic. From `pacmd list-cards`:
CARD="bluez_card.00_19_5D_25_6F_6C"
PROFILE="a2dp_sink"
echo "Switching audio profile to $PROFILE";
pacmd set-card-profile $CARD $PROFILE
exit;
fi;
#### Input
if [ "$1" == "speak" ] ; then
## Change profile to crappy output + mic. From `pacmd list-cards`:
CARD="bluez_card.00_19_5D_25_6F_6C"
pacmd set-card-profile $CARD headset_head_unit
LINE=`pacmd list-sources | grep '\(name:\|alias\)' | grep -B1 F5A | head -1`
if [ "$LINE" == "" ] ; then echo "F5A mic not found."; $0 reset; sleep 2; exit; fi
SOURCE_NAME=`echo "$LINE" | tr '>' '<' | cut -d'<' -f2`;
#SOURCE_NAME="bluez_source.00_19_5D_25_6F_6C.headset_head_unit"
#SOURCE_NAME="bluez_sink.00_19_5D_25_6F_6C.a2dp_sink.monitor"
echo "Switching audio input to $SOURCE_NAME";
pacmd set-default-source "$SOURCE_NAME" || echo 'Try `pacmd list-sources`.';
fi;
#### Resources:
## Why this is needed
# https://jimshaver.net/2015/03/31/going-a2dp-only-on-linux/
## My original question
# https://askubuntu.com/questions/1004712/audio-profile-changes-automatically-to-hsp-bad-quality-when-i-change-input-to/1009156#1009156
## Script to monitor plugged earphones and switch when unplugged (Ubuntu does that, but nice script):
# https://github.com/freundTech/linux-helper-scripts/blob/master/padevswitch/padevswitch
@artem328
Copy link

Thanks for script, i've modified it a little bit to have more universal usage.

#!/bin/bash

# run `pacmd list-sinks  | grep '\(name:\|alias\)'` and fill with value from alias of your bluetooth device
BLUETOOTH_DEVICE="YOUR DEVICE NAME"
PROFILE_A2DP="a2dp_sink"
PROFILE_HEADSET_UNIT="headset_head_unit"

####  Restart Bluetooth
if [ "${1}" == "reset" ] ; then
  sudo rfkill block bluetooth && sleep 0.1 && sudo rfkill unblock bluetooth;
  exit;
fi;

sink_name() {
    pacmd list-sinks  | grep '\(name:\|alias\)' | grep -B1 "${1}"  | head -1 | sed -rn 's/\s*name: <(.*?)>/\1/p'
}

card_name() {
    pacmd list-cards | grep 'name:' | grep "bluez" | sed -rn 's/\s*name: <(.*?)>/\1/p'
}

#### Toggle listen/speak
if [ "${1}" == "" -o "${1}" == "toggle" ] ; then
  SINK_NAME=$(sink_name "${BLUETOOTH_DEVICE}")
 
  if [ "${SINK_NAME}" == "" ] ; then echo "${BLUETOOTH_DEVICE} headset not found."; ${0} reset; sleep 2; exit; fi

  if $(echo "${SINK_NAME}" | grep "${PROFILE_A2DP}" &> /dev/null) ; then
    echo "Detected quality sound output, that means we can't speak; switch that."
    ${0} speak;
  else
    echo "Quality sound not found, switch to the good sound."
    ${0} listen;
  fi
fi

#### Change the output to F5A
if [ "${1}" == "listen" ] ; then
  SINK_NAME=$(sink_name "${BLUETOOTH_DEVICE}")
  if [ "${SINK_NAME}" == "" ] ; then echo "${BLUETOOTH_DEVICE} headset not found."; ${0} reset; sleep 2; exit; fi
  
  ## Switch the output to that.
  echo "Switching audio output to ${SINK_NAME}";
  pacmd set-default-sink "${SINK_NAME}"

  #### Change profile to quality output + no mic.:
  CARD=$(card_name)
  echo "Switching audio profile to ${PROFILE_A2DP}";
  pacmd set-card-profile "${CARD}" "${PROFILE_A2DP}"
  exit;
fi;

#### Input
if [ "${1}" == "speak" ] ; then
  ## Change profile to crappy output + mic.:
  CARD=$(card_name)
  pacmd set-card-profile "${CARD}" "${PROFILE_HEADSET_UNIT}"

  SOURCE_NAME=$(sink_name "${BLUETOOTH_DEVICE}")
  if [ "${SOURCE_NAME}" == "" ] ; then echo "${BLUETOOTH_DEVICE} mic not found."; ${0} reset; sleep 2; exit; fi
  echo "Switching audio input to ${SOURCE_NAME}";
  pacmd set-default-source "${SOURCE_NAME}.monitor" || echo 'Try `pacmd list-sources`.';
fi;


####  Resources:

##  Why this is needed
# https://jimshaver.net/2015/03/31/going-a2dp-only-on-linux/

##  My original question
# https://askubuntu.com/questions/1004712/audio-profile-changes-automatically-to-hsp-bad-quality-when-i-change-input-to/1009156#1009156

##  Script to monitor plugged earphones and switch when unplugged (Ubuntu does that, but nice script):
# https://github.com/freundTech/linux-helper-scripts/blob/master/padevswitch/padevswitch

@emmanuelmathot
Copy link

Thats great! It Works perfectly with My PLT Focus! Thx!

@hesparza
Copy link

hesparza commented Feb 9, 2020

Thank you very much for this effort. I wonder how can it work on Windows? when I use my bluetooth headphones there I can talk and listen with good quality.

@david-alejandro-reyes-milian

really great, thanks. Bose headsets were giving headache with this. Regards!

@Vafa-Andalibi
Copy link

worked like a charm

@jim9835
Copy link

jim9835 commented May 14, 2020

this looks like exactly what I need for my Mpow M5 headset. It works a treat in Ubuntu, but am now trying to get it to work in Manjaro KDE.

However, I'm getting "line 30: command not found"

Am a 'cut and paster' with code, so very limited understanding. My guess is that I've misunderstood the blanks I need to fill for my device.

My only initial change to the code was BLUETOOTH_DEVICE="Mpow M5"

Do I need to make other changes?

@Dentxinho
Copy link

Thanks for script, i've modified it a little bit to have more universal usage.

Awesome, thank you.

@Dentxinho
Copy link

My only initial change to the code was BLUETOOTH_DEVICE="Mpow M5"

Do I need to make other changes?

I don't think so, that's it.

@SkepticDude
Copy link

Hey I am new here. Can anyone please tell me what to do with the script? How to use it?

@weslleyspereira
Copy link

weslleyspereira commented Aug 4, 2020

Thank you for this script. It works nicely as expected! I suggest to use it together with a toggle button, e.g.,

#!/usr/bin/env python

''' switchHeadphones.py
Tkinter toggle button to switch microphone On/Off using a script 

Modification of the solution proposed in
    https://www.daniweb.com/posts/jump/1909448
for the Mic On/Off script from
    https://gist.github.com/OndraZizka/2724d353f695dacd73a50883dfdf0fc6
'''

# Define the path for the script below, e.g.,
script = "./switchHeadphones.sh"

try:
    # Python2
    import Tkinter as tk
except ImportError:
    # Python3
    import tkinter as tk
import os

__author__ = "Weslley S Pereira"
__email__ = "weslley.spereira@gmail.com"

def toggle(tog=[0]):
    '''
    a list default argument has a fixed address
    '''
    tog[0] = not tog[0]
    if tog[0]:
        os.system(script+' speak')
        t_btn.config(text='Switch Mic Off')
    else:
        os.system(script+' listen')
        t_btn.config(text='Switch Mic On')

root = tk.Tk()
root.title('Headphone  ')

t_btn = tk.Button(text='Switch Mic On', width=15, command=toggle)
t_btn.pack(pady=5)

root.mainloop()

@vvillschneider
Copy link

Thanks!

@Paiusco
Copy link

Paiusco commented Aug 20, 2020

I think this should become a repo by it's own. There're nice improvements to be done to be more generic and w/ toggle functionality @OndraZizka what do you think?

@waawawewa
Copy link

waawawewa commented Sep 28, 2020

Thank you for this script. It works nicely as expected! I suggest to use it together with a toggle button, e.g.,

#! /usr/bin/python

''' switchHeadphones.py
Tkinter toggle button to switch microphone On/Off using a script 

Modification of the solution proposed in
    https://www.daniweb.com/posts/jump/1909448
for the Mic On/Off script from
    https://gist.github.com/OndraZizka/2724d353f695dacd73a50883dfdf0fc6
'''

# Define the path for the script below, e.g.,
script = "./switchHeadphones.sh"

try:
    # Python2
    import Tkinter as tk
except ImportError:
    # Python3
    import tkinter as tk
import os

__author__ = "Weslley S Pereira"
__email__ = "weslley.spereira@gmail.com"

def toggle(tog=[0]):
    '''
    a list default argument has a fixed address
    '''
    tog[0] = not tog[0]
    if tog[0]:
        os.system(script+' speak')
        t_btn.config(text='Switch Mic Off')
    else:
        os.system(script+' listen')
        t_btn.config(text='Switch Mic On')

root = tk.Tk()
root.title('Headphone  ')

t_btn = tk.Button(text='Switch Mic On', width=15, command=toggle)
t_btn.pack(pady=5)

root.mainloop()

I don't know how to run this, I'm new to linux. Can someone please help me out ?
@weslleyspereira @vvillschneider

@weslleyspereira
Copy link

weslleyspereira commented Sep 28, 2020

Usage:

  1. First, you may need to change the permissions of the original Shell script switchHeadphones.sh:
    chmod +x switchHeadphones.sh
  2. Then, after connecting your headphones, execute the script Python I suggested as follows:
    python <my_script.py> &
  3. Make sure the variable script holds the correct path for the original Shell script switchHeadphones.sh.

@waawawewa
Copy link

waawawewa commented Sep 28, 2020

Usage:

1. First, you may need to change the permissions of the original Shell script `switchHeadphones.sh`:
   `chmod +x switchHeadphones.sh`

2. Then, after connecting your headphones, execute the script Python I suggested as follows:
   `python <my_script.py> &`

3. Make sure the variable `script` holds the correct path for the original Shell script `switchHeadphones.sh`.

@weslleyspereira, i tried running the above command of your's on ubuntu terminal , the chmod +x switchHeadphones.sh ran perfectly, but later the command python switchHeadphones.py gives me the following error:
File "switchHeadphones.py", line 9 if [ "${1}" == "reset" ] ; then ^ SyntaxError: invalid syntax
i renamed the file from .sh to .py as i could'nt run it
i have saved the file in my /home path. can you please help me out ? it would really mean a lot, thanks ^.^

@weslleyspereira
Copy link

Hi!

  1. Did you configure the Shell script switchHeadphones.sh? In case not, you should. You can do as suggested in the Shell script:
    -- Run pacmd list-sinks | grep '\(name:\|alias\)' and fill with value from alias of your Bluetooth device
  2. The error you showed relates to the @artem328's script. Try running ./switchHeadphones.sh speak then ./switchHeadphones.sh listen in your terminal and see if everything works. My Python script is only an interface for this nice Shell script.

@Dentxinho
Copy link

@thedopepirate you shouldn't rename the .sh to .py
The sh is the script that toggles bt audio. You can use it alone. The py is a GUI that shows a toggle button and it calls the sh under the hood

@appsessive
Copy link

Thanks !

@tvaillantdeguelis
Copy link

Hi!

I was looking for a solution to use the mic of my headphones with A2DP mode (in Ubuntu 20.04). I kind of understand that this is technically impossible. Is that correct?!
Then, I understand that I need to switch to HSP/HFP mode if I'd like to use the mic. However, sound quality is terrible...

This code seems to allow to toggle between the 2 modes. I don't understand if it toggles automatically (for example when muting/unmuting in Skype, Teams, or whatever)? Some folks propose to add a toggle button, so I guess the switching is done manually? Is that different from the Sound Input & Output Device Chooser Gnome extension? See a screenshot here: https://ibb.co/s3gjtsH.

@weslleyspereira
Copy link

Hi!

I was looking for a solution to use the mic of my headphones with A2DP mode (in Ubuntu 20.04). I kind of understand that this is technically impossible. Is that correct?!
Then, I understand that I need to switch to HSP/HFP mode if I'd like to use the mic. However, sound quality is terrible...

As far as I know, it is currently impossible to obtain both good quality mic and phones in Ubuntu.

This code seems to allow to toggle between the 2 modes. I don't understand if it toggles automatically (for example when muting/unmuting in Skype, Teams, or whatever)? Some folks propose to add a toggle button, so I guess the switching is done manually?

The code allows you to toggle between the modes although it is not automatic.

Is that different from the Sound Input & Output Device Chooser Gnome extension? See a screenshot here: https://ibb.co/s3gjtsH.

I've just installed the plugin you suggested, and could not get the menu for changing the output mode like in the screenshot (Ubuntu 18.04.5). Did you have a good experience with that? Anyway, the toggle button I proposed in the Python script works quite fast, which is desirable during a conversation. But sure, it is just a homemade tweak I did to complement the original script.

@tvaillantdeguelis
Copy link

Thanks for your reply.

You said it is currently impossible in Ubuntu. Do you mean it works fine in Windows?

Did you check in the configurations of the extension if there is something to switch on? Maybe your Gnome shell version is too old (< 3.14)? Maybe it only works in Ubuntu 20.04...
Maybe you also need this other Gnome extension in order to make it works: Bluetooth quick connect. I've both installed.

My understanding is that this bash script added to your Python script allow to toggle between the modes in one click while I need 3 clicks with the Gnome extension. I'm willing to try it. Is there any risk to run the bash script (I don't want to break any system file on my PC)? Do you need to run your Python script each time you start a conversation? Do you think your button could appear to the top bar each time I connect my headphones, so the button is always available?

@weslleyspereira
Copy link

Thanks for your reply.

You said it is currently impossible in Ubuntu. Do you mean it works fine in Windows?

No... I didn't test it on Windows, but it works fine in my iOS

Did you check in the configurations of the extension if there is something to switch on? Maybe your Gnome shell version is too old (< 3.14)? Maybe it only works in Ubuntu 20.04...
Maybe you also need this other Gnome extension in order to make it works: Bluetooth quick connect. I've both installed.

I checked the configurations and I found nothing I could do. Maybe Ubuntu 20.04 solves it, I don't know. I also tried the Gnome extension, it didn't solve the problem.

My understanding is that this bash script added to your Python script allow to toggle between the modes in one click while I need 3 clicks with the Gnome extension. I'm willing to try it. Is there any risk to run the bash script (I don't want to break any system file on my PC)? Do you need to run your Python script each time you start a conversation? Do you think your button could appear to the top bar each time I connect my headphones, so the button is always available?

Yes, they both work. I don't think there is any risk. The scripts just change the mic/phone configurations.
Yes, every time we need to run this script. It was just an exercise to test the switcher. I am pretty sure there is a way to put the button on the top bar, you just need to improve ii :)

@gobenavides
Copy link

gobenavides commented Mar 19, 2021

Would it be possible to link this script to .bashrc so it immediately launches every time the device gets connected?

@weslleyspereira
Copy link

weslleyspereira commented Mar 19, 2021

Possible? I think so. The question is how to do it in a clean way.

I just googled "run script when bluetooth device plugged in Linux" and found this: https://askubuntu.com/questions/138522/how-do-i-run-a-script-when-a-bluetooth-device-connects. Maybe it helps.
Maybe you could use the same variable BLUETOOTH_DEVICE from @artem328's comment.

@Fuzzphorescent
Copy link

Hi!

1. Did you configure the Shell script `switchHeadphones.sh`? In case not, you should. You can do as suggested in the Shell script:
   -- Run `pacmd list-sinks  | grep '\(name:\|alias\)'` and fill with value from alias of your Bluetooth device

2. The error you showed relates to the @artem328's script. Try running `./switchHeadphones.sh speak` then `./switchHeadphones.sh listen` in your terminal and see if everything works. My Python script is only an interface for this nice Shell script.

When I run ./switchHeadphones.sh speak, I get the message F5A headset not found.
How do I fix this?

@dethlex
Copy link

dethlex commented Jun 4, 2021

Hello! I wrote small Golang systray app for this: https://github.com/dethlex/headset-switcher. It still in development, but it works.

@shmu26
Copy link

shmu26 commented Jul 1, 2021

I am using the script from @artem328
Maybe someone can help me find the variables I need for this script to work, and where to add them to the script -- for instance, the name of the headset.
I get this error:
headset not found
It is a BOSE QC35 II

On line 4 I have:
BLUETOOTH_DEVICE="BOSE QC35 II"
but I get the above error message, and the headphones disconnect.

EDIT: The correct name is "Bose QC35 II" and then it works like a charm.
Thanks for script!!

details from blueman:
60:AB:D2:45:5D:47 public Bose QC35 II Bose QC35 II 0x240418 0x0000 audio-card yes yes no no yes 00000000-deca-fade-deca-deafdecacaff Proprietary 00001101-0000-1000-8000-00805f9b34fb Serial Port 00001108-0000-1000-8000-00805f9b34fb Headset 0000110b-0000-1000-8000-00805f9b34fb Audio Sink 0000110c-0000-1000-8000-00805f9b34fb Remote Control Target 0000110d-0000-1000-8000-00805f9b34fb Advanced Audio 0000110e-0000-1000-8000-00805f9b34fb Remote Control 0000111e-0000-1000-8000-00805f9b34fb Handsfree 0000112f-0000-1000-8000-00805f9b34fb Phonebook Access (PBAP) - PSE 00001200-0000-1000-8000-00805f9b34fb PnP Information 81c2e72a-0591-443e-a1ff-05f988593351 Proprietary 931c7e8a-540f-4686-b798-e8df0a2ad9f7 Proprietary f8d1fbe4-7966-4334-8024-ff96c9330e15 Proprietary bluetooth:v009Ep4020d0436 /org/bluez/hci0

Kubuntu 20.04

EDIT: The name of device is case-sensitive. The correct name is "Bose QC35 II" and then it works like a charm.
Thanks for script!!

@OndraZizka
Copy link
Author

Hi all, I am glad that there has been quite a lot of activity around my humble script :)

I personally moved to a device with BlueTooth 5, so I can't work on this anymore.

@tvaillantdeguelis wrote a good summary - BlueTooth 4 was not capable of supporting a microphone at a decent quality, unless there were workarounds supported by both the drivers and the device, like setting up 2 BT connections. I could not make that work on Linux.

@JustinFrost47
Copy link

@Fuzzphorescent I ran into the same problem today.

You might have found a solution for your problem by now, but I hope someone else with similar problem finds this useful

Original script searches ( greps) for F5A keyword from list of connected devices (sinks to be accurate) , well your device might have different name like mine. So, that's why you got that error.

The script given by artem328 worked like a charm for me along with toggle button. So try it out

@Fuzzphorescent
Copy link

@JustinFrost47 I never did figure it out but now having read your answer, it seems so obvious what the problem was. Looks like I hadn't skimmed the code back then to find out what prompted the error message.

I've since moved to a newer machine running Windows 10 (which is posing its own set of BT-related challenges). I'm now inspired to dust off the old laptop and give it another go for an undeserved sense of accomplishment. Thanks for the help, cheers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment