summaryrefslogtreecommitdiffstats
path: root/media/libcubeb/src/cubeb_resampler_internal.h
diff options
context:
space:
mode:
Diffstat (limited to 'media/libcubeb/src/cubeb_resampler_internal.h')
-rw-r--r--media/libcubeb/src/cubeb_resampler_internal.h551
1 files changed, 551 insertions, 0 deletions
diff --git a/media/libcubeb/src/cubeb_resampler_internal.h b/media/libcubeb/src/cubeb_resampler_internal.h
new file mode 100644
index 000000000..3c37a04b9
--- /dev/null
+++ b/media/libcubeb/src/cubeb_resampler_internal.h
@@ -0,0 +1,551 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#if !defined(CUBEB_RESAMPLER_INTERNAL)
+#define CUBEB_RESAMPLER_INTERNAL
+
+#include <cmath>
+#include <cassert>
+#include <algorithm>
+#include <memory>
+#ifdef CUBEB_GECKO_BUILD
+#include "mozilla/UniquePtr.h"
+// In libc++, symbols such as std::unique_ptr may be defined in std::__1.
+// The _LIBCPP_BEGIN_NAMESPACE_STD and _LIBCPP_END_NAMESPACE_STD macros
+// will expand to the correct namespace.
+#ifdef _LIBCPP_BEGIN_NAMESPACE_STD
+#define MOZ_BEGIN_STD_NAMESPACE _LIBCPP_BEGIN_NAMESPACE_STD
+#define MOZ_END_STD_NAMESPACE _LIBCPP_END_NAMESPACE_STD
+#else
+#define MOZ_BEGIN_STD_NAMESPACE namespace std {
+#define MOZ_END_STD_NAMESPACE }
+#endif
+MOZ_BEGIN_STD_NAMESPACE
+ using mozilla::DefaultDelete;
+ using mozilla::UniquePtr;
+ #define default_delete DefaultDelete
+ #define unique_ptr UniquePtr
+MOZ_END_STD_NAMESPACE
+#endif
+#include "cubeb/cubeb.h"
+#include "cubeb_utils.h"
+#include "cubeb-speex-resampler.h"
+#include "cubeb_resampler.h"
+#include <stdio.h>
+
+/* This header file contains the internal C++ API of the resamplers, for testing. */
+
+int to_speex_quality(cubeb_resampler_quality q);
+
+struct cubeb_resampler {
+ virtual long fill(void * input_buffer, long * input_frames_count,
+ void * output_buffer, long frames_needed) = 0;
+ virtual long latency() = 0;
+ virtual ~cubeb_resampler() {}
+};
+
+class noop_resampler : public cubeb_resampler {
+public:
+ noop_resampler(cubeb_stream * s,
+ cubeb_data_callback cb,
+ void * ptr)
+ : stream(s)
+ , data_callback(cb)
+ , user_ptr(ptr)
+ {
+ }
+
+ virtual long fill(void * input_buffer, long * input_frames_count,
+ void * output_buffer, long output_frames);
+
+ virtual long latency()
+ {
+ return 0;
+ }
+
+private:
+ cubeb_stream * const stream;
+ const cubeb_data_callback data_callback;
+ void * const user_ptr;
+};
+
+/** Base class for processors. This is just used to share methods for now. */
+class processor {
+public:
+ explicit processor(uint32_t channels)
+ : channels(channels)
+ {}
+protected:
+ size_t frames_to_samples(size_t frames)
+ {
+ return frames * channels;
+ }
+ size_t samples_to_frames(size_t samples)
+ {
+ assert(!(samples % channels));
+ return samples / channels;
+ }
+ /** The number of channel of the audio buffers to be resampled. */
+ const uint32_t channels;
+};
+
+/** Bidirectional resampler, can resample an input and an output stream, or just
+ * an input stream or output stream. In this case a delay is inserted in the
+ * opposite direction to keep the streams synchronized. */
+template<typename T, typename InputProcessing, typename OutputProcessing>
+class cubeb_resampler_speex : public cubeb_resampler {
+public:
+ cubeb_resampler_speex(InputProcessing * input_processor,
+ OutputProcessing * output_processor,
+ cubeb_stream * s,
+ cubeb_data_callback cb,
+ void * ptr);
+
+ virtual ~cubeb_resampler_speex();
+
+ virtual long fill(void * input_buffer, long * input_frames_count,
+ void * output_buffer, long output_frames_needed);
+
+ virtual long latency()
+ {
+ if (input_processor && output_processor) {
+ assert(input_processor->latency() == output_processor->latency());
+ return input_processor->latency();
+ } else if (input_processor) {
+ return input_processor->latency();
+ } else {
+ return output_processor->latency();
+ }
+ }
+
+private:
+ typedef long(cubeb_resampler_speex::*processing_callback)(T * input_buffer, long * input_frames_count, T * output_buffer, long output_frames_needed);
+
+ long fill_internal_duplex(T * input_buffer, long * input_frames_count,
+ T * output_buffer, long output_frames_needed);
+ long fill_internal_input(T * input_buffer, long * input_frames_count,
+ T * output_buffer, long output_frames_needed);
+ long fill_internal_output(T * input_buffer, long * input_frames_count,
+ T * output_buffer, long output_frames_needed);
+
+ std::unique_ptr<InputProcessing> input_processor;
+ std::unique_ptr<OutputProcessing> output_processor;
+ processing_callback fill_internal;
+ cubeb_stream * const stream;
+ const cubeb_data_callback data_callback;
+ void * const user_ptr;
+};
+
+/** Handles one way of a (possibly) duplex resampler, working on interleaved
+ * audio buffers of type T. This class is designed so that the number of frames
+ * coming out of the resampler can be precisely controled. It manages its own
+ * input buffer, and can use the caller's output buffer, or allocate its own. */
+template<typename T>
+class cubeb_resampler_speex_one_way : public processor {
+public:
+ /** The sample type of this resampler, either 16-bit integers or 32-bit
+ * floats. */
+ typedef T sample_type;
+ /** Construct a resampler resampling from #source_rate to #target_rate, that
+ * can be arbitrary, strictly positive number.
+ * @parameter channels The number of channels this resampler will resample.
+ * @parameter source_rate The sample-rate of the audio input.
+ * @parameter target_rate The sample-rate of the audio output.
+ * @parameter quality A number between 0 (fast, low quality) and 10 (slow,
+ * high quality). */
+ cubeb_resampler_speex_one_way(uint32_t channels,
+ uint32_t source_rate,
+ uint32_t target_rate,
+ int quality)
+ : processor(channels)
+ , resampling_ratio(static_cast<float>(source_rate) / target_rate)
+ , additional_latency(0)
+ , leftover_samples(0)
+ {
+ int r;
+ speex_resampler = speex_resampler_init(channels, source_rate,
+ target_rate, quality, &r);
+ assert(r == RESAMPLER_ERR_SUCCESS && "resampler allocation failure");
+ }
+
+ /** Destructor, deallocate the resampler */
+ virtual ~cubeb_resampler_speex_one_way()
+ {
+ speex_resampler_destroy(speex_resampler);
+ }
+
+ /** Sometimes, it is necessary to add latency on one way of a two-way
+ * resampler so that the stream are synchronized. This must be called only on
+ * a fresh resampler, otherwise, silent samples will be inserted in the
+ * stream.
+ * @param frames the number of frames of latency to add. */
+ void add_latency(size_t frames)
+ {
+ additional_latency += frames;
+ resampling_in_buffer.push_silence(frames_to_samples(frames));
+ }
+
+ /* Fill the resampler with `input_frame_count` frames. */
+ void input(T * input_buffer, size_t input_frame_count)
+ {
+ resampling_in_buffer.push(input_buffer,
+ frames_to_samples(input_frame_count));
+ }
+
+ /** Outputs exactly `output_frame_count` into `output_buffer`.
+ * `output_buffer` has to be at least `output_frame_count` long. */
+ size_t output(T * output_buffer, size_t output_frame_count)
+ {
+ uint32_t in_len = samples_to_frames(resampling_in_buffer.length());
+ uint32_t out_len = output_frame_count;
+
+ speex_resample(resampling_in_buffer.data(), &in_len,
+ output_buffer, &out_len);
+
+ /* This shifts back any unresampled samples to the beginning of the input
+ buffer. */
+ resampling_in_buffer.pop(nullptr, frames_to_samples(in_len));
+
+ return out_len;
+ }
+
+ size_t output_for_input(uint32_t input_frames)
+ {
+ return (size_t)floorf((input_frames + samples_to_frames(resampling_in_buffer.length()))
+ / resampling_ratio);
+ }
+
+ /** Returns a buffer containing exactly `output_frame_count` resampled frames.
+ * The consumer should not hold onto the pointer. */
+ T * output(size_t output_frame_count)
+ {
+ if (resampling_out_buffer.capacity() < frames_to_samples(output_frame_count)) {
+ resampling_out_buffer.reserve(frames_to_samples(output_frame_count));
+ }
+
+ uint32_t in_len = samples_to_frames(resampling_in_buffer.length());
+ uint32_t out_len = output_frame_count;
+
+ speex_resample(resampling_in_buffer.data(), &in_len,
+ resampling_out_buffer.data(), &out_len);
+
+ assert(out_len == output_frame_count);
+
+ /* This shifts back any unresampled samples to the beginning of the input
+ buffer. */
+ resampling_in_buffer.pop(nullptr, frames_to_samples(in_len));
+
+ return resampling_out_buffer.data();
+ }
+
+ /** Get the latency of the resampler, in output frames. */
+ uint32_t latency() const
+ {
+ /* The documentation of the resampler talks about "samples" here, but it
+ * only consider a single channel here so it's the same number of frames. */
+ int latency = 0;
+
+ latency =
+ speex_resampler_get_output_latency(speex_resampler) + additional_latency;
+
+ assert(latency >= 0);
+
+ return latency;
+ }
+
+ /** Returns the number of frames to pass in the input of the resampler to have
+ * exactly `output_frame_count` resampled frames. This can return a number
+ * slightly bigger than what is strictly necessary, but it guaranteed that the
+ * number of output frames will be exactly equal. */
+ uint32_t input_needed_for_output(uint32_t output_frame_count)
+ {
+ int32_t unresampled_frames_left = samples_to_frames(resampling_in_buffer.length());
+ int32_t resampled_frames_left = samples_to_frames(resampling_out_buffer.length());
+ float input_frames_needed =
+ (output_frame_count - unresampled_frames_left) * resampling_ratio
+ - resampled_frames_left;
+ if (input_frames_needed < 0) {
+ return 0;
+ }
+ return (uint32_t)ceilf(input_frames_needed);
+ }
+
+ /** Returns a pointer to the input buffer, that contains empty space for at
+ * least `frame_count` elements. This is useful so that consumer can directly
+ * write into the input buffer of the resampler. The pointer returned is
+ * adjusted so that leftover data are not overwritten.
+ */
+ T * input_buffer(size_t frame_count)
+ {
+ leftover_samples = resampling_in_buffer.length();
+ resampling_in_buffer.reserve(leftover_samples +
+ frames_to_samples(frame_count));
+ return resampling_in_buffer.data() + leftover_samples;
+ }
+
+ /** This method works with `input_buffer`, and allows to inform the processor
+ how much frames have been written in the provided buffer. */
+ void written(size_t written_frames)
+ {
+ resampling_in_buffer.set_length(leftover_samples +
+ frames_to_samples(written_frames));
+ }
+private:
+ /** Wrapper for the speex resampling functions to have a typed
+ * interface. */
+ void speex_resample(float * input_buffer, uint32_t * input_frame_count,
+ float * output_buffer, uint32_t * output_frame_count)
+ {
+#ifndef NDEBUG
+ int rv;
+ rv =
+#endif
+ speex_resampler_process_interleaved_float(speex_resampler,
+ input_buffer,
+ input_frame_count,
+ output_buffer,
+ output_frame_count);
+ assert(rv == RESAMPLER_ERR_SUCCESS);
+ }
+
+ void speex_resample(short * input_buffer, uint32_t * input_frame_count,
+ short * output_buffer, uint32_t * output_frame_count)
+ {
+#ifndef NDEBUG
+ int rv;
+ rv =
+#endif
+ speex_resampler_process_interleaved_int(speex_resampler,
+ input_buffer,
+ input_frame_count,
+ output_buffer,
+ output_frame_count);
+ assert(rv == RESAMPLER_ERR_SUCCESS);
+ }
+ /** The state for the speex resampler used internaly. */
+ SpeexResamplerState * speex_resampler;
+ /** Source rate / target rate. */
+ const float resampling_ratio;
+ /** Storage for the input frames, to be resampled. Also contains
+ * any unresampled frames after resampling. */
+ auto_array<T> resampling_in_buffer;
+ /* Storage for the resampled frames, to be passed back to the caller. */
+ auto_array<T> resampling_out_buffer;
+ /** Additional latency inserted into the pipeline for synchronisation. */
+ uint32_t additional_latency;
+ /** When `input_buffer` is called, this allows tracking the number of samples
+ that were in the buffer. */
+ uint32_t leftover_samples;
+};
+
+/** This class allows delaying an audio stream by `frames` frames. */
+template<typename T>
+class delay_line : public processor {
+public:
+ /** Constructor
+ * @parameter frames the number of frames of delay.
+ * @parameter channels the number of channels of this delay line. */
+ delay_line(uint32_t frames, uint32_t channels)
+ : processor(channels)
+ , length(frames)
+ , leftover_samples(0)
+ {
+ /* Fill the delay line with some silent frames to add latency. */
+ delay_input_buffer.push_silence(frames * channels);
+ }
+ /* Add some latency to the delay line.
+ * @param frames the number of frames of latency to add. */
+ void add_latency(size_t frames)
+ {
+ length += frames;
+ delay_input_buffer.push_silence(frames_to_samples(frames));
+ }
+ /** Push some frames into the delay line.
+ * @parameter buffer the frames to push.
+ * @parameter frame_count the number of frames in #buffer. */
+ void input(T * buffer, uint32_t frame_count)
+ {
+ delay_input_buffer.push(buffer, frames_to_samples(frame_count));
+ }
+ /** Pop some frames from the internal buffer, into a internal output buffer.
+ * @parameter frames_needed the number of frames to be returned.
+ * @return a buffer containing the delayed frames. The consumer should not
+ * hold onto the pointer. */
+ T * output(uint32_t frames_needed)
+ {
+ if (delay_output_buffer.capacity() < frames_to_samples(frames_needed)) {
+ delay_output_buffer.reserve(frames_to_samples(frames_needed));
+ }
+
+ delay_output_buffer.clear();
+ delay_output_buffer.push(delay_input_buffer.data(),
+ frames_to_samples(frames_needed));
+ delay_input_buffer.pop(nullptr, frames_to_samples(frames_needed));
+
+ return delay_output_buffer.data();
+ }
+ /** Get a pointer to the first writable location in the input buffer>
+ * @parameter frames_needed the number of frames the user needs to write into
+ * the buffer.
+ * @returns a pointer to a location in the input buffer where #frames_needed
+ * can be writen. */
+ T * input_buffer(uint32_t frames_needed)
+ {
+ leftover_samples = delay_input_buffer.length();
+ delay_input_buffer.reserve(leftover_samples + frames_to_samples(frames_needed));
+ return delay_input_buffer.data() + leftover_samples;
+ }
+ /** This method works with `input_buffer`, and allows to inform the processor
+ how much frames have been written in the provided buffer. */
+ void written(size_t frames_written)
+ {
+ delay_input_buffer.set_length(leftover_samples +
+ frames_to_samples(frames_written));
+ }
+ /** Drains the delay line, emptying the buffer.
+ * @parameter output_buffer the buffer in which the frames are written.
+ * @parameter frames_needed the maximum number of frames to write.
+ * @return the actual number of frames written. */
+ size_t output(T * output_buffer, uint32_t frames_needed)
+ {
+ uint32_t in_len = samples_to_frames(delay_input_buffer.length());
+ uint32_t out_len = frames_needed;
+
+ uint32_t to_pop = std::min(in_len, out_len);
+
+ delay_input_buffer.pop(output_buffer, frames_to_samples(to_pop));
+
+ return to_pop;
+ }
+ /** Returns the number of frames one needs to input into the delay line to get
+ * #frames_needed frames back.
+ * @parameter frames_needed the number of frames one want to write into the
+ * delay_line
+ * @returns the number of frames one will get. */
+ size_t input_needed_for_output(uint32_t frames_needed)
+ {
+ return frames_needed;
+ }
+ /** Returns the number of frames produces for `input_frames` frames in input */
+ size_t output_for_input(uint32_t input_frames)
+ {
+ return input_frames;
+ }
+ /** The number of frames this delay line delays the stream by.
+ * @returns The number of frames of delay. */
+ size_t latency()
+ {
+ return length;
+ }
+private:
+ /** The length, in frames, of this delay line */
+ uint32_t length;
+ /** When `input_buffer` is called, this allows tracking the number of samples
+ that where in the buffer. */
+ uint32_t leftover_samples;
+ /** The input buffer, where the delay is applied. */
+ auto_array<T> delay_input_buffer;
+ /** The output buffer. This is only ever used if using the ::output with a
+ * single argument. */
+ auto_array<T> delay_output_buffer;
+};
+
+/** This sits behind the C API and is more typed. */
+template<typename T>
+cubeb_resampler *
+cubeb_resampler_create_internal(cubeb_stream * stream,
+ cubeb_stream_params * input_params,
+ cubeb_stream_params * output_params,
+ unsigned int target_rate,
+ cubeb_data_callback callback,
+ void * user_ptr,
+ cubeb_resampler_quality quality)
+{
+ std::unique_ptr<cubeb_resampler_speex_one_way<T>> input_resampler = nullptr;
+ std::unique_ptr<cubeb_resampler_speex_one_way<T>> output_resampler = nullptr;
+ std::unique_ptr<delay_line<T>> input_delay = nullptr;
+ std::unique_ptr<delay_line<T>> output_delay = nullptr;
+
+ assert((input_params || output_params) &&
+ "need at least one valid parameter pointer.");
+
+ /* All the streams we have have a sample rate that matches the target
+ sample rate, use a no-op resampler, that simply forwards the buffers to the
+ callback. */
+ if (((input_params && input_params->rate == target_rate) &&
+ (output_params && output_params->rate == target_rate)) ||
+ (input_params && !output_params && (input_params->rate == target_rate)) ||
+ (output_params && !input_params && (output_params->rate == target_rate))) {
+ return new noop_resampler(stream, callback, user_ptr);
+ }
+
+ /* Determine if we need to resampler one or both directions, and create the
+ resamplers. */
+ if (output_params && (output_params->rate != target_rate)) {
+ output_resampler.reset(
+ new cubeb_resampler_speex_one_way<T>(output_params->channels,
+ target_rate,
+ output_params->rate,
+ to_speex_quality(quality)));
+ if (!output_resampler) {
+ return NULL;
+ }
+ }
+
+ if (input_params && (input_params->rate != target_rate)) {
+ input_resampler.reset(
+ new cubeb_resampler_speex_one_way<T>(input_params->channels,
+ input_params->rate,
+ target_rate,
+ to_speex_quality(quality)));
+ if (!input_resampler) {
+ return NULL;
+ }
+ }
+
+ /* If we resample only one direction but we have a duplex stream, insert a
+ * delay line with a length equal to the resampler latency of the
+ * other direction so that the streams are synchronized. */
+ if (input_resampler && !output_resampler && input_params && output_params) {
+ output_delay.reset(new delay_line<T>(input_resampler->latency(),
+ output_params->channels));
+ if (!output_delay) {
+ return NULL;
+ }
+ } else if (output_resampler && !input_resampler && input_params && output_params) {
+ input_delay.reset(new delay_line<T>(output_resampler->latency(),
+ input_params->channels));
+ if (!input_delay) {
+ return NULL;
+ }
+ }
+
+ if (input_resampler && output_resampler) {
+ return new cubeb_resampler_speex<T,
+ cubeb_resampler_speex_one_way<T>,
+ cubeb_resampler_speex_one_way<T>>
+ (input_resampler.release(),
+ output_resampler.release(),
+ stream, callback, user_ptr);
+ } else if (input_resampler) {
+ return new cubeb_resampler_speex<T,
+ cubeb_resampler_speex_one_way<T>,
+ delay_line<T>>
+ (input_resampler.release(),
+ output_delay.release(),
+ stream, callback, user_ptr);
+ } else {
+ return new cubeb_resampler_speex<T,
+ delay_line<T>,
+ cubeb_resampler_speex_one_way<T>>
+ (input_delay.release(),
+ output_resampler.release(),
+ stream, callback, user_ptr);
+ }
+}
+
+#endif /* CUBEB_RESAMPLER_INTERNAL */