How to keep the audio profile at A2DP while using a mic with Bluetooth headset? (Push-to-talk)

48,957

Solution 1

I was about to return the headset and wait for Bluetooth 5.0 headset, but then realized, that's the best functionality I can get with my BT 4.0 laptop. So I kept them.

Still, listening to a French guy over 16 bit 8000 Hz audio wasn't really the right way to have a meeting. For few days, I was switching between the two modes using Ubuntu's sound settings dialog, but that's really, really annoying as you can imagine.

So I wrote this script leveraging pacmd which toggles between the 2 modes:

  • Crappy audio, microphone on
  • Near-CD quality audio, microphone off

It is not polished, has some dead code, and I use my own phones ID's, but it may be an inspiration for your own script. Latest version here.

#!/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"; 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"; 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"; 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

Hope this helps someone :)

Solution 2

Ondra, there is a very long pulse audio merge request discussion that contains most of the information.

tl;dr; to get things working pulse audio, bluez and kernel need to be updated (not trivially). As well a separate daemon hsphfpd is necessary.

Kernel updates are not progressing and user input to maintainers would be helpful in pushing things forward. Think about providing such. See here.

Without the kernel patches, using headphones' mic leads to terrible audio quality (HSP/HFP mode of operation).

But there is chance that only Pulse patches (and support from your headphones) can enable A2DP bi-directional audio which should be alright for most purposes.

And that patch is progressing well at the moment. More feedback on it shouldn't hurt.

Update: THings in above mentioned pull request escalated very quickly and seems like PulseAudio may never implement proper bluetooth support. Lets hope for Pipewire which already has some patches.

Solution 3

Based on this article I fear that Bluetooth won't give me the pleasure of hearing a quality sound and speak over the headset at the same time. :/

Not accepting this answer though, I am still hoping someone will come up with some way to do so.

Solution 4

I suggest the use of a simple script for a toggle button https://gist.github.com/weslleyspereira/e8feeb9f1b7008ae1ffad2777e39d0dd together with the one from Ondra Žižka's :

#! /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__ = "[email protected]"

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()

Solution 5

The feature you are looking for is available in PipeWire (a replacement for PulseAudio) when using pipewire-media-session or wireplumber as a session manager. The first one is the default and enabling automatic profile switching is achieved by setting

bluez5.autoswitch-profile = true

in ~/.config/pipewire/media-session.d/bluez-monitor.conf

After restarting pipewire-media-session, the A2DP profile is used by default. Only when a sound input is detected, Pipewire will switch to HSP/HFP and back to A2DP when it ends.

This is explained in the Arch Wiki as well as this blog article.

Share:
48,957

Related videos on Youtube

Ondra Žižka
Author by

Ondra Žižka

I am. (See my LinkedIn profile. Open to job offers for USA, Germany, Austria, Switzerland). 2012 update: I'm a company-man, a team-player, a paradigm-shifter and a core-competancy-synergizer. :) PS: I worked for Red Hat / JBoss, so my answers may be biased. Currently work for Swiss Re.

Updated on September 18, 2022

Comments

  • Ondra Žižka
    Ondra Žižka almost 2 years

    I have a CEL-TEC F5A headset. The spec says:

    Bluetooth 4.0, profiles: Headset, Hands free, A2DP, AVRCP/HSP/HFP

    It plays nicely with High Fidelity Playback (A2DP sink) profile.

    When I want to switch audio input to the headset's microphone, the output profile automatically changes to Headset Head Unit (HFS/HFP) and the quality is terrible - like 8bit sound or something. When I switch it back to A2DP, the input is back to desktop mic.

    I've read few of other questions and seems A2DP is expected not to support input, right? But the HFP part in HFS/HFP is High Fidelity Playback I guess? That sounds like it could work as a headset and still not sound like 1950's phone.

    How could I simplify switching between the profiles so that it is HFS/HFP when I talk and A2DP when I listen? E.g. as a push-to-talk.

    Ubuntu 17.10, no sound customization IIRC, everything latest.
    https://www.kabelmanie.cz/cel-tec-f5a-active-noise-bluetooth-stereo-sluchatka-s-mikrofonem/

    My output:

    $ pactl list cards
    Card #0
            Name: alsa_card.pci-0000_00_03.0
    ...
    
    Card #4
            Name: bluez_card.00_19_5D_25_6F_6C
            Driver: module-bluez5-device.c
            Owner Module: 30
            Properties:
                    device.description = "F5A"
                    device.string = "00:19:5D:25:6F:6C"
                    device.api = "bluez"
                    device.class = "sound"
                    device.bus = "bluetooth"
                    device.form_factor = "headset"
                    bluez.path = "/org/bluez/hci0/dev_00_19_5D_25_6F_6C"
                    bluez.class = "0x240404"
                    bluez.alias = "F5A"
                    device.icon_name = "audio-headset-bluetooth"
                    device.intended_roles = "phone"
            Profiles:
                    a2dp_sink: High Fidelity Playback (A2DP Sink) (sinks: 1, sources: 0, priority: 10, available: yes)
                    headset_head_unit: Headset Head Unit (HSP/HFP) (sinks: 1, sources: 1, priority: 20, available: yes)
                    off: Off (sinks: 0, sources: 0, priority: 0, available: yes)
            Active Profile: a2dp_sink
            Ports:
                    headset-output: Headset (priority: 0, latency offset: 0 usec, available)
                            Part of profile(s): a2dp_sink, headset_head_unit
                    headset-input: Headset (priority: 0, latency offset: 0 usec)
                            Part of profile(s): headset_head_unit
    

    The problem here is not that the microphone does not work, but rather that the audio quality worsens when it is activated.

  • Marc Wittke
    Marc Wittke over 4 years
    awesome! just found and replaced the magic strings and it works with my Srhythm NC-75
  • Sebastián Vansteenkiste
    Sebastián Vansteenkiste about 4 years
    I'm tempted to take this one step further and make a sort of "push to talk" feature that would toggle modes only when I want to speak, but I'm way too much of a Linux noob for that still. I'm totally putting it in my "wacky ideas simmering pot" in the back burner though.
  • Mr. Y
    Mr. Y almost 4 years
    I have problem in ubuntu 18.04 when accessing mic of my Bluetooth headset audio automatically changes to HSP/HFP where(path) i need to put this script?
  • Shinebayar G
    Shinebayar G almost 4 years
    What does this script do? Can you explain your work in a little bit more detail?
  • Ondra Žižka
    Ondra Žižka almost 4 years
    Thanks Alex, nice to meet you here :)
  • Weslley S. Pereira
    Weslley S. Pereira almost 4 years
    It creates a window with a single button written 'Switch Mic On'. Press it and it will switch to 'Switch Mic Off' and you're now with the headset microphone on and the low-quality audio. Press it again, and you come back to 'Switch Mic On' with high-quality audio but the microphone in the headset is off
  • OJFord
    OJFord over 3 years
    Could you elaborate on what bluetooth 5 changes please? My headset is 5.0, but the USB adaptor I'm using in my PC is certainly not, perhaps not even 4.0, so your answer seems to suggest I could replace it with a 5.0 adaptor and use the microphone at the same time as higher quality audio? 5 has more bandwidth and supports HFP alongside A2DP (or some new protocol)?
  • Jake t
    Jake t over 3 years
    improved your script a bit more, and made it easier to use: gist.github.com/JakeTrock/6158cb81117605d75603fa637e72fd66
  • Uzumaki D. Ichigo
    Uzumaki D. Ichigo over 3 years
    good answer, though reality sucks
  • mondjunge
    mondjunge over 3 years
    How would I disable the HSP/HFP Headset to force the use of high quality sound with the Headset mic?
  • Milan Baran
    Milan Baran over 3 years
    Probably you can't. Cuz you do now have A2DP source capability on your headphones. Both WF 1000XM3 and WH 1000XM4 do not have it.
  • Rustam Aliyev
    Rustam Aliyev over 3 years