diff options
Diffstat (limited to 'media/libcubeb/src/cubeb_resampler_internal.h')
-rw-r--r-- | media/libcubeb/src/cubeb_resampler_internal.h | 348 |
1 files changed, 192 insertions, 156 deletions
diff --git a/media/libcubeb/src/cubeb_resampler_internal.h b/media/libcubeb/src/cubeb_resampler_internal.h index 3c37a04b9c..2eabb7c4c4 100644 --- a/media/libcubeb/src/cubeb_resampler_internal.h +++ b/media/libcubeb/src/cubeb_resampler_internal.h @@ -8,9 +8,9 @@ #if !defined(CUBEB_RESAMPLER_INTERNAL) #define CUBEB_RESAMPLER_INTERNAL -#include <cmath> -#include <cassert> #include <algorithm> +#include <cassert> +#include <cmath> #include <memory> #ifdef CUBEB_GECKO_BUILD #include "mozilla/UniquePtr.h" @@ -25,21 +25,32 @@ #define MOZ_END_STD_NAMESPACE } #endif MOZ_BEGIN_STD_NAMESPACE - using mozilla::DefaultDelete; - using mozilla::UniquePtr; - #define default_delete DefaultDelete - #define unique_ptr UniquePtr +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/cubeb.h" +#include "cubeb_log.h" #include "cubeb_resampler.h" +#include "cubeb_utils.h" #include <stdio.h> -/* This header file contains the internal C++ API of the resamplers, for testing. */ +/* This header file contains the internal C++ API of the resamplers, for + * testing. */ -int to_speex_quality(cubeb_resampler_quality q); +// When dropping audio input frames to prevent building +// an input delay, this function returns the number of frames +// to keep in the buffer. +// @parameter sample_rate The sample rate of the stream. +// @return A number of frames to keep. +uint32_t +min_buffered_audio_frame(uint32_t sample_rate); + +int +to_speex_quality(cubeb_resampler_quality q); struct cubeb_resampler { virtual long fill(void * input_buffer, long * input_frames_count, @@ -48,62 +59,62 @@ struct cubeb_resampler { virtual ~cubeb_resampler() {} }; -class noop_resampler : public cubeb_resampler { +/** Base class for processors. This is just used to share methods for now. */ +class processor { public: - noop_resampler(cubeb_stream * s, - cubeb_data_callback cb, - void * ptr) - : stream(s) - , data_callback(cb) - , user_ptr(ptr) + explicit processor(uint32_t channels) : channels(channels) {} + +protected: + size_t frames_to_samples(size_t frames) const { return frames * channels; } + size_t samples_to_frames(size_t samples) const { + assert(!(samples % channels)); + return samples / channels; } + /** The number of channel of the audio buffers to be resampled. */ + const uint32_t channels; +}; + +template <typename T> +class passthrough_resampler : public cubeb_resampler, public processor { +public: + passthrough_resampler(cubeb_stream * s, cubeb_data_callback cb, void * ptr, + uint32_t input_channels, uint32_t sample_rate); virtual long fill(void * input_buffer, long * input_frames_count, void * output_buffer, long output_frames); - virtual long latency() + virtual long latency() { return 0; } + + void drop_audio_if_needed() { - return 0; + uint32_t to_keep = min_buffered_audio_frame(sample_rate); + uint32_t available = samples_to_frames(internal_input_buffer.length()); + if (available > to_keep) { + internal_input_buffer.pop(nullptr, + frames_to_samples(available - to_keep)); + } } 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; + /* This allows to buffer some input to account for the fact that we buffer + * some inputs. */ + auto_array<T> internal_input_buffer; + uint32_t sample_rate; }; /** 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> +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); + OutputProcessing * output_processor, cubeb_stream * s, + cubeb_data_callback cb, void * ptr); virtual ~cubeb_resampler_speex(); @@ -123,7 +134,9 @@ public: } private: - typedef long(cubeb_resampler_speex::*processing_callback)(T * input_buffer, long * input_frames_count, T * output_buffer, long output_frames_needed); + 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); @@ -138,14 +151,14 @@ private: cubeb_stream * const stream; const cubeb_data_callback data_callback; void * const user_ptr; + bool draining = false; }; /** 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 { +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. */ @@ -157,19 +170,26 @@ public: * @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) + 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), + source_rate(source_rate), additional_latency(0), leftover_samples(0) { int r; - speex_resampler = speex_resampler_init(channels, source_rate, - target_rate, quality, &r); + speex_resampler = + speex_resampler_init(channels, source_rate, target_rate, quality, &r); assert(r == RESAMPLER_ERR_SUCCESS && "resampler allocation failure"); + + uint32_t input_latency = speex_resampler_get_input_latency(speex_resampler); + const size_t LATENCY_SAMPLES = 8192; + T input_buffer[LATENCY_SAMPLES] = {}; + T output_buffer[LATENCY_SAMPLES] = {}; + uint32_t input_frame_count = input_latency; + uint32_t output_frame_count = LATENCY_SAMPLES; + assert(input_latency * channels <= LATENCY_SAMPLES); + speex_resample(input_buffer, &input_frame_count, output_buffer, + &output_frame_count); } /** Destructor, deallocate the resampler */ @@ -178,17 +198,6 @@ public: 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) { @@ -197,14 +206,14 @@ public: } /** Outputs exactly `output_frame_count` into `output_buffer`. - * `output_buffer` has to be at least `output_frame_count` long. */ + * `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); + 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. */ @@ -215,15 +224,17 @@ public: size_t output_for_input(uint32_t input_frames) { - return (size_t)floorf((input_frames + samples_to_frames(resampling_in_buffer.length())) - / resampling_ratio); + 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) + * The consumer should not hold onto the pointer. */ + T * output(size_t output_frame_count, size_t * input_frames_used) { - if (resampling_out_buffer.capacity() < frames_to_samples(output_frame_count)) { + if (resampling_out_buffer.capacity() < + frames_to_samples(output_frame_count)) { resampling_out_buffer.reserve(frames_to_samples(output_frame_count)); } @@ -233,11 +244,21 @@ public: speex_resample(resampling_in_buffer.data(), &in_len, resampling_out_buffer.data(), &out_len); - assert(out_len == output_frame_count); + if (out_len < output_frame_count) { + LOGV("underrun during resampling: got %u frames, expected %zu", + (unsigned)out_len, output_frame_count); + // silence the rightmost part + T * data = resampling_out_buffer.data(); + for (uint32_t i = frames_to_samples(out_len); + i < frames_to_samples(output_frame_count); i++) { + data[i] = 0; + } + } /* This shifts back any unresampled samples to the beginning of the input buffer. */ resampling_in_buffer.pop(nullptr, frames_to_samples(in_len)); + *input_frames_used = in_len; return resampling_out_buffer.data(); } @@ -249,8 +270,8 @@ public: * 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; + latency = speex_resampler_get_output_latency(speex_resampler) + + additional_latency; assert(latency >= 0); @@ -261,13 +282,16 @@ public: * 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) + uint32_t input_needed_for_output(int32_t output_frame_count) const { - int32_t unresampled_frames_left = samples_to_frames(resampling_in_buffer.length()); - int32_t resampled_frames_left = samples_to_frames(resampling_out_buffer.length()); + assert(output_frame_count >= 0); // Check overflow + 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; + (output_frame_count - unresampled_frames_left) * resampling_ratio - + resampled_frames_left; if (input_frames_needed < 0) { return 0; } @@ -294,9 +318,20 @@ public: resampling_in_buffer.set_length(leftover_samples + frames_to_samples(written_frames)); } + + void drop_audio_if_needed() + { + // Keep at most 100ms buffered. + uint32_t available = samples_to_frames(resampling_in_buffer.length()); + uint32_t to_keep = min_buffered_audio_frame(source_rate); + if (available > to_keep) { + resampling_in_buffer.pop(nullptr, frames_to_samples(available - to_keep)); + } + } + private: /** Wrapper for the speex resampling functions to have a typed - * interface. */ + * interface. */ void speex_resample(float * input_buffer, uint32_t * input_frame_count, float * output_buffer, uint32_t * output_frame_count) { @@ -304,11 +339,9 @@ private: int rv; rv = #endif - speex_resampler_process_interleaved_float(speex_resampler, - input_buffer, - input_frame_count, - output_buffer, - output_frame_count); + speex_resampler_process_interleaved_float( + speex_resampler, input_buffer, input_frame_count, output_buffer, + output_frame_count); assert(rv == RESAMPLER_ERR_SUCCESS); } @@ -319,17 +352,16 @@ private: int rv; rv = #endif - speex_resampler_process_interleaved_int(speex_resampler, - input_buffer, - input_frame_count, - output_buffer, - output_frame_count); + 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; + const uint32_t source_rate; /** Storage for the input frames, to be resampled. Also contains * any unresampled frames after resampling. */ auto_array<T> resampling_in_buffer; @@ -343,27 +375,20 @@ private: }; /** This class allows delaying an audio stream by `frames` frames. */ -template<typename T> -class delay_line : public processor { +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) + * @parameter channels the number of channels of this delay line. + * @parameter sample_rate sample-rate of the audio going through this delay + * line */ + delay_line(uint32_t frames, uint32_t channels, uint32_t sample_rate) + : processor(channels), length(frames), leftover_samples(0), + sample_rate(sample_rate) { /* 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. */ @@ -375,7 +400,7 @@ public: * @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) + T * output(uint32_t frames_needed, size_t * input_frames_used) { if (delay_output_buffer.capacity() < frames_to_samples(frames_needed)) { delay_output_buffer.reserve(frames_to_samples(frames_needed)); @@ -385,6 +410,7 @@ public: delay_output_buffer.push(delay_input_buffer.data(), frames_to_samples(frames_needed)); delay_input_buffer.pop(nullptr, frames_to_samples(frames_needed)); + *input_frames_used = frames_needed; return delay_output_buffer.data(); } @@ -396,7 +422,8 @@ public: T * input_buffer(uint32_t frames_needed) { leftover_samples = delay_input_buffer.length(); - delay_input_buffer.reserve(leftover_samples + frames_to_samples(frames_needed)); + 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 @@ -426,21 +453,27 @@ public: * @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) + uint32_t input_needed_for_output(int32_t frames_needed) const { + assert(frames_needed >= 0); // Check overflow 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; - } + /** 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() + size_t latency() { return length; } + + void drop_audio_if_needed() { - return length; + size_t available = samples_to_frames(delay_input_buffer.length()); + uint32_t to_keep = min_buffered_audio_frame(sample_rate); + if (available > to_keep) { + delay_input_buffer.pop(nullptr, frames_to_samples(available - to_keep)); + } } + private: /** The length, in frames, of this delay line */ uint32_t length; @@ -452,17 +485,17 @@ private: /** The output buffer. This is only ever used if using the ::output with a * single argument. */ auto_array<T> delay_output_buffer; + uint32_t sample_rate; }; /** This sits behind the C API and is more typed. */ -template<typename T> +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_data_callback callback, void * user_ptr, cubeb_resampler_quality quality) { std::unique_ptr<cubeb_resampler_speex_one_way<T>> input_resampler = nullptr; @@ -477,31 +510,31 @@ cubeb_resampler_create_internal(cubeb_stream * stream, 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)) || + (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); + (output_params && !input_params && + (output_params->rate == target_rate))) { + LOG("Input and output sample-rate match, target rate of %dHz", target_rate); + return new passthrough_resampler<T>( + stream, callback, user_ptr, input_params ? input_params->channels : 0, + target_rate); } /* 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))); + 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))); + 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; } @@ -512,39 +545,42 @@ cubeb_resampler_create_internal(cubeb_stream * stream, * 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)); + output_params->channels, + output_params->rate)); if (!output_delay) { return NULL; } - } else if (output_resampler && !input_resampler && input_params && output_params) { + } else if (output_resampler && !input_resampler && input_params && + output_params) { input_delay.reset(new delay_line<T>(output_resampler->latency(), - input_params->channels)); + input_params->channels, + output_params->rate)); 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); + LOG("Resampling input (%d) and output (%d) to target rate of %dHz", + input_params->rate, output_params->rate, target_rate); + 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); + LOG("Resampling input (%d) to target and output rate of %dHz", + input_params->rate, target_rate); + 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); + LOG("Resampling output (%dHz) to target and input rate of %dHz", + output_params->rate, target_rate); + 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); } } |