blob: 30a7579a63b7b0322f5dbc5276ce5893019cfa9c [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 <lib/trace/event.h>
#include <algorithm>
#include <cstdint>
#include <memory>
#include <ffl/string.h>
#include "fidl/fuchsia.mediastreams/cpp/wire_types.h"
#include "lib/syslog/cpp/macros.h"
#include "src/media/audio/lib/format2/channel_mapper.h"
#include "src/media/audio/lib/format2/fixed.h"
#include "src/media/audio/lib/format2/format.h"
#include "src/media/audio/lib/processing/channel_strip.h"
#include "src/media/audio/lib/processing/filter.h"
#include "src/media/audio/lib/processing/gain.h"
#include "src/media/audio/lib/processing/position_manager.h"
namespace media_audio {
namespace {
template <typename SourceSampleType, size_t SourceChannelCount, size_t DestChannelCount>
class SincSamplerImpl : public SincSampler {
public:
SincSamplerImpl(int32_t source_frame_rate, int32_t dest_frame_rate)
: SincSampler(SincFilter::Length(source_frame_rate, dest_frame_rate),
// Sinc filters are symmetric.
SincFilter::Length(source_frame_rate, dest_frame_rate)),
source_frame_rate_(source_frame_rate),
dest_frame_rate_(dest_frame_rate),
position_(SourceChannelCount, DestChannelCount, pos_filter_length().raw_value(),
neg_filter_length().raw_value()),
working_data_(DestChannelCount, kDataCacheLength),
// `SincFilter` holds one side of coefficients (positive), we invert the position to
// calculate the other side (negative).
filter_(source_frame_rate_, dest_frame_rate_, pos_filter_length().raw_value()) {
FX_CHECK(pos_filter_length() == neg_filter_length())
<< "SincSampler assumes a symmetric filter, pos_filter_length (" << ffl::String::DecRational
<< pos_filter_length() << ") != neg_filter_length (" << neg_filter_length() << ")";
const int64_t cache_length_needed =
Sampler::Floor(neg_filter_length().raw_value() + pos_filter_length().raw_value() - 1);
FX_CHECK(kDataCacheLength >= cache_length_needed)
<< "Data cache (len " << kDataCacheLength << ") must be at least " << cache_length_needed
<< " long to support SRC ratio " << source_frame_rate << "/" << dest_frame_rate;
}
void EagerlyPrepare() final { filter_.EagerlyPrepare(); }
void Process(Source source, Dest dest, Gain gain, bool accumulate) final {
TRACE_DURATION("audio", "SincSamplerImpl::Process", "source_rate", source_frame_rate_,
"dest_rate", dest_frame_rate_, "source_chans", SourceChannelCount, "dest_chans",
DestChannelCount);
position_.SetSourceValues(source.samples, source.frame_count, source.frame_offset_ptr);
position_.SetDestValues(dest.samples, dest.frame_count, dest.frame_offset_ptr);
switch (gain.type) {
case GainType::kSilent:
if (accumulate) {
ProcessWith<GainType::kSilent, true>(source, dest, gain);
} else {
ProcessWith<GainType::kSilent, false>(source, dest, gain);
}
break;
case GainType::kNonUnity:
if (accumulate) {
ProcessWith<GainType::kNonUnity, true>(source, dest, gain);
} else {
ProcessWith<GainType::kNonUnity, false>(source, dest, gain);
}
break;
case GainType::kUnity:
if (accumulate) {
ProcessWith<GainType::kUnity, true>(source, dest, gain);
} else {
ProcessWith<GainType::kUnity, false>(source, dest, gain);
}
break;
case GainType::kRamping:
if (accumulate) {
ProcessWith<GainType::kRamping, true>(source, dest, gain);
} else {
ProcessWith<GainType::kRamping, false>(source, dest, gain);
}
break;
default:
break;
}
}
void SetRateValues(int64_t step_size, uint64_t rate_modulo, uint64_t denominator,
uint64_t* source_pos_mod) final {
position_.SetRateValues(step_size, rate_modulo, denominator, source_pos_mod);
}
private:
static void PopulateFramesToChannelStrip(const void* source_void_ptr,
int64_t next_source_idx_to_copy,
const int64_t frames_needed, ChannelStrip* channel_strip,
int64_t next_cache_idx_to_fill) {
if constexpr (kTracePositionEvents) {
TRACE_DURATION("audio", __func__, "next_source_idx_to_copy", next_source_idx_to_copy,
"frames_needed", frames_needed, "next_cache_idx_to_fill",
next_cache_idx_to_fill);
}
const SourceSampleType* source_ptr = static_cast<const SourceSampleType*>(source_void_ptr);
for (int64_t source_idx = next_source_idx_to_copy;
source_idx < next_source_idx_to_copy + frames_needed;
++source_idx, ++next_cache_idx_to_fill) {
const SourceSampleType* source_frame = &source_ptr[source_idx * SourceChannelCount];
for (size_t dest_chan = 0; dest_chan < DestChannelCount; ++dest_chan) {
(*channel_strip)[dest_chan][next_cache_idx_to_fill] = mapper_.Map(source_frame, dest_chan);
}
}
}
// Processes `source` into `dest` with gain `Type`.
template <GainType Type, bool Accumulate>
void ProcessWith(const Source& source, const Dest& dest, const Gain& gain) {
int64_t frac_source_offset = source.frame_offset_ptr->raw_value();
const auto frac_filter_width = pos_filter_length().raw_value() - 1;
int64_t next_cache_idx_to_fill = 0;
auto next_source_idx_to_copy = Sampler::Ceiling(frac_source_offset - frac_filter_width);
// Do we need previously-cached values?
if (next_source_idx_to_copy < 0) {
next_cache_idx_to_fill = -next_source_idx_to_copy;
next_source_idx_to_copy = 0;
}
// If we don't have enough source or dest to mix even one frame, get out. Before leaving, if
// we've reached the end of the source buffer, then cache the last few source frames for the
// next mix.
if (!position_.CanFrameBeMixed()) {
if (position_.IsSourceConsumed()) {
const auto frames_needed = source.frame_count - next_source_idx_to_copy;
if (frac_source_offset > 0) {
working_data_.ShiftBy(Sampler::Ceiling(frac_source_offset));
}
// Calculate and store the last few source frames to start of channel_strip, for next time.
// If muted, this is unnecessary because we've already shifted in zeroes (silence).
if constexpr (Type != GainType::kSilent) {
PopulateFramesToChannelStrip(source.samples, next_source_idx_to_copy, frames_needed,
&working_data_, next_cache_idx_to_fill);
}
}
return;
}
if constexpr (Type != media_audio::GainType::kSilent) {
auto frac_source_offset_to_cache =
Sampler::Ceiling(frac_source_offset - frac_filter_width) * kFracOneFrame;
auto frames_needed = std::min(source.frame_count - next_source_idx_to_copy,
kDataCacheLength - next_cache_idx_to_fill);
// Bring in as much as a channel strip of source data (while channel/format-converting).
PopulateFramesToChannelStrip(source.samples, next_source_idx_to_copy, frames_needed,
&working_data_, next_cache_idx_to_fill);
float scale = gain.scale;
int64_t dest_ramp_start = position_.dest_offset(); // only used when ramping
while (position_.CanFrameBeMixed()) {
next_source_idx_to_copy += frames_needed;
int64_t frac_cache_offset = frac_source_offset - frac_source_offset_to_cache;
int64_t frac_interp_fraction = frac_cache_offset & Fixed::Format::FractionalMask;
auto cache_center_idx = Sampler::Floor(frac_cache_offset);
FX_CHECK(Sampler::Ceiling(frac_cache_offset - frac_filter_width) >= 0)
<< Sampler::Ceiling(frac_cache_offset - frac_filter_width) << " should be >= 0";
if constexpr (kTracePositionEvents) {
TRACE_DURATION("audio", "SincSampler::Process chunk", "next_source_idx_to_copy",
next_source_idx_to_copy, "cache_center_idx", cache_center_idx);
}
while (position_.CanFrameBeMixed() &&
frac_cache_offset + frac_filter_width < kFracDataCacheLength) {
float* dest_frame = position_.CurrentDestFrame();
if constexpr (Type == media_audio::GainType::kRamping) {
scale = gain.scale_ramp[position_.dest_offset() - dest_ramp_start];
}
for (size_t dest_chan = 0; dest_chan < DestChannelCount; ++dest_chan) {
const float sample = filter_.ComputeSample(
frac_interp_fraction, &(working_data_[dest_chan][cache_center_idx]));
MixSample<Type, Accumulate>(sample, &dest_frame[dest_chan], scale);
}
frac_source_offset = position_.AdvanceFrame();
frac_cache_offset = frac_source_offset - frac_source_offset_to_cache;
frac_interp_fraction = frac_cache_offset & Fixed::Format::FractionalMask;
cache_center_idx = Sampler::Floor(frac_cache_offset);
}
// idx of the earliest cached frame we must retain == the amount by which we can left-shift.
const auto num_frames_to_shift = Sampler::Ceiling(frac_cache_offset - frac_filter_width);
working_data_.ShiftBy(num_frames_to_shift);
cache_center_idx -= num_frames_to_shift;
next_cache_idx_to_fill = kDataCacheLength - num_frames_to_shift;
frac_source_offset_to_cache =
Sampler::Ceiling(frac_source_offset - frac_filter_width) * kFracOneFrame;
frames_needed = std::min(source.frame_count - next_source_idx_to_copy,
kDataCacheLength - next_cache_idx_to_fill);
PopulateFramesToChannelStrip(source.samples, next_source_idx_to_copy, frames_needed,
&working_data_, next_cache_idx_to_fill);
}
} else {
if constexpr (!Accumulate) {
// Zero fill destination frames.
const int64_t dest_frame_offset = *dest.frame_offset_ptr;
std::fill_n(&dest.samples[dest_frame_offset * DestChannelCount],
(dest.frame_count - dest_frame_offset) * DestChannelCount, 0.0f);
}
auto num_source_frames_skipped = position_.AdvanceToEnd();
working_data_.ShiftBy(num_source_frames_skipped);
}
position_.UpdateOffsets();
}
// Our `ChannelStrip` must fit even the widest filter.
static constexpr int64_t kDataCacheLength = Sampler::Floor(
SincFilter::kMaxFracSideLength + kFracOneFrame + SincFilter::kMaxFracSideLength);
static constexpr int64_t kFracDataCacheLength = kDataCacheLength << Fixed::Format::FractionalBits;
static inline ChannelMapper<SourceSampleType, SourceChannelCount, DestChannelCount> mapper_;
const int32_t source_frame_rate_;
const int32_t dest_frame_rate_;
PositionManager position_;
ChannelStrip working_data_;
SincFilter filter_;
};
// Helper functions to expand the combinations of possible `SincSamplerImpl` configurations.
template <typename SourceSampleType, size_t SourceChannelCount, size_t DestChannelCount>
std::shared_ptr<Sampler> CreateWith(const Format& source_format, const Format& dest_format) {
return std::make_shared<SincSamplerImpl<SourceSampleType, SourceChannelCount, DestChannelCount>>(
static_cast<int32_t>(source_format.frames_per_second()),
static_cast<int32_t>(dest_format.frames_per_second()));
}
template <typename SourceSampleType, size_t SourceChannelCount>
std::shared_ptr<Sampler> CreateWith(const Format& source_format, const Format& dest_format) {
switch (dest_format.channels()) {
case 1:
return CreateWith<SourceSampleType, SourceChannelCount, 1>(source_format, dest_format);
case 2:
return CreateWith<SourceSampleType, SourceChannelCount, 2>(source_format, dest_format);
case 3:
if constexpr (SourceChannelCount <= 3) {
return CreateWith<SourceSampleType, SourceChannelCount, 3>(source_format, dest_format);
}
break;
case 4:
if constexpr (SourceChannelCount != 3) {
return CreateWith<SourceSampleType, SourceChannelCount, 4>(source_format, dest_format);
}
break;
default:
break;
}
FX_LOGS(WARNING) << "SincSampler does not support this channelization: " << SourceChannelCount
<< " -> " << dest_format.channels();
return nullptr;
}
template <typename SourceSampleType>
std::shared_ptr<Sampler> CreateWith(const Format& source_format, const Format& dest_format) {
switch (source_format.channels()) {
case 1:
return CreateWith<SourceSampleType, 1>(source_format, dest_format);
case 2:
return CreateWith<SourceSampleType, 2>(source_format, dest_format);
case 3:
return CreateWith<SourceSampleType, 3>(source_format, dest_format);
case 4:
return CreateWith<SourceSampleType, 4>(source_format, dest_format);
default:
FX_LOGS(WARNING) << "SincSampler does not support this channelization: "
<< source_format.channels() << " -> " << dest_format.channels();
return nullptr;
}
}
} // namespace
std::shared_ptr<Sampler> SincSampler::Create(const Format& source_format,
const Format& dest_format) {
TRACE_DURATION("audio", "SincSampler::Create");
if (dest_format.sample_format() != fuchsia_mediastreams::wire::AudioSampleFormat::kFloat) {
FX_LOGS(WARNING) << "SincSampler does not support this dest sample format: "
<< static_cast<uint32_t>(dest_format.sample_format());
return nullptr;
}
switch (source_format.sample_format()) {
case fuchsia_mediastreams::wire::AudioSampleFormat::kUnsigned8:
return CreateWith<uint8_t>(source_format, dest_format);
case fuchsia_mediastreams::wire::AudioSampleFormat::kSigned16:
return CreateWith<int16_t>(source_format, dest_format);
case fuchsia_mediastreams::wire::AudioSampleFormat::kSigned24In32:
return CreateWith<int32_t>(source_format, dest_format);
case fuchsia_mediastreams::wire::AudioSampleFormat::kFloat:
return CreateWith<float>(source_format, dest_format);
default:
FX_LOGS(WARNING) << "SincSampler does not support this source sample format: "
<< static_cast<uint32_t>(source_format.sample_format());
return nullptr;
}
}
} // namespace media_audio