blob: 94ecbf16d04c74624cfbf85a881ed72f840ca80a [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 <cstdint>
#include <utility>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "ffl/fixed.h"
#include "fidl/fuchsia.mediastreams/cpp/wire_types.h"
#include "lib/syslog/cpp/macros.h"
#include "src/media/audio/lib/format2/fixed.h"
#include "src/media/audio/lib/processing/gain.h"
namespace media_audio {
namespace {
using ::fuchsia_mediastreams::wire::AudioSampleFormat;
using ::testing::Each;
using ::testing::NotNull;
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}, {4, 1}, {4, 2}, {4, 4},
};
constexpr uint32_t kFrameRates[] = {
8000, 11025, 16000, 22050, 24000, 32000, 44100, 48000, 88200, 96000, 176400, 192000,
};
constexpr AudioSampleFormat kSampleFormats[] = {
AudioSampleFormat::kUnsigned8,
AudioSampleFormat::kSigned16,
AudioSampleFormat::kSigned24In32,
AudioSampleFormat::kFloat,
};
Format CreateFormat(uint32_t channel_count, uint32_t frame_rate, AudioSampleFormat sample_format) {
return Format::CreateOrDie({sample_format, 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_format : kSampleFormats) {
EXPECT_THAT(
SincSampler::Create(
CreateFormat(source_channel_count, source_frame_rate, sample_format),
CreateFormat(dest_channel_count, dest_frame_rate, AudioSampleFormat::kFloat)),
NotNull());
}
}
}
}
}
TEST(SincSamplerTest, ProcessSilentGain) {
auto sampler = SincSampler::Create(CreateFormat(1, 48000, AudioSampleFormat::kFloat),
CreateFormat(1, 48000, AudioSampleFormat::kFloat));
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;
const Sampler::Gain gain = {.type = GainType::kSilent, .scale = kMinGainScale};
// Process with silent gain with accumulation, `dest_samples` should remain as-is.
sampler->Process({source_samples.data(), &source_offset, source_frame_count},
{dest_samples.data(), &dest_offset, dest_frame_count}, gain,
/*accumulate=*/true);
EXPECT_THAT(dest_samples, Each(1.0f));
// Reset offsets and process with silent gain again, but this time with no accumulation, which
// should fill `dest_samples` with all zeros now.
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}, gain,
/*accumulate=*/false);
EXPECT_THAT(dest_samples, Each(0.0f));
}
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;
}
};
TEST_F(SincSamplerOutputTest, ProcessOneNoCache) {
auto sampler = SincSampler::Create(CreateFormat(1, 44100, AudioSampleFormat::kFloat),
CreateFormat(1, 44100, AudioSampleFormat::kFloat));
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, AudioSampleFormat::kFloat),
CreateFormat(1, 44100, AudioSampleFormat::kFloat));
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, AudioSampleFormat::kFloat),
CreateFormat(1, 44100, AudioSampleFormat::kFloat));
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;
}
// TODO(fxbug.dev/87651): Move the rest of the `media::audio::mixer::SincSampler` unit tests once
// `media::audio::mixer::Mixer` code is fully migrated.
} // namespace
} // namespace media_audio