Browse Source

Test gradual audio buffer adaptation and nonblocking microphone stop

master
evgeny 2 days ago
parent
commit
ca09073a52
  1. 2
      tools/chatgui/CMakeLists.txt
  2. 75
      tools/chatgui/tests/test_audio_recovery.cpp

2
tools/chatgui/CMakeLists.txt

@ -677,6 +677,8 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
set_tests_properties(test_audio_recovery PROPERTIES TIMEOUT 15)
add_test(NAME test_microphone_watchdog COMMAND test_audio_recovery --microphone-watchdog)
set_tests_properties(test_microphone_watchdog PROPERTIES TIMEOUT 25)
add_test(NAME test_audio_buffer_tuning COMMAND test_audio_recovery --buffer-tuning)
set_tests_properties(test_audio_buffer_tuning PROPERTIES TIMEOUT 20)
add_executable(test_radio_panel tests/test_radio_panel.cpp src/radiopanel.cpp src/radio_audio_engine.cpp
src/radiosettingspage.cpp src/ptt_hotkey_manager.cpp
src/sound_manager.cpp src/voice_audio_io.cpp src/audio_device.cpp

75
tools/chatgui/tests/test_audio_recovery.cpp

@ -321,6 +321,80 @@ static void microphone_watchdog(SoundManager* manager) {
qInfo("microphone watchdog: silence, no callbacks, null PCM, zero frames, no retry, nonblocking close, RX retained PASS");
}
/* Управляемое время проверяет длительные интервалы и предел без минут ожидания. */
static void buffer_tuning_policy() {
AudioBufferTuning tuning;
tuning.begin(0, 10, 3);
REQUIRE(tuning.observe(500, 100) == AudioBufferTuning::Unchanged && !tuning.requestedMs);
REQUIRE(tuning.observe(1500, 100) == AudioBufferTuning::Unchanged); // Одна пачка — один сбой.
REQUIRE(tuning.observe(7000, 1) == AudioBufferTuning::Unchanged); // Старый единичный сбой уже забыт.
REQUIRE(tuning.observe(7200, 1) == AudioBufferTuning::Increase && tuning.requestedMs == 15);
tuning.begin(7300, 15, 3);
REQUIRE(tuning.observe(7500, 100) == AudioBufferTuning::Unchanged);
REQUIRE(tuning.observe(8500, 1) == AudioBufferTuning::Unchanged);
REQUIRE(tuning.observe(8700, 1) == AudioBufferTuning::Unchanged && tuning.requestedMs == 15);
REQUIRE(tuning.observe(12200, 0) == AudioBufferTuning::Unchanged); // Один лишь таймер не запускает reopen.
REQUIRE(tuning.observe(12200, 1) == AudioBufferTuning::Increase && tuning.requestedMs == 20);
int64_t now = 12200;
for (unsigned expected : {25u, 30u, 33u}) {
tuning.begin(now + 100, tuning.requestedMs, 3); now += 5000;
REQUIRE(tuning.observe(now, 1) == AudioBufferTuning::Unchanged);
REQUIRE(tuning.observe(now + 200, 1) == AudioBufferTuning::Increase && tuning.requestedMs == expected);
REQUIRE(tuning.requestedMs * tuning.periods <= 100); now += 200;
}
tuning.begin(now + 100, 33, 3); now += 5000;
REQUIRE(tuning.observe(now, 1) == AudioBufferTuning::Unchanged);
REQUIRE(tuning.observe(now + 200, 1) == AudioBufferTuning::Limit && tuning.requestedMs == 33);
ma_context context{}; context.backend = ma_backend_pulseaudio;
auto config = ma_device_config_init(ma_device_type_capture); config.periodSizeInMilliseconds = 20;
AudioDevice::requestLowLatency(&context, config);
REQUIRE(config.periodSizeInMilliseconds == 10 && !config.periodSizeInFrames);
}
/* Та же notification, что у WASAPI/ALSA/PA; устройства и Qt recovery настоящие, backend виртуальный. */
static void inject_capture_glitch() {
std::lock_guard guard(deviceMutex);
for (auto* device : devices) if (device->type == ma_device_type_capture) {
ma_device_notification event{}; event.pDevice = device; event.type = ma_device_notification_type_capture_overrun;
device->onNotification(&event);
}
}
static void adaptive_buffers(SoundManager* manager) {
buffer_tuning_policy();
instance.ua = uasync_create(); REQUIRE(instance.ua);
REQUIRE(radio_init_instance(&instance) == 0);
REQUIRE(call_audio_init(&instance) == 0 && radio_audio_init(&instance) == 0);
auto* call = CallAudioEngine::instance(); auto* radio = RadioAudioEngine::instance();
REQUIRE(call->start(70) == 0 && radio->start(71) == 0);
pump(1300);
std::vector<unsigned> initial;
for (auto* device : devices) if (device->type == ma_device_type_capture) initial.push_back(device->capture.internalPeriodSizeInFrames);
std::sort(initial.begin(), initial.end()); REQUIRE(initial.size() == 2);
int before = captureStarts;
inject_capture_glitch(); pump(250); REQUIRE(captureStarts == before);
inject_capture_glitch(); pump(600); REQUIRE(captureStarts == before + 2);
std::vector<unsigned> grown;
for (auto* device : devices) if (device->type == ma_device_type_capture) grown.push_back(device->capture.internalPeriodSizeInFrames);
std::sort(grown.begin(), grown.end()); REQUIRE(grown.size() == 2);
REQUIRE(grown[0] == initial[0] + 240 && grown[1] == initial[1] + 240);
REQUIRE(call->captureError().isEmpty() && radio->captureError().isEmpty() && radio->captureAvailable());
pump(4500); slowCaptureClose = true;
inject_capture_glitch(); pump(250); inject_capture_glitch(); pump(250);
QElapsedTimer elapsed; elapsed.start(); call->stop(); radio->stop();
REQUIRE(elapsed.elapsed() < 300); // Stop не ждёт выполняющееся закрытие для изменения буфера.
pump(900); slowCaptureClose = false;
REQUIRE(call->start(72) == 0 && radio->start(73) == 0); pump(100);
std::vector<unsigned> reset;
for (auto* device : devices) if (device->type == ma_device_type_capture) reset.push_back(device->capture.internalPeriodSizeInFrames);
std::sort(reset.begin(), reset.end()); REQUIRE(reset == initial);
call->stop(); radio->stop(); pump(50);
call_audio_destroy(&instance); radio_audio_destroy(&instance); radio_destroy_instance(&instance);
uasync_destroy(instance.ua, 0); instance.ua = nullptr;
manager->shutdown(); REQUIRE(devices.empty());
qInfo("adaptive buffers: startup, isolated faults, cooldown, limit, actual reopen, async stop, new session baseline PASS");
}
int main(int argc, char** argv) {
QCoreApplication app(argc, argv);
latencyProbe = argc >= 2 && std::strcmp(argv[1], "--latency") == 0;
@ -346,6 +420,7 @@ int main(int argc, char** argv) {
auto* manager = SoundManager::instance();
REQUIRE(manager->init() && manager->context());
if (argc == 2 && std::strcmp(argv[1], "--microphone-watchdog") == 0) { microphone_watchdog(manager); return 0; }
if (argc == 2 && std::strcmp(argv[1], "--buffer-tuning") == 0) { adaptive_buffers(manager); return 0; }
if (latencyProbe) { measure_latency(manager, seconds, kind); return 0; }
if (pulseIntegration) { pulse_recovery(manager, QString::fromLocal8Bit(argv[2])); return 0; }
ma_device_id id{};

Loading…
Cancel
Save