TEST(cubeb, resampler_drain) { cubeb_stream_params output_params; int target_rate; output_params.rate = 44100; output_params.channels = 1; output_params.format = CUBEB_SAMPLE_FLOAT32NE; target_rate = 48000; cubeb_resampler * resampler = cubeb_resampler_create((cubeb_stream*)nullptr, nullptr, &output_params, target_rate, test_drain_data_cb, nullptr, CUBEB_RESAMPLER_QUALITY_VOIP); const long out_frames = 128; float out_buffer[out_frames]; long got; do { got = cubeb_resampler_fill(resampler, nullptr, nullptr, out_buffer, out_frames); } while (got == out_frames); /* If the above is not an infinite loop, the drain was a success, just mark * this test as such. */ ASSERT_TRUE(true); cubeb_resampler_destroy(resampler); }
TEST(cubeb, resampler_passthrough_input_only) { // Test that the passthrough resampler works when there is only an output stream. cubeb_stream_params input_params; const size_t input_channels = 2; input_params.channels = input_channels; input_params.rate = 44100; input_params.format = CUBEB_SAMPLE_FLOAT32NE; int target_rate = input_params.rate; cubeb_resampler * resampler = cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, nullptr, target_rate, cb_passthrough_resampler_input, nullptr, CUBEB_RESAMPLER_QUALITY_VOIP); float input_buffer[input_channels * 256]; long got; for (uint32_t i = 0; i < 30; i++) { long int frames = 256; got = cubeb_resampler_fill(resampler, input_buffer, &frames, nullptr, 0); ASSERT_EQ(got, 256); } }
TEST(cubeb, resampler_output_only_noop) { cubeb_stream_params output_params; int target_rate; output_params.rate = 44100; output_params.channels = 1; output_params.format = CUBEB_SAMPLE_FLOAT32NE; target_rate = output_params.rate; cubeb_resampler * resampler = cubeb_resampler_create((cubeb_stream*)nullptr, nullptr, &output_params, target_rate, test_output_only_noop_data_cb, nullptr, CUBEB_RESAMPLER_QUALITY_VOIP); const long out_frames = 128; float out_buffer[out_frames]; long got; got = cubeb_resampler_fill(resampler, nullptr, nullptr, out_buffer, out_frames); ASSERT_EQ(got, out_frames); cubeb_resampler_destroy(resampler); }
static void cbjack_deinterleave_playback_refill_s16ne(cubeb_stream * stream, short ** in, float ** bufs_out, jack_nframes_t nframes) { float * out_interleaved_buffer = nullptr; short * inptr = (in != NULL) ? *in : nullptr; float * outptr = (bufs_out != NULL) ? *bufs_out : nullptr; long needed_frames = (bufs_out != NULL) ? nframes : 0; long done_frames = 0; long input_frames_count = (in != NULL) ? nframes : 0; done_frames = cubeb_resampler_fill(stream->resampler, inptr, &input_frames_count, (bufs_out != NULL) ? stream->context->out_resampled_interleaved_buffer_s16ne : NULL, needed_frames); s16ne_to_float(stream->context->out_resampled_interleaved_buffer_float, stream->context->out_resampled_interleaved_buffer_s16ne, done_frames * stream->out_params.channels); out_interleaved_buffer = stream->context->out_resampled_interleaved_buffer_float; if (outptr) { // convert interleaved output buffers to contiguous buffers for (unsigned int c = 0; c < stream->out_params.channels; c++) { float* buffer = bufs_out[c]; for (long f = 0; f < done_frames; f++) { buffer[f] = out_interleaved_buffer[(f * stream->out_params.channels) + c] * stream->volume; } if (done_frames < needed_frames) { // draining for (long f = done_frames; f < needed_frames; f++) { buffer[f] = 0.f; } } if (done_frames == 0) { // stop, but first zero out the existing buffer for (long f = 0; f < needed_frames; f++) { buffer[f] = 0.f; } } } } if (done_frames >= 0 && done_frames < needed_frames) { // set drained stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_DRAINED); // stop stream cbjack_stream_stop(stream); } if (done_frames > 0 && done_frames <= needed_frames) { // advance stream position stream->position += done_frames * stream->ratio; } if (done_frames < 0 || done_frames > needed_frames) { // stream error stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_ERROR); } }
static void bufferqueue_callback(SLBufferQueueItf caller, void * user_ptr) { cubeb_stream * stm = user_ptr; assert(stm); SLBufferQueueState state; SLresult res; res = (*stm->bufq)->GetState(stm->bufq, &state); assert(res == SL_RESULT_SUCCESS); if (state.count > 1) return; SLuint32 i; for (i = state.count; i < NBUFS; i++) { uint8_t *buf = stm->queuebuf[stm->queuebuf_idx]; long written = 0; pthread_mutex_lock(&stm->mutex); int draining = stm->draining; pthread_mutex_unlock(&stm->mutex); if (!draining) { written = cubeb_resampler_fill(stm->resampler, NULL, NULL, buf, stm->queuebuf_len / stm->framesize); if (written < 0 || written * stm->framesize > stm->queuebuf_len) { (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PAUSED); return; } } // Keep sending silent data even in draining mode to prevent the audio // back-end from being stopped automatically by OpenSL/ES. memset(buf + written * stm->framesize, 0, stm->queuebuf_len - written * stm->framesize); res = (*stm->bufq)->Enqueue(stm->bufq, buf, stm->queuebuf_len); assert(res == SL_RESULT_SUCCESS); stm->queuebuf_idx = (stm->queuebuf_idx + 1) % NBUFS; if (written > 0) { pthread_mutex_lock(&stm->mutex); stm->written += written; pthread_mutex_unlock(&stm->mutex); } if (!draining && written * stm->framesize < stm->queuebuf_len) { pthread_mutex_lock(&stm->mutex); int64_t written_duration = INT64_C(1000) * stm->written * stm->framesize / stm->bytespersec; stm->draining = 1; pthread_mutex_unlock(&stm->mutex); // Use SL_PLAYEVENT_HEADATMARKER event from slPlayCallback of SLPlayItf // to make sure all the data has been processed. (*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)written_duration); return; } } }
static void bufferqueue_callback(SLBufferQueueItf caller, void * user_ptr) { cubeb_stream * stm = user_ptr; SLBufferQueueState state; SLresult res; res = (*stm->bufq)->GetState(stm->bufq, &state); assert(res == SL_RESULT_SUCCESS); if (stm->draining) { if (!state.count) { stm->draining = 0; stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); } return; } if (state.count > 1) return; SLuint32 i; for (i = state.count; i < NBUFS; i++) { void *buf = stm->queuebuf[stm->queuebuf_idx]; long written = cubeb_resampler_fill(stm->resampler, buf, stm->queuebuf_len / stm->framesize); if (written == CUBEB_ERROR) { (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_STOPPED); return; } if (written) { res = (*stm->bufq)->Enqueue(stm->bufq, buf, written * stm->framesize); assert(res == SL_RESULT_SUCCESS); stm->queuebuf_idx = (stm->queuebuf_idx + 1) % NBUFS; } else if (!i) { stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); return; } if ((written * stm->framesize) < stm->queuebuf_len) { stm->draining = 1; return; } } }
void test_resampler_duplex(uint32_t input_channels, uint32_t output_channels, uint32_t input_rate, uint32_t output_rate, uint32_t target_rate, float chunk_duration) { cubeb_stream_params input_params; cubeb_stream_params output_params; osc_state state; input_params.format = output_params.format = cubeb_format<T>(); state.input_channels = input_params.channels = input_channels; state.output_channels = output_params.channels = output_channels; input_params.rate = input_rate; state.output_rate = output_params.rate = output_rate; state.target_rate = target_rate; long got; cubeb_resampler * resampler = cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, &output_params, target_rate, data_cb_resampler, (void*)&state, CUBEB_RESAMPLER_QUALITY_VOIP); long latency = cubeb_resampler_latency(resampler); const uint32_t duration_s = 2; int32_t duration_frames = duration_s * target_rate; uint32_t input_array_frame_count = ceil(chunk_duration * input_rate / 1000) + ceilf(static_cast<float>(input_rate) / target_rate) * 2; uint32_t output_array_frame_count = chunk_duration * output_rate / 1000; auto_array<float> input_buffer(input_channels * input_array_frame_count); auto_array<float> output_buffer(output_channels * output_array_frame_count); auto_array<float> expected_resampled_input(input_channels * duration_frames); auto_array<float> expected_resampled_output(output_channels * output_rate * duration_s); state.max_output_phase_index = duration_s * target_rate; expected_resampled_input.push_silence(input_channels * duration_frames); expected_resampled_output.push_silence(output_channels * output_rate * duration_s); /* expected output is a 440Hz sine wave at 16kHz */ fill_with_sine(expected_resampled_input.data() + latency, target_rate, input_channels, duration_frames - latency, 0); /* expected output is a 440Hz sine wave at 32kHz */ fill_with_sine(expected_resampled_output.data() + latency, output_rate, output_channels, output_rate * duration_s - latency, 0); while (state.output_phase_index != state.max_output_phase_index) { uint32_t leftover_samples = input_buffer.length() * input_channels; input_buffer.reserve(input_array_frame_count); state.input_phase_index = fill_with_sine(input_buffer.data() + leftover_samples, input_rate, input_channels, input_array_frame_count - leftover_samples, state.input_phase_index); long input_consumed = input_array_frame_count; input_buffer.set_length(input_array_frame_count); got = cubeb_resampler_fill(resampler, input_buffer.data(), &input_consumed, output_buffer.data(), output_array_frame_count); /* handle leftover input */ if (input_array_frame_count != static_cast<uint32_t>(input_consumed)) { input_buffer.pop(nullptr, input_consumed * input_channels); } else { input_buffer.clear(); } state.output.push(output_buffer.data(), got * state.output_channels); } dump("input_expected.raw", expected_resampled_input.data(), expected_resampled_input.length()); dump("output_expected.raw", expected_resampled_output.data(), expected_resampled_output.length()); dump("input.raw", state.input.data(), state.input.length()); dump("output.raw", state.output.data(), state.output.length()); ASSERT_TRUE(array_fuzzy_equal(state.input, expected_resampled_input, epsilon<T>(input_rate/target_rate))); ASSERT_TRUE(array_fuzzy_equal(state.output, expected_resampled_output, epsilon<T>(output_rate/target_rate))); cubeb_resampler_destroy(resampler); }
TEST(cubeb, resampler_passthrough_duplex_callback_reordering) { // Test that when pre-buffering on resampler creation, we can survive an input // callback being delayed. cubeb_stream_params input_params; cubeb_stream_params output_params; const int input_channels = 1; const int output_channels = 2; input_params.channels = input_channels; input_params.rate = 44100; input_params.format = CUBEB_SAMPLE_FLOAT32NE; output_params.channels = output_channels; output_params.rate = input_params.rate; output_params.format = CUBEB_SAMPLE_FLOAT32NE; int target_rate = input_params.rate; cubeb_resampler * resampler = cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, &output_params, target_rate, cb_passthrough_resampler_duplex, nullptr, CUBEB_RESAMPLER_QUALITY_VOIP); const long BUF_BASE_SIZE = 256; float input_buffer_prebuffer[input_channels * BUF_BASE_SIZE * 2]; float input_buffer_glitch[input_channels * BUF_BASE_SIZE * 2]; float input_buffer_normal[input_channels * BUF_BASE_SIZE]; float output_buffer[output_channels * BUF_BASE_SIZE]; long seq_idx = 0; long output_seq_idx = 0; long prebuffer_frames = ARRAY_LENGTH(input_buffer_prebuffer) / input_params.channels; seq_idx = seq(input_buffer_prebuffer, input_channels, seq_idx, prebuffer_frames); long got = cubeb_resampler_fill(resampler, input_buffer_prebuffer, &prebuffer_frames, output_buffer, BUF_BASE_SIZE); output_seq_idx += BUF_BASE_SIZE; // prebuffer_frames will hold the frames used by the resampler. ASSERT_EQ(prebuffer_frames, BUF_BASE_SIZE); ASSERT_EQ(got, BUF_BASE_SIZE); for (uint32_t i = 0; i < 300; i++) { long int frames = BUF_BASE_SIZE; // Simulate that sometimes, we don't have the input callback on time if (i != 0 && (i % 100) == 0) { long zero = 0; got = cubeb_resampler_fill(resampler, input_buffer_normal /* unused here */, &zero, output_buffer, BUF_BASE_SIZE); is_seq(output_buffer, 2, BUF_BASE_SIZE, output_seq_idx); output_seq_idx += BUF_BASE_SIZE; } else if (i != 0 && (i % 100) == 1) { // if this is the case, the on the next iteration, we'll have twice the // amount of input frames seq_idx = seq(input_buffer_glitch, input_channels, seq_idx, BUF_BASE_SIZE * 2); frames = 2 * BUF_BASE_SIZE; got = cubeb_resampler_fill(resampler, input_buffer_glitch, &frames, output_buffer, BUF_BASE_SIZE); is_seq(output_buffer, 2, BUF_BASE_SIZE, output_seq_idx); output_seq_idx += BUF_BASE_SIZE; } else { // normal case seq_idx = seq(input_buffer_normal, input_channels, seq_idx, BUF_BASE_SIZE); long normal_input_frame_count = 256; got = cubeb_resampler_fill(resampler, input_buffer_normal, &normal_input_frame_count, output_buffer, BUF_BASE_SIZE); is_seq(output_buffer, 2, BUF_BASE_SIZE, output_seq_idx); output_seq_idx += BUF_BASE_SIZE; } ASSERT_EQ(got, BUF_BASE_SIZE); } }