@@ -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+
11121144TEST (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+
11581270static long
11591271input_error_cb (cubeb_stream *, void *, const void * in, void * out, long n)
11601272{
0 commit comments