diff options
author | Paul Adenot <[email protected]> | 2016-02-02 18:43:27 +0100 |
---|---|---|
committer | Paul Adenot <[email protected]> | 2016-02-06 06:29:24 +0100 |
commit | 0def655163768ff1fc0990473ce79796451b3374 (patch) | |
tree | 9b8bfa7a03922fae24ae4c40b76b8ad274b4f25d /src/cubeb_resampler.cpp | |
parent | a7c662062d07ffa62894e46c542e778f10ac420b (diff) | |
download | cubeb-0def655163768ff1fc0990473ce79796451b3374.tar.gz cubeb-0def655163768ff1fc0990473ce79796451b3374.zip |
Make the resampler handle two opposite streams.
A duplex resampler takes two audio streams (one input and output), with
potentially different sample-rate, and a target rate. It calls back
using the data callback, deliverying input frames at the target rate,
along with a buffer that can be filled with the same number of output
frames at target_rate.
When resampling only one side, a delay line is inserted in the opposite
direction to keep as much synchronization as possible, sacrificing a few
frames of latency. This can help acoustic echo cancelation algorithms.
This is to replace a seemingly no-op resampler, that would end up being
more expensive.
The input rate is the rate of the platform level input stream, i.e. the
sample-rate at which the audio is delivered to cubeb. The output rate is
the rate of the output stream, i.e. the rate at which cubeb should
deliver the audio to the platform API. The target rate is the rate that
was asked for by the user when calling `cubeb_stream_init`.
Consequently, the audio flows like this, where ir and or are input and
output rate, and tr is the target rate:
Platform API --- Input Audio Buffer ---> Duplex Resampler ----.
ir input resampling |
ir -> tr |
prepare output audio
buffer at tr
|
cubeb_data_callback
input and output
buffers at tr
|
|
Platform API <--- Output Audio Buffer --- Duplex Resampler <--`
or output resampling
tr -> or
The configuration of the processing pipeline is done using
template-based static polymorphism and function pointers.
Diffstat (limited to 'src/cubeb_resampler.cpp')
-rw-r--r-- | src/cubeb_resampler.cpp | 213 |
1 files changed, 154 insertions, 59 deletions
diff --git a/src/cubeb_resampler.cpp b/src/cubeb_resampler.cpp index de2e4fa..eff9e27 100644 --- a/src/cubeb_resampler.cpp +++ b/src/cubeb_resampler.cpp @@ -81,82 +81,177 @@ frame_count_at_rate(long frame_count, float rate) } // end of anonymous namespace -cubeb_resampler_speex::cubeb_resampler_speex(SpeexResamplerState * r, - cubeb_stream * s, - cubeb_stream_params params, - uint32_t out_rate, - cubeb_data_callback cb, - long max_count, - void * ptr) - : speex_resampler(r) +template<typename T, typename InputProcessor, typename OutputProcessor> +cubeb_resampler_speex<T, InputProcessor, OutputProcessor> + ::cubeb_resampler_speex(InputProcessor * input_processor, + OutputProcessor * output_processor, + cubeb_stream * s, + cubeb_data_callback cb, + void * ptr) + : input_processor(input_processor) + , output_processor(output_processor) , stream(s) - , stream_params(params) , data_callback(cb) , user_ptr(ptr) - , buffer_frame_count(max_count) - , resampling_ratio(static_cast<float>(params.rate) / out_rate) - , leftover_frame_size(static_cast<uint32_t>(ceilf(1 / resampling_ratio * 2) + 1)) - , leftover_frame_count(0) - , leftover_frames_buffer(auto_array<uint8_t>(frames_to_bytes(params, leftover_frame_size))) - , resampling_src_buffer(auto_array<uint8_t>(frames_to_bytes(params, - frame_count_at_rate(buffer_frame_count, resampling_ratio)))) { - assert(r); + if (input_processor && output_processor) { + // Add some delay on the processor that has the lowest delay so that the + // streams are synchronized. + uint32_t in_latency = input_processor->latency(); + uint32_t out_latency = output_processor->latency(); + if (in_latency > out_latency) { + uint32_t latency_diff = in_latency - out_latency; + output_processor->add_latency(latency_diff); + } else if (in_latency < out_latency) { + uint32_t latency_diff = out_latency - in_latency; + input_processor->add_latency(latency_diff); + } + fill_internal = &cubeb_resampler_speex::fill_internal_duplex; + } else if (input_processor) { + fill_internal = &cubeb_resampler_speex::fill_internal_input; + } else if (output_processor) { + fill_internal = &cubeb_resampler_speex::fill_internal_output; + } } -cubeb_resampler_speex::~cubeb_resampler_speex() +template<typename T, typename InputProcessor, typename OutputProcessor> +cubeb_resampler_speex<T, InputProcessor, OutputProcessor> + ::~cubeb_resampler_speex() +{ } + +template<typename T, typename InputProcessor, typename OutputProcessor> +long +cubeb_resampler_speex<T, InputProcessor, OutputProcessor> +::fill(void * input_buffer, long * input_frames_count, + void * output_buffer, long output_frames_needed) { - speex_resampler_destroy(speex_resampler); + /* Input and output buffers, typed */ + T * in_buffer = reinterpret_cast<T*>(input_buffer); + T * out_buffer = reinterpret_cast<T*>(output_buffer); + return (this->*fill_internal)(in_buffer, input_frames_count, + out_buffer, output_frames_needed); } +template<typename T, typename InputProcessor, typename OutputProcessor> long -cubeb_resampler_speex::fill(void * input_buffer, void * output_buffer, long frames_needed) +cubeb_resampler_speex<T, InputProcessor, OutputProcessor> +::fill_internal_output(T * input_buffer, long * input_frames_count, + T * output_buffer, long output_frames_needed) { - // Use more input frames than strictly necessary, so in the worst case, - // we have leftover unresampled frames at the end, that we can use - // during the next iteration. - assert(frames_needed <= buffer_frame_count); - long before_resampling = frame_count_at_rate(frames_needed, resampling_ratio); - long frames_requested = before_resampling - leftover_frame_count; - - // Copy the previous leftover frames to the front of the buffer. - size_t leftover_bytes = frames_to_bytes(stream_params, leftover_frame_count); - memcpy(resampling_src_buffer.get(), leftover_frames_buffer.get(), leftover_bytes); - uint8_t * buffer_start = resampling_src_buffer.get() + leftover_bytes; - - long got = data_callback(stream, user_ptr, NULL, buffer_start, frames_requested); - assert(got <= frames_requested); - - if (got < 0) { - return CUBEB_ERROR; - } + assert(!input_buffer && !input_frames_count && + output_buffer && output_frames_needed); - uint32_t in_frames = leftover_frame_count + got; - uint32_t out_frames = frames_needed; - uint32_t old_in_frames = in_frames; + long got = 0; +T * out_unprocessed = nullptr; - if (stream_params.format == CUBEB_SAMPLE_FLOAT32NE) { - float * in_buffer = reinterpret_cast<float *>(resampling_src_buffer.get()); - float * out_buffer = reinterpret_cast<float *>(output_buffer); - speex_resampler_process_interleaved_float(speex_resampler, in_buffer, &in_frames, - out_buffer, &out_frames); - } else { - short * in_buffer = reinterpret_cast<short *>(resampling_src_buffer.get()); - short * out_buffer = reinterpret_cast<short *>(output_buffer); - speex_resampler_process_interleaved_int(speex_resampler, in_buffer, &in_frames, - out_buffer, &out_frames); + uint32_t output_frames_before_processing = + output_processor->input_needed_for_output(output_frames_needed); + + /* fill directly the input buffer of the output processor to save a copy */ + out_unprocessed = + output_processor->input_buffer(output_frames_before_processing); + + got = data_callback(stream, user_ptr, + nullptr, nullptr, + output_frames_before_processing); + + /* Process the output. If not enough frames have been returned from the + * callback, drain the processors. */ + if (got != output_frames_before_processing) { + got = output_processor->drain(output_buffer, output_frames_needed); } + else { + output_processor->output(output_buffer, output_frames_needed); + got = output_frames_needed; + } + + return got; + +} + +template<typename T, typename InputProcessor, typename OutputProcessor> +long +cubeb_resampler_speex<T, InputProcessor, OutputProcessor> +::fill_internal_input(T * input_buffer, long * input_frames_count, + T * output_buffer, long output_frames_needed) +{ + assert(input_buffer && input_frames_count && *input_frames_count && + !output_buffer); + + /* The input data, after eventual resampling. This is passed to the callback. */ + T * resampled_input = nullptr; + /* The number of frames returned from the callback. */ + long got = 0; + uint32_t resampled_frame_count = input_processor->output_for_input(*input_frames_count); + + /* process the input, and present exactly `output_frames_needed` in the + * callback. */ + input_processor->input(input_buffer, *input_frames_count); + resampled_input = input_processor->output(resampled_frame_count); + + got = data_callback(stream, user_ptr, + resampled_input, nullptr, resampled_frame_count); + + return *input_frames_count; +} + + +template<typename T, typename InputProcessor, typename OutputProcessor> +long +cubeb_resampler_speex<T, InputProcessor, OutputProcessor> +::fill_internal_duplex(T * in_buffer, long * input_frames_count, + T * out_buffer, long output_frames_needed) +{ + /* The input data, after eventual resampling. This is passed to the callback. */ + T * resampled_input = nullptr; + /* The output buffer passed down in the callback, that might be resampled. */ + T * out_unprocessed = nullptr; + size_t output_frames_before_processing; + /* The number of frames returned from the callback. */ + long got = 0; + + /* We need to determine how much frames to present to the consumer. + * - If we have a two way stream, but we're only resampling input, we resample + * the input to the number of output frames. + * - If we have a two way stream, but we're only resampling the output, we + * resize the input buffer of the outut resampler to the number of input + * frames, and we resample it afterwards. + * - If we resample both ways, we resample the input to the number of frames + * we would need to pass down to the consumer (before resampling the output), + * get the output data, and resample it to the number of frames needed by the + * caller. */ + + output_frames_before_processing = + output_processor->input_needed_for_output(output_frames_needed); + /* fill directly the input buffer of the output processor to save a copy */ + out_unprocessed = + output_processor->input_buffer(output_frames_before_processing); - // Copy the leftover frames to buffer for the next time. - leftover_frame_count = old_in_frames - in_frames; - assert(leftover_frame_count <= leftover_frame_size); + if (in_buffer) { + /* process the input, and present exactly `output_frames_needed` in the + * callback. */ + input_processor->input(in_buffer, *input_frames_count); + resampled_input = + input_processor->output(output_frames_before_processing); + } else { + resampled_input = nullptr; + } - size_t unresampled_bytes = frames_to_bytes(stream_params, leftover_frame_count); - uint8_t * leftover_frames_start = resampling_src_buffer.get(); - leftover_frames_start += frames_to_bytes(stream_params, in_frames); - memcpy(leftover_frames_buffer.get(), leftover_frames_start, unresampled_bytes); + got = data_callback(stream, user_ptr, + resampled_input, out_unprocessed, + output_frames_before_processing); + + + /* Process the output. If not enough frames have been returned from the + * callback, drain the processors. */ + if (got != output_frames_before_processing) { + got = output_processor->drain(out_buffer, output_frames_needed); + } else { + output_processor->output(out_buffer, output_frames_needed); + got = output_frames_needed; + } - return out_frames; + return got; } cubeb_resampler * |