Keep the borrowed audio provider on UTUN_INSTANCE across chat service restarts. Attach and detach Android voice from the core worker and reject DSP startup after detach. Preserve TX owner epochs, pending slots and foreign-instance RX isolation. Remove the unused cross-thread online API and keep its check local to call.c. Add eight lifecycle regressions with real cores and the Android host bridge. Audit public call, DSP, clock, headless and desktop audio contracts and record the findings and validation scope. Validation: voice/desktop 38/38, four native call suites, native and both Android ABI builds, full check 204/204 plus proxy/burst/load. JVM results remain Gradle UP-TO-DATE (155 app and 15 router). No hardware runtime validation.
81 lines
7.0 KiB
C++
81 lines
7.0 KiB
C++
/* Обработка задержки PCM 48кГц mono: прямое ускорение и SoundTouch с общей шкалой.
|
||
* Один playback worker. input возвращает до capacity сэмплов, 0 — вход исчерпан;
|
||
* до возврата input владелец регистрирует PTS этих сэмплов в своей карте времени.
|
||
* source сообщает происхождение выхода в абсолютных индексах от reset(). Локальные
|
||
* Reset SoundTouch наружу не выходят. Владелец сам сбрасывает карту при reset().
|
||
* queued — ещё не декодированный вход, reserve — сетевой запас, оба в сэмплах.
|
||
* Переключение сохраняет хвост исходного PCM; ускорение учитывается отдельно.
|
||
* Вход в SoundTouch при избытке >=120мс, выход <=40мс; низкий запас дополнительно
|
||
* принуждает выход. Прямой режим ускоряет не более чем на 2%, без замедления.
|
||
* Его цель — целая разность входных/выходных кадров по 960 сэмплов. Нижняя огибающая
|
||
* запаса за 150..200мс подавляет пилу пакетов; удаление останавливается точно на цели.
|
||
* При нехватке входа ускорение не расходует reserve: фазу доводим после восстановления.
|
||
* Это выравнивание границ PCM, не синхронизация физических часов двух аудиоустройств.
|
||
* Внутренние очереди ограничены, размеры pull произвольные, heap в нашем hot path нет. */
|
||
#ifndef CALL_DELAY_H
|
||
#define CALL_DELAY_H
|
||
|
||
#include "soundtouch/SoundTouch.h"
|
||
#include <array>
|
||
#include <cstdint>
|
||
|
||
class CallDelay {
|
||
public:
|
||
using Input = unsigned (*)(void*, int16_t*, unsigned);
|
||
enum class Mode { Direct, Stretch, Drain }; // Прямая выдача, подача в SoundTouch, выдача готового DSP-хвоста.
|
||
struct Stats {
|
||
Mode mode = Mode::Direct;
|
||
double tempo = 1, pending = 0; // Заданный темп и ещё не выданная длительность исходного PCM в сэмплах.
|
||
uint64_t cuts = 0, entered = 0, exited = 0; // Удалённые Direct-сэмплы и переходы в/из SoundTouch с последнего reset.
|
||
unsigned phase = 0; // В Direct: ошибка относительно 20мс выхода; 0 — границы совпадают.
|
||
};
|
||
// Оба callback обязательны; контексты заимствованы до уничтожения. После создания проверить valid().
|
||
CallDelay(Input input, void* input_context, soundtouch::SourceCallback source, void* source_context);
|
||
CallDelay(const CallDelay&) = delete;
|
||
CallDelay& operator=(const CallDelay&) = delete;
|
||
// Ошибка обработки необратима для объекта; reset не восстанавливает valid, нужен новый экземпляр.
|
||
bool valid() const { return !failed; }
|
||
// Выдать 0..count PCM-сэмплов, без дополнения тишиной; queued не включает уже принятый этим объектом PCM.
|
||
// output/count должны быть валидны; нехватка input даёт короткую выдачу, ошибка делает valid()==false.
|
||
unsigned pull(int16_t* output, unsigned count, uint64_t queued, unsigned reserve);
|
||
// Удалить PCM/DSP-хвост, счётчики и фазу; внешнюю PTS-карту владелец сбрасывает отдельно.
|
||
void reset();
|
||
void silence(unsigned count); // Тишина устройства тоже сдвигает выходную фазу.
|
||
// Снимок единственного worker; pending — исходные сэмплы, ещё не выданные, не размер готового DSP-выхода.
|
||
Stats stats() const;
|
||
|
||
private:
|
||
static constexpr unsigned Rate = 48000, Frame = 960, Capacity = 8192, Context = 16;
|
||
Input input; // Заимствованный reader исходного PCM.
|
||
void* inputContext;
|
||
soundtouch::SourceCallback source; // Заимствованный получатель происхождения выходного PCM.
|
||
void* sourceContext;
|
||
soundtouch::SoundTouch st; // DSP только режима Stretch; готовый выход дочитывается в Drain.
|
||
std::array<float, Capacity> raw{}; // Кольцо исходного PCM, включая контекст перед границей Direct/Stretch.
|
||
struct Span { soundtouch::SourceSpan value{soundtouch::SourceKind::Copy}; };
|
||
std::array<Span, 32> spans{}; // Произведение DSP/Direct, ещё не учтённое consume.
|
||
unsigned spanHead = 0, spanCount = 0; // Начало/число действующих spans.
|
||
uint64_t written = 0, cursor = 0, generated = 0, consumed = 0; // Принятый вход, позиция raw, произведённый/выданный выход.
|
||
uint64_t stBase = 0, stFed = 0, entry = 0, queuedInput = 0; // Начало DSP-шкалы, переданный вход, граница входа, внешний FIFO.
|
||
uint64_t deviceSamples = 0; // Выданный PCM плюс объявленная локальная тишина.
|
||
int64_t phaseGoal = 0; // Целевая разность cursor-deviceSamples, кратная Frame.
|
||
double playedSource = 0, tempo = 1, cutPhase = 0; // Позиция источника на выходе, темп, дробный долг удаления Direct.
|
||
struct Bucket { uint64_t time = UINT64_MAX; double low = 0, inventory = 0; } buckets[4]; // Четыре интервала по 50мс.
|
||
unsigned trim = 0; // Уже выданный Direct-контекст, который нужно убрать с начала SoundTouch.
|
||
bool failed = false, firstSpan = false, lastCopy = false; // Необратимая ошибка; ожидаем первый DSP-span; хвост — Copy.
|
||
Mode mode = Mode::Direct; // Текущий способ выдачи PCM.
|
||
uint64_t cuts = 0, entered = 0, exited = 0; // Счётчики Direct-удалений и переходов режима с последнего reset.
|
||
|
||
bool fill();
|
||
bool pump();
|
||
void beginStretch();
|
||
void endStretch();
|
||
void emit(soundtouch::SourceSpan span);
|
||
void onSource(const soundtouch::SourceSpan& span);
|
||
void consume(unsigned count);
|
||
unsigned direct(int16_t* output, unsigned count, unsigned reserve, unsigned remaining);
|
||
double lowDepth(double depth, double& inventory);
|
||
void fail(const char* reason);
|
||
};
|
||
#endif
|