2 changed files with 88 additions and 1 deletions
@ -0,0 +1,86 @@ |
|||||||
|
/* Проверяем позицию по самому сигналу: каждый четырёхсэмпловый фрагмент уникален.
|
||||||
|
* Сравниваем номинальный feed/tempo с фактическим потреблением входа SoundTouch. */ |
||||||
|
#include "soundtouch/SoundTouch.h" |
||||||
|
#include "../../../lib/debug_config.h" |
||||||
|
#include <algorithm> |
||||||
|
#include <cmath> |
||||||
|
#include <cstdint> |
||||||
|
#include <cstdlib> |
||||||
|
#include <cstring> |
||||||
|
#include <unordered_map> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
#define CHECK(x) do { if (!(x)) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "clock test line=%d: %s", __LINE__, #x); exit(1); } } while (0) |
||||||
|
|
||||||
|
struct Span { double first, last, sourceFirst, sourceLast; }; |
||||||
|
static double position(const std::vector<Span>& spans, size_t& index, double output) { |
||||||
|
while (index + 1 < spans.size() && spans[index].last <= output) index++; |
||||||
|
const auto& s = spans[index]; |
||||||
|
return s.sourceFirst + (s.sourceLast - s.sourceFirst) * (output - s.first) / (s.last - s.first); |
||||||
|
} |
||||||
|
|
||||||
|
static void check_clock(unsigned mode) { |
||||||
|
constexpr unsigned frames = 3000, block = 960; |
||||||
|
std::vector<float> source(frames * block), output; |
||||||
|
std::unordered_multimap<uint32_t, unsigned> locations; |
||||||
|
uint32_t seed = 123456789; |
||||||
|
for (unsigned i = 0; i < source.size(); i++) { |
||||||
|
seed ^= seed << 13; seed ^= seed >> 17; seed ^= seed << 5; |
||||||
|
source[i] = (int32_t)(seed & 0xffffff) / 16777216.0f - 0.5f; |
||||||
|
uint32_t bits; std::memcpy(&bits, &source[i], sizeof(bits)); locations.emplace(bits, i); |
||||||
|
} |
||||||
|
soundtouch::SoundTouch st; |
||||||
|
st.setSampleRate(48000); st.setChannels(1); |
||||||
|
st.setSetting(SETTING_USE_QUICKSEEK, 1); st.setSetting(SETTING_SEQUENCE_MS, 20); |
||||||
|
st.setSetting(SETTING_SEEKWINDOW_MS, 10); st.setSetting(SETTING_OVERLAP_MS, 4); |
||||||
|
st.setSetting(SETTING_USE_AA_FILTER, 0); |
||||||
|
std::vector<Span> nominal, consumed; |
||||||
|
double expected = 0, mappedEnd = 0; |
||||||
|
unsigned produced = 0; |
||||||
|
for (unsigned i = 0; i < frames; i++) { |
||||||
|
double triangle = std::abs((int)(i % 100) - 50) / 50.0; |
||||||
|
double tempo = mode == 0 ? 1.6 : mode == 1 ? 1.0 + 0.6 * triangle : (i % 2 ? 1.6 : 1.0); |
||||||
|
st.setTempo(tempo); |
||||||
|
nominal.push_back({expected, expected + block / tempo, (double)i * block, (double)(i + 1) * block}); |
||||||
|
expected += block / tempo; |
||||||
|
unsigned before = st.numSamples(); |
||||||
|
st.putSamples(source.data() + i * block, block); |
||||||
|
unsigned added = st.numSamples() - before; |
||||||
|
if (added) { |
||||||
|
// Середина seek-window компенсирует стартовый сдвиг WSOLA; звук не изменяем.
|
||||||
|
double end = (i + 1) * block - st.numUnprocessedSamples() + 240.0; |
||||||
|
consumed.push_back({(double)produced, (double)produced + added, mappedEnd, end}); |
||||||
|
mappedEnd = end; produced += added; |
||||||
|
} |
||||||
|
float pcm[480]; |
||||||
|
for (;;) { |
||||||
|
unsigned count = st.receiveSamples(pcm, 480); |
||||||
|
if (!count) break; |
||||||
|
output.insert(output.end(), pcm, pcm + count); |
||||||
|
} |
||||||
|
} |
||||||
|
size_t nominalIndex = 0, consumedIndex = 0; |
||||||
|
unsigned matches = 0; |
||||||
|
double maxNominal = 0, maxConsumed = 0; |
||||||
|
for (unsigned i = 0; i + 4 < output.size(); i += 97) { |
||||||
|
uint32_t bits; std::memcpy(&bits, &output[i], sizeof(bits)); |
||||||
|
auto found = locations.equal_range(bits); |
||||||
|
for (auto it = found.first; it != found.second; ++it) { |
||||||
|
unsigned at = it->second; |
||||||
|
if (at + 4 > source.size() || std::memcmp(&source[at], &output[i], 4 * sizeof(float))) continue; |
||||||
|
maxNominal = std::max(maxNominal, std::abs(position(nominal, nominalIndex, i) - at) / 48.0); |
||||||
|
maxConsumed = std::max(maxConsumed, std::abs(position(consumed, consumedIndex, i) - at) / 48.0); |
||||||
|
matches++; break; |
||||||
|
} |
||||||
|
} |
||||||
|
DEBUG_INFO(DEBUG_CATEGORY_CALL, "SoundTouch clock mode=%u matches=%u nominal_error_ms=%.3f consumed_error_ms=%.3f", |
||||||
|
mode, matches, maxNominal, maxConsumed); |
||||||
|
CHECK(matches > 10000); |
||||||
|
CHECK(maxConsumed < 20.0); |
||||||
|
} |
||||||
|
|
||||||
|
int main() { |
||||||
|
debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO); |
||||||
|
check_clock(0); check_clock(1); check_clock(2); |
||||||
|
return 0; |
||||||
|
} |
||||||
Loading…
Reference in new issue