Skip to content

fix(viewer): route audio_output to a live PulseAudio sink on x86/arm64#3209

Open
vpetersson wants to merge 3 commits into
masterfrom
fix/x86-arm64-audio-output-routing
Open

fix(viewer): route audio_output to a live PulseAudio sink on x86/arm64#3209
vpetersson wants to merge 3 commits into
masterfrom
fix/x86-arm64-audio-output-routing

Conversation

@vpetersson

Copy link
Copy Markdown
Contributor

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 to QMediaDevices::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:

  • arm64 returned default before even reading settings['audio_output'].
  • x86: hdmidefault; localplughw: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 sinks and 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::resolveAlsaDevice already matches it against QAudioDevice::id() (no C++ change needed). Falls back to default when pulse is unreachable or no matching sink exists, preserving prior behaviour. Raspberry Pi paths (fixed vc4hdmi* / Headphones card 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

  • Unit tests for sink selection (hdmi/local, same-card disambiguation, no-match fallback), pactl parsing, and the arm64/x86 routing paths. Full suite green; ruff check + ruff format --check clean.
  • Full testbed fleet validated (no regression):
    • x86: local routes to the analog sink live; HDMI sink only exists with a display attached (headless testbed correctly falls back to default).
    • Rock Pi 4: decisive test — its default sink is HDMI, and selecting local moved audio to the analog sink (verified RUNNING), proving the runtime sink actually overrides the default end-to-end; hdmi routes to the HDMI sink.
    • Pi 2 / 3 / 4 / 5: device resolution byte-for-byte unchanged.

Fixes #3208

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>
@vpetersson
vpetersson requested a review from a team as a code owner July 17, 2026 17:49
@vpetersson
vpetersson requested a review from Copilot July 17, 2026 17:49

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 map audio_output (hdmi/local) to a matching sink on x86/arm64, with a safe fallback to default.
  • 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, pactl parsing, 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.

Comment thread src/anthias_viewer/media_player.py Outdated
Comment thread src/anthias_viewer/media_player.py
- 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>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

_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>
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

x86 / arm64: HDMI vs 3.5mm audio_output selector is a no-op (always uses PulseAudio default sink)

2 participants