#pragma once #include #include /* Резерв готового PCM desktop: bufferMs = 20..80, кратно 20 (проверяет VoiceAudioIo::start). * Размер callback и настроенный запас задают нижнюю границу; недостача добавляет * один блок, 10 секунд стабильного вывода убирают один. Частично сыгранный блок — сверх резерва. * Только RX worker; время монотонное. Четыре PCM-слота доступны в AudioEventQueue. */ class VoicePlaybackBuffer { public: explicit VoicePlaybackBuffer(unsigned bufferMs = 20) : minimumBlocks(bufferMs / 20) { reset(); } unsigned blocks = 1; unsigned callbackFrames = 960; void reset() { blocks = minimumBlocks; callbackFrames = 960; stableSince = 0; lastGrowth = 0; lastSample = 0; probeUs = 10000000; } void sample(int64_t nowUs, unsigned frames, bool missing) { callbackFrames = frames; unsigned minimum = std::max(minimumBlocks, std::clamp((frames + 959) / 960, 1u, 4u)); if (!stableSince || nowUs - lastSample > 1000000) stableSince = nowUs; lastSample = nowUs; if (minimum > blocks) { blocks = minimum; stableSince = nowUs; } if (missing) { stableSince = nowUs; if (!lastGrowth || nowUs - lastGrowth >= 500000) { blocks = std::min(blocks + 1, 4u); lastGrowth = nowUs; probeUs = std::min(probeUs * 2, int64_t(300000000)); } } else if (blocks > minimum && nowUs - stableSince >= probeUs) { --blocks; stableSince = nowUs; } } private: unsigned minimumBlocks; int64_t stableSince = 0, lastGrowth = 0, lastSample = 0, probeUs = 10000000; };