blob: bf3a53a700d9b375b35b9f1066c0fa9f55051f0d [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/filter.h"
#include <lib/syslog/cpp/macros.h>
#include <lib/trace/event.h>
#include <lib/zx/clock.h>
#include <iomanip>
#include <ios>
#include <limits>
#include <memory>
#include <vector>
#include "src/media/audio/lib/processing/coefficient_table.h"
namespace media_audio {
namespace {
// Debug computation of output values (`ComputeSample`), from coefficients and input values.
// Extremely verbose, only useful in a controlled unittest setting.
constexpr bool kTraceFilterComputation = false;
} // namespace
void Filter::DisplayTable(const CoefficientTable& filter_coefficients) {
FX_LOGS(INFO) << "Filter: source rate " << source_rate_ << ", dest rate " << dest_rate_
<< ", length 0x" << std::hex << side_length_;
FX_LOGS(INFO) << " **************************************************************";
FX_LOGS(INFO) << " *** Displaying filter coefficient data for length " << side_length_ << " ***";
FX_LOGS(INFO) << " **************************************************************";
char str[256];
str[0] = 0;
int n;
for (int64_t idx = 0; idx < side_length_; ++idx) {
if (idx % 16 == 0) {
FX_LOGS(INFO) << str;
n = sprintf(str, " [%5lx] ", idx);
}
if (filter_coefficients[idx] < std::numeric_limits<float>::epsilon() &&
filter_coefficients[idx] > -std::numeric_limits<float>::epsilon() &&
filter_coefficients[idx] != 0.0f) {
n += sprintf(str + n, "!%10.7f!", filter_coefficients[idx]);
} else {
n += sprintf(str + n, " %10.7f ", filter_coefficients[idx]);
}
}
FX_LOGS(INFO) << str;
FX_LOGS(INFO) << " **************************************************************";
}
// For `frac_offset` in [0.0, 1.0) we require source frames on each side depending on filter length.
// Source frames are at integral positions, but we treat `frac_offset` as filter center, so source
// frames appear to be fractionally positioned.
//
// Filter coefficients cover the entire discrete space of fractional positions, but any calculation
// references only a subset of these, using a one-frame stride (`frac_size_`). Coefficient tables
// internally store values with an integer stride contiguously, which is what these loops want.
// Ex:
// coefficient_ptr[1] == filter_coefficients[frac_offset + frac_size_];
//
// We first calculate the contribution of the negative side of the filter, and then the contribution
// of the positive side. To avoid double-counting it, we include center subframe 0 only in the
// negative-side calculation.
float Filter::ComputeSampleFromTable(const CoefficientTable& filter_coefficients,
int64_t frac_offset, float* center) {
FX_DCHECK(frac_offset <= frac_size_) << frac_offset;
if constexpr (kTraceFilterComputation) {
FX_LOGS(INFO) << "For frac_offset 0x" << std::hex << frac_offset << " ("
<< (static_cast<double>(frac_offset) / static_cast<double>(frac_size_)) << "):";
}
// We use some raw pointers here to make loops vectorizable.
float* sample_ptr;
const float* coefficient_ptr;
float result = 0.0f;
// Negative side examples --
// side_length 1.601, frac_offset 0.600 requires source range (-1.001, 0.600]: frames -1 and 0.
// side_length 1.601, frac_offset 0.601 requires source range (-1.000, 0.601]: frame 0.
int64_t source_frames = (side_length_ - 1 + frac_size_ - frac_offset) >> num_frac_bits_;
if (source_frames > 0) {
sample_ptr = center;
coefficient_ptr = filter_coefficients.ReadSlice(frac_offset, source_frames);
FX_CHECK(coefficient_ptr != nullptr);
for (int64_t source_idx = 0; source_idx < source_frames; ++source_idx) {
auto contribution = (*sample_ptr) * coefficient_ptr[source_idx];
if constexpr (kTraceFilterComputation) {
FX_LOGS(INFO) << "Adding source[" << -static_cast<ssize_t>(source_idx) << "] "
<< (*sample_ptr) << " x " << coefficient_ptr[source_idx] << " = "
<< contribution;
}
result += contribution;
--sample_ptr;
}
}
// Positive side examples --
// side_length 1.601, frac_offset 0.400 requires source range (0.400, 2.001): frames 1 and 2.
// side_length 1.601, frac_offset 0.399 requires source range (0.399, 2.000): frame 1.
//
// Reduction of: side_length_ + (frac_size_-1) - (frac_size_-frac_offset)
source_frames = (side_length_ - 1 + frac_offset) >> num_frac_bits_;
if (source_frames > 0) {
sample_ptr = center + 1;
coefficient_ptr = filter_coefficients.ReadSlice(frac_size_ - frac_offset, source_frames);
FX_CHECK(coefficient_ptr != nullptr);
for (int64_t source_idx = 0; source_idx < source_frames; ++source_idx) {
auto contribution = sample_ptr[source_idx] * coefficient_ptr[source_idx];
if constexpr (kTraceFilterComputation) {
FX_LOGS(INFO) << "Adding source[" << 1 + source_idx << "] " << std::setprecision(13)
<< sample_ptr[source_idx] << " x " << coefficient_ptr[source_idx] << " = "
<< contribution;
}
result += contribution;
}
}
if constexpr (kTraceFilterComputation) {
FX_LOGS(INFO) << "... to get " << std::setprecision(13) << result;
}
return result;
}
SincFilter::CacheT* CreateSincFilterCoefficientTableCache() {
auto cache = new SincFilter::CacheT([](SincFilterCoefficientTable::Inputs inputs) {
TRACE_DURATION("audio", "CreateSincFilterTable");
auto start_time = zx::clock::get_monotonic();
auto t = SincFilterCoefficientTable::Create(inputs);
auto end_time = zx::clock::get_monotonic();
FX_LOGS(INFO) << "CreateSincFilterTable took " << (end_time - start_time).to_nsecs()
<< " ns with Inputs { side_length=" << inputs.side_length
<< ", num_frac_bits=" << inputs.num_frac_bits
<< ", rate_conversion_ratio=" << inputs.rate_conversion_ratio << " }";
return t.release();
});
// To avoid lengthy construction time, cache some coefficient tables persistently.
// See fxbug.dev/45074 and fxbug.dev/57666.
SincFilter::persistent_cache_ = new std::vector<SincFilter::CacheT::SharedPtr>;
// First load any coefficient tables that were built into this executable.
for (auto& t : kPrebuiltSincFilterCoefficientTables) {
auto inputs = SincFilterCoefficientTable::MakeInputs(t.source_rate, t.dest_rate);
SincFilter::persistent_cache_->push_back(cache->Add(
inputs, new CoefficientTable(inputs.side_length, inputs.num_frac_bits, t.table)));
}
// Now make sure we have all the coefficient tables we need.
// In practice, this should be a superset of the prebuilt tables.
// TODO(fxbug.dev/86662): Move these to a shared header, to eliminate duplication with
// gen_coefficient_tables.cc. Any "superset" items here that are NOT in gen_coefficient_tables
// would be in a separate list also in that shared header.
SincFilter::persistent_cache_->push_back(
cache->Get(SincFilterCoefficientTable::MakeInputs(48000, 48000)));
SincFilter::persistent_cache_->push_back(
cache->Get(SincFilterCoefficientTable::MakeInputs(96000, 48000)));
SincFilter::persistent_cache_->push_back(
cache->Get(SincFilterCoefficientTable::MakeInputs(48000, 96000)));
SincFilter::persistent_cache_->push_back(
cache->Get(SincFilterCoefficientTable::MakeInputs(96000, 16000)));
SincFilter::persistent_cache_->push_back(
cache->Get(SincFilterCoefficientTable::MakeInputs(48000, 16000)));
SincFilter::persistent_cache_->push_back(
cache->Get(SincFilterCoefficientTable::MakeInputs(44100, 48000)));
SincFilter::persistent_cache_->push_back(
cache->Get(SincFilterCoefficientTable::MakeInputs(16000, 48000)));
return cache;
}
LinearFilter::CacheT* const LinearFilter::cache_ =
new LinearFilter::CacheT([](LinearFilterCoefficientTable::Inputs inputs) {
TRACE_DURATION("audio", "CreateLinearFilterTable");
return LinearFilterCoefficientTable::Create(inputs).release();
});
// Must initialize `persistent_cache_` first as it's used by the `Create` function.
std::vector<SincFilter::CacheT::SharedPtr>* SincFilter::persistent_cache_ = nullptr;
SincFilter::CacheT* const SincFilter::cache_ = CreateSincFilterCoefficientTableCache();
} // namespace media_audio