-
-
Notifications
You must be signed in to change notification settings - Fork 83
[logging] Add opt-in TX capture health summaries for TCI handoffs #4233
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4540987
f9058db
f389892
c722773
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2441,6 +2441,22 @@ QAudioFormat AudioEngine::makeFormat() const | |||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| QJsonArray AudioEngine::audioEndpointDiagnostics() const | ||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||
| QThread* const ownerThread = thread(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (ownerThread && ownerThread != QThread::currentThread()) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (!ownerThread->isRunning()) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return {}; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| QJsonArray endpoints; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const bool invoked = QMetaObject::invokeMethod( | ||||||||||||||||||||||||||||||||||||||||||||||||
| const_cast<AudioEngine*>(this), | ||||||||||||||||||||||||||||||||||||||||||||||||
| [this, &endpoints]() { | ||||||||||||||||||||||||||||||||||||||||||||||||
| endpoints = audioEndpointDiagnostics(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||
| Qt::BlockingQueuedConnection); | ||||||||||||||||||||||||||||||||||||||||||||||||
| return invoked ? endpoints : QJsonArray{}; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const auto outputDescription = [this]() { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const QAudioDevice dev = m_outputDevice.isNull() | ||||||||||||||||||||||||||||||||||||||||||||||||
| ? QMediaDevices::defaultAudioOutput() | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -2525,6 +2541,21 @@ QJsonArray AudioEngine::audioEndpointDiagnostics() const | |||||||||||||||||||||||||||||||||||||||||||||||
| tx["sample_format"] = txRunning ? QStringLiteral("Int16") : QString(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| tx["resampling_active"] = txRunning ? QJsonValue(m_txNeedsResample) : QJsonValue(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| tx["note"] = m_txInputMono ? QStringLiteral("mono input promoted to stereo for radio TX") : QString(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| const TxCaptureHealthTracker::Snapshot txHealth = | ||||||||||||||||||||||||||||||||||||||||||||||||
| m_txCaptureHealth.snapshot(txCaptureNowMs()); | ||||||||||||||||||||||||||||||||||||||||||||||||
| tx["buffer_bytes_available"] = static_cast<double>(txCaptureBufferedBytes()); | ||||||||||||||||||||||||||||||||||||||||||||||||
| tx["buffer_capacity_bytes"] = static_cast<double>(txCaptureBufferCapacityBytes()); | ||||||||||||||||||||||||||||||||||||||||||||||||
| tx["source_was_active"] = txHealth.sourceWasActive; | ||||||||||||||||||||||||||||||||||||||||||||||||
| tx["saturation_observed"] = txHealth.saturationObserved; | ||||||||||||||||||||||||||||||||||||||||||||||||
| tx["tci_suppressed_callbacks"] = static_cast<double>(txHealth.tciSuppressedCallbacks); | ||||||||||||||||||||||||||||||||||||||||||||||||
| tx["full_buffer_during_tci_observations"] = | ||||||||||||||||||||||||||||||||||||||||||||||||
| static_cast<double>(txHealth.fullBufferDuringTciObservations); | ||||||||||||||||||||||||||||||||||||||||||||||||
| tx["idle_during_tci_transitions"] = static_cast<double>(txHealth.idleDuringTciTransitions); | ||||||||||||||||||||||||||||||||||||||||||||||||
| tx["post_tci_local_tx_while_saturated"] = | ||||||||||||||||||||||||||||||||||||||||||||||||
| static_cast<double>(txHealth.postTciLocalTxWhileSaturated); | ||||||||||||||||||||||||||||||||||||||||||||||||
| tx["last_mic_read_age_ms"] = txHealth.lastMicReadAgeMs >= 0 | ||||||||||||||||||||||||||||||||||||||||||||||||
| ? QJsonValue(static_cast<double>(txHealth.lastMicReadAgeMs)) | ||||||||||||||||||||||||||||||||||||||||||||||||
| : QJsonValue(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| endpoints.append(tx); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const bool sidetoneRunning = m_sidetoneSink && m_sidetoneSink->isRunning(); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -6944,6 +6975,128 @@ void AudioEngine::logTxInputChannelDiagnostics(const TxMicChannelNormalizer::Dia | |||||||||||||||||||||||||||||||||||||||||||||||
| << TxMicChannelNormalizer::channelModeName(diagnostics.selectedMode); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| TxCaptureHealthTracker::CaptureState AudioEngine::txCaptureState(QAudio::State state) | ||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||
| switch (state) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| case QAudio::ActiveState: return TxCaptureHealthTracker::CaptureState::Active; | ||||||||||||||||||||||||||||||||||||||||||||||||
| case QAudio::IdleState: return TxCaptureHealthTracker::CaptureState::Idle; | ||||||||||||||||||||||||||||||||||||||||||||||||
| case QAudio::SuspendedState: return TxCaptureHealthTracker::CaptureState::Suspended; | ||||||||||||||||||||||||||||||||||||||||||||||||
| case QAudio::StoppedState: return TxCaptureHealthTracker::CaptureState::Stopped; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| return TxCaptureHealthTracker::CaptureState::Stopped; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| qint64 AudioEngine::txCaptureBufferedBytes() const | ||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||
| #ifdef Q_OS_MAC | ||||||||||||||||||||||||||||||||||||||||||||||||
| return m_micBuffer ? m_micBuffer->size() : 0; | ||||||||||||||||||||||||||||||||||||||||||||||||
| #else | ||||||||||||||||||||||||||||||||||||||||||||||||
| return m_micDevice ? m_micDevice->bytesAvailable() : 0; | ||||||||||||||||||||||||||||||||||||||||||||||||
| #endif | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| qint64 AudioEngine::txCaptureBufferCapacityBytes() const | ||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return m_audioSource ? m_audioSource->bufferSize() : 0; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| qint64 AudioEngine::txCaptureNowMs() const | ||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return m_txCaptureHealthClock.isValid() ? m_txCaptureHealthClock.elapsed() : 0; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| bool AudioEngine::tciAudioFresh() const | ||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return m_tciAudioTimer.isValid() | ||||||||||||||||||||||||||||||||||||||||||||||||
| && m_tciAudioTimer.elapsed() < kTciAudioActiveWindowMs; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| void AudioEngine::observeTxCaptureState(QAudio::State state) | ||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const TxCaptureHealthTracker::Event event = m_txCaptureHealth.observeState( | ||||||||||||||||||||||||||||||||||||||||||||||||
| txCaptureState(state), tciAudioFresh(), txCaptureBufferedBytes()); | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (event != TxCaptureHealthTracker::Event::None) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| logTxCaptureHealthEvent(event); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| void AudioEngine::recordTxCaptureLocalTxAttempt() | ||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (!m_audioSource) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const TxCaptureHealthTracker::Event event = m_txCaptureHealth.recordLocalTxAttempt( | ||||||||||||||||||||||||||||||||||||||||||||||||
| txCaptureState(m_audioSource->state()), | ||||||||||||||||||||||||||||||||||||||||||||||||
| m_transmitting.load(std::memory_order_acquire), | ||||||||||||||||||||||||||||||||||||||||||||||||
| m_daxTxMode.load(std::memory_order_acquire), | ||||||||||||||||||||||||||||||||||||||||||||||||
| tciAudioFresh(), | ||||||||||||||||||||||||||||||||||||||||||||||||
| txCaptureBufferedBytes(), | ||||||||||||||||||||||||||||||||||||||||||||||||
| txCaptureBufferCapacityBytes()); | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (event != TxCaptureHealthTracker::Event::None) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| logTxCaptureHealthEvent(event); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| void AudioEngine::logTxCaptureHealthEvent(TxCaptureHealthTracker::Event event) | ||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||
| switch (event) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| case TxCaptureHealthTracker::Event::BufferSaturatedDuringTci: | ||||||||||||||||||||||||||||||||||||||||||||||||
| logTxCaptureHealthSummary(QStringLiteral("buffer saturated during TCI suppression"), true); | ||||||||||||||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||||||||||||||
| case TxCaptureHealthTracker::Event::LocalTxWhileSaturated: | ||||||||||||||||||||||||||||||||||||||||||||||||
| logTxCaptureHealthSummary(QStringLiteral("local TX with saturated post-TCI capture"), true); | ||||||||||||||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||||||||||||||
| case TxCaptureHealthTracker::Event::None: | ||||||||||||||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| void AudioEngine::logTxCaptureHealthSummary(const QString& reason, bool anomaly) | ||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||
| // TCI server diagnostics use lcCat today. Keep these support summaries | ||||||||||||||||||||||||||||||||||||||||||||||||
| // opt-in with the same Help -> Support debug toggle; warnings must not make | ||||||||||||||||||||||||||||||||||||||||||||||||
| // the capture-health instrumentation default-on by bypassing that choice. | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (!lcCat().isDebugEnabled()) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const TxCaptureHealthTracker::Snapshot health = | ||||||||||||||||||||||||||||||||||||||||||||||||
| m_txCaptureHealth.snapshot(txCaptureNowMs()); | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (!anomaly && health.tciSuppressedCallbacks == 0 | ||||||||||||||||||||||||||||||||||||||||||||||||
| && health.fullBufferDuringTciObservations == 0 | ||||||||||||||||||||||||||||||||||||||||||||||||
| && health.idleDuringTciTransitions == 0 | ||||||||||||||||||||||||||||||||||||||||||||||||
| && health.postTciLocalTxWhileSaturated == 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const QAudioDevice device = m_inputDevice.isNull() | ||||||||||||||||||||||||||||||||||||||||||||||||
| ? QMediaDevices::defaultAudioInput() | ||||||||||||||||||||||||||||||||||||||||||||||||
| : m_inputDevice; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| AudioSummaryLogger::TxCaptureHealthSummary summary; | ||||||||||||||||||||||||||||||||||||||||||||||||
| summary.reason = reason; | ||||||||||||||||||||||||||||||||||||||||||||||||
| summary.deviceDescription = device.description(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| summary.state = m_audioSource | ||||||||||||||||||||||||||||||||||||||||||||||||
| ? audioStateName(m_audioSource->state()) | ||||||||||||||||||||||||||||||||||||||||||||||||
| : QStringLiteral("Stopped"); | ||||||||||||||||||||||||||||||||||||||||||||||||
| summary.error = m_audioSource | ||||||||||||||||||||||||||||||||||||||||||||||||
| ? audioErrorName(m_audioSource->error()) | ||||||||||||||||||||||||||||||||||||||||||||||||
| : QStringLiteral("NoError"); | ||||||||||||||||||||||||||||||||||||||||||||||||
| summary.lifecycleMs = health.lifecycleMs; | ||||||||||||||||||||||||||||||||||||||||||||||||
| summary.bufferedBytes = txCaptureBufferedBytes(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| summary.bufferCapacityBytes = txCaptureBufferCapacityBytes(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| summary.lastMicReadAgeMs = health.lastMicReadAgeMs; | ||||||||||||||||||||||||||||||||||||||||||||||||
| summary.tciSuppressedCallbacks = health.tciSuppressedCallbacks; | ||||||||||||||||||||||||||||||||||||||||||||||||
| summary.suppressedBufferPeakBytes = health.suppressedBufferPeakBytes; | ||||||||||||||||||||||||||||||||||||||||||||||||
| summary.fullBufferDuringTciObservations = health.fullBufferDuringTciObservations; | ||||||||||||||||||||||||||||||||||||||||||||||||
| summary.idleDuringTciTransitions = health.idleDuringTciTransitions; | ||||||||||||||||||||||||||||||||||||||||||||||||
| summary.postTciLocalTxWhileSaturated = health.postTciLocalTxWhileSaturated; | ||||||||||||||||||||||||||||||||||||||||||||||||
| summary.sourceWasActive = health.sourceWasActive; | ||||||||||||||||||||||||||||||||||||||||||||||||
| summary.saturationObserved = health.saturationObserved; | ||||||||||||||||||||||||||||||||||||||||||||||||
| AudioSummaryLogger::logTxCaptureHealth(summary, anomaly); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // ─── TX stream ──────────────────────────────────────────────────────────────── | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| bool AudioEngine::startTxStream(const QHostAddress& radioAddress, quint16 radioPort) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -7284,6 +7437,17 @@ bool AudioEngine::startTxStream(const QHostAddress& radioAddress, quint16 radioP | |||||||||||||||||||||||||||||||||||||||||||||||
| #endif | ||||||||||||||||||||||||||||||||||||||||||||||||
| #endif | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| m_txCaptureHealthClock.restart(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| m_txCaptureHealth.reset(txCaptureState(m_audioSource->state())); | ||||||||||||||||||||||||||||||||||||||||||||||||
| QAudioSource* const observedSource = m_audioSource; | ||||||||||||||||||||||||||||||||||||||||||||||||
| connect(observedSource, &QAudioSource::stateChanged, this, | ||||||||||||||||||||||||||||||||||||||||||||||||
| [this, observedSource](QAudio::State state) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (observedSource != m_audioSource) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| observeTxCaptureState(state); | ||||||||||||||||||||||||||||||||||||||||||||||||
| }, Qt::QueuedConnection); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| m_txSourceStartTime.restart(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| qCWarning(lcAudio) << "AudioEngine: TX stream started ->" << radioAddress.toString() | ||||||||||||||||||||||||||||||||||||||||||||||||
| << ":" << radioPort << "streamId:" << Qt::hex << m_txStreamId | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -7304,6 +7468,9 @@ bool AudioEngine::startTxStream(const QHostAddress& radioAddress, quint16 radioP | |||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| void AudioEngine::stopTxStream() | ||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (m_audioSource) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| logTxCaptureHealthSummary(QStringLiteral("source lifecycle ended"), false); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| ++m_txLifecycleGeneration; | ||||||||||||||||||||||||||||||||||||||||||||||||
| #ifdef Q_OS_MAC | ||||||||||||||||||||||||||||||||||||||||||||||||
| QTimer* pollTimer = m_txPollTimer; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -7428,8 +7595,15 @@ void AudioEngine::onTxAudioReady() | |||||||||||||||||||||||||||||||||||||||||||||||
| // where the default CoreAudio input is a real webcam mic that | ||||||||||||||||||||||||||||||||||||||||||||||||
| // produces continuous ambient packets. The 200 ms window comfortably | ||||||||||||||||||||||||||||||||||||||||||||||||
| // covers the 50 ms TCI frame cadence. | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (m_tciAudioTimer.isValid() | ||||||||||||||||||||||||||||||||||||||||||||||||
| && m_tciAudioTimer.elapsed() < kTciAudioActiveWindowMs) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (tciAudioFresh()) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const TxCaptureHealthTracker::Event event = m_txCaptureHealth.recordSuppressedCallback( | ||||||||||||||||||||||||||||||||||||||||||||||||
| txCaptureBufferedBytes(), txCaptureBufferCapacityBytes()); | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (event != TxCaptureHealthTracker::Event::None) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| logTxCaptureHealthEvent(event); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (m_audioSource) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| observeTxCaptureState(m_audioSource->state()); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+7599
to
+7606
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit fix (per-callback work when the diagnostic is off): gate the hot-path tracker updates + their device queries on the same category
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| #ifdef Q_OS_MAC | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -7450,6 +7624,8 @@ void AudioEngine::onTxAudioReady() | |||||||||||||||||||||||||||||||||||||||||||||||
| m_txReceivedAnyBytes = true; // disarms the WASAPI silent-open watchdog (#2929) | ||||||||||||||||||||||||||||||||||||||||||||||||
| #endif | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| m_txCaptureHealth.recordMicRead(txCaptureNowMs()); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same gate for the normal-path consume marker.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // Canonicalize immediately after capture: TX voice is logically mono | ||||||||||||||||||||||||||||||||||||||||||||||||
| // carried as stereo int16, so choose/average the real mic channel before | ||||||||||||||||||||||||||||||||||||||||||||||||
| // any resampling, RADE/DAX branch, test tone, DSP, gain, limiter, or meter. | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -7930,6 +8106,20 @@ void AudioEngine::setRadioTransmitting(bool tx) | |||||||||||||||||||||||||||||||||||||||||||||||
| if (previous == tx) | ||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| if (tx) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| // radioTransmittingChanged originates on the UI thread while AudioEngine | ||||||||||||||||||||||||||||||||||||||||||||||||
| // owns its QAudioSource and health tracker on the audio thread. Preserve | ||||||||||||||||||||||||||||||||||||||||||||||||
| // the existing immediate atomic TX edge, but sample capture state only | ||||||||||||||||||||||||||||||||||||||||||||||||
| // on the owning thread so diagnostics cannot race readyRead/stateChanged. | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (thread() == QThread::currentThread()) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| recordTxCaptureLocalTxAttempt(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||
| QMetaObject::invokeMethod(this, [this]() { | ||||||||||||||||||||||||||||||||||||||||||||||||
| recordTxCaptureLocalTxAttempt(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| }, Qt::QueuedConnection); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // Close the CW-record over on unkey so the next over re-arms cleanly (the | ||||||||||||||||||||||||||||||||||||||||||||||||
| // pump latches on our keyer, clears here). #2539. | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (!tx) m_cwKeyedThisOver.store(false, std::memory_order_release); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -26,6 +26,11 @@ QString yesNo(bool value) | |||||||||||||||||||||||||||||||
| return value ? QStringLiteral("yes") : QStringLiteral("no"); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| QString ageText(qint64 ageMs) | ||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||
| return ageMs >= 0 ? QStringLiteral("%1ms").arg(ageMs) : QStringLiteral("unknown"); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| QString valueOrUnavailable(QString value) | ||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||
| value = value.trimmed(); | ||||||||||||||||||||||||||||||||
|
|
@@ -217,6 +222,33 @@ QString formatOpenFailure(const OpenFailureSummary& summary) | |||||||||||||||||||||||||||||||
| return lines.join(QLatin1Char('\n')); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| QString formatTxCaptureHealth(const TxCaptureHealthSummary& summary) | ||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||
| QStringList lines; | ||||||||||||||||||||||||||||||||
| lines << QStringLiteral("Audio TX capture health summary:") | ||||||||||||||||||||||||||||||||
| << QStringLiteral(" reason=\"%1\" %2 state=%3 error=%4 lifetime=%5ms") | ||||||||||||||||||||||||||||||||
| .arg(valueOrUnknown(summary.reason), | ||||||||||||||||||||||||||||||||
| field(QStringLiteral("device"), summary.deviceDescription), | ||||||||||||||||||||||||||||||||
| valueOrUnknown(summary.state), | ||||||||||||||||||||||||||||||||
| valueOrUnknown(summary.error)) | ||||||||||||||||||||||||||||||||
| .arg(summary.lifecycleMs) | ||||||||||||||||||||||||||||||||
|
Comment on lines
+229
to
+234
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit fix (
Suggested change
|
||||||||||||||||||||||||||||||||
| << QStringLiteral(" buffered=%1/%2B peakSuppressed=%3B lastMicReadAge=%4") | ||||||||||||||||||||||||||||||||
| .arg(summary.bufferedBytes) | ||||||||||||||||||||||||||||||||
| .arg(summary.bufferCapacityBytes) | ||||||||||||||||||||||||||||||||
| .arg(summary.suppressedBufferPeakBytes) | ||||||||||||||||||||||||||||||||
| .arg(ageText(summary.lastMicReadAgeMs)) | ||||||||||||||||||||||||||||||||
| << QStringLiteral(" suppressedCallbacks=%1 fullDuringTci=%2 idleDuringTci=%3") | ||||||||||||||||||||||||||||||||
| .arg(summary.tciSuppressedCallbacks) | ||||||||||||||||||||||||||||||||
| .arg(summary.fullBufferDuringTciObservations) | ||||||||||||||||||||||||||||||||
| .arg(summary.idleDuringTciTransitions) | ||||||||||||||||||||||||||||||||
| << QStringLiteral(" postTciLocalTxWhileSaturated=%1") | ||||||||||||||||||||||||||||||||
| .arg(summary.postTciLocalTxWhileSaturated) | ||||||||||||||||||||||||||||||||
| << QStringLiteral(" sourceWasActive=%1 saturationObserved=%2") | ||||||||||||||||||||||||||||||||
| .arg(yesNo(summary.sourceWasActive), | ||||||||||||||||||||||||||||||||
| yesNo(summary.saturationObserved)); | ||||||||||||||||||||||||||||||||
| return lines.join(QLatin1Char('\n')); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| void logStartupEnvironment(const QJsonObject& audioDevices) | ||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||
| emitIfChanged(QStringLiteral("startup"), formatStartupEnvironment(audioDevices)); | ||||||||||||||||||||||||||||||||
|
|
@@ -250,4 +282,14 @@ void logOpenFailure(const OpenFailureSummary& summary) | |||||||||||||||||||||||||||||||
| formatOpenFailure(summary)); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| void logTxCaptureHealth(const TxCaptureHealthSummary& summary, bool anomaly) | ||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||
| const QString text = formatTxCaptureHealth(summary); | ||||||||||||||||||||||||||||||||
| if (anomaly) { | ||||||||||||||||||||||||||||||||
| qCWarning(lcAudioSummary).noquote() << text; | ||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| qCInfo(lcAudioSummary).noquote() << text; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| } // namespace AetherSDR::AudioSummaryLogger | ||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
audioEndpointDiagnostics()isconstand invoked fromAutomationServer/DeviceDiagnosticson the main thread, butm_txCaptureHealthandm_micDeviceare owned and mutated on the audio thread (onTxAudioReady, the queuedstateChangedlambda,recordMicRead). Sosnapshot()here — andtxCaptureBufferedBytes()'sm_micDevice->bytesAvailable()call one line below — race the audio thread's writes on non-atomic members.This matches the pre-existing pattern in this same function (it already reads
m_audioSink->state(),m_micDevice->isOpen(), etc. cross-thread), so it's not a regression and the torn reads are diagnostic-only. Worth flagging only because you deliberately marshaledrecordLocalTxAttemptonto the owning thread "so diagnostics cannot race readyRead/stateChanged" — the read side of the same data isn't marshaled. Fine to leave as-is for a support-log snapshot; just noting the asymmetry.