2 changed files with 200 additions and 90 deletions
@ -0,0 +1,135 @@
|
||||
// Настоящий SoundTouch: сверяем PCM с картой источников и проверяем обе границы переключения.
|
||||
#include "call/call_delay.h" |
||||
#include "call/call_timeline.h" |
||||
#include "debug_config.h" |
||||
#include <algorithm> |
||||
#include <cmath> |
||||
#include <cstdlib> |
||||
#include <deque> |
||||
#include <vector> |
||||
|
||||
#define CHECK(x) do { if (!(x)) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "delay test line=%d: %s", __LINE__, #x); std::exit(1); } } while (0) |
||||
|
||||
static int16_t signal(uint64_t index) { |
||||
// Непериодическая последовательность различает соседние сэмплы и ошибки на границе.
|
||||
uint64_t v = index * 6364136223846793005ULL + 1442695040888963407ULL; |
||||
return (int16_t)((int)(v >> 48) - 32768); |
||||
} |
||||
|
||||
struct Trial { |
||||
CallTimeline timeline; |
||||
CallDelay delay; |
||||
uint64_t available = 0, supplied = 0, generated = 0, device = 0, pulls = 0; |
||||
uint64_t boundaries = 0, previousEnd = 0, lastEntered = 0, lastExited = 0; |
||||
soundtouch::SourceKind previousKind = soundtouch::SourceKind::Copy; |
||||
std::deque<int16_t> expected; |
||||
int64_t previousPts = -1; |
||||
|
||||
Trial() : delay(read, this, source, this) { timeline.reset(1); CHECK(delay.valid()); } |
||||
|
||||
static unsigned read(void* context, int16_t* pcm, unsigned capacity) { |
||||
auto& t = *static_cast<Trial*>(context); |
||||
unsigned n = (unsigned)std::min<uint64_t>(t.available, capacity); |
||||
if (!n) return 0; |
||||
t.timeline.feed(1000000 + (int64_t)t.supplied * 1000000 / 48000, n, 9); |
||||
for (unsigned i = 0; i < n; ++i) pcm[i] = signal(t.supplied + i); |
||||
t.supplied += n; t.available -= n; |
||||
return n; |
||||
} |
||||
|
||||
static void source(void* context, const soundtouch::SourceSpan& s) { |
||||
using soundtouch::SourceKind; |
||||
auto& t = *static_cast<Trial*>(context); |
||||
CHECK(s.kind == SourceKind::Copy || s.kind == SourceKind::Mix); |
||||
CHECK(s.output == t.generated && s.count > 0); |
||||
CHECK(s.input0 + s.count <= t.supplied); |
||||
if (s.kind == SourceKind::Mix) CHECK(s.input1 + s.count <= t.supplied); |
||||
auto state = t.delay.stats(); |
||||
if (state.entered != t.lastEntered || state.exited != t.lastExited) { |
||||
CHECK(t.previousKind == SourceKind::Copy && s.kind == SourceKind::Copy); |
||||
CHECK(s.input0 == t.previousEnd); // Нет пропуска или повторного PCM именно на переходе.
|
||||
++t.boundaries; |
||||
t.lastEntered = state.entered; t.lastExited = state.exited; |
||||
} |
||||
for (unsigned i = 0; i < s.count; ++i) { |
||||
double value = signal(s.input0 + i); |
||||
if (s.kind == SourceKind::Mix) value += (signal(s.input1 + i) - value) * i / s.count; |
||||
t.expected.push_back((int16_t)std::lround(value)); |
||||
} |
||||
t.previousKind = s.kind; t.previousEnd = s.input0 + s.count; |
||||
t.generated += s.count; |
||||
t.timeline.source(s); |
||||
} |
||||
|
||||
unsigned pull(unsigned count, unsigned reserve = 960, bool full = true) { |
||||
std::vector<int16_t> pcm(count); |
||||
unsigned n = delay.pull(pcm.data(), count, available, reserve); |
||||
CHECK(delay.valid()); |
||||
if (full) CHECK(n == count); |
||||
for (unsigned i = 0; i < n; ++i) { |
||||
CHECK(!expected.empty()); |
||||
CHECK(std::abs((int)pcm[i] - expected.front()) <= 1); |
||||
expected.pop_front(); |
||||
} |
||||
timeline.read(n); device += n; ++pulls; |
||||
// Прогресс реального устройства запаздывает; карта обязана пережить смену DSP до доигрывания его PCM.
|
||||
int64_t now = (int64_t)device * 1000000 / 48000; |
||||
timeline.progress(1, device, now + 20000, now, true); |
||||
auto clock = timeline.clock(now); |
||||
if (device > 1920) { |
||||
CHECK(clock.valid && clock.epoch == 1 && clock.source_epoch == 9); |
||||
CHECK(clock.pts_us >= previousPts); previousPts = clock.pts_us; |
||||
} |
||||
return n; |
||||
} |
||||
}; |
||||
|
||||
static void steady(unsigned block, unsigned phase) { |
||||
Trial t; |
||||
t.available = 60 * 960; |
||||
uint64_t next = phase; |
||||
for (uint64_t time = 0; time < 60 * 48000; time += block) { |
||||
while (next <= time) { t.available += 960; next += 960; } |
||||
t.pull(block); |
||||
} |
||||
auto s = t.delay.stats(); |
||||
CHECK(s.mode == CallDelay::Mode::Direct && s.entered && s.exited && t.boundaries >= 2); |
||||
CHECK(s.phase == 0 && s.tempo == 1); |
||||
CHECK(t.available + s.pending < 960 * 4); |
||||
DEBUG_INFO(DEBUG_CATEGORY_CALL, "PASS delay steady block=%u phase=%u boundaries=%llu cuts=%llu pending=%.2fms aligned=%u", |
||||
block, phase, (unsigned long long)t.boundaries, (unsigned long long)s.cuts, (t.available + s.pending) / 48, s.phase); |
||||
} |
||||
|
||||
static void switching() { |
||||
Trial t; |
||||
for (unsigned cycle = 0; cycle < 100; ++cycle) { |
||||
t.available += 20 * 960; |
||||
// Входной избыток включает SoundTouch; рост сетевого резерва вызывает выход даже посреди блока.
|
||||
for (unsigned tick = 0; tick < 80; ++tick) { |
||||
if (!(tick % 2)) t.available += 960; |
||||
unsigned reserve = tick < 40 ? 960 : (unsigned)(t.available + t.delay.stats().pending + 960); |
||||
t.pull(480, reserve); |
||||
} |
||||
CHECK(t.delay.stats().mode == CallDelay::Mode::Direct); |
||||
} |
||||
CHECK(t.boundaries >= 200); |
||||
DEBUG_INFO(DEBUG_CATEGORY_CALL, "PASS delay repeated switching boundaries=%llu", (unsigned long long)t.boundaries); |
||||
} |
||||
|
||||
static void tail_and_reset() { |
||||
Trial t; t.available = 100 * 960; |
||||
while (t.pull(777, 960, false)) {} |
||||
CHECK(t.expected.empty()); |
||||
CHECK(t.previousKind == soundtouch::SourceKind::Copy && t.previousEnd == t.supplied); |
||||
CHECK(t.delay.stats().pending == 0); |
||||
t.delay.reset(); |
||||
CHECK(t.delay.stats().pending == 0 && t.delay.stats().cuts == 0 && t.delay.stats().phase == 0); |
||||
DEBUG_INFO(DEBUG_CATEGORY_CALL, "PASS delay final tail preserved without flush, reset clears phase/history"); |
||||
} |
||||
|
||||
int main() { |
||||
debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO); |
||||
for (unsigned block : {240, 480, 777, 960}) for (unsigned phase : {0, 239, 479, 719, 959}) steady(block, phase); |
||||
switching(); tail_and_reset(); |
||||
return 0; |
||||
} |
||||
Loading…
Reference in new issue