You pair a headset. It connects. Music plays. Then you open a meeting app and the mic dropdown looks like a ghost town.
Or the opposite: the mic works, but “High Fidelity (A2DP)” vanished and you’re stuck with telephone-grade audio that sounds like it’s routed through a fax machine.
This isn’t user error. It’s a messy intersection of Bluetooth profiles, codecs, kernel drivers, BlueZ, and whichever audio server your system is running today.
The trick is to stop treating “Bluetooth audio” as one thing. It’s multiple mutually-exclusive modes with different transport paths and policy.
The mental model: profiles, roles, and why options disappear
When people say “my Bluetooth headset,” they usually mean “a tiny computer that can speak multiple Bluetooth dialects, sometimes at the same time, often badly.”
Your OS then chooses which dialect to use based on what it thinks you want: high-quality output, two-way audio, power savings, or “whatever doesn’t crash today.”
Two key truths
-
High-quality stereo playback (A2DP) and headset microphone (HSP/HFP) are typically different profiles.
Many headsets cannot do high-quality stereo output and microphone input simultaneously over classic Bluetooth. -
Profile selection is policy, not fate.
Something on your system (BlueZ + audio server + policy daemon) decides which profile to activate.
If that policy breaks, options disappear from UI like they were never supported.
What “missing” usually means
“Missing A2DP” or “missing mic” rarely means the headset lacks the feature. More often:
- The profile isn’t exposed because the audio stack is missing a module/codec plugin.
- The Bluetooth daemon is running, but the audio server didn’t negotiate the profile.
- Firmware/driver issues prevent setting up the transport (you see it connect, but audio endpoints fail).
- Policy pinned you to the wrong profile because one app asked for a mic once.
- Hands-free support is present but disabled due to missing telephony backend (common on Linux).
One quote that belongs on the wall of anyone debugging this stuff:
“Hope is not a strategy.”
— General Gordon R. Sullivan
That’s your assignment here: replace hope with a repeatable diagnosis.
Interesting facts & short history (that actually helps debugging)
- A2DP arrived to standardize stereo audio. Early Bluetooth audio was mostly phone-headset oriented; “music” support matured later, and it shows in today’s split brain.
- SBC is mandatory for A2DP, not “the best codec.” If you’re getting SBC, that may simply be the only common codec negotiated—not proof your system is “low quality.”
- HSP/HFP were designed for narrowband voice. The “headset” path historically assumed 8 kHz-ish voice. Wideband speech came later and is still uneven across devices.
- Linux Bluetooth audio used to be a PulseAudio feature bolted on top of BlueZ. Old setups used separate modules; modern ones rely on PipeWire/WirePlumber policy and SPA plugins.
- BlueZ intentionally avoids being an “audio server.” It provides Bluetooth plumbing; your audio server picks profiles, codecs, and routing.
- mSBC (wideband) for HFP is a negotiation landmine. Some headsets claim support and then misbehave. Result: “mic missing” or “connects but no audio.”
- LE Audio (LC3) is a new world, not a patch. It changes transport and capabilities. On Linux it’s improving fast, but don’t expect uniform support across distros and kernels yet.
- Some USB Bluetooth dongles ship with barely-tested firmware combos. The same chipset family can behave differently across firmware revisions; “works on my laptop” is not a guarantee.
Joke #1: Bluetooth is the only radio technology that can be blocked by a human body and corporate policy at the same time.
Fast diagnosis playbook: check first/second/third
When options are missing, you want the bottleneck quickly: hardware/driver, Bluetooth daemon, audio server, or policy.
Do these in order. Don’t freestyle.
First: confirm the OS sees the controller and it’s healthy
- If the controller is blocked, no profiles will matter.
- If firmware fails to load, you’ll get flaky connections and missing endpoints.
Second: confirm BlueZ can see the headset’s services
- You’re looking for advertised UUIDs that correspond to audio profiles.
- If the headset doesn’t advertise HFP/HSP, you won’t get a mic profile (except via LE Audio, which is separate).
Third: confirm the audio server is running and has Bluetooth support enabled
- PipeWire needs its Bluetooth SPA plugin; PulseAudio needs its Bluetooth modules.
- WirePlumber (or another session manager) makes policy decisions that hide/show profiles.
Fourth: check negotiated profile and why it was chosen
- If you have A2DP but no mic: you’re in the music profile.
- If you have mic but no A2DP: you’re likely in HSP/HFP (or a policy locked you there).
- If you have neither: failure to create transports or missing plugins.
Fifth: look for codec and transport errors in logs
- Errors mentioning SBC/aptX/LDAC, or “transport acquire,” point at the audio stack integration.
- Errors mentioning HCI timeouts point at controller/firmware/radio issues.
Profile reality: A2DP vs HSP/HFP vs LE Audio (and what “mic” really means)
A2DP (Advanced Audio Distribution Profile)
A2DP is for high-quality audio output. Think music, videos, system sounds.
It is not designed for carrying microphone input in the classic sense.
So when your UI shows “A2DP Sink” and you’re wondering where the mic went: it didn’t go anywhere; it was never in that profile.
A2DP is typically paired with codecs like SBC (baseline), and optionally AAC, aptX, aptX HD, LDAC, and others depending on device and stack support.
Codec availability affects quality and latency, but it doesn’t conjure microphone support.
HSP/HFP (Headset Profile / Hands-Free Profile)
HSP/HFP are for two-way voice. This is where microphone input appears in most classic Bluetooth headsets.
The cost: audio output usually drops to mono, narrowband, and higher compression. Wideband (mSBC) improves it, sometimes.
If your meeting app requires a microphone, your system may switch the headset to HFP automatically.
That’s not malicious. It’s the only way to get mic input on classic Bluetooth for many headsets.
The reason your “A2DP” option disappears is that the system can’t use both simultaneously in the way you expect.
LE Audio (LC3) and why it changes the conversation
LE Audio aims to support more flexible audio streams, better efficiency, and modern codecs (LC3).
It can improve bidirectional audio experiences, but you need support across:
controller, kernel, BlueZ, audio server, and headset firmware. One weak link and you’re back to classic profiles.
Why the UI lies (a little)
Desktop audio UIs tend to present profiles as “quality modes,” but they’re actually different services with different transports.
Some UIs hide profiles that fail to activate, which makes it look like the headset “doesn’t support it.”
It might. Your stack might not.
Joke #2: The “Hands-Free” profile is named for drivers, because your hands will be very busy fixing it.
Linux audio stacks: PulseAudio vs PipeWire vs WirePlumber (who’s in charge)
On modern Linux desktops, you usually have:
- BlueZ for Bluetooth device management and transport endpoints.
- PipeWire as the audio engine (often replacing PulseAudio and sometimes JACK routing).
- WirePlumber (or another session manager) to apply policy: which device, which profile, which routes.
On older setups, you might have:
- PulseAudio as the audio engine with Bluetooth modules.
- Optional integration pieces that vary by distribution.
Why this matters
Missing profiles are often caused by the wrong component owning the decision:
BlueZ sees the device, but PipeWire doesn’t load the Bluetooth plugin; or WirePlumber blocks HFP because it can’t find a telephony backend; or PulseAudio lacks the right module.
Policy is where “it used to work” goes to die
Policy daemons remember preferences, react to apps opening input streams, and try to keep things stable.
Stable is good. Stable can also mean “stuck.”
When the system locks your headset into HFP because a browser tab once asked for mic access, it’s doing exactly what it was told.
Practical tasks (commands, what the output means, and what you do next)
The commands below assume a Linux system with systemd. Adjust service names slightly for your distro.
Each task includes: command, output interpretation, and a decision.
Task 1: Check whether Bluetooth is blocked (rfkill)
cr0x@server:~$ rfkill list
0: hci0: Bluetooth
Soft blocked: no
Hard blocked: no
Meaning: If either block says “yes,” the controller may exist but won’t operate.
Decision: If soft blocked, unblock. If hard blocked, check BIOS switch or laptop key.
Task 2: Verify the controller is up (bluetoothctl)
cr0x@server:~$ bluetoothctl show
Controller 24:EE:9A:12:34:56 (public)
Name: cr0x-laptop
Powered: yes
Discoverable: no
Pairable: yes
Meaning: “Powered: yes” is table stakes. If “Powered: no,” nothing else matters.
Decision: If powered is no, run bluetoothctl power on and then check why it was off (policy, power saving).
Task 3: Confirm kernel sees the Bluetooth HCI and drivers loaded
cr0x@server:~$ lsmod | grep -E 'btusb|bluetooth'
btusb 69632 0
bluetooth 851968 22 btrtl,btintel,btbcm,btusb
Meaning: For USB controllers you typically want btusb. Missing modules can mean the dongle is unsupported or blocked.
Decision: If nothing shows up, confirm the device exists in USB/PCI and that kernel modules aren’t blacklisted.
Task 4: Check for firmware/driver failures in dmesg
cr0x@server:~$ dmesg -T | grep -iE 'bluetooth|btusb|firmware' | tail -n 20
[Tue Feb 4 10:12:21 2026] Bluetooth: hci0: Firmware revision 0x2b
[Tue Feb 4 10:12:21 2026] Bluetooth: hci0: Found device firmware: intel/ibt-20-1-3.sfi
[Tue Feb 4 10:12:22 2026] Bluetooth: hci0: Malformed MSFT vendor event: 0x02
Meaning: Firmware found is good. Repeated timeouts, “failed to load firmware,” or HCI resets point to controller/firmware trouble.
Decision: If firmware is missing, install firmware package for your distro. If timeouts persist, try a different dongle or update kernel/firmware.
Task 5: Verify BlueZ service health
cr0x@server:~$ systemctl status bluetooth --no-pager
● bluetooth.service - Bluetooth service
Loaded: loaded (/usr/lib/systemd/system/bluetooth.service; enabled)
Active: active (running)
Docs: man:bluetoothd(8)
Main PID: 842 (bluetoothd)
Meaning: If not active, pairing might still “sort of” happen via cached states, but audio endpoints won’t behave.
Decision: If failed, check logs and restart; fix config or missing dependencies.
Task 6: Inspect BlueZ logs for profile/transport errors
cr0x@server:~$ journalctl -u bluetooth -b --no-pager | tail -n 40
Feb 04 10:21:08 cr0x-laptop bluetoothd[842]: Endpoint registered: sender=:1.62 path=/MediaEndpoint/A2DPSink/sbc
Feb 04 10:21:08 cr0x-laptop bluetoothd[842]: Endpoint registered: sender=:1.62 path=/MediaEndpoint/A2DPSink/aac
Feb 04 10:22:11 cr0x-laptop bluetoothd[842]: src/service.c:btd_service_connect() a2dp-sink profile connect failed for 88:C9:E8:AA:BB:CC: Protocol not available
Meaning: “Endpoint registered” suggests the audio server registered A2DP endpoints with BlueZ. “Protocol not available” usually means missing transport support or negotiation failure.
Decision: If endpoints never register, focus on PipeWire/PulseAudio Bluetooth integration. If connect fails, focus on codec/profile mismatch or policy.
Task 7: Confirm PipeWire is running (common on modern desktops)
cr0x@server:~$ systemctl --user status pipewire --no-pager
● pipewire.service - PipeWire Multimedia Service
Loaded: loaded (/usr/lib/systemd/user/pipewire.service; enabled)
Active: active (running)
Meaning: If PipeWire isn’t running, profile UI will be empty or misleading.
Decision: Start/enable it, or if using PulseAudio intentionally, ensure PulseAudio is running instead and PipeWire isn’t half-installed.
Task 8: Confirm WirePlumber is running (policy engine)
cr0x@server:~$ systemctl --user status wireplumber --no-pager
● wireplumber.service - Multimedia Service Session Manager
Loaded: loaded (/usr/lib/systemd/user/wireplumber.service; enabled)
Active: active (running)
Meaning: Without a session manager, devices may appear but not be routed or have profiles applied.
Decision: If inactive, start it; if you use an alternative session manager, confirm it’s installed and configured.
Task 9: List PipeWire audio devices and see available profiles
cr0x@server:~$ wpctl status
PipeWire 'pipewire-0' [1.2.7, cr0x@server, cookie:123456]
└─ Clients:
34. WirePlumber [1.2.7]
Audio
├─ Devices:
│ 52. Bluetooth Headset [bluez5]
├─ Sinks:
│ 78. Bluetooth Headset [vol: 0.62]
├─ Sources:
│ 81. Bluetooth Headset [vol: 1.00]
Meaning: Device present is good. If the device exists but source is missing, the mic path isn’t created (likely no HFP/HSP active or blocked).
Decision: If only sink exists, check whether HFP is disabled or unsupported. If neither exists, BlueZ connection may exist but PipeWire didn’t create nodes.
Task 10: Inspect device details for profile state
cr0x@server:~$ wpctl inspect 52 | sed -n '1,120p'
id 52, type PipeWire:Interface:Device
media.class = "Audio/Device"
device.name = "bluez_card.88_C9_E8_AA_BB_CC"
device.description = "Bluetooth Headset"
bluez5.profile = "a2dp-sink"
bluez5.address = "88:C9:E8:AA:BB:CC"
Meaning: You’re explicitly in a2dp-sink. That’s why the mic might be absent or unusable.
Decision: If you need the mic, switch to an HFP/HSP profile (if available) or use a separate mic input device.
Task 11: Switch profiles (PipeWire/WirePlumber)
cr0x@server:~$ wpctl set-profile 52 handsfree-head-unit
Profile set
Meaning: If it succeeds, you should see a source node appear and A2DP disappear or degrade.
Decision: If it fails, the profile is unavailable; confirm the headset advertises HFP/HSP and your stack supports it.
Task 12: Check available profiles the system believes exist
cr0x@server:~$ pactl list cards short
52 bluez_card.88_C9_E8_AA_BB_CC module-bluez5-device.c
Meaning: Even on PipeWire systems, PulseAudio compatibility tools may show cards.
Decision: Use this card name to query profiles and ports via pactl list card.
Task 13: List card profiles (what’s actually selectable)
cr0x@server:~$ pactl list card bluez_card.88_C9_E8_AA_BB_CC | sed -n '/Profiles:/,/Active Profile:/p'
Profiles:
a2dp-sink: High Fidelity Playback (A2DP Sink) (sinks: 1, sources: 0, priority: 40, available: yes)
headset-head-unit: Headset Head Unit (HSP/HFP) (sinks: 1, sources: 1, priority: 30, available: yes)
Active Profile: a2dp-sink
Meaning: This is the money shot. It tells you whether the system thinks HSP/HFP exists.
Decision: If HSP/HFP shows “available: no,” the headset may not advertise it, or policy disabled it, or the backend is missing.
Task 14: Switch profile with pactl (works via PipeWire-Pulse too)
cr0x@server:~$ pactl set-card-profile bluez_card.88_C9_E8_AA_BB_CC headset-head-unit
Meaning: If the mic shows up after this, the “missing mic” was just “wrong profile.”
Decision: Decide whether you accept headset-quality audio output during calls. If not, use a dedicated microphone and keep A2DP.
Task 15: Check whether the headset advertises the right UUIDs (bluetoothctl)
cr0x@server:~$ bluetoothctl info 88:C9:E8:AA:BB:CC
Device 88:C9:E8:AA:BB:CC (public)
Name: Bluetooth Headset
Paired: yes
Trusted: yes
Connected: yes
UUID: Audio Sink (0000110b-0000-1000-8000-00805f9b34fb)
UUID: Handsfree (0000111e-0000-1000-8000-00805f9b34fb)
UUID: AV Remote Control (0000110e-0000-1000-8000-00805f9b34fb)
Meaning: “Audio Sink” aligns with A2DP. “Handsfree” aligns with HFP. If Handsfree/Headset UUIDs are absent, your mic profile may never appear.
Decision: If the UUIDs aren’t there, stop blaming Linux and start suspecting headset mode (some have “gaming” vs “phone” toggles) or vendor quirks.
Task 16: Reset a flaky pairing (remove and re-pair cleanly)
cr0x@server:~$ bluetoothctl
[bluetooth]# disconnect 88:C9:E8:AA:BB:CC
Successful disconnected
[bluetooth]# remove 88:C9:E8:AA:BB:CC
Device has been removed
[bluetooth]# scan on
Discovery started
[bluetooth]# pair 88:C9:E8:AA:BB:CC
Pairing successful
[bluetooth]# trust 88:C9:E8:AA:BB:CC
Changing 88:C9:E8:AA:BB:CC trust succeeded
[bluetooth]# connect 88:C9:E8:AA:BB:CC
Connection successful
Meaning: This clears old cached services and can fix “profile disappeared after update” cases.
Decision: If re-pairing restores profiles, your issue was state corruption or stale service discovery, not a missing driver.
Task 17: Watch realtime logs while switching profiles
cr0x@server:~$ journalctl --user -u pipewire -u wireplumber -f
Feb 04 10:31:42 cr0x-laptop wireplumber[1290]: bluez5: activating profile headset-head-unit
Feb 04 10:31:42 cr0x-laptop pipewire[1210]: bluez5-device: transport acquire 88:C9:E8:AA:BB:CC
Feb 04 10:31:42 cr0x-laptop pipewire[1210]: spa.bluez5: Failed to set codec configuration: Not supported
Meaning: “Failed to set codec configuration” often means the headset and stack disagree on a voice codec (mSBC vs CVSD) or capabilities are misreported.
Decision: Force a simpler codec if possible (disable mSBC in config), or update BlueZ/PipeWire, or accept that this headset’s HFP is broken on this stack.
Task 18: Verify you’re not running conflicting audio servers
cr0x@server:~$ ps -ef | grep -E 'pipewire|pulseaudio' | grep -v grep
cr0x 1210 1090 0 10:18 ? 00:00:02 /usr/bin/pipewire
cr0x 1211 1090 0 10:18 ? 00:00:01 /usr/bin/pipewire-pulse
Meaning: This is normal for PipeWire setups (PipeWire providing PulseAudio compatibility).
If you also see a standalone pulseaudio daemon, you may have a turf war.
Decision: Pick one stack. Don’t run both unless you enjoy diagnosing phantom devices.
Task 19: Check PulseAudio Bluetooth modules (for older PulseAudio systems)
cr0x@server:~$ pactl list modules short | grep -E 'bluez|bluetooth'
28 module-bluetooth-policy
29 module-bluetooth-discover
Meaning: If these modules aren’t loaded, you may get pairing but no audio cards/profiles.
Decision: Load them (temporarily) or fix your PulseAudio config to load at startup.
Task 20: Check codec availability hints (PipeWire properties)
cr0x@server:~$ wpctl inspect 52 | grep -iE 'a2dp|codec|bluez5'
bluez5.profile = "handsfree-head-unit"
bluez5.codec = "CVSD"
Meaning: Seeing CVSD implies you’re in a voice profile with baseline codec. That’s not “bad,” it’s “predictable.”
Decision: If mSBC breaks calls, prefer CVSD stability. Audio quality is worthless when the microphone drops mid-sentence.
Task 21: Check Bluetooth adapter power management state (common flake source)
cr0x@server:~$ cat /sys/module/bluetooth/parameters/disable_ertm
N
Meaning: Some devices have issues with certain link modes; parameters like ERTM toggles can affect stability (depending on kernel/version).
Decision: Don’t flip kernel parameters blindly. Only change if you can reproduce and roll back, ideally in a controlled environment.
Task 22: Validate the ALSA side is not the bottleneck (for non-Bluetooth mics)
cr0x@server:~$ arecord -l
**** List of CAPTURE Hardware Devices ****
card 2: Webcam [USB Webcam], device 0: USB Audio [USB Audio]
Subdevices: 1/1
Subdevice #0: subdevice #0
Meaning: If your headset mic is flaky, using a separate USB mic/webcam mic can be a practical workaround.
Decision: In meetings, prioritize reliability: keep A2DP for output and use a dedicated capture device.
Common mistakes: symptom → root cause → fix
1) Symptom: “A2DP disappeared; only Headset/HFP shows”
- Root cause: An app opened an input stream, policy switched to HFP and stayed there.
- Fix: Switch profile back to A2DP via
wpctl set-profileorpactl set-card-profile. Then deny mic permission to apps that don’t need it.
2) Symptom: “Mic option missing entirely”
- Root cause: Headset doesn’t advertise HSP/HFP UUIDs in current mode, or HFP backend disabled in your stack.
- Fix: Check
bluetoothctl infoUUIDs. If absent, change headset mode/firmware. If present, check PipeWire/WirePlumber config for HFP enablement.
3) Symptom: “Connected, but no sound over A2DP”
- Root cause: Transport acquisition fails; codec negotiation mismatch; or audio server didn’t register endpoints with BlueZ.
- Fix: Inspect
journalctl -u bluetoothand PipeWire logs. Confirm endpoints registered. Try switching codec (or falling back to SBC) and re-pair.
4) Symptom: “Sound works, mic works, but quality is awful”
- Root cause: HSP/HFP voice codec (CVSD) is narrowband by design, and output is often mono.
- Fix: Use A2DP for output and a separate mic; or try wideband (mSBC) if stable; or adopt LE Audio hardware end-to-end.
5) Symptom: “Profile switch fails: Not supported / Protocol not available”
- Root cause: Missing Bluetooth audio plugin/module, or version mismatch between BlueZ and audio server.
- Fix: Verify PipeWire Bluetooth SPA plugin is installed; ensure services running; update to a compatible set (kernel/BlueZ/PipeWire). Reboot if components were updated mid-session.
6) Symptom: “Works after reboot, breaks after suspend”
- Root cause: Controller power management bugs; device reconnect race; stale transport state.
- Fix: Restart user audio services (
systemctl --user restart pipewire wireplumber) and toggle Bluetooth. If frequent, test newer kernel/firmware or different adapter.
7) Symptom: “Headset connects to laptop but mic never appears in meeting apps”
- Root cause: App sandbox permissions or portal settings; input device exists but not selected; or app only lists ALSA devices in some modes.
- Fix: Verify source exists via
wpctl status. Select it explicitly in the app. Check permission prompts and settings.
Checklists / step-by-step plan
Checklist A: “I need A2DP for music and don’t care about the headset mic”
- Set profile to A2DP:
pactl set-card-profile ... a2dp-sink. - Pick a separate mic: webcam mic or USB mic; confirm with
arecord -l. - In meeting apps, select the separate mic explicitly.
- Remove mic permission for random browser tabs and “helpful” chat apps.
Opinionated guidance: this is the most reliable configuration for work calls on classic Bluetooth today.
It’s not elegant. It is stable.
Checklist B: “I need headset mic for calls, accept lower output quality”
- Confirm headset advertises Handsfree/Headset UUIDs via
bluetoothctl info. - Switch to headset profile:
wpctl set-profile ... handsfree-head-unit. - Watch logs while switching; if codec errors appear, prefer CVSD stability over mSBC aspirations.
- Test in a simple recorder first (not a browser meeting) to isolate app issues.
Checklist C: “Profiles are missing; I need them back”
- Check controller is healthy:
rfkill list,bluetoothctl show,dmesgfirmware lines. - Check BlueZ health and logs:
systemctl status bluetooth,journalctl -u bluetooth. - Check audio services:
systemctl --user status pipewire wireplumber. - Check card profiles:
pactl list card .... - Remove and re-pair device to refresh service discovery.
Checklist D: “Works on one laptop, not another”
- Compare kernel versions and BlueZ versions (same major versions matter).
- Compare PipeWire/WirePlumber versions and whether Bluetooth plugins are installed.
- Compare the Bluetooth adapter (internal vs dongle) and firmware logs.
- Don’t ignore radio environment: USB 3 ports and 2.4 GHz interference can degrade stability.
Three corporate mini-stories from production life
Mini-story 1: The incident caused by a wrong assumption
A mid-sized company rolled out a “standard headset” for a hybrid workforce. The procurement checklist said “Bluetooth, noise-canceling, supported on Linux.”
It was the usual corporate optimism: if it pairs, it works.
On day one of an all-hands week, a wave of tickets hit: “My mic is missing.” People could hear the meeting, but nobody could speak.
IT’s first response was to blame the conferencing client. Sensible, except the bug reproduced in three different apps.
The wrong assumption: they thought A2DP implied microphone capability. They had standardized on “High Fidelity” output and never validated bidirectional audio.
The desktop images were configured to prefer A2DP and not auto-switch profiles to HFP because the previous headset’s HFP mode was unstable.
The fix was dull but effective: document the tradeoff, provide a one-click profile switch script, and ship a cheap USB microphone to teams that did lots of calls.
For a subset of users, they chose wired headsets because latency and profile switching were killing them.
Afterward, they updated the hardware acceptance test: “Can you record 30 seconds of audio from the headset mic while connected, without manual UI spelunking?”
Procurement hated it. Operations loved it.
Mini-story 2: The optimization that backfired
Another organization tried to “optimize battery life” on laptops by being aggressive with power management.
They had a fleet config change that tuned suspend behavior and powered down radios quickly when idle.
The unintended consequence: Bluetooth reconnections after suspend started racing the user session startup.
BlueZ would reconnect the headset, but PipeWire/WirePlumber would miss the initial service discovery timing.
Users saw a connected device with missing profiles, or the A2DP profile would appear but fail to acquire transport.
Engineers tried to fix it by adding restart loops (“if it fails, restart services”). That made it worse.
Now, during the first minutes after wake, the system was thrashing: reconnecting, tearing down, re-registering endpoints, and occasionally leaving the headset in a broken state.
The fix was… to stop being clever. They relaxed the radio power-down policy and added a small, deterministic delay before policy applied routing to freshly connected Bluetooth audio devices.
They also added observability: logs captured around profile changes, not just “Bluetooth connected.”
Battery life was still fine. Meeting reliability improved dramatically. And the ticket queue stopped looking like a denial-of-service attack.
Mini-story 3: The boring but correct practice that saved the day
A company with a strict SRE culture treated the “desktop image” as a product. They versioned it, tested it, and shipped updates in rings.
Not exciting. Also not common.
When they started migrating from PulseAudio to PipeWire, they didn’t do it with a big-bang switch.
They ran pilot groups, captured the list of Bluetooth headsets in use, and validated profile availability for each: A2DP codecs, HFP behavior, suspend/resume.
During the rollout, one update changed default Bluetooth codec preferences. A small class of headsets negotiated a “better” codec and then fell over mid-call.
The pilot group caught it within hours, with logs attached that showed codec configuration failures during profile activation.
The boring practice: they had a regression test that literally switched profiles, recorded audio, and verified that nodes existed in PipeWire.
They used the same commands you’re using in this article. Not magic. Just repeatable checks.
They pinned the codec preference to a known-good baseline for that headset class and rolled forward only after verifying stability.
Nobody wrote a triumphant blog post. The helpdesk stayed quiet. That’s the actual win condition.
FAQ
1) Why does my headset mic disappear when I pick “High Fidelity (A2DP)”?
Because A2DP is for playback, not microphone capture. For classic Bluetooth headsets, mic capture typically requires HSP/HFP, which often replaces A2DP.
2) Why does A2DP disappear when I enable the mic?
Switching to HSP/HFP activates a different transport optimized for voice. Many headsets can’t do stereo A2DP output while also providing mic input on classic Bluetooth.
3) My headset supports a mic on my phone. Why not on my Linux laptop?
Phones usually have mature HFP stacks and vendor tuning. On Linux, HFP support depends on BlueZ + audio server + policy configuration. Missing plugins or disabled backends can hide HFP entirely.
4) How do I know if my headset even advertises Hands-Free services?
Use bluetoothctl info <MAC> and look for UUIDs like “Handsfree” or “Headset.” If they aren’t there, the OS won’t invent them.
5) PipeWire is running. Why do I still have no Bluetooth profiles?
PipeWire needs Bluetooth support via its SPA plugins, and a session manager like WirePlumber to apply policy. If endpoints aren’t registered in BlueZ logs, the integration layer is missing or broken.
6) Should I use mSBC wideband for better call quality?
If it’s stable on your headset and stack, yes. If you see codec configuration failures or dropouts, use CVSD. Reliability beats theoretical quality in real meetings.
7) Can I force the system to stay on A2DP and still use the headset mic?
Typically no on classic Bluetooth. The pragmatic approach: keep A2DP for output and use a separate mic (USB/webcam). Or move to LE Audio hardware end-to-end.
8) Why does it work until I open a browser tab, then profiles change?
The browser requested microphone access; policy switched to a voice-capable profile. Deny mic permission where possible, or manually switch back to A2DP after the call.
9) What’s the quickest way to see which profile is active?
On PipeWire: wpctl inspect the Bluetooth device and check bluez5.profile. With pactl: check “Active Profile” under pactl list card.
10) Do I need to reboot to fix this?
Not usually. Restarting user services (pipewire, wireplumber) and re-pairing the headset often clears stale state. Reboot only if kernel/firmware was updated.
Conclusion: next steps that don’t waste your afternoon
Treat missing Bluetooth mic/A2DP options as a negotiation failure, not a mystical curse.
First validate the controller and BlueZ. Then validate the audio server and policy. Then look at what profile is active and why.
The UI is the last place to debug this, not the first.
Practical next steps
-
Run
bluetoothctl infoand confirm the device advertises the audio services you expect. -
Run
pactl list cardorwpctl inspectand confirm which profiles are available and which is active. -
If you need reliability for calls, choose one:
- A2DP output + separate mic (boring, effective), or
- HFP headset mode (lower quality, but integrated), or
- Upgrade path to LE Audio (best future, uneven present).
- If switching profiles fails, stop toggling random settings and read the logs while switching. The error strings usually tell you which layer is broken.
Bluetooth audio is a stack of decisions. Make those decisions explicit, and the “missing option” problem turns back into engineering.