radio: add activation controls, live VAD meters and signal diagnostics
This commit is contained in:
1 parent
fddbba217d
commit
7f3247bfcc
23 files changed
+818
-182
No files matched your search
@@ -205,10 +205,15 @@ void VoiceAudioIo::captureLoop() {
|
||||
int16_t raw[1920]{}, cleaned[1920]{}, referencePcm[1920]{};
|
||||
uint8_t muteMask[960]{};
|
||||
unsigned filled = 0;
|
||||
int64_t frameTime = 0, lastReport = voice_time_us();
|
||||
int64_t frameTime = 0, lastReport = voice_time_us(), lastCapture = lastReport, maxQueueAge = 0;
|
||||
uint64_t expectedSequence = 0, missingReference = 0, reportedReferenceDrops = referenceDrops, captureRoute = 0;
|
||||
int64_t expectedCaptureTime = 0;
|
||||
bool referenceAvailable = false;
|
||||
const char* referenceReason = "empty_history";
|
||||
double referenceGapUs = 0;
|
||||
double rawEnergy = 0, cleanEnergy = 0, referenceEnergy = 0;
|
||||
float rawPeak = 0, cleanPeak = 0, referencePeak = 0;
|
||||
unsigned signalFrames = 0, aecFrames = 0;
|
||||
bool muted = false;
|
||||
auto applyMarker = [&](EventKind kind, bool value) {
|
||||
if (kind == EventKind::Mute) muted = value;
|
||||
@@ -238,6 +243,7 @@ void VoiceAudioIo::captureLoop() {
|
||||
}
|
||||
};
|
||||
auto alignReference = [&]() {
|
||||
referenceReason = "empty_history"; referenceGapUs = 0;
|
||||
if (!historyCount) return false;
|
||||
size_t index = 0;
|
||||
for (unsigned i = 0; i < 960; ++i) {
|
||||
@@ -251,7 +257,11 @@ void VoiceAudioIo::captureLoop() {
|
||||
if (measured > interval * 0.95 && measured < interval * 1.05) interval = measured;
|
||||
}
|
||||
double position = (time - block.time) / interval;
|
||||
if (position < 0 || position >= block.frames) return false;
|
||||
if (position < 0 || position >= block.frames) {
|
||||
referenceReason = position < 0 ? "history_too_new" : index + 1 == historyCount ? "render_not_ready" : "render_gap";
|
||||
referenceGapUs = position < 0 ? -position * interval : (position - block.frames) * interval;
|
||||
return false;
|
||||
}
|
||||
unsigned sample = (unsigned)position;
|
||||
double fraction = position - sample;
|
||||
for (unsigned channel = 0; channel < 2; ++channel) {
|
||||
@@ -260,6 +270,7 @@ void VoiceAudioIo::captureLoop() {
|
||||
referencePcm[2*i+channel] = (int16_t)(first + (second - first) * fraction);
|
||||
}
|
||||
}
|
||||
referenceReason = "aligned";
|
||||
return true;
|
||||
};
|
||||
auto deliver = [&](unsigned frames) {
|
||||
@@ -274,8 +285,42 @@ void VoiceAudioIo::captureLoop() {
|
||||
if (m_kind == Kind::Call && frames == 960) call_audio_feed_prepared_pcm(m_session, cleaned, muteMask);
|
||||
filled = 0; markerCount = 0;
|
||||
};
|
||||
// Отчёт работает и при отсутствии PCM, иначе длительный провал захвата невидим.
|
||||
auto report = [&]() {
|
||||
int64_t now = voice_time_us();
|
||||
if (now - lastReport >= 1000000) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_AEC, "voice-io: capture health session=%llu span=%.1fms pcm_age=%.1fms "
|
||||
"queue_age_max=%.1fms queue=%zu pending=%u sequence=%llu",
|
||||
(unsigned long long)m_session, (now - lastReport) / 1000.0, (now - lastCapture) / 1000.0,
|
||||
maxQueueAge / 1000.0, m_captureEvents.size(), filled, (unsigned long long)expectedSequence);
|
||||
if (now - lastCapture >= 1000000)
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CALL, "voice-io: capture stalled session=%llu no_pcm=%.1fms queue=%zu",
|
||||
(unsigned long long)m_session, (now - lastCapture) / 1000.0, m_captureEvents.size());
|
||||
maxQueueAge = 0;
|
||||
if (signalFrames) {
|
||||
auto db = [](double power) { return 10 * std::log10(std::max(power, 1e-12)); };
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_AEC, "voice-io: signal session=%llu frames=%u aec_frames=%u raw=%.1fdBFS clean=%.1fdBFS "
|
||||
"reference=%.1fdBFS peaks=%.4f/%.4f/%.4f reference_state=%s gap=%.1fus",
|
||||
(unsigned long long)m_session, signalFrames, aecFrames, db(rawEnergy / (signalFrames * 960 * m_channels)),
|
||||
db(cleanEnergy / (signalFrames * 960 * m_channels)), db(aecFrames ? referenceEnergy / (aecFrames * 1920) : 0),
|
||||
rawPeak, cleanPeak, referencePeak, referenceReason, referenceGapUs);
|
||||
signalFrames = aecFrames = 0; rawEnergy = cleanEnergy = referenceEnergy = 0;
|
||||
rawPeak = cleanPeak = referencePeak = 0;
|
||||
}
|
||||
uint64_t captureDrops = m_captureDropped.exchange(0), refDrops = m_referenceDropped.load();
|
||||
uint64_t underruns = m_playbackUnderruns.exchange(0);
|
||||
if (captureDrops || refDrops != reportedReferenceDrops || missingReference || underruns)
|
||||
DEBUG_WARN(DEBUG_CATEGORY_AEC, "voice-io: session=%llu capture_drop=%llu reference_drop=%llu missing_ref=%llu output_missing=%llu",
|
||||
(unsigned long long)m_session, (unsigned long long)captureDrops, (unsigned long long)refDrops,
|
||||
(unsigned long long)missingReference, (unsigned long long)underruns);
|
||||
else DEBUG_DEBUG(DEBUG_CATEGORY_AEC, "voice-io: session=%llu reference history=%zu capture_queue=%zu",
|
||||
(unsigned long long)m_session, historyCount, m_captureEvents.size());
|
||||
missingReference = 0; reportedReferenceDrops = refDrops; lastReport = now;
|
||||
}
|
||||
};
|
||||
bool stopping = false;
|
||||
while (m_capturing && !stopping) {
|
||||
report();
|
||||
consumeReference();
|
||||
Event event{};
|
||||
if (!m_captureEvents.pop(event)) {
|
||||
@@ -289,9 +334,12 @@ void VoiceAudioIo::captureLoop() {
|
||||
else { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "voice-io: too many controls within one capture frame"); stopping = true; }
|
||||
continue;
|
||||
}
|
||||
if (voice_time_us() - event.enqueued > 100000) {
|
||||
int64_t received = voice_time_us();
|
||||
maxQueueAge = std::max(maxQueueAge, received - event.enqueued);
|
||||
if (received - event.enqueued > 100000) {
|
||||
m_captureDropped.fetch_add(event.frames); continue;
|
||||
}
|
||||
lastCapture = received;
|
||||
bool clockJump = expectedCaptureTime && std::abs(event.time - expectedCaptureTime) > 20000;
|
||||
if (event.sequence != expectedSequence || (captureRoute && event.route != captureRoute) || clockJump) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CALL, "voice-io: capture discontinuity expected=%llu actual=%llu pending=%u route=%016llx->%016llx",
|
||||
@@ -315,28 +363,34 @@ void VoiceAudioIo::captureLoop() {
|
||||
memcpy(cleaned, raw, 960 * m_channels * sizeof(int16_t));
|
||||
consumeReference();
|
||||
bool available = aec && alignReference();
|
||||
if (!aec) referenceReason = "disabled";
|
||||
if (available) {
|
||||
speex_aec_feed_playback(aec, referencePcm, 1920);
|
||||
speex_aec_process_capture(aec, raw, 960 * m_channels, cleaned);
|
||||
} else if (aec) {
|
||||
++missingReference;
|
||||
if (referenceAvailable) speex_aec_reset(aec);
|
||||
if (referenceAvailable) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_AEC, "voice-io: AEC bypass/reset session=%llu reason=%s gap=%.1fus history=%zu capture_time=%lld",
|
||||
(unsigned long long)m_session, referenceReason, referenceGapUs, historyCount, (long long)frameTime);
|
||||
speex_aec_reset(aec);
|
||||
}
|
||||
}
|
||||
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_AEC)) {
|
||||
++signalFrames; if (available) ++aecFrames;
|
||||
for (int i = 0; i < 960 * m_channels; ++i) {
|
||||
float in = raw[i] / 32768.0f, out = cleaned[i] / 32768.0f;
|
||||
rawEnergy += in * in; cleanEnergy += out * out;
|
||||
rawPeak = std::max(rawPeak, std::abs(in)); cleanPeak = std::max(cleanPeak, std::abs(out));
|
||||
}
|
||||
if (available) for (int i = 0; i < 1920; ++i) {
|
||||
float ref = referencePcm[i] / 32768.0f;
|
||||
referenceEnergy += ref * ref; referencePeak = std::max(referencePeak, std::abs(ref));
|
||||
}
|
||||
}
|
||||
referenceAvailable = available;
|
||||
deliver(960);
|
||||
}
|
||||
int64_t now = voice_time_us();
|
||||
if (now - lastReport >= 1000000) {
|
||||
uint64_t captureDrops = m_captureDropped.exchange(0), refDrops = m_referenceDropped.load();
|
||||
uint64_t underruns = m_playbackUnderruns.exchange(0);
|
||||
if (captureDrops || refDrops != reportedReferenceDrops || missingReference || underruns)
|
||||
DEBUG_WARN(DEBUG_CATEGORY_AEC, "voice-io: session=%llu capture_drop=%llu reference_drop=%llu missing_ref=%llu output_missing=%llu",
|
||||
(unsigned long long)m_session, (unsigned long long)captureDrops, (unsigned long long)refDrops,
|
||||
(unsigned long long)missingReference, (unsigned long long)underruns);
|
||||
else DEBUG_DEBUG(DEBUG_CATEGORY_AEC, "voice-io: session=%llu aligned history=%zu capture_queue=%zu",
|
||||
(unsigned long long)m_session, historyCount, m_captureEvents.size());
|
||||
missingReference = 0; reportedReferenceDrops = refDrops; lastReport = now;
|
||||
}
|
||||
|
||||
}
|
||||
if (filled) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CALL, "voice-io: capture tail=%u frames bypasses AEC; call tail discarded, radio tail preserved", filled);
|
||||
|
||||
Reference in new issue
Block a user