fix(viewer): route audio_output to a live PulseAudio sink on x86/arm64#3209
Merged
Conversation
On x86 and arm64 SBCs the HDMI / 3.5mm output selector was a no-op: every setting collapsed to QMediaDevices::defaultAudioOutput() (the PulseAudio default sink), so audio followed whatever sink pulse defaulted to rather than the port the user picked (forum #6749 — Dell Latitude, HDMI selected but sound only on the internal speakers). Discover sinks at runtime via `pactl list short sinks` and map the setting onto the matching sink by its standardised profile token (hdmi-* / analog-*), returning the sink name for VideoView::resolveAlsaDevice to match. Fall back to `default` when pulse is unreachable or no sink matches, preserving prior behaviour. Raspberry Pi paths (fixed vc4hdmi* / Headphones card names) are unchanged. Validated across the full testbed fleet: x86 and Rock Pi 4 route hdmi/local to the correct sink (Rock Pi `local` moved audio off the HDMI default sink to the analog sink, verified RUNNING); Pi 2/3/4/5 device resolution unchanged. Fixes #3208 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes the viewer’s audio_output routing on non-Raspberry-Pi devices (x86 + generic arm64) by resolving the configured output to an actual, currently-available PulseAudio sink instead of always following PulseAudio’s default sink.
Changes:
- Added runtime PulseAudio sink discovery (
pactl list short sinks) and selection logic to mapaudio_output(hdmi/local) to a matching sink on x86/arm64, with a safe fallback todefault. - Refactored Raspberry Pi ALSA-device resolution into a dedicated
_pi_alsa_device()path while keeping Pi behavior unchanged (including the existing Pi 5 “local → HDMI” behavior). - Expanded unit tests to cover sink selection,
pactlparsing, x86/arm64 routing, and fallback behavior (issue #3208).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/anthias_viewer/media_player.py |
Implements PulseAudio sink listing/selection for x86/arm64 while preserving Pi-specific ALSA mapping and adding transition-based logging. |
tests/test_media_player.py |
Adds unit coverage for sink selection, pactl parsing, and the new arm64/x86 routing + fallback paths. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Cache `pactl list short sinks` for a short TTL so a fast-rotating playlist doesn't spawn a subprocess on every play()/set_asset(); the window is short enough to pick up an HDMI hotplug within seconds, and only successful lists are cached. - Note in `_pi_alsa_device` that the Pi 5 `local` selection resolves to the connected HDMI device (no analog jack; the UI hides 3.5mm). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
_select_pulse_sink assigned a 4-tuple then a 3-tuple to `keywords`; mypy inferred the fixed-length type from the first branch and rejected the second. Annotate as `tuple[str, ...]`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…uting The sink-listing approach only worked on multi-card topologies (Rock Pi's separate hdmisound/Analog cards, or a discrete HDMI card next to onboard analog), where both sinks are always instantiated. On a single-card HDA system — laptops/desktops, the forum #6749 Dell topology — PulseAudio instantiates only the *active* profile's sink, so `pactl list short sinks` never shows the HDMI sink while analog is active. Selecting HDMI then silently fell back to the default (analog) even with an HDMI-audio-capable display attached. On a sink miss, parse `pactl list cards`, find the card whose *pure* output profile (`output:hdmi-*` / `output:analog-*`, ignoring the combined `+input:` profiles whose availability is masked by the analog input — issue #6749) matches the requested output and is available, and `pactl set-card-profile` it if not already active. Then re-list and route to the freshly instantiated sink. When no available matching profile exists (a video-only HDMI display, all `output:hdmi-*` available: no), nothing is switched and it falls back to default as before. The card work runs only on a miss, so the steady-state path stays a single cached `pactl list short sinks`. Validated on real single-card hardware: with analog active, requesting HDMI switched the card to its available digital profile and routed to the newly-created sink; requesting analog switched back. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… key log on setting - Skip the `pactl list cards` profile-activation probe when `_list_pulse_sinks()` returns empty (pulse unreachable) — the single- card recovery path always has ≥1 active sink, so guarding on `if sinks` avoids a redundant failing subprocess in the down case. - Key the resolve-log suppression on (audio_output, result) instead of the resolved sink alone, so a setting change (hdmi→local) that both fall back to `default` still logs once and stays correlatable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
test_x86_falls_back_to_default_when_no_matching_sink now patches _activate_output_profile so the miss-recovery path doesn't spawn a real `pactl list cards` subprocess on the test runner. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ailable - _select_pulse_sink now applies a stable preference among matches (plain *-stereo first, then non-surround, then lexical) so a multi-HDMI box doesn't pick a surround/secondary port from pactl's unspecified ordering. - _list_pulse_cards treats only `available: yes` as available; `available: unknown` (port not probeable) no longer counts, so we never switch a working analog profile for an unconfirmed HDMI one. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
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 x86 and arm64 (non-Raspberry-Pi) boards, the HDMI / 3.5mm audio output selector in Settings is a no-op.
get_alsa_audio_device()collapsed every setting toQMediaDevices::defaultAudioOutput()(the PulseAudio default sink), so playback audio followed whatever sink pulse defaulted to instead of the port the user chose.Reported on the forum (Dell Latitude E5570, Debian 13): HDMI selected in the UI but sound only played through the internal analog speakers; selecting 3.5mm changed nothing.
Root cause, in
src/anthias_viewer/media_player.py:defaultbefore even readingsettings['audio_output'].hdmi→default;local→plughw:CARD=Headphones(a Pi-only card that matches nothing on x86, so it also fell back to the default sink).Whether HDMI or analog happened to work was pure luck of which sink PulseAudio defaulted to.
Fix
On non-Pi (Qt 6 + PulseAudio) boards, discover sinks at runtime with
pactl list short sinksand map the setting onto the matching sink by its standardised profile token in the sink name:hdmi→ the HDMI sink (*.hdmi-*/iec958/displayport)local→ the analog / headphone sink (*.analog-*/headphone/speaker)The bare sink name is returned;
VideoView::resolveAlsaDevicealready matches it againstQAudioDevice::id()(no C++ change needed). Falls back todefaultwhen pulse is unreachable or no matching sink exists, preserving prior behaviour. Raspberry Pi paths (fixedvc4hdmi*/Headphonescard names) are unchanged.The profile token — not the card name — is the discriminator: on the reporter's Dell both an analog and an HDMI sink live on the same HDA card, so card name alone can't tell them apart.
Testing
pactlparsing, and the arm64/x86 routing paths. Full suite green;ruff check+ruff format --checkclean.localroutes to the analog sink live; HDMI sink only exists with a display attached (headless testbed correctly falls back to default).localmoved audio to the analog sink (verifiedRUNNING), proving the runtime sink actually overrides the default end-to-end;hdmiroutes to the HDMI sink.Fixes #3208
Update: single-card HDA hardening
Sink-listing alone only routes correctly on multi-card topologies (Rock Pi's separate
hdmisound/Analogcards, or a discrete HDMI card beside onboard analog), where both sinks are always instantiated. On a single-card HDA system — laptops/desktops, exactly the forum #6749 Dell topology — PulseAudio instantiates only the active profile's sink, sopactl list short sinksnever shows the HDMI sink while analog is active; selecting HDMI would silently fall back to analog.On a sink miss, the fix now parses
pactl list cards, finds the card whose pure output profile (output:hdmi-*/output:analog-*) matches the requested output and is available, andpactl set-card-profiles it. Combinedoutput:hdmi-*+input:analog-stereoprofiles are ignored because theiravailable: yesis masked by the analog input even when the HDMI output is unusable (the Dell's exact quirk). When no available matching profile exists (video-only HDMI display), nothing is switched and it falls back to default. The card work runs only on a miss, so steady state stays a single cachedpactl list short sinks.Validated on real single-card hardware: with analog active, requesting HDMI switched the card to its available digital profile and routed to the freshly-created sink; requesting analog switched back.