|
|
|
|
@ -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{}; |
|
|
|
|
|