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_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); }
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); } }
static int opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, cubeb_stream_params stream_params, unsigned int latency, cubeb_data_callback data_callback, cubeb_state_callback state_callback, void * user_ptr) { cubeb_stream * stm; assert(ctx); *stream = NULL; if (stream_params.channels < 1 || stream_params.channels > 32 || latency < 1 || latency > 2000) { return CUBEB_ERROR_INVALID_FORMAT; } SLDataFormat_PCM format; format.formatType = SL_DATAFORMAT_PCM; format.numChannels = stream_params.channels; // samplesPerSec is in milliHertz format.samplesPerSec = stream_params.rate * 1000; format.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; format.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; format.channelMask = stream_params.channels == 1 ? SL_SPEAKER_FRONT_CENTER : SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; switch (stream_params.format) { case CUBEB_SAMPLE_S16LE: format.endianness = SL_BYTEORDER_LITTLEENDIAN; break; case CUBEB_SAMPLE_S16BE: format.endianness = SL_BYTEORDER_BIGENDIAN; break; default: return CUBEB_ERROR_INVALID_FORMAT; } stm = calloc(1, sizeof(*stm)); assert(stm); stm->context = ctx; stm->data_callback = data_callback; stm->state_callback = state_callback; stm->user_ptr = user_ptr; stm->inputrate = stream_params.rate; stm->latency = latency; stm->stream_type = stream_params.stream_type; stm->framesize = stream_params.channels * sizeof(int16_t); int r = pthread_mutex_init(&stm->mutex, NULL); assert(r == 0); SLDataLocator_BufferQueue loc_bufq; loc_bufq.locatorType = SL_DATALOCATOR_BUFFERQUEUE; loc_bufq.numBuffers = NBUFS; SLDataSource source; source.pLocator = &loc_bufq; source.pFormat = &format; SLDataLocator_OutputMix loc_outmix; loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX; loc_outmix.outputMix = ctx->outmixObj; SLDataSink sink; sink.pLocator = &loc_outmix; sink.pFormat = NULL; #if defined(__ANDROID__) const SLInterfaceID ids[] = {ctx->SL_IID_BUFFERQUEUE, ctx->SL_IID_VOLUME, ctx->SL_IID_ANDROIDCONFIGURATION}; const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; #else const SLInterfaceID ids[] = {ctx->SL_IID_BUFFERQUEUE, ctx->SL_IID_VOLUME}; const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; #endif assert(NELEMS(ids) == NELEMS(req)); SLresult res = (*ctx->eng)->CreateAudioPlayer(ctx->eng, &stm->playerObj, &source, &sink, NELEMS(ids), ids, req); uint32_t preferred_sampling_rate = stm->inputrate; // Sample rate not supported? Try again with primary sample rate! if (res == SL_RESULT_CONTENT_UNSUPPORTED) { if (opensl_get_preferred_sample_rate(ctx, &preferred_sampling_rate)) { opensl_stream_destroy(stm); return CUBEB_ERROR; } format.samplesPerSec = preferred_sampling_rate * 1000; res = (*ctx->eng)->CreateAudioPlayer(ctx->eng, &stm->playerObj, &source, &sink, NELEMS(ids), ids, req); } if (res != SL_RESULT_SUCCESS) { opensl_stream_destroy(stm); return CUBEB_ERROR; } stm->outputrate = preferred_sampling_rate; stm->bytespersec = preferred_sampling_rate * stm->framesize; stm->queuebuf_len = (stm->bytespersec * latency) / (1000 * NBUFS); // round up to the next multiple of stm->framesize, if needed. if (stm->queuebuf_len % stm->framesize) { stm->queuebuf_len += stm->framesize - (stm->queuebuf_len % stm->framesize); } stm->resampler = cubeb_resampler_create(stm, stream_params, preferred_sampling_rate, data_callback, stm->queuebuf_len / stm->framesize, user_ptr, CUBEB_RESAMPLER_QUALITY_DEFAULT); if (!stm->resampler) { opensl_stream_destroy(stm); return CUBEB_ERROR; } int i; for (i = 0; i < NBUFS; i++) { stm->queuebuf[i] = malloc(stm->queuebuf_len); assert(stm->queuebuf[i]); } #if defined(__ANDROID__) SLuint32 stream_type = convert_stream_type_to_sl_stream(stream_params.stream_type); if (stream_type != 0xFFFFFFFF) { SLAndroidConfigurationItf playerConfig; res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_ANDROIDCONFIGURATION, &playerConfig); res = (*playerConfig)->SetConfiguration(playerConfig, SL_ANDROID_KEY_STREAM_TYPE, &stream_type, sizeof(SLint32)); if (res != SL_RESULT_SUCCESS) { opensl_stream_destroy(stm); return CUBEB_ERROR; } } #endif res = (*stm->playerObj)->Realize(stm->playerObj, SL_BOOLEAN_FALSE); if (res != SL_RESULT_SUCCESS) { opensl_stream_destroy(stm); return CUBEB_ERROR; } res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_PLAY, &stm->play); if (res != SL_RESULT_SUCCESS) { opensl_stream_destroy(stm); return CUBEB_ERROR; } res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_BUFFERQUEUE, &stm->bufq); if (res != SL_RESULT_SUCCESS) { opensl_stream_destroy(stm); return CUBEB_ERROR; } res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_VOLUME, &stm->volume); if (res != SL_RESULT_SUCCESS) { opensl_stream_destroy(stm); return CUBEB_ERROR; } res = (*stm->play)->RegisterCallback(stm->play, play_callback, stm); if (res != SL_RESULT_SUCCESS) { opensl_stream_destroy(stm); return CUBEB_ERROR; } res = (*stm->play)->SetCallbackEventsMask(stm->play, (SLuint32)SL_PLAYEVENT_HEADATMARKER); if (res != SL_RESULT_SUCCESS) { opensl_stream_destroy(stm); return CUBEB_ERROR; } res = (*stm->bufq)->RegisterCallback(stm->bufq, bufferqueue_callback, stm); if (res != SL_RESULT_SUCCESS) { opensl_stream_destroy(stm); return CUBEB_ERROR; } *stream = stm; return CUBEB_OK; }
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); }
static int cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name, cubeb_devid input_device, cubeb_stream_params * input_stream_params, cubeb_devid output_device, cubeb_stream_params * output_stream_params, unsigned int latency, cubeb_data_callback data_callback, cubeb_state_callback state_callback, void * user_ptr) { int stream_actual_rate = 0; int jack_rate = api_jack_get_sample_rate(context->jack_client); if (output_stream_params && (output_stream_params->format != CUBEB_SAMPLE_FLOAT32NE && output_stream_params->format != CUBEB_SAMPLE_S16NE) ) { return CUBEB_ERROR_INVALID_FORMAT; } if (input_stream_params && (input_stream_params->format != CUBEB_SAMPLE_FLOAT32NE && input_stream_params->format != CUBEB_SAMPLE_S16NE) ) { return CUBEB_ERROR_INVALID_FORMAT; } *stream = NULL; // Find a free stream. pthread_mutex_lock(&context->mutex); cubeb_stream * stm = context_alloc_stream(context, stream_name); // No free stream? if (stm == NULL) { pthread_mutex_unlock(&context->mutex); return CUBEB_ERROR; } // unlock context mutex pthread_mutex_unlock(&context->mutex); // Lock active stream pthread_mutex_lock(&stm->mutex); stm->ports_ready = false; stm->user_ptr = user_ptr; stm->context = context; stm->devs = NONE; if (output_stream_params && !input_stream_params) { stm->out_params = *output_stream_params; stream_actual_rate = stm->out_params.rate; stm->out_params.rate = jack_rate; stm->devs = OUT_ONLY; if (stm->out_params.format == CUBEB_SAMPLE_FLOAT32NE) { context->output_bytes_per_frame = sizeof(float); } else { context->output_bytes_per_frame = sizeof(short); } } if (input_stream_params && output_stream_params) { stm->in_params = *input_stream_params; stm->out_params = *output_stream_params; stream_actual_rate = stm->out_params.rate; stm->in_params.rate = jack_rate; stm->out_params.rate = jack_rate; stm->devs = DUPLEX; if (stm->out_params.format == CUBEB_SAMPLE_FLOAT32NE) { context->output_bytes_per_frame = sizeof(float); stm->in_params.format = CUBEB_SAMPLE_FLOAT32NE; } else { context->output_bytes_per_frame = sizeof(short); stm->in_params.format = CUBEB_SAMPLE_S16NE; } } else if (input_stream_params && !output_stream_params) { stm->in_params = *input_stream_params; stream_actual_rate = stm->in_params.rate; stm->in_params.rate = jack_rate; stm->devs = IN_ONLY; if (stm->in_params.format == CUBEB_SAMPLE_FLOAT32NE) { context->output_bytes_per_frame = sizeof(float); } else { context->output_bytes_per_frame = sizeof(short); } } stm->ratio = (float)stream_actual_rate / (float)jack_rate; stm->data_callback = data_callback; stm->state_callback = state_callback; stm->position = 0; stm->volume = 1.0f; context->jack_buffer_size = api_jack_get_buffer_size(context->jack_client); context->fragment_size = context->jack_buffer_size; if (stm->devs == NONE) { pthread_mutex_unlock(&stm->mutex); return CUBEB_ERROR; } stm->resampler = NULL; if (stm->devs == DUPLEX) { stm->resampler = cubeb_resampler_create(stm, &stm->in_params, &stm->out_params, stream_actual_rate, stm->data_callback, stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP); } else if (stm->devs == IN_ONLY) { stm->resampler = cubeb_resampler_create(stm, &stm->in_params, nullptr, stream_actual_rate, stm->data_callback, stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP); } else if (stm->devs == OUT_ONLY) { stm->resampler = cubeb_resampler_create(stm, nullptr, &stm->out_params, stream_actual_rate, stm->data_callback, stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP); } if (!stm->resampler) { stm->in_use = false; pthread_mutex_unlock(&stm->mutex); return CUBEB_ERROR; } if (stm->devs == DUPLEX || stm->devs == OUT_ONLY) { for (unsigned int c = 0; c < stm->out_params.channels; c++) { char portname[256]; snprintf(portname, 255, "%s_out_%d", stm->stream_name, c); stm->output_ports[c] = api_jack_port_register(stm->context->jack_client, portname, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); } } if (stm->devs == DUPLEX || stm->devs == IN_ONLY) { for (unsigned int c = 0; c < stm->in_params.channels; c++) { char portname[256]; snprintf(portname, 255, "%s_in_%d", stm->stream_name, c); stm->input_ports[c] = api_jack_port_register(stm->context->jack_client, portname, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); } } cbjack_connect_ports(stm); *stream = stm; stm->ports_ready = true; stm->pause = true; pthread_mutex_unlock(&stm->mutex); return CUBEB_OK; }
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); } }