75 lines
4.4 KiB
C++
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
|