| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| #include "utils/resample.h" |
| #include "utils/logger.h" |
|
|
| #include <cassert> |
| #include <cmath> |
| #include <cstdio> |
| #include <cstdlib> |
| #include <type_traits> |
| #include <vector> |
| #include <memory> |
|
|
| #ifndef M_2PI |
| #define M_2PI 6.283185307179586476925286766559005 |
| #endif |
|
|
| #ifndef M_PI |
| #define M_PI 3.1415926535897932384626433832795 |
| #endif |
|
|
| namespace utils { |
|
|
| template <class I> |
| static I Gcd(I m, I n) { |
| |
| if (m == 0 || n == 0) { |
| if (m == 0 && n == 0) { |
| fprintf(stderr, "Undefined GCD since m = 0, n = 0.\n"); |
| exit(-1); |
| } |
| return (m == 0 ? (n > 0 ? n : -n) : (m > 0 ? m : -m)); |
| |
| } |
| |
| |
| static_assert(std::is_integral_v<I>); |
| while (true) { |
| m %= n; |
| if (m == 0) return (n > 0 ? n : -n); |
| n %= m; |
| if (n == 0) return (m > 0 ? m : -m); |
| } |
| } |
|
|
| |
| |
| template <class I> |
| static I Lcm(I m, I n) { |
| |
| assert(m > 0 && n > 0); |
| I gcd = Gcd(m, n); |
| return gcd * (m / gcd) * (n / gcd); |
| } |
|
|
| static float DotProduct(const float *a, const float *b, int32_t n) { |
| float sum = 0; |
| for (int32_t i = 0; i != n; ++i) { |
| sum += a[i] * b[i]; |
| } |
| return sum; |
| } |
|
|
| LinearResample::LinearResample(int32_t samp_rate_in_hz, |
| int32_t samp_rate_out_hz, float filter_cutoff_hz, |
| int32_t num_zeros) |
| : samp_rate_in_(samp_rate_in_hz), |
| samp_rate_out_(samp_rate_out_hz), |
| filter_cutoff_(filter_cutoff_hz), |
| num_zeros_(num_zeros) { |
| assert(samp_rate_in_hz > 0.0 && samp_rate_out_hz > 0.0 && |
| filter_cutoff_hz > 0.0 && filter_cutoff_hz * 2 <= samp_rate_in_hz && |
| filter_cutoff_hz * 2 <= samp_rate_out_hz && num_zeros > 0); |
|
|
| |
| |
| int32_t base_freq = Gcd(samp_rate_in_, samp_rate_out_); |
| input_samples_in_unit_ = samp_rate_in_ / base_freq; |
| output_samples_in_unit_ = samp_rate_out_ / base_freq; |
|
|
| SetIndexesAndWeights(); |
| Reset(); |
| } |
|
|
| void LinearResample::SetIndexesAndWeights() { |
| first_index_.resize(output_samples_in_unit_); |
| weights_.resize(output_samples_in_unit_); |
|
|
| double window_width = num_zeros_ / (2.0 * filter_cutoff_); |
|
|
| for (int32_t i = 0; i < output_samples_in_unit_; i++) { |
| double output_t = i / static_cast<double>(samp_rate_out_); |
| double min_t = output_t - window_width, max_t = output_t + window_width; |
| |
| |
| |
| |
| |
| |
| |
| int32_t min_input_index = ceil(min_t * samp_rate_in_), |
| max_input_index = floor(max_t * samp_rate_in_), |
| num_indices = max_input_index - min_input_index + 1; |
| first_index_[i] = min_input_index; |
| weights_[i].resize(num_indices); |
| for (int32_t j = 0; j < num_indices; j++) { |
| int32_t input_index = min_input_index + j; |
| double input_t = input_index / static_cast<double>(samp_rate_in_), |
| delta_t = input_t - output_t; |
| |
| weights_[i][j] = FilterFunc(delta_t) / samp_rate_in_; |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| float LinearResample::FilterFunc(float t) const { |
| float window = 0, |
| |
| filter = 0; |
| if (std::fabs(t) < num_zeros_ / (2.0 * filter_cutoff_)) |
| window = 0.5 * (1 + cos(M_2PI * filter_cutoff_ / num_zeros_ * t)); |
| else |
| window = 0.0; |
| if (t != 0) |
| filter = sin(M_2PI * filter_cutoff_ * t) / (M_PI * t); |
| else |
| filter = 2 * filter_cutoff_; |
| return filter * window; |
| } |
|
|
| void LinearResample::Reset() { |
| input_sample_offset_ = 0; |
| output_sample_offset_ = 0; |
| input_remainder_.resize(0); |
| } |
|
|
| void LinearResample::Resample(const float *input, int32_t input_dim, bool flush, |
| std::vector<float> *output) { |
| int64_t tot_input_samp = input_sample_offset_ + input_dim, |
| tot_output_samp = GetNumOutputSamples(tot_input_samp, flush); |
|
|
| assert(tot_output_samp >= output_sample_offset_); |
|
|
| output->resize(tot_output_samp - output_sample_offset_); |
|
|
| |
| |
| for (int64_t samp_out = output_sample_offset_; samp_out < tot_output_samp; |
| samp_out++) { |
| int64_t first_samp_in = 0; |
| int32_t samp_out_wrapped = 0; |
| GetIndexes(samp_out, &first_samp_in, &samp_out_wrapped); |
| const std::vector<float> &weights = weights_[samp_out_wrapped]; |
| |
| |
| int32_t first_input_index = |
| static_cast<int32_t>(first_samp_in - input_sample_offset_); |
| float this_output = 0; |
| if (first_input_index >= 0 && |
| first_input_index + static_cast<int32_t>(weights.size()) <= input_dim) { |
| this_output = |
| DotProduct(input + first_input_index, weights.data(), weights.size()); |
| } else { |
| this_output = 0.0; |
| for (int32_t i = 0; i < static_cast<int32_t>(weights.size()); i++) { |
| float weight = weights[i]; |
| int32_t input_index = first_input_index + i; |
| if (input_index < 0 && |
| static_cast<int32_t>(input_remainder_.size()) + input_index >= 0) { |
| this_output += |
| weight * input_remainder_[input_remainder_.size() + input_index]; |
| } else if (input_index >= 0 && input_index < input_dim) { |
| this_output += weight * input[input_index]; |
| } else if (input_index >= input_dim) { |
| |
| |
| |
| assert(flush); |
| } |
| } |
| } |
| int32_t output_index = |
| static_cast<int32_t>(samp_out - output_sample_offset_); |
| (*output)[output_index] = this_output; |
| } |
|
|
| if (flush) { |
| Reset(); |
| } else { |
| SetRemainder(input, input_dim); |
| input_sample_offset_ = tot_input_samp; |
| output_sample_offset_ = tot_output_samp; |
| } |
| } |
|
|
| int64_t LinearResample::GetNumOutputSamples(int64_t input_num_samp, |
| bool flush) const { |
| |
| |
| |
| int32_t tick_freq = Lcm(samp_rate_in_, samp_rate_out_); |
| int32_t ticks_per_input_period = tick_freq / samp_rate_in_; |
|
|
| |
| |
| int64_t interval_length_in_ticks = input_num_samp * ticks_per_input_period; |
| if (!flush) { |
| float window_width = num_zeros_ / (2.0 * filter_cutoff_); |
| |
| |
| |
| |
| |
| |
| |
| |
| int32_t window_width_ticks = std::floor(window_width * tick_freq); |
| |
| |
| |
| |
| interval_length_in_ticks -= window_width_ticks; |
| } |
| if (interval_length_in_ticks <= 0) return 0; |
|
|
| int32_t ticks_per_output_period = tick_freq / samp_rate_out_; |
| |
| |
| |
| |
| int64_t last_output_samp = interval_length_in_ticks / ticks_per_output_period; |
| |
| |
| if (last_output_samp * ticks_per_output_period == interval_length_in_ticks) |
| last_output_samp--; |
|
|
| |
| |
| int64_t num_output_samp = last_output_samp + 1; |
| return num_output_samp; |
| } |
|
|
| |
| void LinearResample::GetIndexes(int64_t samp_out, int64_t *first_samp_in, |
| int32_t *samp_out_wrapped) const { |
| |
| |
| |
| int64_t unit_index = samp_out / output_samples_in_unit_; |
| |
| *samp_out_wrapped = |
| static_cast<int32_t>(samp_out - unit_index * output_samples_in_unit_); |
| *first_samp_in = |
| first_index_[*samp_out_wrapped] + unit_index * input_samples_in_unit_; |
| } |
|
|
| void LinearResample::SetRemainder(const float *input, int32_t input_dim) { |
| std::vector<float> old_remainder(input_remainder_); |
| |
| |
| |
| |
| |
| int32_t max_remainder_needed = |
| std::ceil(samp_rate_in_ * num_zeros_ / filter_cutoff_); |
| input_remainder_.resize(max_remainder_needed); |
| for (int32_t index = -static_cast<int32_t>(input_remainder_.size()); |
| index < 0; index++) { |
| |
| |
| int32_t input_index = index + input_dim; |
| if (input_index >= 0) { |
| input_remainder_[index + static_cast<int32_t>(input_remainder_.size())] = |
| input[input_index]; |
| } else if (input_index + static_cast<int32_t>(old_remainder.size()) >= 0) { |
| input_remainder_[index + static_cast<int32_t>(input_remainder_.size())] = |
| old_remainder[input_index + |
| static_cast<int32_t>(old_remainder.size())]; |
| |
| } |
| } |
| } |
|
|
| std::vector<float> resample(const std::vector<float>& audio_data, int orig_sr, int target_sr) { |
| if (orig_sr != target_sr) { |
| ALOGD("Audio resample: %d -> %d", orig_sr, target_sr); |
| std::vector<float> resampled_data; |
| float min_freq = std::min<int32_t>(orig_sr, target_sr); |
| float lowpass_cutoff = 0.99 * 0.5 * min_freq; |
|
|
| int32_t lowpass_filter_width = 6; |
| auto resampler = std::make_unique<LinearResample>( |
| orig_sr, target_sr, lowpass_cutoff, |
| lowpass_filter_width); |
|
|
| resampler->Resample(audio_data.data(), audio_data.size(), true, &resampled_data); |
| return resampled_data; |
| } else { |
| return audio_data; |
| } |
| } |
|
|
| } |
|
|