Skip to content

Commit 6a0e672

Browse files
committed
resampler: Bound delay_line::output reads to buffered frames
The pointer-returning delay_line::output copied frames_to_samples(frames_needed) samples out of delay_input_buffer unconditionally. When the delay line holds fewer frames than requested -- as happens at stream startup or during a capture underrun in a duplex stream that resamples only its output side -- this read past the end of the buffer. Copy only the buffered samples, zero-pad the remainder, and report the number of frames actually consumed, mirroring the underrun handling already present in cubeb_resampler_speex_one_way::output.
1 parent 6ad18e2 commit 6a0e672

2 files changed

Lines changed: 49 additions & 6 deletions

File tree

src/cubeb_resampler_internal.h

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -432,15 +432,24 @@ template <typename T> class delay_line : public processor {
432432
* hold onto the pointer. */
433433
T * output(uint32_t frames_needed, size_t * input_frames_used)
434434
{
435-
if (delay_output_buffer.capacity() < frames_to_samples(frames_needed)) {
436-
delay_output_buffer.reserve(frames_to_samples(frames_needed));
435+
size_t samples_needed = frames_to_samples(frames_needed);
436+
if (delay_output_buffer.capacity() < samples_needed) {
437+
delay_output_buffer.reserve(samples_needed);
437438
}
438439

439440
delay_output_buffer.clear();
440-
delay_output_buffer.push(delay_input_buffer.data(),
441-
frames_to_samples(frames_needed));
442-
delay_input_buffer.pop(nullptr, frames_to_samples(frames_needed));
443-
*input_frames_used = frames_needed;
441+
442+
// Copy only what is buffered and zero-pad the rest, in case fewer frames
443+
// are available than requested.
444+
size_t samples_available =
445+
std::min(samples_needed, delay_input_buffer.length());
446+
447+
delay_output_buffer.push(delay_input_buffer.data(), samples_available);
448+
if (samples_available < samples_needed) {
449+
delay_output_buffer.push_silence(samples_needed - samples_available);
450+
}
451+
delay_input_buffer.pop(nullptr, samples_available);
452+
*input_frames_used = samples_to_frames(samples_available);
444453

445454
return delay_output_buffer.data();
446455
}

test/test_resampler.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1169,6 +1169,40 @@ TEST(cubeb, individual_methods)
11691169
ASSERT_EQ(frames_needed2, 0u);
11701170
}
11711171

1172+
TEST(cubeb, delay_line_output_underrun)
1173+
{
1174+
const uint32_t channels = 2;
1175+
const uint32_t sample_rate = 48000;
1176+
const uint32_t latency_frames = 40;
1177+
1178+
delay_line<float> dl(latency_frames, channels, sample_rate);
1179+
1180+
const uint32_t pushed_frames = 4;
1181+
float in[pushed_frames * channels];
1182+
for (uint32_t i = 0; i < pushed_frames * channels; i++) {
1183+
in[i] = static_cast<float>(i + 1);
1184+
}
1185+
dl.input(in, pushed_frames);
1186+
1187+
const uint32_t frames_needed = 482;
1188+
size_t input_frames_used = 0;
1189+
float * out = dl.output(frames_needed, &input_frames_used);
1190+
1191+
ASSERT_NE(out, nullptr);
1192+
ASSERT_EQ(input_frames_used, latency_frames + pushed_frames);
1193+
1194+
for (uint32_t i = 0; i < latency_frames * channels; i++) {
1195+
ASSERT_EQ(out[i], 0.0f);
1196+
}
1197+
for (uint32_t i = 0; i < pushed_frames * channels; i++) {
1198+
ASSERT_EQ(out[latency_frames * channels + i], in[i]);
1199+
}
1200+
for (uint32_t i = (latency_frames + pushed_frames) * channels;
1201+
i < frames_needed * channels; i++) {
1202+
ASSERT_EQ(out[i], 0.0f);
1203+
}
1204+
}
1205+
11721206
struct sine_wave_state {
11731207
float frequency;
11741208
int sample_rate;

0 commit comments

Comments
 (0)