diff options
Diffstat (limited to 'media/libcubeb/src/cubeb_resampler_internal.h')
-rw-r--r-- | media/libcubeb/src/cubeb_resampler_internal.h | 551 |
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 */ |