Pipewire bluetooth audio mSBC/SBC codec not selectable

I am trying to get my bluetooth headset (Airpods) recognized as a headset in order to use the microphone properly.
The HSP config option is now available, but only with the noticably low quality CSVD codec.

This is my pipewire config:

services.pipewire  = {
  enable = true;
  alsa.enable = true;
  alsa.support32Bit = true;
  pulse.enable = true;
  media-session.config.bluez-monitor.rules = [
    {
      # Matches all cards
      matches = [ { "device.name" = "~bluez_card.*"; } ];
      actions = {
        "update-props" = {
          "bluez5.reconnect-profiles" = [ "hfp_hf" "hsp_hs" "a2dp_sink" ];
          # mSBC is not expected to work on all headset + adapter combinations.
          "bluez5.msbc-support" = true;
          # SBC-XQ is not expected to work on all headset + adapter combinations.
          "bluez5.sbc-xq-support" = true;
        };
      };
    }
    {
      matches = [
        # Matches all sources
        { "node.name" = "~bluez_input.*"; }
        # Matches all outputs
        { "node.name" = "~bluez_output.*"; }
      ];
      actions = {
        "node.pause-on-idle" = false;
      };
    }
  ];
};

Any ideas on what could cause this? I am on Nixos 21.11 with Kernel 5.16.5.

Anecdotally, sbc-xq works out of the box for me with other headsets.

The config looks correct, so I don’t actually think this will help, but have you tried with no config at all?

I think your first match block may not be necessary, at least, since that appears to be default behavior for devices that support those things, and only potentially forces codecs for devices that don’t support them. That should rarely be necessary with pipewire, since it has an upstream database for device support.

Interestingly, msbc is disabled for air pods here, but not sbc-xq.

Is there anything interesting in your logs?

Thanks for the fast reply.

I also tested it without any media-session.config.bluez-monitor.rules with no difference.

There are some errors in the bluetooth service logs. But this seems to be for the a2dp profile.

Feb 11 20:36:44 nixos bluetoothd[941]: profiles/audio/avdtp.c:avdtp_connect_cb() connect to F8:4D:89:14:40:3C: Host is down (112)
Feb 11 20:36:58 nixos bluetoothd[941]: profiles/audio/avdtp.c:avdtp_connect_cb() connect to F8:4D:89:14:40:3C: Host is down (112)
Feb 11 20:37:20 nixos bluetoothd[941]: profiles/audio/avdtp.c:avdtp_connect_cb() connect to F8:4D:89:14:40:3C: Host is down (112)
Feb 11 20:37:58 nixos bluetoothd[941]: profiles/audio/avdtp.c:avdtp_connect_cb() connect to F8:4D:89:14:40:3C: Host is down (112)
Feb 11 20:39:08 nixos bluetoothd[941]: profiles/audio/avdtp.c:avdtp_connect_cb() connect to F8:4D:89:14:40:3C: Host is down (112)
Feb 11 21:32:31 nixos bluetoothd[941]: src/service.c:btd_service_connect() a2dp-sink profile connect failed for F8:4D:89:14:40:3C: Device or resource busy
Feb 11 21:32:36 nixos bluetoothd[941]: profiles/audio/avdtp.c:handle_unanswered_req() No reply to SetConfiguration request
Feb 11 21:32:41 nixos bluetoothd[941]: src/profile.c:ext_io_disconnected() Unable to get io data for Hands-Free Voice gateway: getpeername: Transport endpoint is not connected (107)
Feb 11 22:36:13 nixos bluetoothd[941]: /org/bluez/hci0/dev_F8_4D_89_14_40_3C/fd2: fd(43) ready
Feb 11 23:13:17 nixos bluetoothd[941]: src/profile.c:ext_io_disconnected() Unable to get io data for Hands-Free Voice gateway: getpeername: Transport endpoint is not connected (107)

Not sure about pipewire info, but seems fine for me.
These are some of the objects that i have taken a look at:

id: 64
	permissions: rwxm
	type: PipeWire:Interface:Node/3
*	input ports: 0/0
*	output ports: 1/64
*	state: "running"
*	properties:
*		api.bluez5.transport = ""
*		api.bluez5.profile = "headset-head-unit"
*		api.bluez5.codec = "cvsd"
*		api.bluez5.address = "F8:4D:89:14:40:3C"
*		device.routes = "1"
*		card.profile.device = "0"
*		device.intended-roles = "Communication"
*		device.form-factor = "headphone"
*		device.bus = "bluetooth"
*		device.id = "65"
*		node.description = "AirPods"
*		node.name = "bluez_input.F8_4D_89_14_40_3C.headset-head-unit"
*		factory.name = "api.bluez5.sco.source"
*		priority.driver = "2010"
*		priority.session = "2010"
*		device.icon-name = "audio-headphones-bluetooth"
*		factory.id = "8"
*		device.api = "bluez5"
*		media.class = "Audio/Source"
*		node.driver = "true"
*		factory.mode = "split"
*		audio.adapt.follower = ""
*		library.name = "audioconvert/libspa-audioconvert"
*		object.id = "64"
*		client.id = "31"
*	params: (8)
*	  3 (Spa:Enum:ParamId:EnumFormat) r-
*	  1 (Spa:Enum:ParamId:PropInfo) r-
*	  2 (Spa:Enum:ParamId:Props) rw
*	  4 (Spa:Enum:ParamId:Format) rw
*	  10 (Spa:Enum:ParamId:EnumPortConfig) r-
*	  11 (Spa:Enum:ParamId:PortConfig) rw
*	  15 (Spa:Enum:ParamId:Latency) rw
*	  16 (Spa:Enum:ParamId:ProcessLatency) rw

id: 65
	permissions: rwxm
	type: PipeWire:Interface:Device/3
*	properties:
*		device.api = "bluez5"
*		device.bus = "bluetooth"
*		media.class = "Audio/Device"
*		device.name = "bluez_card.F8_4D_89_14_40_3C"
*		device.description = "AirPods"
*		device.alias = "AirPods"
*		device.vendor.id = "bluetooth:004c"
*		device.product.id = "0x2013"
*		device.form-factor = "headphone"
*		device.string = "F8:4D:89:14:40:3C"
*		api.bluez5.icon = "audio-headphones"
*		api.bluez5.path = "/org/bluez/hci0/dev_F8_4D_89_14_40_3C"
*		api.bluez5.address = "F8:4D:89:14:40:3C"
*		api.bluez5.device = ""
*		api.bluez5.class = "0x240418"
*		api.bluez5.connection = "connected"
*		device.icon-name = "audio-headphones-bluetooth"
*		bluez5.reconnect-profiles = "[
            "hfp_hf",
            "hsp_hs",
            "a2dp_sink"
          ]"
*		bluez5.sbc-xq-support = "true"
*		factory.id = "14"
*		client.id = "31"
*		object.id = "65"
*	params: (6)
*	  8 (Spa:Enum:ParamId:EnumProfile) r-
*	  9 (Spa:Enum:ParamId:Profile) rw
*	  12 (Spa:Enum:ParamId:EnumRoute) r-
*	  13 (Spa:Enum:ParamId:Route) rw
*	  1 (Spa:Enum:ParamId:PropInfo) r-
*	  2 (Spa:Enum:ParamId:Props) rw

id: 69
	permissions: rwxm
	type: PipeWire:Interface:Node/3
*	input ports: 1/64
*	output ports: 1/0
*	state: "suspended"
*	properties:
*		api.bluez5.transport = ""
*		api.bluez5.profile = "headset-head-unit"
*		api.bluez5.codec = "cvsd"
*		api.bluez5.address = "F8:4D:89:14:40:3C"
*		device.routes = "1"
*		card.profile.device = "1"
*		device.intended-roles = "Communication"
*		device.form-factor = "headphone"
*		device.bus = "bluetooth"
*		device.id = "65"
*		node.description = "AirPods"
*		node.name = "bluez_output.F8_4D_89_14_40_3C.headset-head-unit"
*		factory.name = "api.bluez5.sco.sink"
*		priority.driver = "1010"
*		priority.session = "1010"
*		device.icon-name = "audio-headphones-bluetooth"
*		factory.id = "8"
*		device.api = "bluez5"
*		media.class = "Audio/Sink"
*		node.driver = "true"
*		factory.mode = "merge"
*		audio.adapt.follower = ""
*		library.name = "audioconvert/libspa-audioconvert"
*		object.id = "69"
*		client.id = "31"
*	params: (8)
*	  3 (Spa:Enum:ParamId:EnumFormat) r-
*	  1 (Spa:Enum:ParamId:PropInfo) r-
*	  2 (Spa:Enum:ParamId:Props) rw
*	  4 (Spa:Enum:ParamId:Format) -w
*	  10 (Spa:Enum:ParamId:EnumPortConfig) r-
*	  11 (Spa:Enum:ParamId:PortConfig) rw
*	  15 (Spa:Enum:ParamId:Latency) rw
*	  16 (Spa:Enum:ParamId:ProcessLatency) rw

id: 70
	permissions: rwxm
	type: PipeWire:Interface:Port/3
	direction: "output"
*	properties:
*		format.dsp = "32 bit float mono audio"
*		audio.channel = "MONO"
*		port.monitor = "true"
*		port.id = "0"
*		object.path = "AirPods:monitor_0"
*		port.name = "monitor_MONO"
*		port.alias = "AirPods:monitor_MONO"
*		port.direction = "out"
*		node.id = "69"
*		object.id = "70"
*	params: (6)
*	  3 (Spa:Enum:ParamId:EnumFormat) r-
*	  6 (Spa:Enum:ParamId:Meta) r-
*	  7 (Spa:Enum:ParamId:IO) r-
*	  4 (Spa:Enum:ParamId:Format) -w
*	  5 (Spa:Enum:ParamId:Buffers) --
*	  15 (Spa:Enum:ParamId:Latency) rw

id: 76
	permissions: rwxm
	type: PipeWire:Interface:Port/3
	direction: "input"
*	properties:
*		format.dsp = "32 bit float mono audio"
*		audio.channel = "MONO"
*		port.id = "0"
*		port.physical = "true"
*		port.terminal = "true"
*		object.path = "AirPods:playback_0"
*		port.name = "playback_MONO"
*		port.alias = "AirPods:playback_MONO"
*		port.direction = "in"
*		node.id = "69"
*		object.id = "76"
*	params: (6)
*	  3 (Spa:Enum:ParamId:EnumFormat) r-
*	  6 (Spa:Enum:ParamId:Meta) r-
*	  7 (Spa:Enum:ParamId:IO) r-
*	  4 (Spa:Enum:ParamId:Format) -w
*	  5 (Spa:Enum:ParamId:Buffers) --
*	  15 (Spa:Enum:ParamId:Latency) rw

id: 79
	permissions: rwxm
	type: PipeWire:Interface:Port/3
	direction: "output"
*	properties:
*		format.dsp = "32 bit float mono audio"
*		audio.channel = "MONO"
*		port.id = "0"
*		port.physical = "true"
*		port.terminal = "true"
*		object.path = "AirPods:capture_0"
*		port.name = "capture_MONO"
*		port.alias = "AirPods:capture_MONO"
*		port.direction = "out"
*		node.id = "64"
*		object.id = "79"
*	params: (6)
*	  3 (Spa:Enum:ParamId:EnumFormat) r-
*	  6 (Spa:Enum:ParamId:Meta) r-
*	  7 (Spa:Enum:ParamId:IO) r-
*	  4 (Spa:Enum:ParamId:Format) rw
*	  5 (Spa:Enum:ParamId:Buffers) r-
*	  15 (Spa:Enum:ParamId:Latency) rw
$ pw-metadata -m
update: id:0 key:'default.configured.audio.source' value:'{ "name": "bluez_input.F8_4D_89_14_40_3C.headset-head-unit" }' type:'Spa:String:JSON'
update: id:0 key:'default.configured.audio.sink' value:'{ "name": "bluez_output.F8_4D_89_14_40_3C.a2dp-sink" }' type:'Spa:String:JSON'
update: id:0 key:'default.audio.sink' value:'{ "name": "alsa_output.pci-0000_04_00.6.HiFi__hw_Generic_1__sink" }' type:'Spa:String:JSON'
update: id:0 key:'default.audio.source' value:'{ "name": "alsa_input.pci-0000_04_00.6.HiFi__hw_acp__source" }' type:'Spa:String:JSON'

You’ll find that sbc-xq is a codec used to transmit the actual data sent via a2dp. I’m unsure if that error is a red herring, though, considering your headset clearly shows up as having an a2dp sink - perhaps you’re actually using the hfp, but it looks like you’re not?

Are your headphones connected to anything else before you attempt this? In my experience that can cause issues where the device seems connected but isn’t really; I think that’s an issue with the device, technically, it affects all OSes. Perhaps airpods don’t make all features available if they try to serve multiple devices.

It would also be helpful to check if you can get sbc-xq on a different headset, just to exclude hardware issues on your computer side.

If that’s not it, looking at the documentation here: media-session.d/bluez-monitor.conf · 0.4.1 · PipeWire / media-session, and also referencing against wireplumber: Bluetooth configuration — WirePlumber 0.4.8 documentation

It looks like you may need to toggle sbc-xq globally:

media-session.config.bluez-monitor.properties.bluez5.enable-sbc-xq = true;

I don’t need this on my end though, so I’m confused as to why it would be required. Are you using unstable? Perhaps it is a regression of sorts.

Edit:

I believe that’s the basic speech codec, and it’s present on both your input and output info dump there. This makes me suspect it is indeed set to HFP mode. Have you toggled the a2dp mode in pavucontrol?

The following modes are available in pavucontol:

  • High Fidelity Playback (A2DP Sink)
  • Headset Head Unit (HSP/HFP)
  • High Fidelity Playback (A2DP Sink, SBC)
  • High Fidelity Playback (A2DP Sink, SBC-XQ)
  • High Fidelity Playback (A2DP Sink, AAC)
  • High Fidelity Playback (HSP/HFP, CSVD)

The A2DP modes work as expected, but do not allow the use of the Airpods microphones.
I noticed that these errors are with the A2DP mode, have not seem those while using HSP/HFP.

The Airpods are also sometimes connected to my phone, but after a repair to my laptop the bluetooth audio config modes did not change. It would be nice to have the mode: High Fidelity Playback (HSP/HFP, SBX-QX (or some other better codec)). I expect that i then can use the Airpods microphones.

Also tried the global sbc-xq config. Was not able to test another headset yet, but will try that soon.
I am using the nixos-21.11 channel.

1 Like

HFP+sbc-xq isn’t an option at all to my knowledge, on any platform. Two channels at that high of a bitrate simply isn’t possible.

HFP+mSBC is a better option, but evidently doesn’t work with airpods, so for the moment you’re stuck with the 8kb CVSD mode. I’m not sure why that is broken, the commit messages and comments don’t explain it, but perhaps some issue diving can help, and maybe it can even be fixed.

Personally, I use my headsets in a2dp+sbc-xq mode, and use my computer’s microphone, adding something like https://github.com/werman/noise-suppression-for-voice to deal with background noise.

Edit: Oh, right, I’m in nix land, so I can even share my config in case you want a pointer: https://github.com/TLATER/dotfiles/blob/f809f0d61d7733ad40ea9147ca8138e78eeae83c/nixpkgs/configurations/tty-programs/pipewire-rnnoise.nix

It’s outdated, an issue that prevents including the config directly in my nix module due to ordering semantics was solved not too long ago. Note this is a user service, I don’t think it works as a system service.

Thank you for your help. I will setup another mic that has a better quality than the built in mic.
Maybe someone sees this thread and knows how to implement HFP+mSBC for Airpods in the future.

1 Like