Skip to content

Commit dd1b785

Browse files
committed
resampler: pad duplex capture underruns
1 parent cdb54bb commit dd1b785

2 files changed

Lines changed: 116 additions & 1 deletion

File tree

src/cubeb_resampler.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,10 @@ cubeb_resampler_speex<T, InputProcessor, OutputProcessor>::fill_internal_duplex(
315315
input_queue.push(in_buffer, *input_frames_count * input_channels);
316316
long available = static_cast<long>(input_queue.length() / input_channels);
317317
if (available < output_frames_before_processing) {
318-
output_frames_before_processing = available;
318+
/* Capture underrun: pad with silence rather than presenting fewer
319+
* frames to the callback, which would shorten the output. */
320+
input_queue.push_silence((output_frames_before_processing - available) *
321+
input_channels);
319322
}
320323
resampled_input = input_queue.data();
321324
*input_frames_count = original_count;

test/test_resampler.cpp

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,6 +1109,38 @@ input_queue_cb(cubeb_stream *, void * ptr, const void * in, void * out, long n)
11091109
return n;
11101110
}
11111111

1112+
struct duplex_underrun_state {
1113+
long expected_callback_frames = 0;
1114+
long real_input_frames = 0;
1115+
long callback_count = 0;
1116+
long last_callback_frames = 0;
1117+
};
1118+
1119+
static long
1120+
backend_duplex_underrun_cb(cubeb_stream *, void * ptr, const void * in,
1121+
void * out, long n)
1122+
{
1123+
duplex_underrun_state * state = static_cast<duplex_underrun_state *>(ptr);
1124+
state->callback_count++;
1125+
state->last_callback_frames = n;
1126+
1127+
EXPECT_NE(in, nullptr);
1128+
EXPECT_NE(out, nullptr);
1129+
EXPECT_EQ(n, state->expected_callback_frames);
1130+
1131+
const float * input = static_cast<const float *>(in);
1132+
for (long i = 0; i < std::min(n, state->real_input_frames); i++) {
1133+
EXPECT_EQ(input[i], 1.0f);
1134+
}
1135+
for (long i = state->real_input_frames; i < n; i++) {
1136+
EXPECT_EQ(input[i], 0.0f);
1137+
}
1138+
1139+
float * output = static_cast<float *>(out);
1140+
std::fill(output, output + n, 0.0f);
1141+
return n;
1142+
}
1143+
11121144
TEST(cubeb, resampler_speex_input_queue)
11131145
{
11141146
// input_rate == target_rate != output_rate: exercises fill_internal_duplex
@@ -1155,6 +1187,86 @@ TEST(cubeb, resampler_speex_input_queue)
11551187
cubeb_resampler_destroy(r);
11561188
}
11571189

1190+
TEST(cubeb, resampler_passthrough_duplex_capture_underrun_pads_input)
1191+
{
1192+
// Same backend-style short capture block as the Speex input-queue test, but
1193+
// with input/output/target rates matching so cubeb_resampler_create uses the
1194+
// passthrough resampler. Duplex backends treat a short output fill as drain,
1195+
// so capture underruns must not make fill return fewer than requested.
1196+
cubeb_stream_params in_p, out_p;
1197+
in_p.format = out_p.format = CUBEB_SAMPLE_FLOAT32NE;
1198+
in_p.channels = out_p.channels = 1;
1199+
in_p.rate = out_p.rate = 48000;
1200+
in_p.prefs = out_p.prefs = CUBEB_STREAM_PREF_NONE;
1201+
1202+
duplex_underrun_state state;
1203+
cubeb_resampler * r = cubeb_resampler_create(
1204+
nullptr, &in_p, &out_p, 48000, backend_duplex_underrun_cb, &state,
1205+
CUBEB_RESAMPLER_QUALITY_VOIP, CUBEB_RESAMPLER_RECLOCK_NONE);
1206+
ASSERT_NE(r, nullptr);
1207+
1208+
const long out_chunk = 480;
1209+
const long short_input = out_chunk - 60;
1210+
state.expected_callback_frames =
1211+
cubeb_resampler_input_needed_for_output(r, out_chunk);
1212+
state.real_input_frames = short_input;
1213+
ASSERT_EQ(state.expected_callback_frames, out_chunk);
1214+
1215+
std::vector<float> in_buf(short_input, 1.0f);
1216+
std::vector<float> out_buf(out_chunk);
1217+
long in_count = short_input;
1218+
long got = cubeb_resampler_fill(r, in_buf.data(), &in_count, out_buf.data(),
1219+
out_chunk);
1220+
1221+
EXPECT_EQ(in_count, short_input);
1222+
EXPECT_EQ(state.callback_count, 1);
1223+
EXPECT_EQ(state.last_callback_frames, state.expected_callback_frames);
1224+
EXPECT_EQ(got, out_chunk);
1225+
1226+
cubeb_resampler_destroy(r);
1227+
}
1228+
1229+
TEST(cubeb, resampler_duplex_capture_underrun_pads_input_queue)
1230+
{
1231+
// Backend-style duplex call: the input side provides the frames it captured,
1232+
// which can be less than the output resampler needs for the requested output.
1233+
// Duplex backends treat a short output fill as drain, so capture underruns
1234+
// must not make fill return fewer than requested.
1235+
cubeb_stream_params in_p, out_p;
1236+
in_p.format = out_p.format = CUBEB_SAMPLE_FLOAT32NE;
1237+
in_p.channels = out_p.channels = 1;
1238+
in_p.rate = 48000;
1239+
out_p.rate = 44100;
1240+
in_p.prefs = out_p.prefs = CUBEB_STREAM_PREF_NONE;
1241+
1242+
duplex_underrun_state state;
1243+
cubeb_resampler * r = cubeb_resampler_create(
1244+
nullptr, &in_p, &out_p, 48000, backend_duplex_underrun_cb, &state,
1245+
CUBEB_RESAMPLER_QUALITY_VOIP, CUBEB_RESAMPLER_RECLOCK_NONE);
1246+
ASSERT_NE(r, nullptr);
1247+
1248+
const long out_chunk = 441;
1249+
const long duration_matched_input = 480;
1250+
const long short_input = duration_matched_input - 60;
1251+
state.expected_callback_frames =
1252+
cubeb_resampler_input_needed_for_output(r, out_chunk);
1253+
state.real_input_frames = short_input;
1254+
ASSERT_GT(state.expected_callback_frames, short_input);
1255+
1256+
std::vector<float> in_buf(short_input, 1.0f);
1257+
std::vector<float> out_buf(out_chunk);
1258+
long in_count = short_input;
1259+
long got = cubeb_resampler_fill(r, in_buf.data(), &in_count, out_buf.data(),
1260+
out_chunk);
1261+
1262+
EXPECT_EQ(in_count, short_input);
1263+
EXPECT_EQ(state.callback_count, 1);
1264+
EXPECT_EQ(state.last_callback_frames, state.expected_callback_frames);
1265+
EXPECT_EQ(got, out_chunk);
1266+
1267+
cubeb_resampler_destroy(r);
1268+
}
1269+
11581270
static long
11591271
input_error_cb(cubeb_stream *, void *, const void * in, void * out, long n)
11601272
{

0 commit comments

Comments
 (0)