aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/cubeb_resampler.cpp
diff options
context:
space:
mode:
authorPaul Adenot <[email protected]>2016-02-02 18:43:27 +0100
committerPaul Adenot <[email protected]>2016-02-06 06:29:24 +0100
commit0def655163768ff1fc0990473ce79796451b3374 (patch)
tree9b8bfa7a03922fae24ae4c40b76b8ad274b4f25d /src/cubeb_resampler.cpp
parenta7c662062d07ffa62894e46c542e778f10ac420b (diff)
downloadcubeb-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.cpp213
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 *