Skip to content

Add RX audio stream diagnostics#2889

Merged
ten9876 merged 1 commit into
aethersdr:mainfrom
rfoust:codex/audio-stream-diagnostics
May 23, 2026
Merged

Add RX audio stream diagnostics#2889
ten9876 merged 1 commit into
aethersdr:mainfrom
rfoust:codex/audio-stream-diagnostics

Conversation

@rfoust
Copy link
Copy Markdown
Collaborator

@rfoust rfoust commented May 20, 2026

Summary

  • Add RX audio stream diagnostics for feed rate, slow delivery, late arrivals, packet gaps, stream format, and packet freshness.
  • Surface user-friendly audio health in Network Diagnostics with per-stream rows and trend charts.
  • Combine the Audio and Audio Feed views into a single Audio tab.
  • Reset audio diagnostics when the remote audio stream stops and restarts.
  • Restyle Network Diagnostics to match the AetherModem window, including rounded panels, compact Details layout, and updated graph/table/log styling.

Verification

  • git diff --check
  • cmake --build build --parallel 8
  • Checked clean merge compatibility with PR 2879 in both merge orders.

@rfoust rfoust marked this pull request as ready for review May 20, 2026 13:55
@rfoust rfoust requested review from jensenpat and ten9876 as code owners May 20, 2026 13:55
Copilot AI review requested due to automatic review settings May 20, 2026 13:55
Copy link
Copy Markdown

Copilot AI left a comment

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 adds per-RX-audio-stream diagnostics (rate/deficit/late/gaps/freshness/format) sourced from PanadapterStream, resets those diagnostics when the RX audio stream lifecycle changes, and surfaces the results in the Network Diagnostics UI (new Audio tab table + graphs) with an updated dialog style.

Changes:

  • Add PanadapterStream::AudioStreamDiagnostics tracking, snapshot, and reset hooks for VITA audio packets.
  • Expose audio diagnostics through RadioModel, and reset diagnostics on RX audio create/adopt/remove events.
  • Expand/restyle NetworkDiagnosticsDialog to display audio stream health, per-stream rows, and timing/feed trend charts.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/models/RadioModel.h Exposes audio stream diagnostics getters/reset on the model.
src/models/RadioModel.cpp Implements diagnostics getter/reset and wires resets into RX audio stream lifecycle.
src/gui/NetworkDiagnosticsDialog.h Extends sampling/state to include RX audio feed/late/gap/format fields and new UI widgets.
src/gui/NetworkDiagnosticsDialog.cpp Implements new RX audio formatting/health logic, table + graph UI, and dialog restyling.
src/core/PanadapterStream.h Defines audio diagnostics/tracker structures and adds snapshot/reset APIs.
src/core/PanadapterStream.cpp Records per-audio-stream timing metrics from incoming packets and provides snapshot/reset behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/core/PanadapterStream.cpp Outdated
Comment on lines +23 to +24
constexpr int kAudioSampleRate = 24000;
constexpr int kOpusFramesPerPacket = 240;
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Addressed in the amended commit: kAudioSampleRate now references AudioEngine::DEFAULT_SAMPLE_RATE instead of duplicating 24000.

Comment thread src/core/PanadapterStream.cpp Outdated
Comment on lines +163 to +167
@@ -162,6 +164,7 @@ bool PanadapterStream::start(RadioConnection* conn)
m_audioPacketTimerStarted = false;
m_previousAudioPacketGapMs = 0;
m_audioPacketJitterEstimateMs = 0.0;
resetAudioStreamStats();
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Addressed in the amended commit: removed the duplicate manual audio gap/jitter resets from start, rebindToEphemeralPort, startWan, and stop; resetAudioStreamStats() is now the single reset path.

Comment thread src/gui/NetworkDiagnosticsDialog.cpp Outdated
Comment on lines +2095 to +2099
const QString audioHealth = formatAudioHealth(sample);
m_audioStreamHealthLabel->setText(audioHealth);
if (audioHealth == "Healthy") {
m_audioStreamHealthLabel->setStyleSheet("QLabel { color: #64d36e; font-weight: 700; }");
} else if (audioHealth == "Audio stream is arriving too slowly"
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Addressed in the amended commit: formatAudioHealth() now returns an AudioHealthStatus with an AudioHealthState enum plus display text, and the label color is selected from the enum instead of comparing user-facing strings.

Copy link
Copy Markdown
Contributor

@aethersdr-agent aethersdr-agent Bot left a comment

Choose a reason for hiding this comment

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

Thanks @rfoust — nice diagnostic work, and the per-stream feed-rate / deficit / late-arrival metrics will be genuinely useful when triaging RX audio glitches in the field. The wiring through recordAudioStreamPacketLocked inside the existing stats-mutex region looks correct, and resetting on every (re)start / stop / clearRegisteredStreams is the right call.

A few comments — none are blockers.

Copilot findings — all three look valid:

  1. kAudioSampleRate = 24000 in PanadapterStream.cpp:24 duplicates AudioEngine::DEFAULT_SAMPLE_RATE (defined at src/core/AudioEngine.h:71, used in ~40 places across AudioEngine.cpp). Worth referencing the shared constant so the deficit/feed-rate math stays correct if the engine sample rate ever changes. A simple #include "AudioEngine.h" and constexpr int kAudioSampleRate = AudioEngine::DEFAULT_SAMPLE_RATE; would do it.

  2. Duplicate field resets in start / rebindToEphemeralPort / startWan / stop. Each call site now does:

    m_audioPacketTimerStarted = false;
    m_previousAudioPacketGapMs = 0;
    m_audioPacketJitterEstimateMs = 0.0;
    resetAudioStreamStats();  // ← also resets all three

    resetAudioStreamStats() already covers those three members. Drop the manual lines at the four call sites and let resetAudioStreamStats() be the single source of truth — that's exactly what it's there for.

  3. Brittle string comparisons in the audio-health styling (NetworkDiagnosticsDialog.cpp:2099). The if (audioHealth == "Healthy") ... else if (audioHealth == "Audio stream is arriving too slowly" ...) branches will silently fall to the warning-color fallback if formatAudioHealth()'s wording ever drifts. Acknowledging this mirrors the existing networkQuality() pattern in the same function, so it's not out of step with the file. Optional follow-up: have formatAudioHealth return an enum (or a {status, displayText} pair) and switch on the enum. Not blocking.

Scope note (minor): The PR description does call out the restyle, so it's not hidden, but the kNetworkDiagnosticsStyle block + Audio/Audio-Feed tab consolidation are really a separate UI refresh from the diagnostics feature. Easier to bisect later if they land as two commits, but it's a one-author PR so I won't push on it.

Things I didn't find issues with:

  • The Locked suffix convention is honored — recordAudioStreamPacketLocked is called from inside the m_statsMutex-held region in processDatagram, and audioStreamDiagnostics() / resetAudioStreamStats() both re-acquire the lock for snapshot/reset. Looks thread-safe.
  • audioPayloadFrames correctly returns 0 for unknown PCCs and recordAudioStreamPacketLocked early-exits on frames <= 0, so unknown codecs don't pollute the per-stream tracker.
  • Reset on clearRegisteredStreams matches the existing pattern from #2738.

@rfoust rfoust force-pushed the codex/audio-stream-diagnostics branch from 9853c41 to 7fceb3e Compare May 20, 2026 14:12
Copy link
Copy Markdown
Collaborator

@jensenpat jensenpat left a comment

Choose a reason for hiding this comment

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

Slice 0 RX flow: untouched — only adds passive observation in the existing audio-decode branch. Low risk.
Transmit interlock, command ordering, timers: unchanged.
GUI restyle: affects only NetworkDiagnosticsDialog. Per CLAUDE.md, visual design is aligned per Jeremy's chat feedback.

@rfoust rfoust force-pushed the codex/audio-stream-diagnostics branch from 7fceb3e to eff0fe1 Compare May 21, 2026 03:40
@ten9876 ten9876 merged commit 16cc497 into aethersdr:main May 23, 2026
5 checks passed
@ten9876
Copy link
Copy Markdown
Collaborator

ten9876 commented May 23, 2026

Claude here — merged. Thanks @rfoust, RX audio diagnostics is
exactly the kind of observability the project has been missing.

What stood out on review:

The thread-safety design is intentional and well-marked. Naming
`recordAudioStreamPacketLocked()` explicitly to flag that it's
called with the mutex held, making `resetAudioStreamDiagnostics()`
a `Q_INVOKABLE` so RadioModel can dispatch it cross-thread via
`QMetaObject::invokeMethod(..., Qt::AutoConnection)` — these
patterns disappear into bugs when they're not done deliberately.

The health-status rules are conservative in the right way. Compound
late-packet condition (`latePacketsPerSecond >= 2.0` OR `>= 1.0` AND
`feedDeficitMs >= 10.0`) catches recurring problems while ignoring
the inevitable single-packet jitter at the noise floor. The 95%
feed-rate threshold likewise allows for small dropouts without
crying wolf.

And the lifecycle-reset discipline — 5 reset sites covering every
RadioModel path that creates/adopts/removes a stream — prevents the
classic "stale counter from the previous stream session" footgun.

Auto-merge clean against #2879's TCI tab work despite both touching
`NetworkDiagnosticsDialog` — you verified both merge orders before
submitting, which is the right way to ship parallel UI work.

Ships in v26.5.3. Operators chasing audio-stream weirdness now have
real visibility into per-stream feed rate, late packets, and clock
skew rather than guessing from the audio buffer dial.

73,
Jeremy KK7GWY & Claude (AI dev partner)

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.

4 participants