Skip to content

Commit

Permalink
Test improvements: check continuity
Browse files Browse the repository at this point in the history
  • Loading branch information
padenot committed Jan 30, 2025
1 parent 1fddd7a commit 1f24fd2
Showing 1 changed file with 100 additions and 9 deletions.
109 changes: 100 additions & 9 deletions test/test_resampler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#ifndef NOMINMAX
#define NOMINMAX
#include "cubeb/cubeb.h"
#include "cubeb_audio_dump.h"
#include "cubeb_log.h"
#include "cubeb_resampler.h"
#endif // NOMINMAX
Expand All @@ -16,6 +17,7 @@
#include "cubeb_resampler_internal.h"
#include "gtest/gtest.h"
#include <algorithm>
#include <cmath>
#include <iostream>
#include <stdio.h>

Expand Down Expand Up @@ -1165,19 +1167,56 @@ TEST(cubeb, individual_methods)
ASSERT_EQ(frames_needed2, 0u);
}

constexpr float TOLERANCE =
0.1f; // Allow slight deviation due to resampling artifacts

struct sine_wave_state {
float phase = 0.0f;
float frequency;
int sample_rate;
sine_wave_state(float freq, int rate) : frequency(freq), sample_rate(rate) {}
};

long
data_cb(cubeb_stream * stream, void * user_ptr, void const * input_buffer,
void * output_buffer, long nframes)
{
LOGV("%ld frames requested\n", nframes);
sine_wave_state * state = static_cast<sine_wave_state *>(user_ptr);
float * out = static_cast<float *>(output_buffer);
// never drain, fill with silence
for (int i = 0; i < nframes * 2; i++) {
out[i] = 0.0;
float phase_increment = 2.0f * M_PI * state->frequency / state->sample_rate;

for (int i = 0; i < nframes; i++) {
float sample = sin(state->phase);
state->phase += phase_increment;
if (state->phase > 2.0f * M_PI) {
state->phase -= 2.0f * M_PI;
}
out[i] = sample;
}
return nframes;
}

void
compare_sine_wave(const std::vector<float> & data, float expected_freq,
int sample_rate, float & phase, size_t idx)
{
float phase_increment = 2.0f * M_PI * expected_freq / sample_rate;

for (size_t i = 0; i < data.size(); i++) {
float expected_sample = sin(phase);
if (std::fabs(data[i] - expected_sample) > TOLERANCE) {
printf("index: %zu %f %f %f phase: %f (frame index in stream: %zu)\n", i,
expected_sample, data[i], expected_sample - data[i], phase,
idx + i);
}
ASSERT_LE(std::fabs(data[i] - expected_sample), TOLERANCE);
phase += phase_increment;
if (phase > 2.0f * M_PI) {
phase -= 2.0f * M_PI;
}
}
}

// This tests checks two things:
// - Whenever resampling from a source rate to a target rate with a certain
// block size, the correct number of frames is provided back from the
Expand Down Expand Up @@ -1251,29 +1290,39 @@ TEST(cubeb, resampler_typical_uses)
// IAudioClient2. 96, 192 are not uncommon on some Android devices.
constexpr int WASAPI_MS_BLOCK = 10;
const int block_sizes[] = {
WASAPI_MS_BLOCK, 96, 128, 192, 256, 512, 1024, 2048};
WASAPI_MS_BLOCK}; //, 96, 128, 192, 256, 512, 1024, 2048};
// Enough iterations to catch rounding/drift issues, but not too many to avoid
// having a test that is too long to run.
constexpr int ITERATION_COUNT = 1000;
cubeb * ctx;
const float input_freq = 1000.0f; // 1 kHz input sine wave
common_init(&ctx, "Cubeb resampler test");
// Cartesian product of all parameters
for (int source_rate : rates) {
for (int target_rate : rates) {
// for (int source_rate : {16000}) {
// for (int target_rate : {32000}) {
for (int block_size : block_sizes) {
// special case: Windows/WASAPI works in blocks of 10ms regardless of
// the rate.
if (block_size == WASAPI_MS_BLOCK) {
block_size = target_rate / 100; // 10ms
}
sine_wave_state state(input_freq, source_rate);
cubeb_stream_params out_params = {};
out_params.channels = 2;
out_params.channels = 1;
out_params.rate = target_rate;
out_params.format = CUBEB_SAMPLE_FLOAT32NE;

cubeb_audio_dump_session_t session;
cubeb_audio_dump_init(&session);
cubeb_audio_dump_stream_t dump_stream;
cubeb_audio_dump_stream_init(session, &dump_stream, out_params,
"test.wav");
cubeb_audio_dump_start(session);
cubeb_resampler * resampler = cubeb_resampler_create(
nullptr, nullptr, &out_params, source_rate, data_cb, nullptr,
CUBEB_RESAMPLER_QUALITY_VOIP, CUBEB_RESAMPLER_RECLOCK_NONE);
nullptr, nullptr, &out_params, source_rate, data_cb, &state,
CUBEB_RESAMPLER_QUALITY_DEFAULT, CUBEB_RESAMPLER_RECLOCK_NONE);
ASSERT_NE(resampler, nullptr);

std::vector<float> data(block_size * out_params.channels);
Expand All @@ -1292,17 +1341,59 @@ TEST(cubeb, resampler_typical_uses)
block_size);
monotonic_state out_out_max("out_out", source_rate, target_rate,
block_size);

// cubeb_resampler_skip_zeros(resampler);

float output_phase = 0.0f;
size_t idx = 0;
size_t skipped = 0;
while (i--) {
int64_t got = cubeb_resampler_fill(resampler, nullptr, nullptr,
data.data(), block_size);
ASSERT_EQ(got, block_size);

cubeb_resampler_stats stats = cubeb_resampler_stats_get(resampler);
if (skipped == 0) {
// try to fit a sin at 1khz on a few points
const size_t POINTS = 10;
while (skipped < 1000) {
float phase = 0;
float phase_increment = 2.0f * M_PI * input_freq / target_rate;
bool fits_sine = true;
for (size_t i = 0; i < POINTS; i++) {
float expected = sin(phase);
float actual = data[skipped + i];
if (fabs(expected - actual) > 0.1) {
// doesn't fit a sine, skip to next start point
fits_sine = false;
break;
}
phase += phase_increment;
if (phase > 2.0f * M_PI) {
phase -= 2.0f * M_PI;
}
}
if (!fits_sine) {
skipped++;
continue;
}
size_t sine_start = skipped;
printf("printf: fitted sin at %zu\n", sine_start);
data.erase(data.begin(), data.begin() + sine_start);
skipped = sine_start;
break;
}
}
compare_sine_wave(data, input_freq, target_rate, output_phase, idx);
idx += data.size();
cubeb_audio_dump_write(dump_stream, data.data(), got);
in_in_max.set_new_value(stats.input_input_buffer_size);
in_out_max.set_new_value(stats.input_output_buffer_size);
out_in_max.set_new_value(stats.output_input_buffer_size);
out_out_max.set_new_value(stats.output_output_buffer_size);
}

cubeb_audio_dump_stop(session);
cubeb_audio_dump_stream_shutdown(session, dump_stream);
cubeb_resampler_destroy(resampler);
}
}
Expand Down

0 comments on commit 1f24fd2

Please sign in to comment.