Files
utun2/src/call/call_delay.h
T

75 lines
4.4 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 };
struct Stats {
Mode mode = Mode::Direct;
double tempo = 1, pending = 0; // Заданный темп и ещё не выданная длительность исходного PCM в сэмплах.
uint64_t cuts = 0, entered = 0, exited = 0;
unsigned phase = 0; // В Direct: ошибка относительно 20мс выхода; 0 — границы совпадают.
};
CallDelay(Input input, void* input_context, soundtouch::SourceCallback source, void* source_context);
CallDelay(const CallDelay&) = delete;
CallDelay& operator=(const CallDelay&) = delete;
bool valid() const { return !failed; }
unsigned pull(int16_t* output, unsigned count, uint64_t queued, unsigned reserve);
void reset();
void silence(unsigned count); // Тишина устройства тоже сдвигает выходную фазу.
Stats stats() const;
private:
static constexpr unsigned Rate = 48000, Frame = 960, Capacity = 8192, Context = 16;
Input input;
void* inputContext;
soundtouch::SourceCallback source;
void* sourceContext;
soundtouch::SoundTouch st;
std::array<float, Capacity> raw{};
struct Span { soundtouch::SourceSpan value{soundtouch::SourceKind::Copy}; };
std::array<Span, 32> spans{};
unsigned spanHead = 0, spanCount = 0;
uint64_t written = 0, cursor = 0, generated = 0, consumed = 0;
uint64_t stBase = 0, stFed = 0, entry = 0, queuedInput = 0;
uint64_t deviceSamples = 0;
int64_t phaseGoal = 0;
double playedSource = 0, tempo = 1, cutPhase = 0;
struct Bucket { uint64_t time = UINT64_MAX; double low = 0, inventory = 0; } buckets[4];
unsigned trim = 0;
bool failed = false, firstSpan = false, lastCopy = false;
Mode mode = Mode::Direct;
uint64_t cuts = 0, entered = 0, exited = 0;
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