diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /third_party/libwebrtc/common_audio/resampler | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/common_audio/resampler')
17 files changed, 3200 insertions, 0 deletions
diff --git a/third_party/libwebrtc/common_audio/resampler/include/push_resampler.h b/third_party/libwebrtc/common_audio/resampler/include/push_resampler.h new file mode 100644 index 0000000000..3da67120f0 --- /dev/null +++ b/third_party/libwebrtc/common_audio/resampler/include/push_resampler.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef COMMON_AUDIO_RESAMPLER_INCLUDE_PUSH_RESAMPLER_H_ +#define COMMON_AUDIO_RESAMPLER_INCLUDE_PUSH_RESAMPLER_H_ + +#include <memory> +#include <vector> + +namespace webrtc { + +class PushSincResampler; + +// Wraps PushSincResampler to provide stereo support. +// TODO(ajm): add support for an arbitrary number of channels. +template <typename T> +class PushResampler { + public: + PushResampler(); + virtual ~PushResampler(); + + // Must be called whenever the parameters change. Free to be called at any + // time as it is a no-op if parameters have not changed since the last call. + int InitializeIfNeeded(int src_sample_rate_hz, + int dst_sample_rate_hz, + size_t num_channels); + + // Returns the total number of samples provided in destination (e.g. 32 kHz, + // 2 channel audio gives 640 samples). + int Resample(const T* src, size_t src_length, T* dst, size_t dst_capacity); + + private: + int src_sample_rate_hz_; + int dst_sample_rate_hz_; + size_t num_channels_; + // Vector that is needed to provide the proper inputs and outputs to the + // interleave/de-interleave methods used in Resample. This needs to be + // heap-allocated on the state to support an arbitrary number of channels + // without doing run-time heap-allocations in the Resample method. + std::vector<T*> channel_data_array_; + + struct ChannelResampler { + std::unique_ptr<PushSincResampler> resampler; + std::vector<T> source; + std::vector<T> destination; + }; + + std::vector<ChannelResampler> channel_resamplers_; +}; +} // namespace webrtc + +#endif // COMMON_AUDIO_RESAMPLER_INCLUDE_PUSH_RESAMPLER_H_ diff --git a/third_party/libwebrtc/common_audio/resampler/include/resampler.h b/third_party/libwebrtc/common_audio/resampler/include/resampler.h new file mode 100644 index 0000000000..41940f9a12 --- /dev/null +++ b/third_party/libwebrtc/common_audio/resampler/include/resampler.h @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * A wrapper for resampling a numerous amount of sampling combinations. + */ + +#ifndef COMMON_AUDIO_RESAMPLER_INCLUDE_RESAMPLER_H_ +#define COMMON_AUDIO_RESAMPLER_INCLUDE_RESAMPLER_H_ + +#include <stddef.h> +#include <stdint.h> + +namespace webrtc { + +// All methods return 0 on success and -1 on failure. +class Resampler { + public: + Resampler(); + Resampler(int inFreq, int outFreq, size_t num_channels); + ~Resampler(); + + // Reset all states + int Reset(int inFreq, int outFreq, size_t num_channels); + + // Reset all states if any parameter has changed + int ResetIfNeeded(int inFreq, int outFreq, size_t num_channels); + + // Resample samplesIn to samplesOut. + int Push(const int16_t* samplesIn, + size_t lengthIn, + int16_t* samplesOut, + size_t maxLen, + size_t& outLen); // NOLINT: to avoid changing APIs + + private: + enum ResamplerMode { + kResamplerMode1To1, + kResamplerMode1To2, + kResamplerMode1To3, + kResamplerMode1To4, + kResamplerMode1To6, + kResamplerMode1To12, + kResamplerMode2To3, + kResamplerMode2To11, + kResamplerMode4To11, + kResamplerMode8To11, + kResamplerMode11To16, + kResamplerMode11To32, + kResamplerMode2To1, + kResamplerMode3To1, + kResamplerMode4To1, + kResamplerMode6To1, + kResamplerMode12To1, + kResamplerMode3To2, + kResamplerMode11To2, + kResamplerMode11To4, + kResamplerMode11To8 + }; + + // Computes the resampler mode for a given sampling frequency pair. + // Returns -1 for unsupported frequency pairs. + static int ComputeResamplerMode(int in_freq_hz, + int out_freq_hz, + ResamplerMode* mode); + + // Generic pointers since we don't know what states we'll need + void* state1_; + void* state2_; + void* state3_; + + // Storage if needed + int16_t* in_buffer_; + int16_t* out_buffer_; + size_t in_buffer_size_; + size_t out_buffer_size_; + size_t in_buffer_size_max_; + size_t out_buffer_size_max_; + + int my_in_frequency_khz_; + int my_out_frequency_khz_; + ResamplerMode my_mode_; + size_t num_channels_; + + // Extra instance for stereo + Resampler* helper_left_; + Resampler* helper_right_; +}; + +} // namespace webrtc + +#endif // COMMON_AUDIO_RESAMPLER_INCLUDE_RESAMPLER_H_ diff --git a/third_party/libwebrtc/common_audio/resampler/push_resampler.cc b/third_party/libwebrtc/common_audio/resampler/push_resampler.cc new file mode 100644 index 0000000000..810d778993 --- /dev/null +++ b/third_party/libwebrtc/common_audio/resampler/push_resampler.cc @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "common_audio/resampler/include/push_resampler.h" + +#include <stdint.h> +#include <string.h> + +#include <memory> + +#include "common_audio/include/audio_util.h" +#include "common_audio/resampler/push_sinc_resampler.h" +#include "rtc_base/checks.h" + +namespace webrtc { + +template <typename T> +PushResampler<T>::PushResampler() + : src_sample_rate_hz_(0), dst_sample_rate_hz_(0), num_channels_(0) {} + +template <typename T> +PushResampler<T>::~PushResampler() {} + +template <typename T> +int PushResampler<T>::InitializeIfNeeded(int src_sample_rate_hz, + int dst_sample_rate_hz, + size_t num_channels) { + // These checks used to be factored out of this template function due to + // Windows debug build issues with clang. http://crbug.com/615050 + RTC_DCHECK_GT(src_sample_rate_hz, 0); + RTC_DCHECK_GT(dst_sample_rate_hz, 0); + RTC_DCHECK_GT(num_channels, 0); + + if (src_sample_rate_hz == src_sample_rate_hz_ && + dst_sample_rate_hz == dst_sample_rate_hz_ && + num_channels == num_channels_) { + // No-op if settings haven't changed. + return 0; + } + + if (src_sample_rate_hz <= 0 || dst_sample_rate_hz <= 0 || num_channels <= 0) { + return -1; + } + + src_sample_rate_hz_ = src_sample_rate_hz; + dst_sample_rate_hz_ = dst_sample_rate_hz; + num_channels_ = num_channels; + + const size_t src_size_10ms_mono = + static_cast<size_t>(src_sample_rate_hz / 100); + const size_t dst_size_10ms_mono = + static_cast<size_t>(dst_sample_rate_hz / 100); + channel_resamplers_.clear(); + for (size_t i = 0; i < num_channels; ++i) { + channel_resamplers_.push_back(ChannelResampler()); + auto channel_resampler = channel_resamplers_.rbegin(); + channel_resampler->resampler = std::make_unique<PushSincResampler>( + src_size_10ms_mono, dst_size_10ms_mono); + channel_resampler->source.resize(src_size_10ms_mono); + channel_resampler->destination.resize(dst_size_10ms_mono); + } + + channel_data_array_.resize(num_channels_); + + return 0; +} + +template <typename T> +int PushResampler<T>::Resample(const T* src, + size_t src_length, + T* dst, + size_t dst_capacity) { + // These checks used to be factored out of this template function due to + // Windows debug build issues with clang. http://crbug.com/615050 + const size_t src_size_10ms = (src_sample_rate_hz_ / 100) * num_channels_; + const size_t dst_size_10ms = (dst_sample_rate_hz_ / 100) * num_channels_; + RTC_DCHECK_EQ(src_length, src_size_10ms); + RTC_DCHECK_GE(dst_capacity, dst_size_10ms); + + if (src_sample_rate_hz_ == dst_sample_rate_hz_) { + // The old resampler provides this memcpy facility in the case of matching + // sample rates, so reproduce it here for the sinc resampler. + memcpy(dst, src, src_length * sizeof(T)); + return static_cast<int>(src_length); + } + + const size_t src_length_mono = src_length / num_channels_; + const size_t dst_capacity_mono = dst_capacity / num_channels_; + + for (size_t ch = 0; ch < num_channels_; ++ch) { + channel_data_array_[ch] = channel_resamplers_[ch].source.data(); + } + + Deinterleave(src, src_length_mono, num_channels_, channel_data_array_.data()); + + size_t dst_length_mono = 0; + + for (auto& resampler : channel_resamplers_) { + dst_length_mono = resampler.resampler->Resample( + resampler.source.data(), src_length_mono, resampler.destination.data(), + dst_capacity_mono); + } + + for (size_t ch = 0; ch < num_channels_; ++ch) { + channel_data_array_[ch] = channel_resamplers_[ch].destination.data(); + } + + Interleave(channel_data_array_.data(), dst_length_mono, num_channels_, dst); + return static_cast<int>(dst_length_mono * num_channels_); +} + +// Explictly generate required instantiations. +template class PushResampler<int16_t>; +template class PushResampler<float>; + +} // namespace webrtc diff --git a/third_party/libwebrtc/common_audio/resampler/push_resampler_unittest.cc b/third_party/libwebrtc/common_audio/resampler/push_resampler_unittest.cc new file mode 100644 index 0000000000..91f2233aad --- /dev/null +++ b/third_party/libwebrtc/common_audio/resampler/push_resampler_unittest.cc @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "common_audio/resampler/include/push_resampler.h" + +#include "rtc_base/checks.h" // RTC_DCHECK_IS_ON +#include "test/gtest.h" +#include "test/testsupport/rtc_expect_death.h" + +// Quality testing of PushResampler is done in audio/remix_resample_unittest.cc. + +namespace webrtc { + +TEST(PushResamplerTest, VerifiesInputParameters) { + PushResampler<int16_t> resampler; + EXPECT_EQ(0, resampler.InitializeIfNeeded(16000, 16000, 1)); + EXPECT_EQ(0, resampler.InitializeIfNeeded(16000, 16000, 2)); + EXPECT_EQ(0, resampler.InitializeIfNeeded(16000, 16000, 8)); +} + +#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) +TEST(PushResamplerDeathTest, VerifiesBadInputParameters1) { + PushResampler<int16_t> resampler; + RTC_EXPECT_DEATH(resampler.InitializeIfNeeded(-1, 16000, 1), + "src_sample_rate_hz"); +} + +TEST(PushResamplerDeathTest, VerifiesBadInputParameters2) { + PushResampler<int16_t> resampler; + RTC_EXPECT_DEATH(resampler.InitializeIfNeeded(16000, -1, 1), + "dst_sample_rate_hz"); +} + +TEST(PushResamplerDeathTest, VerifiesBadInputParameters3) { + PushResampler<int16_t> resampler; + RTC_EXPECT_DEATH(resampler.InitializeIfNeeded(16000, 16000, 0), + "num_channels"); +} +#endif + +} // namespace webrtc diff --git a/third_party/libwebrtc/common_audio/resampler/push_sinc_resampler.cc b/third_party/libwebrtc/common_audio/resampler/push_sinc_resampler.cc new file mode 100644 index 0000000000..d4b7eed026 --- /dev/null +++ b/third_party/libwebrtc/common_audio/resampler/push_sinc_resampler.cc @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "common_audio/resampler/push_sinc_resampler.h" + +#include <cstring> + +#include "common_audio/include/audio_util.h" +#include "rtc_base/checks.h" + +namespace webrtc { + +PushSincResampler::PushSincResampler(size_t source_frames, + size_t destination_frames) + : resampler_(new SincResampler(source_frames * 1.0 / destination_frames, + source_frames, + this)), + source_ptr_(nullptr), + source_ptr_int_(nullptr), + destination_frames_(destination_frames), + first_pass_(true), + source_available_(0) {} + +PushSincResampler::~PushSincResampler() {} + +size_t PushSincResampler::Resample(const int16_t* source, + size_t source_length, + int16_t* destination, + size_t destination_capacity) { + if (!float_buffer_.get()) + float_buffer_.reset(new float[destination_frames_]); + + source_ptr_int_ = source; + // Pass nullptr as the float source to have Run() read from the int16 source. + Resample(nullptr, source_length, float_buffer_.get(), destination_frames_); + FloatS16ToS16(float_buffer_.get(), destination_frames_, destination); + source_ptr_int_ = nullptr; + return destination_frames_; +} + +size_t PushSincResampler::Resample(const float* source, + size_t source_length, + float* destination, + size_t destination_capacity) { + RTC_CHECK_EQ(source_length, resampler_->request_frames()); + RTC_CHECK_GE(destination_capacity, destination_frames_); + // Cache the source pointer. Calling Resample() will immediately trigger + // the Run() callback whereupon we provide the cached value. + source_ptr_ = source; + source_available_ = source_length; + + // On the first pass, we call Resample() twice. During the first call, we + // provide dummy input and discard the output. This is done to prime the + // SincResampler buffer with the correct delay (half the kernel size), thereby + // ensuring that all later Resample() calls will only result in one input + // request through Run(). + // + // If this wasn't done, SincResampler would call Run() twice on the first + // pass, and we'd have to introduce an entire `source_frames` of delay, rather + // than the minimum half kernel. + // + // It works out that ChunkSize() is exactly the amount of output we need to + // request in order to prime the buffer with a single Run() request for + // `source_frames`. + if (first_pass_) + resampler_->Resample(resampler_->ChunkSize(), destination); + + resampler_->Resample(destination_frames_, destination); + source_ptr_ = nullptr; + return destination_frames_; +} + +void PushSincResampler::Run(size_t frames, float* destination) { + // Ensure we are only asked for the available samples. This would fail if + // Run() was triggered more than once per Resample() call. + RTC_CHECK_EQ(source_available_, frames); + + if (first_pass_) { + // Provide dummy input on the first pass, the output of which will be + // discarded, as described in Resample(). + std::memset(destination, 0, frames * sizeof(*destination)); + first_pass_ = false; + return; + } + + if (source_ptr_) { + std::memcpy(destination, source_ptr_, frames * sizeof(*destination)); + } else { + for (size_t i = 0; i < frames; ++i) + destination[i] = static_cast<float>(source_ptr_int_[i]); + } + source_available_ -= frames; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/common_audio/resampler/push_sinc_resampler.h b/third_party/libwebrtc/common_audio/resampler/push_sinc_resampler.h new file mode 100644 index 0000000000..7946ef8f82 --- /dev/null +++ b/third_party/libwebrtc/common_audio/resampler/push_sinc_resampler.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef COMMON_AUDIO_RESAMPLER_PUSH_SINC_RESAMPLER_H_ +#define COMMON_AUDIO_RESAMPLER_PUSH_SINC_RESAMPLER_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <memory> + +#include "common_audio/resampler/sinc_resampler.h" + +namespace webrtc { + +// A thin wrapper over SincResampler to provide a push-based interface as +// required by WebRTC. SincResampler uses a pull-based interface, and will +// use SincResamplerCallback::Run() to request data upon a call to Resample(). +// These Run() calls will happen on the same thread Resample() is called on. +class PushSincResampler : public SincResamplerCallback { + public: + // Provide the size of the source and destination blocks in samples. These + // must correspond to the same time duration (typically 10 ms) as the sample + // ratio is inferred from them. + PushSincResampler(size_t source_frames, size_t destination_frames); + ~PushSincResampler() override; + + PushSincResampler(const PushSincResampler&) = delete; + PushSincResampler& operator=(const PushSincResampler&) = delete; + + // Perform the resampling. `source_frames` must always equal the + // `source_frames` provided at construction. `destination_capacity` must be + // at least as large as `destination_frames`. Returns the number of samples + // provided in destination (for convenience, since this will always be equal + // to `destination_frames`). + size_t Resample(const int16_t* source, + size_t source_frames, + int16_t* destination, + size_t destination_capacity); + size_t Resample(const float* source, + size_t source_frames, + float* destination, + size_t destination_capacity); + + // Delay due to the filter kernel. Essentially, the time after which an input + // sample will appear in the resampled output. + static float AlgorithmicDelaySeconds(int source_rate_hz) { + return 1.f / source_rate_hz * SincResampler::kKernelSize / 2; + } + + protected: + // Implements SincResamplerCallback. + void Run(size_t frames, float* destination) override; + + private: + friend class PushSincResamplerTest; + SincResampler* get_resampler_for_testing() { return resampler_.get(); } + + std::unique_ptr<SincResampler> resampler_; + std::unique_ptr<float[]> float_buffer_; + const float* source_ptr_; + const int16_t* source_ptr_int_; + const size_t destination_frames_; + + // True on the first call to Resample(), to prime the SincResampler buffer. + bool first_pass_; + + // Used to assert we are only requested for as much data as is available. + size_t source_available_; +}; + +} // namespace webrtc + +#endif // COMMON_AUDIO_RESAMPLER_PUSH_SINC_RESAMPLER_H_ diff --git a/third_party/libwebrtc/common_audio/resampler/push_sinc_resampler_unittest.cc b/third_party/libwebrtc/common_audio/resampler/push_sinc_resampler_unittest.cc new file mode 100644 index 0000000000..8f82199d1d --- /dev/null +++ b/third_party/libwebrtc/common_audio/resampler/push_sinc_resampler_unittest.cc @@ -0,0 +1,367 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "common_audio/resampler/push_sinc_resampler.h" + +#include <algorithm> +#include <cmath> +#include <cstring> +#include <memory> + +#include "common_audio/include/audio_util.h" +#include "common_audio/resampler/sinusoidal_linear_chirp_source.h" +#include "rtc_base/time_utils.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +// Almost all conversions have an RMS error of around -14 dbFS. +const double kResamplingRMSError = -14.42; + +// Used to convert errors to dbFS. +template <typename T> +T DBFS(T x) { + return 20 * std::log10(x); +} + +} // namespace + +class PushSincResamplerTest : public ::testing::TestWithParam< + ::testing::tuple<int, int, double, double>> { + public: + PushSincResamplerTest() + : input_rate_(::testing::get<0>(GetParam())), + output_rate_(::testing::get<1>(GetParam())), + rms_error_(::testing::get<2>(GetParam())), + low_freq_error_(::testing::get<3>(GetParam())) {} + + ~PushSincResamplerTest() override {} + + protected: + void ResampleBenchmarkTest(bool int_format); + void ResampleTest(bool int_format); + + int input_rate_; + int output_rate_; + double rms_error_; + double low_freq_error_; +}; + +class ZeroSource : public SincResamplerCallback { + public: + void Run(size_t frames, float* destination) override { + std::memset(destination, 0, sizeof(float) * frames); + } +}; + +void PushSincResamplerTest::ResampleBenchmarkTest(bool int_format) { + const size_t input_samples = static_cast<size_t>(input_rate_ / 100); + const size_t output_samples = static_cast<size_t>(output_rate_ / 100); + const int kResampleIterations = 500000; + + // Source for data to be resampled. + ZeroSource resampler_source; + + std::unique_ptr<float[]> resampled_destination(new float[output_samples]); + std::unique_ptr<float[]> source(new float[input_samples]); + std::unique_ptr<int16_t[]> source_int(new int16_t[input_samples]); + std::unique_ptr<int16_t[]> destination_int(new int16_t[output_samples]); + + resampler_source.Run(input_samples, source.get()); + for (size_t i = 0; i < input_samples; ++i) { + source_int[i] = static_cast<int16_t>(floor(32767 * source[i] + 0.5)); + } + + printf("Benchmarking %d iterations of %d Hz -> %d Hz:\n", kResampleIterations, + input_rate_, output_rate_); + const double io_ratio = input_rate_ / static_cast<double>(output_rate_); + SincResampler sinc_resampler(io_ratio, SincResampler::kDefaultRequestSize, + &resampler_source); + int64_t start = rtc::TimeNanos(); + for (int i = 0; i < kResampleIterations; ++i) { + sinc_resampler.Resample(output_samples, resampled_destination.get()); + } + double total_time_sinc_us = + (rtc::TimeNanos() - start) / rtc::kNumNanosecsPerMicrosec; + printf("SincResampler took %.2f us per frame.\n", + total_time_sinc_us / kResampleIterations); + + PushSincResampler resampler(input_samples, output_samples); + start = rtc::TimeNanos(); + if (int_format) { + for (int i = 0; i < kResampleIterations; ++i) { + EXPECT_EQ(output_samples, + resampler.Resample(source_int.get(), input_samples, + destination_int.get(), output_samples)); + } + } else { + for (int i = 0; i < kResampleIterations; ++i) { + EXPECT_EQ(output_samples, resampler.Resample(source.get(), input_samples, + resampled_destination.get(), + output_samples)); + } + } + double total_time_us = + (rtc::TimeNanos() - start) / rtc::kNumNanosecsPerMicrosec; + printf( + "PushSincResampler took %.2f us per frame; which is a %.1f%% overhead " + "on SincResampler.\n\n", + total_time_us / kResampleIterations, + (total_time_us - total_time_sinc_us) / total_time_sinc_us * 100); +} + +// Disabled because it takes too long to run routinely. Use for performance +// benchmarking when needed. +TEST_P(PushSincResamplerTest, DISABLED_BenchmarkInt) { + ResampleBenchmarkTest(true); +} + +TEST_P(PushSincResamplerTest, DISABLED_BenchmarkFloat) { + ResampleBenchmarkTest(false); +} + +// Tests resampling using a given input and output sample rate. +void PushSincResamplerTest::ResampleTest(bool int_format) { + // Make comparisons using one second of data. + static const double kTestDurationSecs = 1; + // 10 ms blocks. + const size_t kNumBlocks = static_cast<size_t>(kTestDurationSecs * 100); + const size_t input_block_size = static_cast<size_t>(input_rate_ / 100); + const size_t output_block_size = static_cast<size_t>(output_rate_ / 100); + const size_t input_samples = + static_cast<size_t>(kTestDurationSecs * input_rate_); + const size_t output_samples = + static_cast<size_t>(kTestDurationSecs * output_rate_); + + // Nyquist frequency for the input sampling rate. + const double input_nyquist_freq = 0.5 * input_rate_; + + // Source for data to be resampled. + SinusoidalLinearChirpSource resampler_source(input_rate_, input_samples, + input_nyquist_freq, 0); + + PushSincResampler resampler(input_block_size, output_block_size); + + // TODO(dalecurtis): If we switch to AVX/SSE optimization, we'll need to + // allocate these on 32-byte boundaries and ensure they're sized % 32 bytes. + std::unique_ptr<float[]> resampled_destination(new float[output_samples]); + std::unique_ptr<float[]> pure_destination(new float[output_samples]); + std::unique_ptr<float[]> source(new float[input_samples]); + std::unique_ptr<int16_t[]> source_int(new int16_t[input_block_size]); + std::unique_ptr<int16_t[]> destination_int(new int16_t[output_block_size]); + + // The sinc resampler has an implicit delay of approximately half the kernel + // size at the input sample rate. By moving to a push model, this delay + // becomes explicit and is managed by zero-stuffing in PushSincResampler. We + // deal with it in the test by delaying the "pure" source to match. It must be + // checked before the first call to Resample(), because ChunkSize() will + // change afterwards. + const size_t output_delay_samples = + output_block_size - resampler.get_resampler_for_testing()->ChunkSize(); + + // Generate resampled signal. + // With the PushSincResampler, we produce the signal block-by-10ms-block + // rather than in a single pass, to exercise how it will be used in WebRTC. + resampler_source.Run(input_samples, source.get()); + if (int_format) { + for (size_t i = 0; i < kNumBlocks; ++i) { + FloatToS16(&source[i * input_block_size], input_block_size, + source_int.get()); + EXPECT_EQ(output_block_size, + resampler.Resample(source_int.get(), input_block_size, + destination_int.get(), output_block_size)); + S16ToFloat(destination_int.get(), output_block_size, + &resampled_destination[i * output_block_size]); + } + } else { + for (size_t i = 0; i < kNumBlocks; ++i) { + EXPECT_EQ( + output_block_size, + resampler.Resample(&source[i * input_block_size], input_block_size, + &resampled_destination[i * output_block_size], + output_block_size)); + } + } + + // Generate pure signal. + SinusoidalLinearChirpSource pure_source( + output_rate_, output_samples, input_nyquist_freq, output_delay_samples); + pure_source.Run(output_samples, pure_destination.get()); + + // Range of the Nyquist frequency (0.5 * min(input rate, output_rate)) which + // we refer to as low and high. + static const double kLowFrequencyNyquistRange = 0.7; + static const double kHighFrequencyNyquistRange = 0.9; + + // Calculate Root-Mean-Square-Error and maximum error for the resampling. + double sum_of_squares = 0; + double low_freq_max_error = 0; + double high_freq_max_error = 0; + int minimum_rate = std::min(input_rate_, output_rate_); + double low_frequency_range = kLowFrequencyNyquistRange * 0.5 * minimum_rate; + double high_frequency_range = kHighFrequencyNyquistRange * 0.5 * minimum_rate; + + for (size_t i = 0; i < output_samples; ++i) { + double error = fabs(resampled_destination[i] - pure_destination[i]); + + if (pure_source.Frequency(i) < low_frequency_range) { + if (error > low_freq_max_error) + low_freq_max_error = error; + } else if (pure_source.Frequency(i) < high_frequency_range) { + if (error > high_freq_max_error) + high_freq_max_error = error; + } + // TODO(dalecurtis): Sanity check frequencies > kHighFrequencyNyquistRange. + + sum_of_squares += error * error; + } + + double rms_error = sqrt(sum_of_squares / output_samples); + + rms_error = DBFS(rms_error); + // In order to keep the thresholds in this test identical to SincResamplerTest + // we must account for the quantization error introduced by truncating from + // float to int. This happens twice (once at input and once at output) and we + // allow for the maximum possible error (1 / 32767) for each step. + // + // The quantization error is insignificant in the RMS calculation so does not + // need to be accounted for there. + low_freq_max_error = DBFS(low_freq_max_error - 2.0 / 32767); + high_freq_max_error = DBFS(high_freq_max_error - 2.0 / 32767); + + EXPECT_LE(rms_error, rms_error_); + EXPECT_LE(low_freq_max_error, low_freq_error_); + + // All conversions currently have a high frequency error around -6 dbFS. + static const double kHighFrequencyMaxError = -6.01; + EXPECT_LE(high_freq_max_error, kHighFrequencyMaxError); +} + +TEST_P(PushSincResamplerTest, ResampleInt) { + ResampleTest(true); +} + +TEST_P(PushSincResamplerTest, ResampleFloat) { + ResampleTest(false); +} + +// Thresholds chosen arbitrarily based on what each resampling reported during +// testing. All thresholds are in dbFS, http://en.wikipedia.org/wiki/DBFS. +INSTANTIATE_TEST_SUITE_P( + PushSincResamplerTest, + PushSincResamplerTest, + ::testing::Values( + // First run through the rates tested in SincResamplerTest. The + // thresholds are identical. + // + // We don't directly test rates which fail to provide an integer number + // of samples in a 10 ms block (22050 and 11025 Hz), they are replaced + // by nearby rates in order to simplify testing. + // + // The PushSincResampler is in practice sample rate agnostic and derives + // resampling ratios from the block size, which for WebRTC purposes are + // blocks of floor(sample_rate/100) samples. So the 22050 Hz case is + // treated identically to the 22000 Hz case. Direct tests of 22050 Hz + // have to account for the simulated clock drift induced by the + // resampler inferring an incorrect sample rate ratio, without testing + // anything new within the resampler itself. + + // To 22kHz + std::make_tuple(8000, 22000, kResamplingRMSError, -62.73), + std::make_tuple(11000, 22000, kResamplingRMSError, -74.17), + std::make_tuple(16000, 22000, kResamplingRMSError, -62.54), + std::make_tuple(22000, 22000, kResamplingRMSError, -73.53), + std::make_tuple(32000, 22000, kResamplingRMSError, -46.45), + std::make_tuple(44100, 22000, kResamplingRMSError, -28.34), + std::make_tuple(48000, 22000, -15.01, -25.56), + std::make_tuple(96000, 22000, -18.49, -13.30), + std::make_tuple(192000, 22000, -20.50, -9.20), + + // To 44.1kHz + ::testing::make_tuple(8000, 44100, kResamplingRMSError, -62.73), + ::testing::make_tuple(11000, 44100, kResamplingRMSError, -63.57), + ::testing::make_tuple(16000, 44100, kResamplingRMSError, -62.54), + ::testing::make_tuple(22000, 44100, kResamplingRMSError, -62.73), + ::testing::make_tuple(32000, 44100, kResamplingRMSError, -63.32), + ::testing::make_tuple(44100, 44100, kResamplingRMSError, -73.53), + ::testing::make_tuple(48000, 44100, -15.01, -64.04), + ::testing::make_tuple(96000, 44100, -18.49, -25.51), + ::testing::make_tuple(192000, 44100, -20.50, -13.31), + + // To 48kHz + ::testing::make_tuple(8000, 48000, kResamplingRMSError, -63.43), + ::testing::make_tuple(11000, 48000, kResamplingRMSError, -63.96), + ::testing::make_tuple(16000, 48000, kResamplingRMSError, -63.96), + ::testing::make_tuple(22000, 48000, kResamplingRMSError, -63.80), + ::testing::make_tuple(32000, 48000, kResamplingRMSError, -64.04), + ::testing::make_tuple(44100, 48000, kResamplingRMSError, -62.63), + ::testing::make_tuple(48000, 48000, kResamplingRMSError, -73.52), + ::testing::make_tuple(96000, 48000, -18.40, -28.44), + ::testing::make_tuple(192000, 48000, -20.43, -14.11), + + // To 96kHz + ::testing::make_tuple(8000, 96000, kResamplingRMSError, -63.19), + ::testing::make_tuple(11000, 96000, kResamplingRMSError, -63.89), + ::testing::make_tuple(16000, 96000, kResamplingRMSError, -63.39), + ::testing::make_tuple(22000, 96000, kResamplingRMSError, -63.39), + ::testing::make_tuple(32000, 96000, kResamplingRMSError, -63.95), + ::testing::make_tuple(44100, 96000, kResamplingRMSError, -62.63), + ::testing::make_tuple(48000, 96000, kResamplingRMSError, -73.52), + ::testing::make_tuple(96000, 96000, kResamplingRMSError, -73.52), + ::testing::make_tuple(192000, 96000, kResamplingRMSError, -28.41), + + // To 192kHz + ::testing::make_tuple(8000, 192000, kResamplingRMSError, -63.10), + ::testing::make_tuple(11000, 192000, kResamplingRMSError, -63.17), + ::testing::make_tuple(16000, 192000, kResamplingRMSError, -63.14), + ::testing::make_tuple(22000, 192000, kResamplingRMSError, -63.14), + ::testing::make_tuple(32000, 192000, kResamplingRMSError, -63.38), + ::testing::make_tuple(44100, 192000, kResamplingRMSError, -62.63), + ::testing::make_tuple(48000, 192000, kResamplingRMSError, -73.44), + ::testing::make_tuple(96000, 192000, kResamplingRMSError, -73.52), + ::testing::make_tuple(192000, 192000, kResamplingRMSError, -73.52), + + // Next run through some additional cases interesting for WebRTC. + // We skip some extreme downsampled cases (192 -> {8, 16}, 96 -> 8) + // because they violate `kHighFrequencyMaxError`, which is not + // unexpected. It's very unlikely that we'll see these conversions in + // practice anyway. + + // To 8 kHz + ::testing::make_tuple(8000, 8000, kResamplingRMSError, -75.50), + ::testing::make_tuple(16000, 8000, -18.56, -28.79), + ::testing::make_tuple(32000, 8000, -20.36, -14.13), + ::testing::make_tuple(44100, 8000, -21.00, -11.39), + ::testing::make_tuple(48000, 8000, -20.96, -11.04), + + // To 16 kHz + ::testing::make_tuple(8000, 16000, kResamplingRMSError, -70.30), + ::testing::make_tuple(11000, 16000, kResamplingRMSError, -72.31), + ::testing::make_tuple(16000, 16000, kResamplingRMSError, -75.51), + ::testing::make_tuple(22000, 16000, kResamplingRMSError, -52.08), + ::testing::make_tuple(32000, 16000, -18.48, -28.59), + ::testing::make_tuple(44100, 16000, -19.30, -19.67), + ::testing::make_tuple(48000, 16000, -19.81, -18.11), + ::testing::make_tuple(96000, 16000, -20.95, -10.9596), + + // To 32 kHz + ::testing::make_tuple(8000, 32000, kResamplingRMSError, -70.30), + ::testing::make_tuple(11000, 32000, kResamplingRMSError, -71.34), + ::testing::make_tuple(16000, 32000, kResamplingRMSError, -75.51), + ::testing::make_tuple(22000, 32000, kResamplingRMSError, -72.05), + ::testing::make_tuple(32000, 32000, kResamplingRMSError, -75.51), + ::testing::make_tuple(44100, 32000, -16.44, -51.0349), + ::testing::make_tuple(48000, 32000, -16.90, -43.9967), + ::testing::make_tuple(96000, 32000, -19.61, -18.04), + ::testing::make_tuple(192000, 32000, -21.02, -10.94))); + +} // namespace webrtc diff --git a/third_party/libwebrtc/common_audio/resampler/resampler.cc b/third_party/libwebrtc/common_audio/resampler/resampler.cc new file mode 100644 index 0000000000..0fdb249052 --- /dev/null +++ b/third_party/libwebrtc/common_audio/resampler/resampler.cc @@ -0,0 +1,923 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * A wrapper for resampling a numerous amount of sampling combinations. + */ + +#include "common_audio/resampler/include/resampler.h" + +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "common_audio/signal_processing/include/signal_processing_library.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +Resampler::Resampler() + : state1_(nullptr), + state2_(nullptr), + state3_(nullptr), + in_buffer_(nullptr), + out_buffer_(nullptr), + in_buffer_size_(0), + out_buffer_size_(0), + in_buffer_size_max_(0), + out_buffer_size_max_(0), + my_in_frequency_khz_(0), + my_out_frequency_khz_(0), + my_mode_(kResamplerMode1To1), + num_channels_(0), + helper_left_(nullptr), + helper_right_(nullptr) {} + +Resampler::Resampler(int inFreq, int outFreq, size_t num_channels) + : Resampler() { + Reset(inFreq, outFreq, num_channels); +} + +Resampler::~Resampler() { + if (state1_) { + free(state1_); + } + if (state2_) { + free(state2_); + } + if (state3_) { + free(state3_); + } + if (in_buffer_) { + free(in_buffer_); + } + if (out_buffer_) { + free(out_buffer_); + } + if (helper_left_) { + delete helper_left_; + } + if (helper_right_) { + delete helper_right_; + } +} + +int Resampler::ResetIfNeeded(int inFreq, int outFreq, size_t num_channels) { + int tmpInFreq_kHz = inFreq / 1000; + int tmpOutFreq_kHz = outFreq / 1000; + + if ((tmpInFreq_kHz != my_in_frequency_khz_) || + (tmpOutFreq_kHz != my_out_frequency_khz_) || + (num_channels != num_channels_)) { + return Reset(inFreq, outFreq, num_channels); + } else { + return 0; + } +} + +int Resampler::Reset(int inFreq, int outFreq, size_t num_channels) { + if (num_channels != 1 && num_channels != 2) { + RTC_LOG(LS_WARNING) + << "Reset() called with unsupported channel count, num_channels = " + << num_channels; + return -1; + } + ResamplerMode mode; + if (ComputeResamplerMode(inFreq, outFreq, &mode) != 0) { + RTC_LOG(LS_WARNING) + << "Reset() called with unsupported sample rates, inFreq = " << inFreq + << ", outFreq = " << outFreq; + return -1; + } + // Reinitialize internal state for the frequencies and sample rates. + num_channels_ = num_channels; + my_mode_ = mode; + + if (state1_) { + free(state1_); + state1_ = nullptr; + } + if (state2_) { + free(state2_); + state2_ = nullptr; + } + if (state3_) { + free(state3_); + state3_ = nullptr; + } + if (in_buffer_) { + free(in_buffer_); + in_buffer_ = nullptr; + } + if (out_buffer_) { + free(out_buffer_); + out_buffer_ = nullptr; + } + if (helper_left_) { + delete helper_left_; + helper_left_ = nullptr; + } + if (helper_right_) { + delete helper_right_; + helper_right_ = nullptr; + } + + in_buffer_size_ = 0; + out_buffer_size_ = 0; + in_buffer_size_max_ = 0; + out_buffer_size_max_ = 0; + + // We need to track what domain we're in. + my_in_frequency_khz_ = inFreq / 1000; + my_out_frequency_khz_ = outFreq / 1000; + + if (num_channels_ == 2) { + // Create two mono resamplers. + helper_left_ = new Resampler(inFreq, outFreq, 1); + helper_right_ = new Resampler(inFreq, outFreq, 1); + } + + // Now create the states we need. + switch (my_mode_) { + case kResamplerMode1To1: + // No state needed; + break; + case kResamplerMode1To2: + state1_ = malloc(8 * sizeof(int32_t)); + memset(state1_, 0, 8 * sizeof(int32_t)); + break; + case kResamplerMode1To3: + state1_ = malloc(sizeof(WebRtcSpl_State16khzTo48khz)); + WebRtcSpl_ResetResample16khzTo48khz( + static_cast<WebRtcSpl_State16khzTo48khz*>(state1_)); + break; + case kResamplerMode1To4: + // 1:2 + state1_ = malloc(8 * sizeof(int32_t)); + memset(state1_, 0, 8 * sizeof(int32_t)); + // 2:4 + state2_ = malloc(8 * sizeof(int32_t)); + memset(state2_, 0, 8 * sizeof(int32_t)); + break; + case kResamplerMode1To6: + // 1:2 + state1_ = malloc(8 * sizeof(int32_t)); + memset(state1_, 0, 8 * sizeof(int32_t)); + // 2:6 + state2_ = malloc(sizeof(WebRtcSpl_State16khzTo48khz)); + WebRtcSpl_ResetResample16khzTo48khz( + static_cast<WebRtcSpl_State16khzTo48khz*>(state2_)); + break; + case kResamplerMode1To12: + // 1:2 + state1_ = malloc(8 * sizeof(int32_t)); + memset(state1_, 0, 8 * sizeof(int32_t)); + // 2:4 + state2_ = malloc(8 * sizeof(int32_t)); + memset(state2_, 0, 8 * sizeof(int32_t)); + // 4:12 + state3_ = malloc(sizeof(WebRtcSpl_State16khzTo48khz)); + WebRtcSpl_ResetResample16khzTo48khz( + static_cast<WebRtcSpl_State16khzTo48khz*>(state3_)); + break; + case kResamplerMode2To3: + // 2:6 + state1_ = malloc(sizeof(WebRtcSpl_State16khzTo48khz)); + WebRtcSpl_ResetResample16khzTo48khz( + static_cast<WebRtcSpl_State16khzTo48khz*>(state1_)); + // 6:3 + state2_ = malloc(8 * sizeof(int32_t)); + memset(state2_, 0, 8 * sizeof(int32_t)); + break; + case kResamplerMode2To11: + state1_ = malloc(8 * sizeof(int32_t)); + memset(state1_, 0, 8 * sizeof(int32_t)); + + state2_ = malloc(sizeof(WebRtcSpl_State8khzTo22khz)); + WebRtcSpl_ResetResample8khzTo22khz( + static_cast<WebRtcSpl_State8khzTo22khz*>(state2_)); + break; + case kResamplerMode4To11: + state1_ = malloc(sizeof(WebRtcSpl_State8khzTo22khz)); + WebRtcSpl_ResetResample8khzTo22khz( + static_cast<WebRtcSpl_State8khzTo22khz*>(state1_)); + break; + case kResamplerMode8To11: + state1_ = malloc(sizeof(WebRtcSpl_State16khzTo22khz)); + WebRtcSpl_ResetResample16khzTo22khz( + static_cast<WebRtcSpl_State16khzTo22khz*>(state1_)); + break; + case kResamplerMode11To16: + state1_ = malloc(8 * sizeof(int32_t)); + memset(state1_, 0, 8 * sizeof(int32_t)); + + state2_ = malloc(sizeof(WebRtcSpl_State22khzTo16khz)); + WebRtcSpl_ResetResample22khzTo16khz( + static_cast<WebRtcSpl_State22khzTo16khz*>(state2_)); + break; + case kResamplerMode11To32: + // 11 -> 22 + state1_ = malloc(8 * sizeof(int32_t)); + memset(state1_, 0, 8 * sizeof(int32_t)); + + // 22 -> 16 + state2_ = malloc(sizeof(WebRtcSpl_State22khzTo16khz)); + WebRtcSpl_ResetResample22khzTo16khz( + static_cast<WebRtcSpl_State22khzTo16khz*>(state2_)); + + // 16 -> 32 + state3_ = malloc(8 * sizeof(int32_t)); + memset(state3_, 0, 8 * sizeof(int32_t)); + + break; + case kResamplerMode2To1: + state1_ = malloc(8 * sizeof(int32_t)); + memset(state1_, 0, 8 * sizeof(int32_t)); + break; + case kResamplerMode3To1: + state1_ = malloc(sizeof(WebRtcSpl_State48khzTo16khz)); + WebRtcSpl_ResetResample48khzTo16khz( + static_cast<WebRtcSpl_State48khzTo16khz*>(state1_)); + break; + case kResamplerMode4To1: + // 4:2 + state1_ = malloc(8 * sizeof(int32_t)); + memset(state1_, 0, 8 * sizeof(int32_t)); + // 2:1 + state2_ = malloc(8 * sizeof(int32_t)); + memset(state2_, 0, 8 * sizeof(int32_t)); + break; + case kResamplerMode6To1: + // 6:2 + state1_ = malloc(sizeof(WebRtcSpl_State48khzTo16khz)); + WebRtcSpl_ResetResample48khzTo16khz( + static_cast<WebRtcSpl_State48khzTo16khz*>(state1_)); + // 2:1 + state2_ = malloc(8 * sizeof(int32_t)); + memset(state2_, 0, 8 * sizeof(int32_t)); + break; + case kResamplerMode12To1: + // 12:4 + state1_ = malloc(sizeof(WebRtcSpl_State48khzTo16khz)); + WebRtcSpl_ResetResample48khzTo16khz( + static_cast<WebRtcSpl_State48khzTo16khz*>(state1_)); + // 4:2 + state2_ = malloc(8 * sizeof(int32_t)); + memset(state2_, 0, 8 * sizeof(int32_t)); + // 2:1 + state3_ = malloc(8 * sizeof(int32_t)); + memset(state3_, 0, 8 * sizeof(int32_t)); + break; + case kResamplerMode3To2: + // 3:6 + state1_ = malloc(8 * sizeof(int32_t)); + memset(state1_, 0, 8 * sizeof(int32_t)); + // 6:2 + state2_ = malloc(sizeof(WebRtcSpl_State48khzTo16khz)); + WebRtcSpl_ResetResample48khzTo16khz( + static_cast<WebRtcSpl_State48khzTo16khz*>(state2_)); + break; + case kResamplerMode11To2: + state1_ = malloc(sizeof(WebRtcSpl_State22khzTo8khz)); + WebRtcSpl_ResetResample22khzTo8khz( + static_cast<WebRtcSpl_State22khzTo8khz*>(state1_)); + + state2_ = malloc(8 * sizeof(int32_t)); + memset(state2_, 0, 8 * sizeof(int32_t)); + + break; + case kResamplerMode11To4: + state1_ = malloc(sizeof(WebRtcSpl_State22khzTo8khz)); + WebRtcSpl_ResetResample22khzTo8khz( + static_cast<WebRtcSpl_State22khzTo8khz*>(state1_)); + break; + case kResamplerMode11To8: + state1_ = malloc(sizeof(WebRtcSpl_State22khzTo16khz)); + WebRtcSpl_ResetResample22khzTo16khz( + static_cast<WebRtcSpl_State22khzTo16khz*>(state1_)); + break; + } + + return 0; +} + +int Resampler::ComputeResamplerMode(int in_freq_hz, + int out_freq_hz, + ResamplerMode* mode) { + // Start with a math exercise, Euclid's algorithm to find the gcd: + int a = in_freq_hz; + int b = out_freq_hz; + int c = a % b; + while (c != 0) { + a = b; + b = c; + c = a % b; + } + // b is now the gcd; + + // Scale with GCD + const int reduced_in_freq = in_freq_hz / b; + const int reduced_out_freq = out_freq_hz / b; + + if (reduced_in_freq == reduced_out_freq) { + *mode = kResamplerMode1To1; + } else if (reduced_in_freq == 1) { + switch (reduced_out_freq) { + case 2: + *mode = kResamplerMode1To2; + break; + case 3: + *mode = kResamplerMode1To3; + break; + case 4: + *mode = kResamplerMode1To4; + break; + case 6: + *mode = kResamplerMode1To6; + break; + case 12: + *mode = kResamplerMode1To12; + break; + default: + return -1; + } + } else if (reduced_out_freq == 1) { + switch (reduced_in_freq) { + case 2: + *mode = kResamplerMode2To1; + break; + case 3: + *mode = kResamplerMode3To1; + break; + case 4: + *mode = kResamplerMode4To1; + break; + case 6: + *mode = kResamplerMode6To1; + break; + case 12: + *mode = kResamplerMode12To1; + break; + default: + return -1; + } + } else if ((reduced_in_freq == 2) && (reduced_out_freq == 3)) { + *mode = kResamplerMode2To3; + } else if ((reduced_in_freq == 2) && (reduced_out_freq == 11)) { + *mode = kResamplerMode2To11; + } else if ((reduced_in_freq == 4) && (reduced_out_freq == 11)) { + *mode = kResamplerMode4To11; + } else if ((reduced_in_freq == 8) && (reduced_out_freq == 11)) { + *mode = kResamplerMode8To11; + } else if ((reduced_in_freq == 3) && (reduced_out_freq == 2)) { + *mode = kResamplerMode3To2; + } else if ((reduced_in_freq == 11) && (reduced_out_freq == 2)) { + *mode = kResamplerMode11To2; + } else if ((reduced_in_freq == 11) && (reduced_out_freq == 4)) { + *mode = kResamplerMode11To4; + } else if ((reduced_in_freq == 11) && (reduced_out_freq == 16)) { + *mode = kResamplerMode11To16; + } else if ((reduced_in_freq == 11) && (reduced_out_freq == 32)) { + *mode = kResamplerMode11To32; + } else if ((reduced_in_freq == 11) && (reduced_out_freq == 8)) { + *mode = kResamplerMode11To8; + } else { + return -1; + } + return 0; +} + +// Synchronous resampling, all output samples are written to samplesOut +int Resampler::Push(const int16_t* samplesIn, + size_t lengthIn, + int16_t* samplesOut, + size_t maxLen, + size_t& outLen) { + if (num_channels_ == 2) { + // Split up the signal and call the helper object for each channel + int16_t* left = + static_cast<int16_t*>(malloc(lengthIn * sizeof(int16_t) / 2)); + int16_t* right = + static_cast<int16_t*>(malloc(lengthIn * sizeof(int16_t) / 2)); + int16_t* out_left = + static_cast<int16_t*>(malloc(maxLen / 2 * sizeof(int16_t))); + int16_t* out_right = + static_cast<int16_t*>(malloc(maxLen / 2 * sizeof(int16_t))); + int res = 0; + for (size_t i = 0; i < lengthIn; i += 2) { + left[i >> 1] = samplesIn[i]; + right[i >> 1] = samplesIn[i + 1]; + } + + // It's OK to overwrite the local parameter, since it's just a copy + lengthIn = lengthIn / 2; + + size_t actualOutLen_left = 0; + size_t actualOutLen_right = 0; + // Do resampling for right channel + res |= helper_left_->Push(left, lengthIn, out_left, maxLen / 2, + actualOutLen_left); + res |= helper_right_->Push(right, lengthIn, out_right, maxLen / 2, + actualOutLen_right); + if (res || (actualOutLen_left != actualOutLen_right)) { + free(left); + free(right); + free(out_left); + free(out_right); + return -1; + } + + // Reassemble the signal + for (size_t i = 0; i < actualOutLen_left; i++) { + samplesOut[i * 2] = out_left[i]; + samplesOut[i * 2 + 1] = out_right[i]; + } + outLen = 2 * actualOutLen_left; + + free(left); + free(right); + free(out_left); + free(out_right); + + return 0; + } + + // Containers for temp samples + int16_t* tmp; + int16_t* tmp_2; + // tmp data for resampling routines + int32_t* tmp_mem; + + switch (my_mode_) { + case kResamplerMode1To1: + memcpy(samplesOut, samplesIn, lengthIn * sizeof(int16_t)); + outLen = lengthIn; + break; + case kResamplerMode1To2: + if (maxLen < (lengthIn * 2)) { + return -1; + } + WebRtcSpl_UpsampleBy2(samplesIn, lengthIn, samplesOut, + static_cast<int32_t*>(state1_)); + outLen = lengthIn * 2; + return 0; + case kResamplerMode1To3: + + // We can only handle blocks of 160 samples + // Can be fixed, but I don't think it's needed + if ((lengthIn % 160) != 0) { + return -1; + } + if (maxLen < (lengthIn * 3)) { + return -1; + } + tmp_mem = static_cast<int32_t*>(malloc(336 * sizeof(int32_t))); + + for (size_t i = 0; i < lengthIn; i += 160) { + WebRtcSpl_Resample16khzTo48khz( + samplesIn + i, samplesOut + i * 3, + static_cast<WebRtcSpl_State16khzTo48khz*>(state1_), tmp_mem); + } + outLen = lengthIn * 3; + free(tmp_mem); + return 0; + case kResamplerMode1To4: + if (maxLen < (lengthIn * 4)) { + return -1; + } + + tmp = static_cast<int16_t*>(malloc(sizeof(int16_t) * 2 * lengthIn)); + // 1:2 + WebRtcSpl_UpsampleBy2(samplesIn, lengthIn, tmp, + static_cast<int32_t*>(state1_)); + // 2:4 + WebRtcSpl_UpsampleBy2(tmp, lengthIn * 2, samplesOut, + static_cast<int32_t*>(state2_)); + outLen = lengthIn * 4; + free(tmp); + return 0; + case kResamplerMode1To6: + // We can only handle blocks of 80 samples + // Can be fixed, but I don't think it's needed + if ((lengthIn % 80) != 0) { + return -1; + } + if (maxLen < (lengthIn * 6)) { + return -1; + } + + // 1:2 + + tmp_mem = static_cast<int32_t*>(malloc(336 * sizeof(int32_t))); + tmp = static_cast<int16_t*>(malloc(sizeof(int16_t) * 2 * lengthIn)); + + WebRtcSpl_UpsampleBy2(samplesIn, lengthIn, tmp, + static_cast<int32_t*>(state1_)); + outLen = lengthIn * 2; + + for (size_t i = 0; i < outLen; i += 160) { + WebRtcSpl_Resample16khzTo48khz( + tmp + i, samplesOut + i * 3, + static_cast<WebRtcSpl_State16khzTo48khz*>(state2_), tmp_mem); + } + outLen = outLen * 3; + free(tmp_mem); + free(tmp); + + return 0; + case kResamplerMode1To12: + // We can only handle blocks of 40 samples + // Can be fixed, but I don't think it's needed + if ((lengthIn % 40) != 0) { + return -1; + } + if (maxLen < (lengthIn * 12)) { + return -1; + } + + tmp_mem = static_cast<int32_t*>(malloc(336 * sizeof(int32_t))); + tmp = static_cast<int16_t*>(malloc(sizeof(int16_t) * 4 * lengthIn)); + // 1:2 + WebRtcSpl_UpsampleBy2(samplesIn, lengthIn, samplesOut, + static_cast<int32_t*>(state1_)); + outLen = lengthIn * 2; + // 2:4 + WebRtcSpl_UpsampleBy2(samplesOut, outLen, tmp, + static_cast<int32_t*>(state2_)); + outLen = outLen * 2; + // 4:12 + for (size_t i = 0; i < outLen; i += 160) { + // WebRtcSpl_Resample16khzTo48khz() takes a block of 160 samples + // as input and outputs a resampled block of 480 samples. The + // data is now actually in 32 kHz sampling rate, despite the + // function name, and with a resampling factor of three becomes + // 96 kHz. + WebRtcSpl_Resample16khzTo48khz( + tmp + i, samplesOut + i * 3, + static_cast<WebRtcSpl_State16khzTo48khz*>(state3_), tmp_mem); + } + outLen = outLen * 3; + free(tmp_mem); + free(tmp); + + return 0; + case kResamplerMode2To3: + if (maxLen < (lengthIn * 3 / 2)) { + return -1; + } + // 2:6 + // We can only handle blocks of 160 samples + // Can be fixed, but I don't think it's needed + if ((lengthIn % 160) != 0) { + return -1; + } + tmp = static_cast<int16_t*>(malloc(sizeof(int16_t) * lengthIn * 3)); + tmp_mem = static_cast<int32_t*>(malloc(336 * sizeof(int32_t))); + for (size_t i = 0; i < lengthIn; i += 160) { + WebRtcSpl_Resample16khzTo48khz( + samplesIn + i, tmp + i * 3, + static_cast<WebRtcSpl_State16khzTo48khz*>(state1_), tmp_mem); + } + lengthIn = lengthIn * 3; + // 6:3 + WebRtcSpl_DownsampleBy2(tmp, lengthIn, samplesOut, + static_cast<int32_t*>(state2_)); + outLen = lengthIn / 2; + free(tmp); + free(tmp_mem); + return 0; + case kResamplerMode2To11: + + // We can only handle blocks of 80 samples + // Can be fixed, but I don't think it's needed + if ((lengthIn % 80) != 0) { + return -1; + } + if (maxLen < ((lengthIn * 11) / 2)) { + return -1; + } + tmp = static_cast<int16_t*>(malloc(sizeof(int16_t) * 2 * lengthIn)); + // 1:2 + WebRtcSpl_UpsampleBy2(samplesIn, lengthIn, tmp, + static_cast<int32_t*>(state1_)); + lengthIn *= 2; + + tmp_mem = static_cast<int32_t*>(malloc(98 * sizeof(int32_t))); + + for (size_t i = 0; i < lengthIn; i += 80) { + WebRtcSpl_Resample8khzTo22khz( + tmp + i, samplesOut + (i * 11) / 4, + static_cast<WebRtcSpl_State8khzTo22khz*>(state2_), tmp_mem); + } + outLen = (lengthIn * 11) / 4; + free(tmp_mem); + free(tmp); + return 0; + case kResamplerMode4To11: + + // We can only handle blocks of 80 samples + // Can be fixed, but I don't think it's needed + if ((lengthIn % 80) != 0) { + return -1; + } + if (maxLen < ((lengthIn * 11) / 4)) { + return -1; + } + tmp_mem = static_cast<int32_t*>(malloc(98 * sizeof(int32_t))); + + for (size_t i = 0; i < lengthIn; i += 80) { + WebRtcSpl_Resample8khzTo22khz( + samplesIn + i, samplesOut + (i * 11) / 4, + static_cast<WebRtcSpl_State8khzTo22khz*>(state1_), tmp_mem); + } + outLen = (lengthIn * 11) / 4; + free(tmp_mem); + return 0; + case kResamplerMode8To11: + // We can only handle blocks of 160 samples + // Can be fixed, but I don't think it's needed + if ((lengthIn % 160) != 0) { + return -1; + } + if (maxLen < ((lengthIn * 11) / 8)) { + return -1; + } + tmp_mem = static_cast<int32_t*>(malloc(88 * sizeof(int32_t))); + + for (size_t i = 0; i < lengthIn; i += 160) { + WebRtcSpl_Resample16khzTo22khz( + samplesIn + i, samplesOut + (i * 11) / 8, + static_cast<WebRtcSpl_State16khzTo22khz*>(state1_), tmp_mem); + } + outLen = (lengthIn * 11) / 8; + free(tmp_mem); + return 0; + + case kResamplerMode11To16: + // We can only handle blocks of 110 samples + if ((lengthIn % 110) != 0) { + return -1; + } + if (maxLen < ((lengthIn * 16) / 11)) { + return -1; + } + + tmp_mem = static_cast<int32_t*>(malloc(104 * sizeof(int32_t))); + tmp = static_cast<int16_t*>(malloc((sizeof(int16_t) * lengthIn * 2))); + + WebRtcSpl_UpsampleBy2(samplesIn, lengthIn, tmp, + static_cast<int32_t*>(state1_)); + + for (size_t i = 0; i < (lengthIn * 2); i += 220) { + WebRtcSpl_Resample22khzTo16khz( + tmp + i, samplesOut + (i / 220) * 160, + static_cast<WebRtcSpl_State22khzTo16khz*>(state2_), tmp_mem); + } + + outLen = (lengthIn * 16) / 11; + + free(tmp_mem); + free(tmp); + return 0; + + case kResamplerMode11To32: + + // We can only handle blocks of 110 samples + if ((lengthIn % 110) != 0) { + return -1; + } + if (maxLen < ((lengthIn * 32) / 11)) { + return -1; + } + + tmp_mem = static_cast<int32_t*>(malloc(104 * sizeof(int32_t))); + tmp = static_cast<int16_t*>(malloc((sizeof(int16_t) * lengthIn * 2))); + + // 11 -> 22 kHz in samplesOut + WebRtcSpl_UpsampleBy2(samplesIn, lengthIn, samplesOut, + static_cast<int32_t*>(state1_)); + + // 22 -> 16 in tmp + for (size_t i = 0; i < (lengthIn * 2); i += 220) { + WebRtcSpl_Resample22khzTo16khz( + samplesOut + i, tmp + (i / 220) * 160, + static_cast<WebRtcSpl_State22khzTo16khz*>(state2_), tmp_mem); + } + + // 16 -> 32 in samplesOut + WebRtcSpl_UpsampleBy2(tmp, (lengthIn * 16) / 11, samplesOut, + static_cast<int32_t*>(state3_)); + + outLen = (lengthIn * 32) / 11; + + free(tmp_mem); + free(tmp); + return 0; + + case kResamplerMode2To1: + if (maxLen < (lengthIn / 2)) { + return -1; + } + WebRtcSpl_DownsampleBy2(samplesIn, lengthIn, samplesOut, + static_cast<int32_t*>(state1_)); + outLen = lengthIn / 2; + return 0; + case kResamplerMode3To1: + // We can only handle blocks of 480 samples + // Can be fixed, but I don't think it's needed + if ((lengthIn % 480) != 0) { + return -1; + } + if (maxLen < (lengthIn / 3)) { + return -1; + } + tmp_mem = static_cast<int32_t*>(malloc(496 * sizeof(int32_t))); + + for (size_t i = 0; i < lengthIn; i += 480) { + WebRtcSpl_Resample48khzTo16khz( + samplesIn + i, samplesOut + i / 3, + static_cast<WebRtcSpl_State48khzTo16khz*>(state1_), tmp_mem); + } + outLen = lengthIn / 3; + free(tmp_mem); + return 0; + case kResamplerMode4To1: + if (maxLen < (lengthIn / 4)) { + return -1; + } + tmp = static_cast<int16_t*>(malloc(sizeof(int16_t) * lengthIn / 2)); + // 4:2 + WebRtcSpl_DownsampleBy2(samplesIn, lengthIn, tmp, + static_cast<int32_t*>(state1_)); + // 2:1 + WebRtcSpl_DownsampleBy2(tmp, lengthIn / 2, samplesOut, + static_cast<int32_t*>(state2_)); + outLen = lengthIn / 4; + free(tmp); + return 0; + + case kResamplerMode6To1: + // We can only handle blocks of 480 samples + // Can be fixed, but I don't think it's needed + if ((lengthIn % 480) != 0) { + return -1; + } + if (maxLen < (lengthIn / 6)) { + return -1; + } + + tmp_mem = static_cast<int32_t*>(malloc(496 * sizeof(int32_t))); + tmp = static_cast<int16_t*>(malloc((sizeof(int16_t) * lengthIn) / 3)); + + for (size_t i = 0; i < lengthIn; i += 480) { + WebRtcSpl_Resample48khzTo16khz( + samplesIn + i, tmp + i / 3, + static_cast<WebRtcSpl_State48khzTo16khz*>(state1_), tmp_mem); + } + outLen = lengthIn / 3; + free(tmp_mem); + WebRtcSpl_DownsampleBy2(tmp, outLen, samplesOut, + static_cast<int32_t*>(state2_)); + free(tmp); + outLen = outLen / 2; + return 0; + case kResamplerMode12To1: + // We can only handle blocks of 480 samples + // Can be fixed, but I don't think it's needed + if ((lengthIn % 480) != 0) { + return -1; + } + if (maxLen < (lengthIn / 12)) { + return -1; + } + + tmp_mem = static_cast<int32_t*>(malloc(496 * sizeof(int32_t))); + tmp = static_cast<int16_t*>(malloc((sizeof(int16_t) * lengthIn) / 3)); + tmp_2 = static_cast<int16_t*>(malloc((sizeof(int16_t) * lengthIn) / 6)); + // 12:4 + for (size_t i = 0; i < lengthIn; i += 480) { + // WebRtcSpl_Resample48khzTo16khz() takes a block of 480 samples + // as input and outputs a resampled block of 160 samples. The + // data is now actually in 96 kHz sampling rate, despite the + // function name, and with a resampling factor of 1/3 becomes + // 32 kHz. + WebRtcSpl_Resample48khzTo16khz( + samplesIn + i, tmp + i / 3, + static_cast<WebRtcSpl_State48khzTo16khz*>(state1_), tmp_mem); + } + outLen = lengthIn / 3; + free(tmp_mem); + // 4:2 + WebRtcSpl_DownsampleBy2(tmp, outLen, tmp_2, + static_cast<int32_t*>(state2_)); + outLen = outLen / 2; + free(tmp); + // 2:1 + WebRtcSpl_DownsampleBy2(tmp_2, outLen, samplesOut, + static_cast<int32_t*>(state3_)); + free(tmp_2); + outLen = outLen / 2; + return 0; + case kResamplerMode3To2: + if (maxLen < (lengthIn * 2 / 3)) { + return -1; + } + // 3:6 + tmp = static_cast<int16_t*>(malloc(sizeof(int16_t) * lengthIn * 2)); + WebRtcSpl_UpsampleBy2(samplesIn, lengthIn, tmp, + static_cast<int32_t*>(state1_)); + lengthIn *= 2; + // 6:2 + // We can only handle blocks of 480 samples + // Can be fixed, but I don't think it's needed + if ((lengthIn % 480) != 0) { + free(tmp); + return -1; + } + tmp_mem = static_cast<int32_t*>(malloc(496 * sizeof(int32_t))); + for (size_t i = 0; i < lengthIn; i += 480) { + WebRtcSpl_Resample48khzTo16khz( + tmp + i, samplesOut + i / 3, + static_cast<WebRtcSpl_State48khzTo16khz*>(state2_), tmp_mem); + } + outLen = lengthIn / 3; + free(tmp); + free(tmp_mem); + return 0; + case kResamplerMode11To2: + // We can only handle blocks of 220 samples + // Can be fixed, but I don't think it's needed + if ((lengthIn % 220) != 0) { + return -1; + } + if (maxLen < ((lengthIn * 2) / 11)) { + return -1; + } + tmp_mem = static_cast<int32_t*>(malloc(126 * sizeof(int32_t))); + tmp = + static_cast<int16_t*>(malloc((lengthIn * 4) / 11 * sizeof(int16_t))); + + for (size_t i = 0; i < lengthIn; i += 220) { + WebRtcSpl_Resample22khzTo8khz( + samplesIn + i, tmp + (i * 4) / 11, + static_cast<WebRtcSpl_State22khzTo8khz*>(state1_), tmp_mem); + } + lengthIn = (lengthIn * 4) / 11; + + WebRtcSpl_DownsampleBy2(tmp, lengthIn, samplesOut, + static_cast<int32_t*>(state2_)); + outLen = lengthIn / 2; + + free(tmp_mem); + free(tmp); + return 0; + case kResamplerMode11To4: + // We can only handle blocks of 220 samples + // Can be fixed, but I don't think it's needed + if ((lengthIn % 220) != 0) { + return -1; + } + if (maxLen < ((lengthIn * 4) / 11)) { + return -1; + } + tmp_mem = static_cast<int32_t*>(malloc(126 * sizeof(int32_t))); + + for (size_t i = 0; i < lengthIn; i += 220) { + WebRtcSpl_Resample22khzTo8khz( + samplesIn + i, samplesOut + (i * 4) / 11, + static_cast<WebRtcSpl_State22khzTo8khz*>(state1_), tmp_mem); + } + outLen = (lengthIn * 4) / 11; + free(tmp_mem); + return 0; + case kResamplerMode11To8: + // We can only handle blocks of 160 samples + // Can be fixed, but I don't think it's needed + if ((lengthIn % 220) != 0) { + return -1; + } + if (maxLen < ((lengthIn * 8) / 11)) { + return -1; + } + tmp_mem = static_cast<int32_t*>(malloc(104 * sizeof(int32_t))); + + for (size_t i = 0; i < lengthIn; i += 220) { + WebRtcSpl_Resample22khzTo16khz( + samplesIn + i, samplesOut + (i * 8) / 11, + static_cast<WebRtcSpl_State22khzTo16khz*>(state1_), tmp_mem); + } + outLen = (lengthIn * 8) / 11; + free(tmp_mem); + return 0; + } + return 0; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/common_audio/resampler/resampler_unittest.cc b/third_party/libwebrtc/common_audio/resampler/resampler_unittest.cc new file mode 100644 index 0000000000..1b90d3e30b --- /dev/null +++ b/third_party/libwebrtc/common_audio/resampler/resampler_unittest.cc @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "common_audio/resampler/include/resampler.h" + +#include <array> + +#include "rtc_base/strings/string_builder.h" +#include "test/gtest.h" + +// TODO(andrew): this is a work-in-progress. Many more tests are needed. + +namespace webrtc { +namespace { + +const int kNumChannels[] = {1, 2}; +const size_t kNumChannelsSize = sizeof(kNumChannels) / sizeof(*kNumChannels); + +// Rates we must support. +const int kMaxRate = 96000; +const int kRates[] = {8000, 16000, 32000, 44000, 48000, kMaxRate}; +const size_t kRatesSize = sizeof(kRates) / sizeof(*kRates); +const int kMaxChannels = 2; +const size_t kDataSize = static_cast<size_t>(kMaxChannels * kMaxRate / 100); + +// TODO(andrew): should we be supporting these combinations? +bool ValidRates(int in_rate, int out_rate) { + // Not the most compact notation, for clarity. + if ((in_rate == 44000 && (out_rate == 48000 || out_rate == 96000)) || + (out_rate == 44000 && (in_rate == 48000 || in_rate == 96000))) { + return false; + } + + return true; +} + +class ResamplerTest : public ::testing::Test { + protected: + ResamplerTest(); + void SetUp() override; + void TearDown() override; + + void ResetIfNeededAndPush(int in_rate, int out_rate, int num_channels); + + Resampler rs_; + int16_t data_in_[kDataSize]; + int16_t data_out_[kDataSize]; +}; + +ResamplerTest::ResamplerTest() {} + +void ResamplerTest::SetUp() { + // Initialize input data with anything. The tests are content independent. + memset(data_in_, 1, sizeof(data_in_)); +} + +void ResamplerTest::TearDown() {} + +void ResamplerTest::ResetIfNeededAndPush(int in_rate, + int out_rate, + int num_channels) { + rtc::StringBuilder ss; + ss << "Input rate: " << in_rate << ", output rate: " << out_rate + << ", channel count: " << num_channels; + SCOPED_TRACE(ss.str()); + + if (ValidRates(in_rate, out_rate)) { + size_t in_length = static_cast<size_t>(in_rate / 100); + size_t out_length = 0; + EXPECT_EQ(0, rs_.ResetIfNeeded(in_rate, out_rate, num_channels)); + EXPECT_EQ(0, + rs_.Push(data_in_, in_length, data_out_, kDataSize, out_length)); + EXPECT_EQ(static_cast<size_t>(out_rate / 100), out_length); + } else { + EXPECT_EQ(-1, rs_.ResetIfNeeded(in_rate, out_rate, num_channels)); + } +} + +TEST_F(ResamplerTest, Reset) { + // The only failure mode for the constructor is if Reset() fails. For the + // time being then (until an Init function is added), we rely on Reset() + // to test the constructor. + + // Check that all required combinations are supported. + for (size_t i = 0; i < kRatesSize; ++i) { + for (size_t j = 0; j < kRatesSize; ++j) { + for (size_t k = 0; k < kNumChannelsSize; ++k) { + rtc::StringBuilder ss; + ss << "Input rate: " << kRates[i] << ", output rate: " << kRates[j] + << ", channels: " << kNumChannels[k]; + SCOPED_TRACE(ss.str()); + if (ValidRates(kRates[i], kRates[j])) + EXPECT_EQ(0, rs_.Reset(kRates[i], kRates[j], kNumChannels[k])); + else + EXPECT_EQ(-1, rs_.Reset(kRates[i], kRates[j], kNumChannels[k])); + } + } + } +} + +// TODO(tlegrand): Replace code inside the two tests below with a function +// with number of channels and ResamplerType as input. +TEST_F(ResamplerTest, Mono) { + const int kChannels = 1; + for (size_t i = 0; i < kRatesSize; ++i) { + for (size_t j = 0; j < kRatesSize; ++j) { + rtc::StringBuilder ss; + ss << "Input rate: " << kRates[i] << ", output rate: " << kRates[j]; + SCOPED_TRACE(ss.str()); + + if (ValidRates(kRates[i], kRates[j])) { + size_t in_length = static_cast<size_t>(kRates[i] / 100); + size_t out_length = 0; + EXPECT_EQ(0, rs_.Reset(kRates[i], kRates[j], kChannels)); + EXPECT_EQ( + 0, rs_.Push(data_in_, in_length, data_out_, kDataSize, out_length)); + EXPECT_EQ(static_cast<size_t>(kRates[j] / 100), out_length); + } else { + EXPECT_EQ(-1, rs_.Reset(kRates[i], kRates[j], kChannels)); + } + } + } +} + +TEST_F(ResamplerTest, Stereo) { + const int kChannels = 2; + for (size_t i = 0; i < kRatesSize; ++i) { + for (size_t j = 0; j < kRatesSize; ++j) { + rtc::StringBuilder ss; + ss << "Input rate: " << kRates[i] << ", output rate: " << kRates[j]; + SCOPED_TRACE(ss.str()); + + if (ValidRates(kRates[i], kRates[j])) { + size_t in_length = static_cast<size_t>(kChannels * kRates[i] / 100); + size_t out_length = 0; + EXPECT_EQ(0, rs_.Reset(kRates[i], kRates[j], kChannels)); + EXPECT_EQ( + 0, rs_.Push(data_in_, in_length, data_out_, kDataSize, out_length)); + EXPECT_EQ(static_cast<size_t>(kChannels * kRates[j] / 100), out_length); + } else { + EXPECT_EQ(-1, rs_.Reset(kRates[i], kRates[j], kChannels)); + } + } + } +} + +// Try multiple resets between a few supported and unsupported rates. +TEST_F(ResamplerTest, MultipleResets) { + constexpr size_t kNumChanges = 5; + constexpr std::array<int, kNumChanges> kInRates = { + {8000, 44000, 44000, 32000, 32000}}; + constexpr std::array<int, kNumChanges> kOutRates = { + {16000, 48000, 48000, 16000, 16000}}; + constexpr std::array<int, kNumChanges> kNumChannels = {{2, 2, 2, 2, 1}}; + for (size_t i = 0; i < kNumChanges; ++i) { + ResetIfNeededAndPush(kInRates[i], kOutRates[i], kNumChannels[i]); + } +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/common_audio/resampler/sinc_resampler.cc b/third_party/libwebrtc/common_audio/resampler/sinc_resampler.cc new file mode 100644 index 0000000000..66a99b6190 --- /dev/null +++ b/third_party/libwebrtc/common_audio/resampler/sinc_resampler.cc @@ -0,0 +1,366 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// Modified from the Chromium original: +// src/media/base/sinc_resampler.cc + +// Initial input buffer layout, dividing into regions r0_ to r4_ (note: r0_, r3_ +// and r4_ will move after the first load): +// +// |----------------|-----------------------------------------|----------------| +// +// request_frames_ +// <---------------------------------------------------------> +// r0_ (during first load) +// +// kKernelSize / 2 kKernelSize / 2 kKernelSize / 2 kKernelSize / 2 +// <---------------> <---------------> <---------------> <---------------> +// r1_ r2_ r3_ r4_ +// +// block_size_ == r4_ - r2_ +// <---------------------------------------> +// +// request_frames_ +// <------------------ ... -----------------> +// r0_ (during second load) +// +// On the second request r0_ slides to the right by kKernelSize / 2 and r3_, r4_ +// and block_size_ are reinitialized via step (3) in the algorithm below. +// +// These new regions remain constant until a Flush() occurs. While complicated, +// this allows us to reduce jitter by always requesting the same amount from the +// provided callback. +// +// The algorithm: +// +// 1) Allocate input_buffer of size: request_frames_ + kKernelSize; this ensures +// there's enough room to read request_frames_ from the callback into region +// r0_ (which will move between the first and subsequent passes). +// +// 2) Let r1_, r2_ each represent half the kernel centered around r0_: +// +// r0_ = input_buffer_ + kKernelSize / 2 +// r1_ = input_buffer_ +// r2_ = r0_ +// +// r0_ is always request_frames_ in size. r1_, r2_ are kKernelSize / 2 in +// size. r1_ must be zero initialized to avoid convolution with garbage (see +// step (5) for why). +// +// 3) Let r3_, r4_ each represent half the kernel right aligned with the end of +// r0_ and choose block_size_ as the distance in frames between r4_ and r2_: +// +// r3_ = r0_ + request_frames_ - kKernelSize +// r4_ = r0_ + request_frames_ - kKernelSize / 2 +// block_size_ = r4_ - r2_ = request_frames_ - kKernelSize / 2 +// +// 4) Consume request_frames_ frames into r0_. +// +// 5) Position kernel centered at start of r2_ and generate output frames until +// the kernel is centered at the start of r4_ or we've finished generating +// all the output frames. +// +// 6) Wrap left over data from the r3_ to r1_ and r4_ to r2_. +// +// 7) If we're on the second load, in order to avoid overwriting the frames we +// just wrapped from r4_ we need to slide r0_ to the right by the size of +// r4_, which is kKernelSize / 2: +// +// r0_ = r0_ + kKernelSize / 2 = input_buffer_ + kKernelSize +// +// r3_, r4_, and block_size_ then need to be reinitialized, so goto (3). +// +// 8) Else, if we're not on the second load, goto (4). +// +// Note: we're glossing over how the sub-sample handling works with +// `virtual_source_idx_`, etc. + +// MSVC++ requires this to be set before any other includes to get M_PI. +#define _USE_MATH_DEFINES + +#include "common_audio/resampler/sinc_resampler.h" + +#include <math.h> +#include <stdint.h> +#include <string.h> + +#include <limits> + +#include "rtc_base/checks.h" +#include "rtc_base/system/arch.h" +#include "system_wrappers/include/cpu_features_wrapper.h" // kSSE2, WebRtc_G... + +namespace webrtc { + +namespace { + +double SincScaleFactor(double io_ratio) { + // `sinc_scale_factor` is basically the normalized cutoff frequency of the + // low-pass filter. + double sinc_scale_factor = io_ratio > 1.0 ? 1.0 / io_ratio : 1.0; + + // The sinc function is an idealized brick-wall filter, but since we're + // windowing it the transition from pass to stop does not happen right away. + // So we should adjust the low pass filter cutoff slightly downward to avoid + // some aliasing at the very high-end. + // TODO(crogers): this value is empirical and to be more exact should vary + // depending on kKernelSize. + sinc_scale_factor *= 0.9; + + return sinc_scale_factor; +} + +} // namespace + +const size_t SincResampler::kKernelSize; + +// If we know the minimum architecture at compile time, avoid CPU detection. +void SincResampler::InitializeCPUSpecificFeatures() { +#if defined(WEBRTC_HAS_NEON) + convolve_proc_ = Convolve_NEON; +#elif defined(WEBRTC_ARCH_X86_FAMILY) + // Using AVX2 instead of SSE2 when AVX2/FMA3 supported. + if (GetCPUInfo(kAVX2) && GetCPUInfo(kFMA3)) + convolve_proc_ = Convolve_AVX2; + else if (GetCPUInfo(kSSE2)) + convolve_proc_ = Convolve_SSE; + else + convolve_proc_ = Convolve_C; +#else + // Unknown architecture. + convolve_proc_ = Convolve_C; +#endif +} + +SincResampler::SincResampler(double io_sample_rate_ratio, + size_t request_frames, + SincResamplerCallback* read_cb) + : io_sample_rate_ratio_(io_sample_rate_ratio), + read_cb_(read_cb), + request_frames_(request_frames), + input_buffer_size_(request_frames_ + kKernelSize), + // Create input buffers with a 32-byte alignment for SIMD optimizations. + kernel_storage_(static_cast<float*>( + AlignedMalloc(sizeof(float) * kKernelStorageSize, 32))), + kernel_pre_sinc_storage_(static_cast<float*>( + AlignedMalloc(sizeof(float) * kKernelStorageSize, 32))), + kernel_window_storage_(static_cast<float*>( + AlignedMalloc(sizeof(float) * kKernelStorageSize, 32))), + input_buffer_(static_cast<float*>( + AlignedMalloc(sizeof(float) * input_buffer_size_, 32))), + convolve_proc_(nullptr), + r1_(input_buffer_.get()), + r2_(input_buffer_.get() + kKernelSize / 2) { + InitializeCPUSpecificFeatures(); + RTC_DCHECK(convolve_proc_); + RTC_DCHECK_GT(request_frames_, 0); + Flush(); + RTC_DCHECK_GT(block_size_, kKernelSize); + + memset(kernel_storage_.get(), 0, + sizeof(*kernel_storage_.get()) * kKernelStorageSize); + memset(kernel_pre_sinc_storage_.get(), 0, + sizeof(*kernel_pre_sinc_storage_.get()) * kKernelStorageSize); + memset(kernel_window_storage_.get(), 0, + sizeof(*kernel_window_storage_.get()) * kKernelStorageSize); + + InitializeKernel(); +} + +SincResampler::~SincResampler() {} + +void SincResampler::UpdateRegions(bool second_load) { + // Setup various region pointers in the buffer (see diagram above). If we're + // on the second load we need to slide r0_ to the right by kKernelSize / 2. + r0_ = input_buffer_.get() + (second_load ? kKernelSize : kKernelSize / 2); + r3_ = r0_ + request_frames_ - kKernelSize; + r4_ = r0_ + request_frames_ - kKernelSize / 2; + block_size_ = r4_ - r2_; + + // r1_ at the beginning of the buffer. + RTC_DCHECK_EQ(r1_, input_buffer_.get()); + // r1_ left of r2_, r4_ left of r3_ and size correct. + RTC_DCHECK_EQ(r2_ - r1_, r4_ - r3_); + // r2_ left of r3. + RTC_DCHECK_LT(r2_, r3_); +} + +void SincResampler::InitializeKernel() { + // Blackman window parameters. + static const double kAlpha = 0.16; + static const double kA0 = 0.5 * (1.0 - kAlpha); + static const double kA1 = 0.5; + static const double kA2 = 0.5 * kAlpha; + + // Generates a set of windowed sinc() kernels. + // We generate a range of sub-sample offsets from 0.0 to 1.0. + const double sinc_scale_factor = SincScaleFactor(io_sample_rate_ratio_); + for (size_t offset_idx = 0; offset_idx <= kKernelOffsetCount; ++offset_idx) { + const float subsample_offset = + static_cast<float>(offset_idx) / kKernelOffsetCount; + + for (size_t i = 0; i < kKernelSize; ++i) { + const size_t idx = i + offset_idx * kKernelSize; + const float pre_sinc = static_cast<float>( + M_PI * (static_cast<int>(i) - static_cast<int>(kKernelSize / 2) - + subsample_offset)); + kernel_pre_sinc_storage_[idx] = pre_sinc; + + // Compute Blackman window, matching the offset of the sinc(). + const float x = (i - subsample_offset) / kKernelSize; + const float window = static_cast<float>(kA0 - kA1 * cos(2.0 * M_PI * x) + + kA2 * cos(4.0 * M_PI * x)); + kernel_window_storage_[idx] = window; + + // Compute the sinc with offset, then window the sinc() function and store + // at the correct offset. + kernel_storage_[idx] = static_cast<float>( + window * ((pre_sinc == 0) + ? sinc_scale_factor + : (sin(sinc_scale_factor * pre_sinc) / pre_sinc))); + } + } +} + +void SincResampler::SetRatio(double io_sample_rate_ratio) { + if (fabs(io_sample_rate_ratio_ - io_sample_rate_ratio) < + std::numeric_limits<double>::epsilon()) { + return; + } + + io_sample_rate_ratio_ = io_sample_rate_ratio; + + // Optimize reinitialization by reusing values which are independent of + // `sinc_scale_factor`. Provides a 3x speedup. + const double sinc_scale_factor = SincScaleFactor(io_sample_rate_ratio_); + for (size_t offset_idx = 0; offset_idx <= kKernelOffsetCount; ++offset_idx) { + for (size_t i = 0; i < kKernelSize; ++i) { + const size_t idx = i + offset_idx * kKernelSize; + const float window = kernel_window_storage_[idx]; + const float pre_sinc = kernel_pre_sinc_storage_[idx]; + + kernel_storage_[idx] = static_cast<float>( + window * ((pre_sinc == 0) + ? sinc_scale_factor + : (sin(sinc_scale_factor * pre_sinc) / pre_sinc))); + } + } +} + +void SincResampler::Resample(size_t frames, float* destination) { + size_t remaining_frames = frames; + + // Step (1) -- Prime the input buffer at the start of the input stream. + if (!buffer_primed_ && remaining_frames) { + read_cb_->Run(request_frames_, r0_); + buffer_primed_ = true; + } + + // Step (2) -- Resample! const what we can outside of the loop for speed. It + // actually has an impact on ARM performance. See inner loop comment below. + const double current_io_ratio = io_sample_rate_ratio_; + const float* const kernel_ptr = kernel_storage_.get(); + while (remaining_frames) { + // `i` may be negative if the last Resample() call ended on an iteration + // that put `virtual_source_idx_` over the limit. + // + // Note: The loop construct here can severely impact performance on ARM + // or when built with clang. See https://codereview.chromium.org/18566009/ + for (int i = static_cast<int>( + ceil((block_size_ - virtual_source_idx_) / current_io_ratio)); + i > 0; --i) { + RTC_DCHECK_LT(virtual_source_idx_, block_size_); + + // `virtual_source_idx_` lies in between two kernel offsets so figure out + // what they are. + const int source_idx = static_cast<int>(virtual_source_idx_); + const double subsample_remainder = virtual_source_idx_ - source_idx; + + const double virtual_offset_idx = + subsample_remainder * kKernelOffsetCount; + const int offset_idx = static_cast<int>(virtual_offset_idx); + + // We'll compute "convolutions" for the two kernels which straddle + // `virtual_source_idx_`. + const float* const k1 = kernel_ptr + offset_idx * kKernelSize; + const float* const k2 = k1 + kKernelSize; + + // Ensure `k1`, `k2` are 32-byte aligned for SIMD usage. Should always be + // true so long as kKernelSize is a multiple of 32. + RTC_DCHECK_EQ(0, reinterpret_cast<uintptr_t>(k1) % 32); + RTC_DCHECK_EQ(0, reinterpret_cast<uintptr_t>(k2) % 32); + + // Initialize input pointer based on quantized `virtual_source_idx_`. + const float* const input_ptr = r1_ + source_idx; + + // Figure out how much to weight each kernel's "convolution". + const double kernel_interpolation_factor = + virtual_offset_idx - offset_idx; + *destination++ = + convolve_proc_(input_ptr, k1, k2, kernel_interpolation_factor); + + // Advance the virtual index. + virtual_source_idx_ += current_io_ratio; + + if (!--remaining_frames) + return; + } + + // Wrap back around to the start. + virtual_source_idx_ -= block_size_; + + // Step (3) -- Copy r3_, r4_ to r1_, r2_. + // This wraps the last input frames back to the start of the buffer. + memcpy(r1_, r3_, sizeof(*input_buffer_.get()) * kKernelSize); + + // Step (4) -- Reinitialize regions if necessary. + if (r0_ == r2_) + UpdateRegions(true); + + // Step (5) -- Refresh the buffer with more input. + read_cb_->Run(request_frames_, r0_); + } +} + +#undef CONVOLVE_FUNC + +size_t SincResampler::ChunkSize() const { + return static_cast<size_t>(block_size_ / io_sample_rate_ratio_); +} + +void SincResampler::Flush() { + virtual_source_idx_ = 0; + buffer_primed_ = false; + memset(input_buffer_.get(), 0, + sizeof(*input_buffer_.get()) * input_buffer_size_); + UpdateRegions(false); +} + +float SincResampler::Convolve_C(const float* input_ptr, + const float* k1, + const float* k2, + double kernel_interpolation_factor) { + float sum1 = 0; + float sum2 = 0; + + // Generate a single output sample. Unrolling this loop hurt performance in + // local testing. + size_t n = kKernelSize; + while (n--) { + sum1 += *input_ptr * *k1++; + sum2 += *input_ptr++ * *k2++; + } + + // Linearly interpolate the two "convolutions". + return static_cast<float>((1.0 - kernel_interpolation_factor) * sum1 + + kernel_interpolation_factor * sum2); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/common_audio/resampler/sinc_resampler.h b/third_party/libwebrtc/common_audio/resampler/sinc_resampler.h new file mode 100644 index 0000000000..c6a43abd01 --- /dev/null +++ b/third_party/libwebrtc/common_audio/resampler/sinc_resampler.h @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// Modified from the Chromium original here: +// src/media/base/sinc_resampler.h + +#ifndef COMMON_AUDIO_RESAMPLER_SINC_RESAMPLER_H_ +#define COMMON_AUDIO_RESAMPLER_SINC_RESAMPLER_H_ + +#include <stddef.h> + +#include <memory> + +#include "rtc_base/gtest_prod_util.h" +#include "rtc_base/memory/aligned_malloc.h" +#include "rtc_base/system/arch.h" + +namespace webrtc { + +// Callback class for providing more data into the resampler. Expects `frames` +// of data to be rendered into `destination`; zero padded if not enough frames +// are available to satisfy the request. +class SincResamplerCallback { + public: + virtual ~SincResamplerCallback() {} + virtual void Run(size_t frames, float* destination) = 0; +}; + +// SincResampler is a high-quality single-channel sample-rate converter. +class SincResampler { + public: + // The kernel size can be adjusted for quality (higher is better) at the + // expense of performance. Must be a multiple of 32. + // TODO(dalecurtis): Test performance to see if we can jack this up to 64+. + static const size_t kKernelSize = 32; + + // Default request size. Affects how often and for how much SincResampler + // calls back for input. Must be greater than kKernelSize. + static const size_t kDefaultRequestSize = 512; + + // The kernel offset count is used for interpolation and is the number of + // sub-sample kernel shifts. Can be adjusted for quality (higher is better) + // at the expense of allocating more memory. + static const size_t kKernelOffsetCount = 32; + static const size_t kKernelStorageSize = + kKernelSize * (kKernelOffsetCount + 1); + + // Constructs a SincResampler with the specified `read_cb`, which is used to + // acquire audio data for resampling. `io_sample_rate_ratio` is the ratio + // of input / output sample rates. `request_frames` controls the size in + // frames of the buffer requested by each `read_cb` call. The value must be + // greater than kKernelSize. Specify kDefaultRequestSize if there are no + // request size constraints. + SincResampler(double io_sample_rate_ratio, + size_t request_frames, + SincResamplerCallback* read_cb); + virtual ~SincResampler(); + + SincResampler(const SincResampler&) = delete; + SincResampler& operator=(const SincResampler&) = delete; + + // Resample `frames` of data from `read_cb_` into `destination`. + void Resample(size_t frames, float* destination); + + // The maximum size in frames that guarantees Resample() will only make a + // single call to `read_cb_` for more data. + size_t ChunkSize() const; + + size_t request_frames() const { return request_frames_; } + + // Flush all buffered data and reset internal indices. Not thread safe, do + // not call while Resample() is in progress. + void Flush(); + + // Update `io_sample_rate_ratio_`. SetRatio() will cause a reconstruction of + // the kernels used for resampling. Not thread safe, do not call while + // Resample() is in progress. + // + // TODO(ajm): Use this in PushSincResampler rather than reconstructing + // SincResampler. We would also need a way to update `request_frames_`. + void SetRatio(double io_sample_rate_ratio); + + float* get_kernel_for_testing() { return kernel_storage_.get(); } + + private: + FRIEND_TEST_ALL_PREFIXES(SincResamplerTest, Convolve); + FRIEND_TEST_ALL_PREFIXES(SincResamplerTest, ConvolveBenchmark); + + void InitializeKernel(); + void UpdateRegions(bool second_load); + + // Selects runtime specific CPU features like SSE. Must be called before + // using SincResampler. + // TODO(ajm): Currently managed by the class internally. See the note with + // `convolve_proc_` below. + void InitializeCPUSpecificFeatures(); + + // Compute convolution of `k1` and `k2` over `input_ptr`, resultant sums are + // linearly interpolated using `kernel_interpolation_factor`. On x86 and ARM + // the underlying implementation is chosen at run time. + static float Convolve_C(const float* input_ptr, + const float* k1, + const float* k2, + double kernel_interpolation_factor); +#if defined(WEBRTC_ARCH_X86_FAMILY) + static float Convolve_SSE(const float* input_ptr, + const float* k1, + const float* k2, + double kernel_interpolation_factor); + static float Convolve_AVX2(const float* input_ptr, + const float* k1, + const float* k2, + double kernel_interpolation_factor); +#elif defined(WEBRTC_HAS_NEON) + static float Convolve_NEON(const float* input_ptr, + const float* k1, + const float* k2, + double kernel_interpolation_factor); +#endif + + // The ratio of input / output sample rates. + double io_sample_rate_ratio_; + + // An index on the source input buffer with sub-sample precision. It must be + // double precision to avoid drift. + double virtual_source_idx_; + + // The buffer is primed once at the very beginning of processing. + bool buffer_primed_; + + // Source of data for resampling. + SincResamplerCallback* read_cb_; + + // The size (in samples) to request from each `read_cb_` execution. + const size_t request_frames_; + + // The number of source frames processed per pass. + size_t block_size_; + + // The size (in samples) of the internal buffer used by the resampler. + const size_t input_buffer_size_; + + // Contains kKernelOffsetCount kernels back-to-back, each of size kKernelSize. + // The kernel offsets are sub-sample shifts of a windowed sinc shifted from + // 0.0 to 1.0 sample. + std::unique_ptr<float[], AlignedFreeDeleter> kernel_storage_; + std::unique_ptr<float[], AlignedFreeDeleter> kernel_pre_sinc_storage_; + std::unique_ptr<float[], AlignedFreeDeleter> kernel_window_storage_; + + // Data from the source is copied into this buffer for each processing pass. + std::unique_ptr<float[], AlignedFreeDeleter> input_buffer_; + + // Stores the runtime selection of which Convolve function to use. + // TODO(ajm): Move to using a global static which must only be initialized + // once by the user. We're not doing this initially, because we don't have + // e.g. a LazyInstance helper in webrtc. + typedef float (*ConvolveProc)(const float*, + const float*, + const float*, + double); + ConvolveProc convolve_proc_; + + // Pointers to the various regions inside `input_buffer_`. See the diagram at + // the top of the .cc file for more information. + float* r0_; + float* const r1_; + float* const r2_; + float* r3_; + float* r4_; +}; + +} // namespace webrtc + +#endif // COMMON_AUDIO_RESAMPLER_SINC_RESAMPLER_H_ diff --git a/third_party/libwebrtc/common_audio/resampler/sinc_resampler_avx2.cc b/third_party/libwebrtc/common_audio/resampler/sinc_resampler_avx2.cc new file mode 100644 index 0000000000..d945a10be2 --- /dev/null +++ b/third_party/libwebrtc/common_audio/resampler/sinc_resampler_avx2.cc @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include <immintrin.h> +#include <stddef.h> +#include <stdint.h> +#include <xmmintrin.h> + +#include "common_audio/resampler/sinc_resampler.h" + +namespace webrtc { + +float SincResampler::Convolve_AVX2(const float* input_ptr, + const float* k1, + const float* k2, + double kernel_interpolation_factor) { + __m256 m_input; + __m256 m_sums1 = _mm256_setzero_ps(); + __m256 m_sums2 = _mm256_setzero_ps(); + + // Based on `input_ptr` alignment, we need to use loadu or load. Unrolling + // these loops has not been tested or benchmarked. + bool aligned_input = (reinterpret_cast<uintptr_t>(input_ptr) & 0x1F) == 0; + if (!aligned_input) { + for (size_t i = 0; i < kKernelSize; i += 8) { + m_input = _mm256_loadu_ps(input_ptr + i); + m_sums1 = _mm256_fmadd_ps(m_input, _mm256_load_ps(k1 + i), m_sums1); + m_sums2 = _mm256_fmadd_ps(m_input, _mm256_load_ps(k2 + i), m_sums2); + } + } else { + for (size_t i = 0; i < kKernelSize; i += 8) { + m_input = _mm256_load_ps(input_ptr + i); + m_sums1 = _mm256_fmadd_ps(m_input, _mm256_load_ps(k1 + i), m_sums1); + m_sums2 = _mm256_fmadd_ps(m_input, _mm256_load_ps(k2 + i), m_sums2); + } + } + + // Linearly interpolate the two "convolutions". + __m128 m128_sums1 = _mm_add_ps(_mm256_extractf128_ps(m_sums1, 0), + _mm256_extractf128_ps(m_sums1, 1)); + __m128 m128_sums2 = _mm_add_ps(_mm256_extractf128_ps(m_sums2, 0), + _mm256_extractf128_ps(m_sums2, 1)); + m128_sums1 = _mm_mul_ps( + m128_sums1, + _mm_set_ps1(static_cast<float>(1.0 - kernel_interpolation_factor))); + m128_sums2 = _mm_mul_ps( + m128_sums2, _mm_set_ps1(static_cast<float>(kernel_interpolation_factor))); + m128_sums1 = _mm_add_ps(m128_sums1, m128_sums2); + + // Sum components together. + float result; + m128_sums2 = _mm_add_ps(_mm_movehl_ps(m128_sums1, m128_sums1), m128_sums1); + _mm_store_ss(&result, _mm_add_ss(m128_sums2, + _mm_shuffle_ps(m128_sums2, m128_sums2, 1))); + + return result; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/common_audio/resampler/sinc_resampler_neon.cc b/third_party/libwebrtc/common_audio/resampler/sinc_resampler_neon.cc new file mode 100644 index 0000000000..9ee918bca3 --- /dev/null +++ b/third_party/libwebrtc/common_audio/resampler/sinc_resampler_neon.cc @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// Modified from the Chromium original: +// src/media/base/sinc_resampler.cc + +#include <arm_neon.h> + +#include "common_audio/resampler/sinc_resampler.h" + +namespace webrtc { + +float SincResampler::Convolve_NEON(const float* input_ptr, + const float* k1, + const float* k2, + double kernel_interpolation_factor) { + float32x4_t m_input; + float32x4_t m_sums1 = vmovq_n_f32(0); + float32x4_t m_sums2 = vmovq_n_f32(0); + + const float* upper = input_ptr + kKernelSize; + for (; input_ptr < upper;) { + m_input = vld1q_f32(input_ptr); + input_ptr += 4; + m_sums1 = vmlaq_f32(m_sums1, m_input, vld1q_f32(k1)); + k1 += 4; + m_sums2 = vmlaq_f32(m_sums2, m_input, vld1q_f32(k2)); + k2 += 4; + } + + // Linearly interpolate the two "convolutions". + m_sums1 = vmlaq_f32( + vmulq_f32(m_sums1, vmovq_n_f32(1.0 - kernel_interpolation_factor)), + m_sums2, vmovq_n_f32(kernel_interpolation_factor)); + + // Sum components together. + float32x2_t m_half = vadd_f32(vget_high_f32(m_sums1), vget_low_f32(m_sums1)); + return vget_lane_f32(vpadd_f32(m_half, m_half), 0); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/common_audio/resampler/sinc_resampler_sse.cc b/third_party/libwebrtc/common_audio/resampler/sinc_resampler_sse.cc new file mode 100644 index 0000000000..30a8d1b2d9 --- /dev/null +++ b/third_party/libwebrtc/common_audio/resampler/sinc_resampler_sse.cc @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// Modified from the Chromium original: +// src/media/base/simd/sinc_resampler_sse.cc + +#include <stddef.h> +#include <stdint.h> +#include <xmmintrin.h> + +#include "common_audio/resampler/sinc_resampler.h" + +namespace webrtc { + +float SincResampler::Convolve_SSE(const float* input_ptr, + const float* k1, + const float* k2, + double kernel_interpolation_factor) { + __m128 m_input; + __m128 m_sums1 = _mm_setzero_ps(); + __m128 m_sums2 = _mm_setzero_ps(); + + // Based on `input_ptr` alignment, we need to use loadu or load. Unrolling + // these loops hurt performance in local testing. + if (reinterpret_cast<uintptr_t>(input_ptr) & 0x0F) { + for (size_t i = 0; i < kKernelSize; i += 4) { + m_input = _mm_loadu_ps(input_ptr + i); + m_sums1 = _mm_add_ps(m_sums1, _mm_mul_ps(m_input, _mm_load_ps(k1 + i))); + m_sums2 = _mm_add_ps(m_sums2, _mm_mul_ps(m_input, _mm_load_ps(k2 + i))); + } + } else { + for (size_t i = 0; i < kKernelSize; i += 4) { + m_input = _mm_load_ps(input_ptr + i); + m_sums1 = _mm_add_ps(m_sums1, _mm_mul_ps(m_input, _mm_load_ps(k1 + i))); + m_sums2 = _mm_add_ps(m_sums2, _mm_mul_ps(m_input, _mm_load_ps(k2 + i))); + } + } + + // Linearly interpolate the two "convolutions". + m_sums1 = _mm_mul_ps( + m_sums1, + _mm_set_ps1(static_cast<float>(1.0 - kernel_interpolation_factor))); + m_sums2 = _mm_mul_ps( + m_sums2, _mm_set_ps1(static_cast<float>(kernel_interpolation_factor))); + m_sums1 = _mm_add_ps(m_sums1, m_sums2); + + // Sum components together. + float result; + m_sums2 = _mm_add_ps(_mm_movehl_ps(m_sums1, m_sums1), m_sums1); + _mm_store_ss(&result, + _mm_add_ss(m_sums2, _mm_shuffle_ps(m_sums2, m_sums2, 1))); + + return result; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/common_audio/resampler/sinc_resampler_unittest.cc b/third_party/libwebrtc/common_audio/resampler/sinc_resampler_unittest.cc new file mode 100644 index 0000000000..b267c89c8b --- /dev/null +++ b/third_party/libwebrtc/common_audio/resampler/sinc_resampler_unittest.cc @@ -0,0 +1,393 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// Modified from the Chromium original: +// src/media/base/sinc_resampler_unittest.cc + +// MSVC++ requires this to be set before any other includes to get M_PI. +#define _USE_MATH_DEFINES + +#include "common_audio/resampler/sinc_resampler.h" + +#include <math.h> + +#include <algorithm> +#include <memory> +#include <tuple> + +#include "common_audio/resampler/sinusoidal_linear_chirp_source.h" +#include "rtc_base/system/arch.h" +#include "rtc_base/time_utils.h" +#include "system_wrappers/include/cpu_features_wrapper.h" +#include "test/gmock.h" +#include "test/gtest.h" + +using ::testing::_; + +namespace webrtc { + +static const double kSampleRateRatio = 192000.0 / 44100.0; +static const double kKernelInterpolationFactor = 0.5; + +// Helper class to ensure ChunkedResample() functions properly. +class MockSource : public SincResamplerCallback { + public: + MOCK_METHOD(void, Run, (size_t frames, float* destination), (override)); +}; + +ACTION(ClearBuffer) { + memset(arg1, 0, arg0 * sizeof(float)); +} + +ACTION(FillBuffer) { + // Value chosen arbitrarily such that SincResampler resamples it to something + // easily representable on all platforms; e.g., using kSampleRateRatio this + // becomes 1.81219. + memset(arg1, 64, arg0 * sizeof(float)); +} + +// Test requesting multiples of ChunkSize() frames results in the proper number +// of callbacks. +TEST(SincResamplerTest, ChunkedResample) { + MockSource mock_source; + + // Choose a high ratio of input to output samples which will result in quick + // exhaustion of SincResampler's internal buffers. + SincResampler resampler(kSampleRateRatio, SincResampler::kDefaultRequestSize, + &mock_source); + + static const int kChunks = 2; + size_t max_chunk_size = resampler.ChunkSize() * kChunks; + std::unique_ptr<float[]> resampled_destination(new float[max_chunk_size]); + + // Verify requesting ChunkSize() frames causes a single callback. + EXPECT_CALL(mock_source, Run(_, _)).Times(1).WillOnce(ClearBuffer()); + resampler.Resample(resampler.ChunkSize(), resampled_destination.get()); + + // Verify requesting kChunks * ChunkSize() frames causes kChunks callbacks. + ::testing::Mock::VerifyAndClear(&mock_source); + EXPECT_CALL(mock_source, Run(_, _)) + .Times(kChunks) + .WillRepeatedly(ClearBuffer()); + resampler.Resample(max_chunk_size, resampled_destination.get()); +} + +// Test flush resets the internal state properly. +TEST(SincResamplerTest, Flush) { + MockSource mock_source; + SincResampler resampler(kSampleRateRatio, SincResampler::kDefaultRequestSize, + &mock_source); + std::unique_ptr<float[]> resampled_destination( + new float[resampler.ChunkSize()]); + + // Fill the resampler with junk data. + EXPECT_CALL(mock_source, Run(_, _)).Times(1).WillOnce(FillBuffer()); + resampler.Resample(resampler.ChunkSize() / 2, resampled_destination.get()); + ASSERT_NE(resampled_destination[0], 0); + + // Flush and request more data, which should all be zeros now. + resampler.Flush(); + ::testing::Mock::VerifyAndClear(&mock_source); + EXPECT_CALL(mock_source, Run(_, _)).Times(1).WillOnce(ClearBuffer()); + resampler.Resample(resampler.ChunkSize() / 2, resampled_destination.get()); + for (size_t i = 0; i < resampler.ChunkSize() / 2; ++i) + ASSERT_FLOAT_EQ(resampled_destination[i], 0); +} + +// Test flush resets the internal state properly. +TEST(SincResamplerTest, DISABLED_SetRatioBench) { + MockSource mock_source; + SincResampler resampler(kSampleRateRatio, SincResampler::kDefaultRequestSize, + &mock_source); + + int64_t start = rtc::TimeNanos(); + for (int i = 1; i < 10000; ++i) + resampler.SetRatio(1.0 / i); + double total_time_c_us = + (rtc::TimeNanos() - start) / rtc::kNumNanosecsPerMicrosec; + printf("SetRatio() took %.2fms.\n", total_time_c_us / 1000); +} + +// Ensure various optimized Convolve() methods return the same value. Only run +// this test if other optimized methods exist, otherwise the default Convolve() +// will be tested by the parameterized SincResampler tests below. +TEST(SincResamplerTest, Convolve) { +#if defined(WEBRTC_ARCH_X86_FAMILY) + ASSERT_TRUE(GetCPUInfo(kSSE2)); +#elif defined(WEBRTC_ARCH_ARM_V7) + ASSERT_TRUE(GetCPUFeaturesARM() & kCPUFeatureNEON); +#endif + + // Initialize a dummy resampler. + MockSource mock_source; + SincResampler resampler(kSampleRateRatio, SincResampler::kDefaultRequestSize, + &mock_source); + + // The optimized Convolve methods are slightly more precise than Convolve_C(), + // so comparison must be done using an epsilon. + static const double kEpsilon = 0.00000005; + + // Use a kernel from SincResampler as input and kernel data, this has the + // benefit of already being properly sized and aligned for Convolve_SSE(). + double result = resampler.Convolve_C( + resampler.kernel_storage_.get(), resampler.kernel_storage_.get(), + resampler.kernel_storage_.get(), kKernelInterpolationFactor); + double result2 = resampler.convolve_proc_( + resampler.kernel_storage_.get(), resampler.kernel_storage_.get(), + resampler.kernel_storage_.get(), kKernelInterpolationFactor); + EXPECT_NEAR(result2, result, kEpsilon); + + // Test Convolve() w/ unaligned input pointer. + result = resampler.Convolve_C( + resampler.kernel_storage_.get() + 1, resampler.kernel_storage_.get(), + resampler.kernel_storage_.get(), kKernelInterpolationFactor); + result2 = resampler.convolve_proc_( + resampler.kernel_storage_.get() + 1, resampler.kernel_storage_.get(), + resampler.kernel_storage_.get(), kKernelInterpolationFactor); + EXPECT_NEAR(result2, result, kEpsilon); +} + +// Benchmark for the various Convolve() methods. Make sure to build with +// branding=Chrome so that RTC_DCHECKs are compiled out when benchmarking. +// Original benchmarks were run with --convolve-iterations=50000000. +TEST(SincResamplerTest, ConvolveBenchmark) { + // Initialize a dummy resampler. + MockSource mock_source; + SincResampler resampler(kSampleRateRatio, SincResampler::kDefaultRequestSize, + &mock_source); + + // Retrieve benchmark iterations from command line. + // TODO(ajm): Reintroduce this as a command line option. + const int kConvolveIterations = 1000000; + + printf("Benchmarking %d iterations:\n", kConvolveIterations); + + // Benchmark Convolve_C(). + int64_t start = rtc::TimeNanos(); + for (int i = 0; i < kConvolveIterations; ++i) { + resampler.Convolve_C( + resampler.kernel_storage_.get(), resampler.kernel_storage_.get(), + resampler.kernel_storage_.get(), kKernelInterpolationFactor); + } + double total_time_c_us = + (rtc::TimeNanos() - start) / rtc::kNumNanosecsPerMicrosec; + printf("Convolve_C took %.2fms.\n", total_time_c_us / 1000); + +#if defined(WEBRTC_ARCH_X86_FAMILY) + ASSERT_TRUE(GetCPUInfo(kSSE2)); +#elif defined(WEBRTC_ARCH_ARM_V7) + ASSERT_TRUE(GetCPUFeaturesARM() & kCPUFeatureNEON); +#endif + + // Benchmark with unaligned input pointer. + start = rtc::TimeNanos(); + for (int j = 0; j < kConvolveIterations; ++j) { + resampler.convolve_proc_( + resampler.kernel_storage_.get() + 1, resampler.kernel_storage_.get(), + resampler.kernel_storage_.get(), kKernelInterpolationFactor); + } + double total_time_optimized_unaligned_us = + (rtc::TimeNanos() - start) / rtc::kNumNanosecsPerMicrosec; + printf( + "convolve_proc_(unaligned) took %.2fms; which is %.2fx " + "faster than Convolve_C.\n", + total_time_optimized_unaligned_us / 1000, + total_time_c_us / total_time_optimized_unaligned_us); + + // Benchmark with aligned input pointer. + start = rtc::TimeNanos(); + for (int j = 0; j < kConvolveIterations; ++j) { + resampler.convolve_proc_( + resampler.kernel_storage_.get(), resampler.kernel_storage_.get(), + resampler.kernel_storage_.get(), kKernelInterpolationFactor); + } + double total_time_optimized_aligned_us = + (rtc::TimeNanos() - start) / rtc::kNumNanosecsPerMicrosec; + printf( + "convolve_proc_ (aligned) took %.2fms; which is %.2fx " + "faster than Convolve_C and %.2fx faster than " + "convolve_proc_ (unaligned).\n", + total_time_optimized_aligned_us / 1000, + total_time_c_us / total_time_optimized_aligned_us, + total_time_optimized_unaligned_us / total_time_optimized_aligned_us); +} + +typedef std::tuple<int, int, double, double> SincResamplerTestData; +class SincResamplerTest + : public ::testing::TestWithParam<SincResamplerTestData> { + public: + SincResamplerTest() + : input_rate_(std::get<0>(GetParam())), + output_rate_(std::get<1>(GetParam())), + rms_error_(std::get<2>(GetParam())), + low_freq_error_(std::get<3>(GetParam())) {} + + virtual ~SincResamplerTest() {} + + protected: + int input_rate_; + int output_rate_; + double rms_error_; + double low_freq_error_; +}; + +// Tests resampling using a given input and output sample rate. +TEST_P(SincResamplerTest, Resample) { + // Make comparisons using one second of data. + static const double kTestDurationSecs = 1; + const size_t input_samples = + static_cast<size_t>(kTestDurationSecs * input_rate_); + const size_t output_samples = + static_cast<size_t>(kTestDurationSecs * output_rate_); + + // Nyquist frequency for the input sampling rate. + const double input_nyquist_freq = 0.5 * input_rate_; + + // Source for data to be resampled. + SinusoidalLinearChirpSource resampler_source(input_rate_, input_samples, + input_nyquist_freq, 0); + + const double io_ratio = input_rate_ / static_cast<double>(output_rate_); + SincResampler resampler(io_ratio, SincResampler::kDefaultRequestSize, + &resampler_source); + + // Force an update to the sample rate ratio to ensure dynamic sample rate + // changes are working correctly. + std::unique_ptr<float[]> kernel(new float[SincResampler::kKernelStorageSize]); + memcpy(kernel.get(), resampler.get_kernel_for_testing(), + SincResampler::kKernelStorageSize); + resampler.SetRatio(M_PI); + ASSERT_NE(0, memcmp(kernel.get(), resampler.get_kernel_for_testing(), + SincResampler::kKernelStorageSize)); + resampler.SetRatio(io_ratio); + ASSERT_EQ(0, memcmp(kernel.get(), resampler.get_kernel_for_testing(), + SincResampler::kKernelStorageSize)); + + // TODO(dalecurtis): If we switch to AVX/SSE optimization, we'll need to + // allocate these on 32-byte boundaries and ensure they're sized % 32 bytes. + std::unique_ptr<float[]> resampled_destination(new float[output_samples]); + std::unique_ptr<float[]> pure_destination(new float[output_samples]); + + // Generate resampled signal. + resampler.Resample(output_samples, resampled_destination.get()); + + // Generate pure signal. + SinusoidalLinearChirpSource pure_source(output_rate_, output_samples, + input_nyquist_freq, 0); + pure_source.Run(output_samples, pure_destination.get()); + + // Range of the Nyquist frequency (0.5 * min(input rate, output_rate)) which + // we refer to as low and high. + static const double kLowFrequencyNyquistRange = 0.7; + static const double kHighFrequencyNyquistRange = 0.9; + + // Calculate Root-Mean-Square-Error and maximum error for the resampling. + double sum_of_squares = 0; + double low_freq_max_error = 0; + double high_freq_max_error = 0; + int minimum_rate = std::min(input_rate_, output_rate_); + double low_frequency_range = kLowFrequencyNyquistRange * 0.5 * minimum_rate; + double high_frequency_range = kHighFrequencyNyquistRange * 0.5 * minimum_rate; + for (size_t i = 0; i < output_samples; ++i) { + double error = fabs(resampled_destination[i] - pure_destination[i]); + + if (pure_source.Frequency(i) < low_frequency_range) { + if (error > low_freq_max_error) + low_freq_max_error = error; + } else if (pure_source.Frequency(i) < high_frequency_range) { + if (error > high_freq_max_error) + high_freq_max_error = error; + } + // TODO(dalecurtis): Sanity check frequencies > kHighFrequencyNyquistRange. + + sum_of_squares += error * error; + } + + double rms_error = sqrt(sum_of_squares / output_samples); + +// Convert each error to dbFS. +#define DBFS(x) 20 * log10(x) + rms_error = DBFS(rms_error); + low_freq_max_error = DBFS(low_freq_max_error); + high_freq_max_error = DBFS(high_freq_max_error); + + EXPECT_LE(rms_error, rms_error_); + EXPECT_LE(low_freq_max_error, low_freq_error_); + + // All conversions currently have a high frequency error around -6 dbFS. + static const double kHighFrequencyMaxError = -6.02; + EXPECT_LE(high_freq_max_error, kHighFrequencyMaxError); +} + +// Almost all conversions have an RMS error of around -14 dbFS. +static const double kResamplingRMSError = -14.58; + +// Thresholds chosen arbitrarily based on what each resampling reported during +// testing. All thresholds are in dbFS, http://en.wikipedia.org/wiki/DBFS. +INSTANTIATE_TEST_SUITE_P( + SincResamplerTest, + SincResamplerTest, + ::testing::Values( + // To 22.05kHz + std::make_tuple(8000, 22050, kResamplingRMSError, -62.73), + std::make_tuple(11025, 22050, kResamplingRMSError, -72.19), + std::make_tuple(16000, 22050, kResamplingRMSError, -62.54), + std::make_tuple(22050, 22050, kResamplingRMSError, -73.53), + std::make_tuple(32000, 22050, kResamplingRMSError, -46.45), + std::make_tuple(44100, 22050, kResamplingRMSError, -28.49), + std::make_tuple(48000, 22050, -15.01, -25.56), + std::make_tuple(96000, 22050, -18.49, -13.42), + std::make_tuple(192000, 22050, -20.50, -9.23), + + // To 44.1kHz + std::make_tuple(8000, 44100, kResamplingRMSError, -62.73), + std::make_tuple(11025, 44100, kResamplingRMSError, -72.19), + std::make_tuple(16000, 44100, kResamplingRMSError, -62.54), + std::make_tuple(22050, 44100, kResamplingRMSError, -73.53), + std::make_tuple(32000, 44100, kResamplingRMSError, -63.32), + std::make_tuple(44100, 44100, kResamplingRMSError, -73.52), + std::make_tuple(48000, 44100, -15.01, -64.04), + std::make_tuple(96000, 44100, -18.49, -25.51), + std::make_tuple(192000, 44100, -20.50, -13.31), + + // To 48kHz + std::make_tuple(8000, 48000, kResamplingRMSError, -63.43), + std::make_tuple(11025, 48000, kResamplingRMSError, -62.61), + std::make_tuple(16000, 48000, kResamplingRMSError, -63.95), + std::make_tuple(22050, 48000, kResamplingRMSError, -62.42), + std::make_tuple(32000, 48000, kResamplingRMSError, -64.04), + std::make_tuple(44100, 48000, kResamplingRMSError, -62.63), + std::make_tuple(48000, 48000, kResamplingRMSError, -73.52), + std::make_tuple(96000, 48000, -18.40, -28.44), + std::make_tuple(192000, 48000, -20.43, -14.11), + + // To 96kHz + std::make_tuple(8000, 96000, kResamplingRMSError, -63.19), + std::make_tuple(11025, 96000, kResamplingRMSError, -62.61), + std::make_tuple(16000, 96000, kResamplingRMSError, -63.39), + std::make_tuple(22050, 96000, kResamplingRMSError, -62.42), + std::make_tuple(32000, 96000, kResamplingRMSError, -63.95), + std::make_tuple(44100, 96000, kResamplingRMSError, -62.63), + std::make_tuple(48000, 96000, kResamplingRMSError, -73.52), + std::make_tuple(96000, 96000, kResamplingRMSError, -73.52), + std::make_tuple(192000, 96000, kResamplingRMSError, -28.41), + + // To 192kHz + std::make_tuple(8000, 192000, kResamplingRMSError, -63.10), + std::make_tuple(11025, 192000, kResamplingRMSError, -62.61), + std::make_tuple(16000, 192000, kResamplingRMSError, -63.14), + std::make_tuple(22050, 192000, kResamplingRMSError, -62.42), + std::make_tuple(32000, 192000, kResamplingRMSError, -63.38), + std::make_tuple(44100, 192000, kResamplingRMSError, -62.63), + std::make_tuple(48000, 192000, kResamplingRMSError, -73.44), + std::make_tuple(96000, 192000, kResamplingRMSError, -73.52), + std::make_tuple(192000, 192000, kResamplingRMSError, -73.52))); + +} // namespace webrtc diff --git a/third_party/libwebrtc/common_audio/resampler/sinusoidal_linear_chirp_source.cc b/third_party/libwebrtc/common_audio/resampler/sinusoidal_linear_chirp_source.cc new file mode 100644 index 0000000000..2afdd1be47 --- /dev/null +++ b/third_party/libwebrtc/common_audio/resampler/sinusoidal_linear_chirp_source.cc @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// MSVC++ requires this to be set before any other includes to get M_PI. +#define _USE_MATH_DEFINES + +#include "common_audio/resampler/sinusoidal_linear_chirp_source.h" + +#include <math.h> + +namespace webrtc { + +SinusoidalLinearChirpSource::SinusoidalLinearChirpSource(int sample_rate, + size_t samples, + double max_frequency, + double delay_samples) + : sample_rate_(sample_rate), + total_samples_(samples), + max_frequency_(max_frequency), + current_index_(0), + delay_samples_(delay_samples) { + // Chirp rate. + double duration = static_cast<double>(total_samples_) / sample_rate_; + k_ = (max_frequency_ - kMinFrequency) / duration; +} + +void SinusoidalLinearChirpSource::Run(size_t frames, float* destination) { + for (size_t i = 0; i < frames; ++i, ++current_index_) { + // Filter out frequencies higher than Nyquist. + if (Frequency(current_index_) > 0.5 * sample_rate_) { + destination[i] = 0; + } else { + // Calculate time in seconds. + if (current_index_ < delay_samples_) { + destination[i] = 0; + } else { + // Sinusoidal linear chirp. + double t = (current_index_ - delay_samples_) / sample_rate_; + destination[i] = sin(2 * M_PI * (kMinFrequency * t + (k_ / 2) * t * t)); + } + } + } +} + +double SinusoidalLinearChirpSource::Frequency(size_t position) { + return kMinFrequency + (position - delay_samples_) * + (max_frequency_ - kMinFrequency) / total_samples_; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/common_audio/resampler/sinusoidal_linear_chirp_source.h b/third_party/libwebrtc/common_audio/resampler/sinusoidal_linear_chirp_source.h new file mode 100644 index 0000000000..ccd11bbd61 --- /dev/null +++ b/third_party/libwebrtc/common_audio/resampler/sinusoidal_linear_chirp_source.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// Modified from the Chromium original here: +// src/media/base/sinc_resampler_unittest.cc + +#ifndef COMMON_AUDIO_RESAMPLER_SINUSOIDAL_LINEAR_CHIRP_SOURCE_H_ +#define COMMON_AUDIO_RESAMPLER_SINUSOIDAL_LINEAR_CHIRP_SOURCE_H_ + +#include "common_audio/resampler/sinc_resampler.h" + +namespace webrtc { + +// Fake audio source for testing the resampler. Generates a sinusoidal linear +// chirp (http://en.wikipedia.org/wiki/Chirp) which can be tuned to stress the +// resampler for the specific sample rate conversion being used. +class SinusoidalLinearChirpSource : public SincResamplerCallback { + public: + // `delay_samples` can be used to insert a fractional sample delay into the + // source. It will produce zeros until non-negative time is reached. + SinusoidalLinearChirpSource(int sample_rate, + size_t samples, + double max_frequency, + double delay_samples); + + ~SinusoidalLinearChirpSource() override {} + + SinusoidalLinearChirpSource(const SinusoidalLinearChirpSource&) = delete; + SinusoidalLinearChirpSource& operator=(const SinusoidalLinearChirpSource&) = + delete; + + void Run(size_t frames, float* destination) override; + + double Frequency(size_t position); + + private: + static constexpr int kMinFrequency = 5; + + int sample_rate_; + size_t total_samples_; + double max_frequency_; + double k_; + size_t current_index_; + double delay_samples_; +}; + +} // namespace webrtc + +#endif // COMMON_AUDIO_RESAMPLER_SINUSOIDAL_LINEAR_CHIRP_SOURCE_H_ |