blob: baf3c1d8f9ffa66e6cee1e2d67a5223dedb98322 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/media/audio/lib/processing/sinc_sampler.h"
#include <fidl/fuchsia.audio/cpp/wire_types.h>
#include <lib/syslog/cpp/macros.h>
#include <cstdint>
#include <utility>
#include <vector>
#include <ffl/string.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/media/audio/lib/format2/channel_mapper.h"
#include "src/media/audio/lib/format2/fixed.h"
#include "src/media/audio/lib/format2/sample_converter.h"
#include "src/media/audio/lib/processing/filter.h"
#include "src/media/audio/lib/processing/gain.h"
#include "src/media/audio/lib/timeline/timeline_rate.h"
namespace media_audio {
namespace {
using ::fuchsia_audio::SampleType;
using ::media::TimelineRate;
using ::testing::Each;
using ::testing::FloatEq;
using ::testing::IsNull;
using ::testing::NotNull;
using ::testing::Pointwise;
constexpr std::pair<uint32_t, uint32_t> kChannelConfigs[] = {
{1, 1}, {1, 2}, {1, 3}, {1, 4}, {2, 1}, {2, 2}, {2, 3}, {2, 4},
{3, 1}, {3, 2}, {3, 3}, {3, 4}, {4, 1}, {4, 2}, {4, 3}, {4, 4},
};
constexpr uint32_t kFrameRates[] = {
8000, 11025, 16000, 22050, 24000, 32000, 44100, 48000, 88200, 96000, 176400, 192000,
};
constexpr SampleType kSampleTypes[] = {
SampleType::kUint8,
SampleType::kInt16,
SampleType::kInt32,
SampleType::kFloat32,
};
Format CreateFormat(int64_t channel_count, int64_t frame_rate, SampleType sample_type) {
return Format::CreateOrDie({sample_type, channel_count, frame_rate});
}
TEST(SincSamplerTest, CreateWithValidConfigs) {
for (const auto& [source_channel_count, dest_channel_count] : kChannelConfigs) {
for (const auto& source_frame_rate : kFrameRates) {
for (const auto& dest_frame_rate : kFrameRates) {
for (const auto& sample_type : kSampleTypes) {
EXPECT_THAT(SincSampler::Create(
CreateFormat(source_channel_count, source_frame_rate, sample_type),
CreateFormat(dest_channel_count, dest_frame_rate, SampleType::kFloat32)),
NotNull());
}
}
}
}
}
TEST(SincSamplerTest, CreateFailsWithUnsupportedChannelConfigs) {
const std::pair<uint32_t, uint32_t> unsupported_channel_configs[] = {
{1, 5}, {2, 5}, {3, 5}, {4, 5}, {5, 1}, {5, 5}, {6, 2}, {6, 6}, {9, 1},
};
for (const auto& [source_channel_count, dest_channel_count] : unsupported_channel_configs) {
for (const auto& frame_rate : kFrameRates) {
for (const auto& sample_type : kSampleTypes) {
EXPECT_THAT(
SincSampler::Create(CreateFormat(source_channel_count, frame_rate, sample_type),
CreateFormat(dest_channel_count, frame_rate, SampleType::kFloat32)),
IsNull());
}
}
}
}
TEST(SincSamplerTest, CreateFailsWithUnsupportedDestSampleFormats) {
const uint32_t frame_rate = 44100;
for (const auto& [source_channel_count, dest_channel_count] : kChannelConfigs) {
for (const auto& source_sample_type : kSampleTypes) {
for (const auto& dest_sample_type : kSampleTypes) {
if (dest_sample_type != SampleType::kFloat32) {
EXPECT_THAT(SincSampler::Create(
CreateFormat(source_channel_count, frame_rate, source_sample_type),
CreateFormat(dest_channel_count, frame_rate, dest_sample_type)),
IsNull());
}
}
}
}
}
TEST(SincSamplerTest, Process) {
auto sampler = SincSampler::Create(CreateFormat(1, 48000, SampleType::kFloat32),
CreateFormat(1, 48000, SampleType::kFloat32));
ASSERT_THAT(sampler, NotNull());
const int64_t dest_frame_count = 5;
// Make sure to provide enough samples to compensate for the filter length.
const int64_t source_frame_count = dest_frame_count + sampler->pos_filter_length().Floor();
std::vector<float> source_samples(source_frame_count);
for (int i = 0; i < source_frame_count; ++i) {
source_samples[i] = static_cast<float>(i + 1);
}
std::vector<float> dest_samples(dest_frame_count, 1.0f);
Fixed source_offset = Fixed(0);
int64_t dest_offset = 0;
// All source samples should be accumulated into destination samples as-is.
sampler->Process({source_samples.data(), &source_offset, source_frame_count},
{dest_samples.data(), &dest_offset, dest_frame_count},
{.type = GainType::kUnity, .scale = kUnityGainScale},
/*accumulate=*/true);
for (int i = 0; i < dest_frame_count; ++i) {
EXPECT_FLOAT_EQ(dest_samples[i], source_samples[i] + 1.0f) << i;
}
}
TEST(SincSamplerTest, ProcessDownSample) {
const uint32_t kSourceFrameRate = 48000;
const uint32_t kDestFrameRate = 44100;
auto sampler = SincSampler::Create(CreateFormat(1, kSourceFrameRate, SampleType::kFloat32),
CreateFormat(1, kDestFrameRate, SampleType::kFloat32));
ASSERT_THAT(sampler, NotNull());
const int64_t dest_frame_count = 512;
std::vector<float> dest_samples(dest_frame_count, 0.0f);
int64_t dest_offset = 0;
const int64_t source_frame_count = dest_frame_count / 2;
const std::vector<float> source_samples(source_frame_count, 1.0f);
Fixed source_offset = Fixed(0);
sampler->state().ResetSourceStride(
TimelineRate(Fixed(kSourceFrameRate).raw_value(), kDestFrameRate));
// Process the first half of the destination.
sampler->Process({source_samples.data(), &source_offset, source_frame_count},
{dest_samples.data(), &dest_offset, dest_frame_count},
{.type = GainType::kUnity, .scale = kUnityGainScale},
/*accumulate=*/false);
EXPECT_GT(source_offset + sampler->pos_filter_length(), Fixed(source_frame_count));
const int64_t first_half_dest_offset = dest_offset;
// Now process the rest.
source_offset -= Fixed(source_frame_count);
sampler->Process({source_samples.data(), &source_offset, source_frame_count},
{dest_samples.data(), &dest_offset, dest_frame_count},
{.type = GainType::kUnity, .scale = kUnityGainScale},
/*accumulate=*/false);
EXPECT_GT(source_offset + sampler->pos_filter_length(), Fixed(source_frame_count));
// The "seam" between the source and destination samples should be invisible.
const float kSeamThreshold = 0.001f;
for (int64_t i = first_half_dest_offset - 2; i < first_half_dest_offset + 2; ++i) {
EXPECT_NEAR(dest_samples[i], 1.0f, kSeamThreshold) << i;
}
}
TEST(SincSamplerTest, ProcessUpSample) {
const uint32_t kSourceFrameRate = 12000;
const uint32_t kDestFrameRate = 48000;
auto sampler = SincSampler::Create(CreateFormat(1, kSourceFrameRate, SampleType::kFloat32),
CreateFormat(1, kDestFrameRate, SampleType::kFloat32));
ASSERT_THAT(sampler, NotNull());
const int64_t dest_frame_count = 1024;
std::vector<float> dest_samples(dest_frame_count, 0.0f);
int64_t dest_offset = 0;
const int64_t source_frame_count = dest_frame_count / 8;
const std::vector<float> source_samples(source_frame_count, 1.0f);
Fixed source_offset = Fixed(0);
sampler->state().ResetSourceStride(
TimelineRate(Fixed(kSourceFrameRate).raw_value(), kDestFrameRate));
// Process the first half of the destination.
sampler->Process({source_samples.data(), &source_offset, source_frame_count},
{dest_samples.data(), &dest_offset, dest_frame_count},
{.type = GainType::kUnity, .scale = kUnityGainScale},
/*accumulate=*/false);
EXPECT_GT(source_offset + sampler->pos_filter_length(), Fixed(source_frame_count));
EXPECT_EQ(Fixed(source_offset * 4).Floor(), dest_offset);
const int64_t first_half_dest_offset = dest_offset;
// Now process the rest.
source_offset -= Fixed(source_frame_count);
sampler->Process({source_samples.data(), &source_offset, source_frame_count},
{dest_samples.data(), &dest_offset, dest_frame_count},
{.type = GainType::kUnity, .scale = kUnityGainScale},
/*accumulate=*/false);
EXPECT_GT(source_offset + sampler->pos_filter_length(), Fixed(source_frame_count));
// The "seam" between the source and destination samples should be invisible.
const float kSeamThreshold = 0.001f;
for (int64_t i = first_half_dest_offset - 2; i < first_half_dest_offset + 2; ++i) {
EXPECT_NEAR(dest_samples[i], 1.0f, kSeamThreshold) << i;
}
}
TEST(SincSamplerTest, ProcessWithConstantGain) {
auto sampler = SincSampler::Create(CreateFormat(1, 48000, SampleType::kFloat32),
CreateFormat(1, 48000, SampleType::kFloat32));
ASSERT_THAT(sampler, NotNull());
const int64_t dest_frame_count = 5;
// Make sure to provide enough samples to compensate for the filter length.
const int64_t source_frame_count = dest_frame_count + sampler->pos_filter_length().Floor();
std::vector<float> source_samples(source_frame_count);
for (int i = 0; i < source_frame_count; ++i) {
source_samples[i] = static_cast<float>(i + 1);
}
std::vector<float> dest_samples(dest_frame_count, 1.0f);
Fixed source_offset = Fixed(0);
int64_t dest_offset = 0;
// Source samples should be scaled with constant gain and accumulated into destination samples.
sampler->Process({source_samples.data(), &source_offset, source_frame_count},
{dest_samples.data(), &dest_offset, dest_frame_count},
{.type = GainType::kNonUnity, .scale = 10.0f},
/*accumulate=*/true);
for (int i = 0; i < dest_frame_count; ++i) {
EXPECT_FLOAT_EQ(dest_samples[i], 10.0f * static_cast<float>(i + 1) + 1.0f) << i;
}
}
TEST(SincSamplerTest, ProcessWithRampingGain) {
auto sampler = SincSampler::Create(CreateFormat(1, 48000, SampleType::kFloat32),
CreateFormat(1, 48000, SampleType::kFloat32));
ASSERT_THAT(sampler, NotNull());
const int64_t dest_frame_count = 5;
// Make sure to provide enough samples to compensate for the filter length.
const int64_t source_frame_count = dest_frame_count + sampler->pos_filter_length().Floor();
std::vector<float> source_samples(source_frame_count);
for (int i = 0; i < source_frame_count; ++i) {
source_samples[i] = static_cast<float>(i + 1);
}
std::vector<float> dest_samples(dest_frame_count, 1.0f);
Fixed source_offset = Fixed(0);
int64_t dest_offset = 0;
// Source samples should be scaled with ramping gain and accumulated into destination samples.
const std::vector<float> scale_ramp = {2.0f, 4.0f, 6.0f, 8.0f, 10.0f};
sampler->Process({source_samples.data(), &source_offset, source_frame_count},
{dest_samples.data(), &dest_offset, dest_frame_count},
{.type = GainType::kRamping, .scale_ramp = scale_ramp.data()},
/*accumulate=*/true);
for (int i = 0; i < dest_frame_count; ++i) {
EXPECT_FLOAT_EQ(dest_samples[i], scale_ramp[i] * static_cast<float>(i + 1) + 1.0f) << i;
}
}
TEST(SincSamplerTest, ProcessWithSilentGain) {
auto sampler = SincSampler::Create(CreateFormat(1, 48000, SampleType::kFloat32),
CreateFormat(1, 48000, SampleType::kFloat32));
ASSERT_THAT(sampler, NotNull());
const int64_t dest_frame_count = 5;
// Make sure to provide enough samples to compensate for the filter length.
const int64_t source_frame_count = dest_frame_count + sampler->pos_filter_length().Floor();
std::vector<float> source_samples(source_frame_count);
for (int i = 0; i < source_frame_count; ++i) {
source_samples[i] = static_cast<float>(i + 1);
}
std::vector<float> dest_samples(dest_frame_count, 1.0f);
Fixed source_offset = Fixed(0);
int64_t dest_offset = 0;
// Nothing should be accumulated into destination samples when gain is silent.
sampler->Process({source_samples.data(), &source_offset, source_frame_count},
{dest_samples.data(), &dest_offset, dest_frame_count},
{.type = GainType::kSilent, .scale = kMinGainScale},
/*accumulate=*/true);
EXPECT_THAT(dest_samples, Each(1.0f));
// If no accumulation, destination samples should be filled with zeros.
source_offset = Fixed(0);
dest_offset = 0;
sampler->Process({source_samples.data(), &source_offset, source_frame_count},
{dest_samples.data(), &dest_offset, dest_frame_count},
{.type = GainType::kSilent, .scale = kMinGainScale},
/*accumulate=*/false);
EXPECT_THAT(dest_samples, Each(0.0f));
}
TEST(SincSamplerTest, ProcessWithSourceOffsetAtFrameBoundary) {
auto sampler = SincSampler::Create(CreateFormat(1, 44100, SampleType::kFloat32),
CreateFormat(1, 44100, SampleType::kFloat32));
ASSERT_THAT(sampler, NotNull());
// Source offset is 46 of 50 frames, destination offset is 1 of 10, which should advance by 4.
const int64_t dest_frame_count = 10;
std::vector<float> dest_samples(dest_frame_count);
int64_t dest_offset = 1;
const int64_t source_frame_count = 50;
std::vector<float> source_samples(source_frame_count);
Fixed source_offset =
Fixed(source_frame_count - 4) - sampler->pos_filter_length() + Fixed::FromRaw(1);
const int64_t expected_advance = 4;
const Fixed expected_source_offset = source_offset + Fixed(expected_advance);
const int64_t expected_dest_offset = dest_offset + expected_advance;
sampler->Process({source_samples.data(), &source_offset, source_frame_count},
{dest_samples.data(), &dest_offset, dest_frame_count},
{.type = GainType::kSilent, .scale = kMinGainScale},
/*accumulate=*/false);
EXPECT_EQ(dest_offset, expected_dest_offset);
EXPECT_EQ(source_offset, expected_source_offset) << ffl::String::DecRational << source_offset;
}
TEST(SincSamplerTest, ProcessWithSourceOffsetJustBeforeFrameBoundary) {
auto sampler = SincSampler::Create(CreateFormat(1, 44100, SampleType::kFloat32),
CreateFormat(1, 44100, SampleType::kFloat32));
ASSERT_THAT(sampler, NotNull());
// Source offset is 45.99 of 50 frames, destination offset is 1 of 10, which should advance by 5.
const int64_t dest_frame_count = 10;
std::vector<float> dest_samples(dest_frame_count);
int64_t dest_offset = 1;
const int64_t source_frame_count = 50;
std::vector<float> source_samples(source_frame_count);
Fixed source_offset = Fixed(source_frame_count - 4) - sampler->pos_filter_length();
const int64_t expected_advance = 5;
const Fixed expected_source_offset = source_offset + Fixed(expected_advance);
const int64_t expected_dest_offset = dest_offset + expected_advance;
sampler->Process({source_samples.data(), &source_offset, source_frame_count},
{dest_samples.data(), &dest_offset, dest_frame_count},
{.type = GainType::kSilent, .scale = kMinGainScale},
/*accumulate=*/false);
EXPECT_EQ(dest_offset, expected_dest_offset);
EXPECT_EQ(source_offset, expected_source_offset) << ffl::String::DecRational << source_offset;
}
TEST(SincSamplerTest, ProcessWithSourceOffsetAtEnd) {
auto sampler = SincSampler::Create(CreateFormat(1, 44100, SampleType::kFloat32),
CreateFormat(1, 44100, SampleType::kFloat32));
ASSERT_THAT(sampler, NotNull());
// Source offset is at the end already, destination offset is 0 of 50, which should not advance.
const int64_t dest_frame_count = 50;
std::vector<float> dest_samples(dest_frame_count);
int64_t dest_offset = 0;
const int64_t source_frame_count = 50;
std::vector<float> source_samples(source_frame_count);
Fixed source_offset =
Fixed(source_frame_count) - sampler->pos_filter_length() + Fixed::FromRaw(1);
const Fixed expected_source_offset = source_offset;
sampler->Process({source_samples.data(), &source_offset, source_frame_count},
{dest_samples.data(), &dest_offset, dest_frame_count},
{.type = GainType::kSilent, .scale = kMinGainScale},
/*accumulate=*/false);
EXPECT_EQ(dest_offset, 0);
EXPECT_EQ(source_offset, expected_source_offset) << ffl::String::DecRational << source_offset;
}
TEST(SincSamplerTest, ProcessWithZeroStepSizeModuloNoRollover) {
auto sampler = SincSampler::Create(CreateFormat(1, 44100, SampleType::kFloat32),
CreateFormat(1, 44100, SampleType::kFloat32));
ASSERT_THAT(sampler, NotNull());
auto& state = sampler->state();
state.ResetSourceStride(TimelineRate(Fixed(10000).raw_value() + 3333, 10000));
EXPECT_EQ(state.step_size(), kOneFrame);
EXPECT_EQ(state.step_size_modulo(), 3333ul);
EXPECT_EQ(state.step_size_denominator(), 10000ul);
const int64_t dest_frame_count = 3;
std::vector<float> dest_samples(dest_frame_count);
int64_t dest_offset = 0;
const int64_t source_frame_count = 50;
std::vector<float> source_samples(source_frame_count);
Fixed source_offset = Fixed(0);
sampler->Process({source_samples.data(), &source_offset, source_frame_count},
{dest_samples.data(), &dest_offset, dest_frame_count},
{.type = GainType::kSilent, .scale = kMinGainScale},
/*accumulate=*/false);
EXPECT_EQ(dest_offset, dest_frame_count);
EXPECT_EQ(source_offset, Fixed(3)) << ffl::String::DecRational << source_offset;
EXPECT_EQ(state.source_pos_modulo(), 9999u);
}
TEST(SincSamplerTest, ProcessWithZeroStepSizeModuloWithRollover) {
auto sampler = SincSampler::Create(CreateFormat(1, 44100, SampleType::kFloat32),
CreateFormat(1, 44100, SampleType::kFloat32));
ASSERT_THAT(sampler, NotNull());
auto& state = sampler->state();
state.ResetSourceStride(TimelineRate(Fixed(10000).raw_value() + 5000, 10000));
EXPECT_EQ(state.step_size(), kOneFrame);
EXPECT_EQ(state.step_size_modulo(), 1ul);
EXPECT_EQ(state.step_size_denominator(), 2ul);
const int64_t dest_frame_count = 3;
std::vector<float> dest_samples(dest_frame_count);
int64_t dest_offset = 1;
const int64_t source_frame_count = 50;
std::vector<float> source_samples(source_frame_count);
Fixed source_offset = kOneFrame - Fixed::FromRaw(1);
sampler->Process({source_samples.data(), &source_offset, source_frame_count},
{dest_samples.data(), &dest_offset, dest_frame_count},
{.type = GainType::kSilent, .scale = kMinGainScale},
/*accumulate=*/false);
EXPECT_EQ(dest_offset, dest_frame_count);
EXPECT_EQ(source_offset, Fixed(3)) << ffl::String::DecRational << source_offset;
EXPECT_EQ(state.source_pos_modulo(), 0u);
}
TEST(SincSamplerTest, ProcessWithNonZeroStepSizeModuloNoRollover) {
auto sampler = SincSampler::Create(CreateFormat(1, 44100, SampleType::kFloat32),
CreateFormat(1, 44100, SampleType::kFloat32));
ASSERT_THAT(sampler, NotNull());
auto& state = sampler->state();
state.ResetSourceStride(TimelineRate(Fixed(10000).raw_value() + 3331, 10000));
EXPECT_EQ(state.step_size(), kOneFrame);
EXPECT_EQ(state.step_size_modulo(), 3331ul);
EXPECT_EQ(state.step_size_denominator(), 10000ul);
state.set_source_pos_modulo(6);
const int64_t dest_frame_count = 3;
std::vector<float> dest_samples(dest_frame_count);
int64_t dest_offset = 0;
const int64_t source_frame_count = 50;
std::vector<float> source_samples(source_frame_count);
Fixed source_offset = Fixed(0);
sampler->Process({source_samples.data(), &source_offset, source_frame_count},
{dest_samples.data(), &dest_offset, dest_frame_count},
{.type = GainType::kSilent, .scale = kMinGainScale},
/*accumulate=*/false);
EXPECT_EQ(dest_offset, dest_frame_count);
EXPECT_EQ(source_offset, Fixed(3)) << ffl::String::DecRational << source_offset;
EXPECT_EQ(state.source_pos_modulo(), 9999u);
}
TEST(SincSamplerTest, ProcessWithNonZeroStepSizeModuloRollover) {
auto sampler = SincSampler::Create(CreateFormat(1, 44100, SampleType::kFloat32),
CreateFormat(1, 44100, SampleType::kFloat32));
ASSERT_THAT(sampler, NotNull());
auto& state = sampler->state();
state.ResetSourceStride(TimelineRate(Fixed(10000).raw_value() + 3331, 10000));
EXPECT_EQ(state.step_size(), kOneFrame);
EXPECT_EQ(state.step_size_modulo(), 3331ul);
EXPECT_EQ(state.step_size_denominator(), 10000ul);
state.set_source_pos_modulo(3338);
const int64_t dest_frame_count = 3;
std::vector<float> dest_samples(dest_frame_count);
int64_t dest_offset = 1;
const int64_t source_frame_count = 50;
std::vector<float> source_samples(source_frame_count);
Fixed source_offset = Fixed(1) - Fixed::FromRaw(1);
sampler->Process({source_samples.data(), &source_offset, source_frame_count},
{dest_samples.data(), &dest_offset, dest_frame_count},
{.type = GainType::kSilent, .scale = kMinGainScale},
/*accumulate=*/false);
EXPECT_EQ(dest_offset, dest_frame_count);
EXPECT_EQ(source_offset, Fixed(3)) << ffl::String::DecRational << source_offset;
EXPECT_EQ(state.source_pos_modulo(), 0u);
}
TEST(SincSamplerTest, ProcessWithSourcePostModuloExactRollover) {
auto sampler = SincSampler::Create(CreateFormat(1, 44100, SampleType::kFloat32),
CreateFormat(1, 44100, SampleType::kFloat32));
ASSERT_THAT(sampler, NotNull());
auto& state = sampler->state();
state.ResetSourceStride(TimelineRate(Fixed(3).raw_value() - 1, 3));
EXPECT_EQ(state.step_size(), kOneFrame - Fixed::FromRaw(1));
EXPECT_EQ(state.step_size_modulo(), 2ul);
EXPECT_EQ(state.step_size_denominator(), 3ul);
state.set_source_pos_modulo(2);
const int64_t dest_frame_count = 3;
std::vector<float> dest_samples(dest_frame_count);
int64_t dest_offset = 0;
const int64_t source_frame_count = 10;
std::vector<float> source_samples(source_frame_count);
Fixed source_offset =
Fixed(source_frame_count - 2) - sampler->pos_filter_length() + Fixed::FromRaw(1);
sampler->Process({source_samples.data(), &source_offset, source_frame_count},
{dest_samples.data(), &dest_offset, dest_frame_count},
{.type = GainType::kSilent, .scale = kMinGainScale},
/*accumulate=*/false);
EXPECT_EQ(dest_offset, 2);
EXPECT_EQ(source_offset,
Fixed(Fixed(source_frame_count) - sampler->pos_filter_length() + Fixed::FromRaw(1)))
<< ffl::String::DecRational << source_offset;
EXPECT_EQ(state.source_pos_modulo(), 0u);
}
class SincSamplerOutputTest : public testing::Test {
protected:
// Based on an arbitrary near-zero source position (-1/128), with a sinc curve for unity rate
// conversion, we use data values calculated so that if these first 13 values (the filter's
// negative wing) are ignored, we expect a generated output value of
// `kValueWithoutPreviousFrames`. If they are NOT ignored, then we expect the result
// `kValueWithPreviousFrames`.
static constexpr float kSource[] = {
1330.10897f, -1330.10897f, 1330.10897f, -1330.10897f, 1330.10897f, -1330.10897f,
1330.10897f, -1330.10897f, 1330.10897f, -1330.10897f, 1330.10897f, -1330.10897f,
1330.10897f, // ... source frames to satisfy negative filter length.
-10.001010f, // Center source frame
268.88298f, // Source frames to satisfy positive filter length ...
-268.88298f, 268.88298f, -268.88298f, 268.88298f, -268.88298f, 268.88298f,
-268.88298f, 268.88298f, -268.88298f, 268.88298f, -268.88298f, 268.88298f,
};
static constexpr Fixed kProcessOneFrameSourceOffset = ffl::FromRatio(1, 128);
// The center frame should contribute -10.0, the positive wing -5.0, and the negative wing +25.0.
static constexpr float kValueWithoutPreviousFrames = -15.0;
static constexpr float kValueWithPreviousFrames = 10.0;
// Processes a single frame of output based on `kSource[0]`.
static float ProcessOneFrame(Sampler& sampler, Fixed source_offset) {
auto neg_length = sampler.neg_filter_length().Floor();
auto pos_length = sampler.pos_filter_length().Floor();
EXPECT_NE(Fixed(pos_length).raw_value(), sampler.neg_filter_length().raw_value() - 1)
<< "This test assumes SincSampler is symmetric, and that negative width includes a "
"fraction";
float dest_sample = 0.0f;
int64_t dest_offset = 0;
sampler.Process(Sampler::Source{&kSource[neg_length - 1], &source_offset, pos_length},
Sampler::Dest{&dest_sample, &dest_offset, 1}, {}, false);
EXPECT_EQ(dest_offset, 1u) << "No output frame was produced";
FX_LOGS(INFO) << "Coefficients " << std::setprecision(12) << kSource[12] << " " << kSource[13]
<< " " << kSource[14] << ", value " << dest_sample;
return dest_sample;
}
// Tests sampler with a passthrough process of a given `source_samples` with a given
// `channel_count` and `source_sample_type`.
template <typename SourceSampleType>
static void TestPassthrough(uint32_t channel_count, SampleType source_sample_type,
const std::vector<SourceSampleType>& source_samples) {
// Create sampler.
auto sampler = SincSampler::Create(CreateFormat(channel_count, 48000, source_sample_type),
CreateFormat(channel_count, 48000, SampleType::kFloat32));
ASSERT_THAT(sampler, NotNull());
EXPECT_EQ(sampler->pos_filter_length().raw_value(), SincFilter::kFracSideLength);
EXPECT_EQ(sampler->neg_filter_length().raw_value(), SincFilter::kFracSideLength);
// Process samples with unity gain.
const int64_t dest_frame_count = static_cast<int64_t>(source_samples.size() / channel_count);
// Make sure to provide enough samples to compensate for the filter length.
const int64_t source_frame_count = dest_frame_count + sampler->pos_filter_length().Floor();
auto padded_source_samples = source_samples;
padded_source_samples.insert(padded_source_samples.end(),
channel_count * sampler->pos_filter_length().Floor(), 0.0f);
Fixed source_offset = Fixed();
std::vector<float> dest_samples(source_samples.size(), 0.0f);
int64_t dest_offset = 0;
sampler->Process({padded_source_samples.data(), &source_offset, source_frame_count},
{dest_samples.data(), &dest_offset, dest_frame_count},
{.type = GainType::kUnity, .scale = kUnityGainScale},
/*accumulate=*/false);
EXPECT_EQ(dest_offset, dest_frame_count);
EXPECT_EQ((source_offset), Fixed(dest_frame_count));
for (int i = 0; i < dest_frame_count; ++i) {
EXPECT_FLOAT_EQ(SampleConverter<SourceSampleType>::ToFloat(source_samples[i]),
dest_samples[i])
<< i;
}
}
// Tests sampler with a rechannelization process of a given `source_samples` against
// `expected_dest_samples` for a given `SourceChannelCount` and `DestChannelCount`.
template <uint32_t SourceChannelCount, uint32_t DestChannelCount>
static void TestRechannelization(const std::vector<float>& source_samples,
const std::vector<float>& expected_dest_samples) {
// Create sampler.
auto sampler =
SincSampler::Create(CreateFormat(SourceChannelCount, 48000, SampleType::kFloat32),
CreateFormat(DestChannelCount, 48000, SampleType::kFloat32));
EXPECT_EQ(sampler->pos_filter_length().raw_value(), SincFilter::kFracSideLength);
EXPECT_EQ(sampler->neg_filter_length().raw_value(), SincFilter::kFracSideLength);
// Process samples with unity gain.
const int64_t dest_frame_count =
static_cast<int64_t>(source_samples.size() / SourceChannelCount);
ASSERT_EQ(dest_frame_count * DestChannelCount,
static_cast<int64_t>(expected_dest_samples.size()));
// Make sure to provide enough samples to compensate for the filter length.
const int64_t source_frame_count = dest_frame_count + sampler->pos_filter_length().Floor();
auto padded_source_samples = source_samples;
padded_source_samples.insert(padded_source_samples.end(),
SourceChannelCount * sampler->pos_filter_length().Floor(), 0.0f);
Fixed source_offset = Fixed(0);
std::vector<float> dest_samples(expected_dest_samples.size(), 0.0f);
int64_t dest_offset = 0;
sampler->Process({padded_source_samples.data(), &source_offset, source_frame_count},
{dest_samples.data(), &dest_offset, dest_frame_count},
{.type = GainType::kUnity, .scale = kUnityGainScale},
/*accumulate=*/false);
EXPECT_EQ(dest_offset, dest_frame_count);
EXPECT_EQ((source_offset), Fixed(dest_frame_count));
EXPECT_THAT(dest_samples, Pointwise(FloatEq(), expected_dest_samples));
}
};
TEST_F(SincSamplerOutputTest, ProcessOneNoCache) {
auto sampler = SincSampler::Create(CreateFormat(1, 44100, SampleType::kFloat32),
CreateFormat(1, 44100, SampleType::kFloat32));
ASSERT_THAT(sampler, NotNull());
// Process a single frame. We use a slightly non-zero position because at true 0, only the sample
// (not the positive or negative wings) are used. In this case we not provided previous frames.
const float dest_sample = ProcessOneFrame(*sampler, -kProcessOneFrameSourceOffset);
// If we incorrectly shifted/retained even a single frame of the above data, this won't match.
EXPECT_FLOAT_EQ(dest_sample, kValueWithoutPreviousFrames) << std::setprecision(12) << dest_sample;
}
TEST_F(SincSamplerOutputTest, ProcessOneWithCache) {
auto sampler = SincSampler::Create(CreateFormat(1, 44100, SampleType::kFloat32),
CreateFormat(1, 44100, SampleType::kFloat32));
ASSERT_THAT(sampler, NotNull());
auto neg_length = sampler->neg_filter_length().Floor();
// Now, populate the cache with previous frames, instead of using default (silence) values.
// The output value of `source_offset` tells us the cache is populated with `neg_length - 1`
// frames, which is ideal for sampling a subsequent source buffer starting at source position 0.
float dest_sample = 0.0f;
int64_t dest_offset = 0;
const auto source_frame_count = neg_length - 1;
Fixed source_offset = Fixed(source_frame_count) - kProcessOneFrameSourceOffset;
sampler->Process(Sampler::Source{&kSource[0], &source_offset, source_frame_count},
Sampler::Dest{&dest_sample, &dest_offset, 1}, {}, false);
EXPECT_EQ(source_offset, Fixed(source_frame_count) - kProcessOneFrameSourceOffset);
EXPECT_EQ(dest_offset, 0u) << "Unexpectedly produced output " << dest_sample;
// Process a single frame. We use a slightly non-zero position because at true 0, only the sample
// itself (not positive or negative widths) are needed. In this case we provide previous frames.
dest_sample = ProcessOneFrame(*sampler, -kProcessOneFrameSourceOffset);
// If we incorrectly shifted/retained even a single frame of the above data, this won't match.
EXPECT_FLOAT_EQ(dest_sample, kValueWithPreviousFrames) << std::setprecision(12) << dest_sample;
}
TEST_F(SincSamplerOutputTest, ProcessFrameByFrameCached) {
auto sampler = SincSampler::Create(CreateFormat(1, 44100, SampleType::kFloat32),
CreateFormat(1, 44100, SampleType::kFloat32));
ASSERT_THAT(sampler, NotNull());
auto neg_length = sampler->neg_filter_length().Floor();
// Now, populate the cache with previous data, one frame at a time.
float dest_sample = 0.0f;
int64_t dest_offset = 0;
const auto source_frame_count = 1;
Fixed source_offset = Fixed(source_frame_count) - kProcessOneFrameSourceOffset;
for (auto neg_idx = 0; neg_idx < neg_length - 1; ++neg_idx) {
sampler->Process(Sampler::Source{&kSource[neg_idx], &source_offset, source_frame_count},
Sampler::Dest{&dest_sample, &dest_offset, 1}, {}, false);
EXPECT_EQ(source_offset, Fixed(source_frame_count) - kProcessOneFrameSourceOffset);
EXPECT_EQ(dest_offset, 0u) << "Unexpectedly produced output " << dest_sample;
}
// Process a single frame. We use a slightly non-zero position because at true 0, only the sample
// itself (not positive or negative widths) are needed. In this case we provide previous frames.
dest_sample = ProcessOneFrame(*sampler, -kProcessOneFrameSourceOffset);
// If we incorrectly shifted/retained even a single frame of the above data, this won't match.
EXPECT_FLOAT_EQ(dest_sample, kValueWithPreviousFrames) << std::setprecision(12) << dest_sample;
}
TEST_F(SincSamplerOutputTest, ProcessPassthroughUint8) {
const std::vector<uint8_t> source_samples = {0x00, 0xFF, 0x27, 0xCD, 0x7F, 0x80, 0xA6, 0x6D};
// Test mono.
TestPassthrough<uint8_t>(/*channel_count=*/1, SampleType::kUint8, source_samples);
// Test stereo.
TestPassthrough<uint8_t>(/*channel_count=*/2, SampleType::kUint8, source_samples);
// Test 4 channels.
TestPassthrough<uint8_t>(/*channel_count=*/4, SampleType::kUint8, source_samples);
}
TEST_F(SincSamplerOutputTest, ProcessPassthroughInt16) {
const std::vector<int16_t> source_samples = {-0x8000, 0x7FFF, -0x67A7, 0x4D4D,
-0x123, 0, 0x2600, -0x2DCB};
// Test mono.
TestPassthrough<int16_t>(/*channel_count=*/1, SampleType::kInt16, source_samples);
// Test stereo.
TestPassthrough<int16_t>(/*channel_count=*/2, SampleType::kInt16, source_samples);
// Test 4 channels.
TestPassthrough<int16_t>(/*channel_count=*/4, SampleType::kInt16, source_samples);
}
TEST_F(SincSamplerOutputTest, ProcessPassthroughInt24In32) {
const std::vector<int32_t> source_samples = {kMinInt24In32, kMaxInt24In32, -0x67A7E700,
0x4D4D4D00, -0x1234500, 0,
0x26006200, -0x2DCBA900};
// Test mono.
TestPassthrough<int32_t>(/*channel_count=*/1, SampleType::kInt32, source_samples);
// Test stereo.
TestPassthrough<int32_t>(/*channel_count=*/2, SampleType::kInt32, source_samples);
// Test 4 channels.
TestPassthrough<int32_t>(/*channel_count=*/4, SampleType::kInt32, source_samples);
}
TEST_F(SincSamplerOutputTest, ProcessPassthroughFloat) {
const std::vector<float> source_samples = {
-1.0, 1.0f, -0.809783935f, 0.603912353f, -0.00888061523f, 0.0f, 0.296875f, -0.357757568f};
// Test mono.
TestPassthrough<float>(/*channel_count=*/1, SampleType::kFloat32, source_samples);
// Test stereo.
TestPassthrough<float>(/*channel_count=*/2, SampleType::kFloat32, source_samples);
// Test 4 channels.
TestPassthrough<float>(/*channel_count=*/4, SampleType::kFloat32, source_samples);
}
TEST_F(SincSamplerOutputTest, ProcessRechannelizationMono) {
const std::vector<float> source_samples = {-1.0f, 1.0f, 0.3f};
// Test mono to stereo.
TestRechannelization<1, 2>(source_samples, {-1.0f, -1.0f, 1.0f, 1.0f, 0.3f, 0.3f});
// Test mono to 3 channels.
TestRechannelization<1, 3>(source_samples,
{-1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 0.3f, 0.3f, 0.3f});
// Test mono to quad.
TestRechannelization<1, 4>(
source_samples, {-1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.3f, 0.3f, 0.3f, 0.3f});
}
TEST_F(SincSamplerOutputTest, ProcessRechannelizationStereo) {
const std::vector<float> source_samples = {-1.0f, 1.0f, 0.3f, 0.1f};
// Test stereo to mono.
TestRechannelization<2, 1>(source_samples, {0.0f, 0.2f});
// Test stereo to 3 channels.
TestRechannelization<2, 3>(source_samples, {-1.0f, 1.0f, 0.0f, 0.3f, 0.1f, 0.2f});
// Test stereo to quad.
TestRechannelization<2, 4>(source_samples, {-1.0f, 1.0f, -1.0f, 1.0f, 0.3f, 0.1f, 0.3f, 0.1f});
}
TEST_F(SincSamplerOutputTest, ProcessRechannelizationQuad) {
const std::vector<float> source_samples = {-1.0f, 0.8f, 1.0f, -0.8f, 0.1f, 0.3f, -0.3f, -0.9f};
// Test quad to mono.
if constexpr (kEnable4ChannelWorkaround) {
TestRechannelization<4, 1>(source_samples, {-0.1f, 0.2f});
} else {
TestRechannelization<4, 1>(source_samples, {0.0f, -0.2f});
}
// Test quad to stereo.
if constexpr (kEnable4ChannelWorkaround) {
TestRechannelization<4, 2>(source_samples, {-1.0f, 0.8f, 0.1f, 0.3f});
} else {
TestRechannelization<4, 2>(source_samples, {0.0f, 0.0f, -0.1f, -0.3f});
}
}
} // namespace
} // namespace media_audio