fix(linux): stop fighting the A2DP profile, and make the codec choosable - #766
Open
injkgz wants to merge 4 commits into
Open
fix(linux): stop fighting the A2DP profile, and make the codec choosable#766injkgz wants to merge 4 commits into
injkgz wants to merge 4 commits into
Conversation
Three related problems made playback controls unreliable on Linux. MediaController re-activated its cached A2DP profile on every playback start, even when the card was already using another A2DP variant. Switching profiles recreates the PipeWire sink and stops the stream that triggered the activation, so a play command from the AirPods stem was cancelled about half a second later by the resulting pause. Any active a2dp-sink variant is now left untouched, which also keeps the negotiated codec unchanged. Fixes librepods-org#680. The cached PulseAudio/PipeWire card index went stale across reconnects, since PipeWire may assign a different index. Availability checks then queried a card that no longer existed and concluded A2DP was unavailable. The card is now resolved by Bluetooth MAC before each activation and the cached index refreshed. Fixes librepods-org#681. A freshly connected card can appear before its profiles are enumerated. The first missing-profile result triggered an immediate WirePlumber restart, which interrupts audio for several seconds and races pipewire-pulse rebuilding the card. Enumeration now gets a bounded grace period, the restart remains as a last resort, and post-restart retries are bounded too, without sleeping after the final attempt. Fixes librepods-org#682.
The profile preference order was hardcoded as SBC-XQ, SBC, AAC, so AAC was never selected even though it is the codec AirPods use natively with Apple devices, and it is what PipeWire ranks highest for them. Add a "Preferred audio codec" setting (AAC, SBC-XQ, SBC) to the settings tab, stored as `preferred_codec` in app_settings.json and defaulting to AAC. The chosen codec is tried first and the remaining ones act as fallbacks, so a card that cannot negotiate the preferred codec still gets a working A2DP profile.
The LE monitor decodes left, right and case levels from the proximity
advertisement but only pushed them into the tray. The window kept showing
whatever AACP last reported, and over AACP the case reports itself as
disconnected whenever the buds are outside it, so the case level was
missing from the device view.
Forward the decoded levels to the UI as an AACPEvent::BatteryInfo, the
same event the AACP path already sends, skipping components the
advertisement marks as unavailable (0xff).
While here, move the "Auto-connect is disabled" log into the else branch.
It sat inside the auto-connect branch, so it was printed right after a
successful connection:
Successfully connected to AirPods 34:0E:22:CE:A2:80
Auto-connect is disabled for 34:0E:22:CE:A2:80, not attempting to connect.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011cAE8Gb2vCuv9WLMh91R9g
async_main's result was unwrapped, so any Bluetooth error at startup
panicked. With panic = "abort" in the release profile, a panic on the
backend thread takes the whole process down, and the user sees a core
dump with no explanation. Powering on a soft-blocked adapter is the
common way to hit this:
thread '<unnamed>' panicked at src/main.rs:98:18:
called `Result::unwrap()` on an `Err` value: Error { kind: Failed, message: "Failed" }
Log what actually went wrong for each startup step - D-Bus connection,
adapter lookup, powering on - and exit with status 1 instead of aborting.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A7MEAowWUpkKaDm8W2ws3C
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
On Linux, pressing play on the AirPods stem did not resume playback reliably. Watching the MPRIS traffic showed the play command arriving and being cancelled right after:
The cause is in
MediaController: it re-activates its cached A2DP profile on every playback start. Switching the card profile recreates the PipeWire sink, which stops the stream that triggered the activation, and WirePlumber pauses the MPRIS player.While tracing that, two more problems in the same path showed up, both matching existing issues: the cached card index goes stale across reconnects, and a not-yet-enumerated card triggers an immediate WirePlumber restart that silences audio for the better part of a minute.
Changes
fix(linux): stop fighting the A2DP profile on playback start— closes #680, #681, #682a2dp-sink*profile is left untouched; the preferred profile is selected only when the card is off or on a non-A2DP profile. The negotiated codec is preserved.feat(linux): let the user pick the preferred A2DP codecThe preference order was hardcoded as
["a2dp-sink-sbc_xq", "a2dp-sink-sbc", "a2dp-sink"], so AAC was never selected — even though it is what AirPods use natively with Apple devices and what PipeWire ranks highest for them. A Preferred audio codec setting (AAC / SBC-XQ / SBC) now lives in the settings tab, is stored aspreferred_codecinapp_settings.json, and defaults to AAC. The remaining codecs are kept as fallbacks.Testing
Tested on Fedora 44 (KDE Plasma, Wayland), PipeWire 1.6.8, WirePlumber 0.5.14, BlueZ 5.87, with AirPods Pro 3 (model A3064):
kded6sends MPRIS commands now, no cancellingPausefollows, and the card stays ona2dp-sinkthroughout.A2DP profile a2dp-sink already active, leaving it unchangedand performs no profile switch.headset-head-unitwhile a client captures from the AirPods microphone and returns toa2dp-sinkafterwards.cargo build --releaseis clean; no new warnings.