blob: 4afbddd144688286769333c99ea4a387f7180b72 [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/sampler.h"
#include <lib/syslog/cpp/macros.h>
#include <lib/trace/event.h>
#include <lib/zx/time.h>
#include <cstdint>
#include <limits>
#include "src/media/audio/lib/format2/fixed.h"
#include "src/media/audio/lib/format2/format.h"
#include "src/media/audio/lib/processing/flags.h"
#include "src/media/audio/lib/processing/point_sampler.h"
#include "src/media/audio/lib/processing/sinc_sampler.h"
#include "src/media/audio/lib/timeline/timeline_function.h"
namespace media_audio {
void Sampler::State::AdvanceAllPositionsTo(int64_t dest_target_frame) {
AdvancePositionsBy(dest_target_frame - next_dest_frame_, /*advance_source_pos_modulo=*/true);
}
void Sampler::State::AdvanceAllPositionsBy(int64_t dest_frames) {
AdvancePositionsBy(dest_frames, /*advance_source_pos_modulo=*/true);
}
void Sampler::State::UpdateRunningPositionsBy(int64_t dest_frames) {
AdvancePositionsBy(dest_frames, /*advance_source_pos_modulo=*/false);
}
void Sampler::State::ResetSourceStride(
const media::TimelineRate& source_frac_frame_per_dest_frame) {
if constexpr (kTracePositionEvents) {
TRACE_DURATION("audio", __func__, "step_size_modulo", step_size_modulo_,
"step_size_denominator", step_size_denominator_);
}
step_size_ = Fixed::FromRaw(source_frac_frame_per_dest_frame.Scale(1));
// Now that we have a new step size, calculate the new step size modulo and denominator values to
// account for the limitations of `step_size_`.
step_size_modulo_ = source_frac_frame_per_dest_frame.subject_delta() -
(source_frac_frame_per_dest_frame.reference_delta() * step_size_.raw_value());
const uint64_t new_step_size_denominator = source_frac_frame_per_dest_frame.reference_delta();
FX_DCHECK(new_step_size_denominator > 0)
<< "new_step_size_denominator: " << new_step_size_denominator;
FX_DCHECK(step_size_modulo_ < new_step_size_denominator)
<< "step_size_modulo: " << step_size_modulo_
<< ", new_step_size_denominator: " << new_step_size_denominator;
// Only rescale `source_pos_modulo_` if `step_size_denominator_` changes, unless the new rate is
// zero (even if they requested a different denominator). That way we largely retain our running
// sub-frame fraction, across step size modulo and denominator changes.
if (new_step_size_denominator != step_size_denominator_ && step_size_modulo_ > 0) {
// Ensure that `new_source_pos_modulo / new_step_size_denominator == source_pos_modulo_ /
// step_size_denominator_`, which means `new_source_pos_modulo = source_pos_modulo_ *
// new_step_size_denominator / step_size_denominator_`. For higher precision, round the result
// by adding "1/2":
//
// ```
// new_source_pos_modulo =
// floor((source_pos_modulo_ * new_step_size_denominator / step_size_denominator_) + 1/2)
// ```
//
// Avoid float math and floor, and let int-division do the truncation for us:
//
// ```
// new_source_pos_modulo =
// (source_pos_modulo_ * new_step_size_denominator + step_size_denominator_ / 2) /
// step_size_denominator_
// ```
//
// The max `source_pos_modulo_` is `UINT64_MAX - 1`. New and old denominators should never be
// equal; but even if both are `UINT64_MAX`, the maximum `source_pos_modulo_ *
// new_step_size_denominator` product is `< UINT128_MAX - UINT64_MAX`. Even after adding
// `UINT64_MAX / 2 (for rounding), `new_source_pos_modulo` cannot overflow its `uint128_t`.
//
// Since `source_pos_modulo_ < step_size_denominator_`, our conceptual "+1/2" for rounding could
// only make `new_source_pos_modulo` eequal to `step_size_denominator_`, but never exceed it. So
// our new `source_pos_modulo_` cannot overflow its `uint64_t`.
__uint128_t new_source_pos_modulo =
static_cast<__uint128_t>(source_pos_modulo_) * new_step_size_denominator;
new_source_pos_modulo += static_cast<__uint128_t>(step_size_denominator_ / 2);
new_source_pos_modulo /= static_cast<__uint128_t>(step_size_denominator_);
if (static_cast<uint64_t>(new_source_pos_modulo) == new_step_size_denominator) {
new_source_pos_modulo = 0;
next_source_frame_ += Fixed::FromRaw(1);
}
source_pos_modulo_ = static_cast<uint64_t>(new_source_pos_modulo);
step_size_denominator_ = new_step_size_denominator;
FX_DCHECK(source_pos_modulo_ < step_size_denominator_)
<< "source_pos_modulo: " << source_pos_modulo_
<< ", new_step_size_denominator: " << step_size_denominator_;
}
}
int64_t Sampler::State::DestFromSourceLength(Fixed source_length) const {
FX_DCHECK(source_length >= Fixed(0));
FX_DCHECK(step_size_ >= Fixed::FromRaw(1));
FX_DCHECK(step_size_denominator_ > 0);
FX_DCHECK(step_size_modulo_ < step_size_denominator_);
FX_DCHECK(source_pos_modulo_ < step_size_denominator_);
if (step_size_modulo_ == 0) {
// Ceiling discards any fractional remainder less than `Fixed::FromRaw(1)` because it floors to
// `Fixed::FromRaw(1)` precision before rounding up.
int64_t steps = source_length.raw_value() / step_size_.raw_value();
if (source_length > step_size_ * steps) {
++steps;
}
return steps;
}
// Both calculations fit into `__int128_t`; where `source_length.raw_value` and
// `step_size.raw_value` are both `int64_t`, and the internal state values are each `uint64_t`.
// The largest possible `step_size_` and `step_size_denominator_` still leave more than enough
// room for the max possible `step_size_modulo_`, and the largest possible `step_size_rebased`
// exceeds the largest possible `source_length_rebased`.
const auto source_length_rebased =
static_cast<__int128_t>(source_length.raw_value()) * step_size_denominator_ -
source_pos_modulo_;
const auto step_size_rebased =
static_cast<__int128_t>(step_size_.raw_value()) * step_size_denominator_ + step_size_modulo_;
// We know this `DCHECK` holds, because if we divide both top and bottom by
// `step_size_denominator_`, then top is `std::numeric_limits<int64_t>::max()` or less, and bottom
// is 1 or more.
FX_DCHECK(source_length_rebased / step_size_rebased <= std::numeric_limits<int64_t>::max());
auto steps = static_cast<int64_t>(source_length_rebased / step_size_rebased);
if (source_length_rebased % step_size_rebased) {
++steps;
}
return steps;
}
Fixed Sampler::State::SourceFromDestLength(int64_t dest_length) const {
// `step_size_modulo_` and `step_size_denominator_` are both arbitrarily large 64-bit types, so we
// must up-cast to 128-bit.
const auto running_modulo =
static_cast<__int128_t>(step_size_modulo_) * static_cast<__int128_t>(dest_length) +
static_cast<__int128_t>(source_pos_modulo_);
// But `step_size_modulo_` and `source_pos_modulo_` are both `< step_size_denominator_`, so
// `mod_contribution <= dest_length`.
const __int128_t mod_contribution =
running_modulo / static_cast<__int128_t>(step_size_denominator_);
FX_DCHECK(mod_contribution <= std::numeric_limits<int64_t>::max());
// Max `step_size_` is 192, which is 21 bits in `Fixed` (8.13). Also, `mod_contribution` cannot
// exceed `dest_length`, which means:
// `source_length_raw <= (step_size_.raw_value() + 1) * dest_length`
// Thus, `source_length_raw` will overflow an `int64_t` only if `dest_length >=
// 2 ^ 63 / (192 * 2 ^ 13 + 1)`, which is `dest_length > 5.86e12`, which is `dest_length > 353
// days at 192khz`.
const int64_t source_length_raw =
step_size_.raw_value() * dest_length + static_cast<int64_t>(mod_contribution);
return Fixed::FromRaw(source_length_raw);
}
// This method resets long-running and per-Mix position counters, called when a destination
// discontinuity occurs. It sets next_dest_frame_ to the specified value and calculates
// next_source_frame_ based on the dest_frames_to_frac_source_frames transform.
void Sampler::State::ResetPositions(
int64_t target_dest_frame, const media::TimelineFunction& dest_frames_to_frac_source_frames) {
if constexpr (kTracePositionEvents) {
TRACE_DURATION("audio", __func__, "target_dest_frame", target_dest_frame);
}
next_dest_frame_ = target_dest_frame;
next_source_frame_ = Fixed::FromRaw(dest_frames_to_frac_source_frames.Apply(target_dest_frame));
source_pos_error_ = zx::duration(0);
source_pos_modulo_ = 0;
}
zx::time Sampler::State::MonoTimeFromRunningSource(
const media::TimelineFunction& clock_mono_to_frac_source_frames) const {
FX_DCHECK(source_pos_modulo_ < step_size_denominator_);
const __int128_t frac_source_from_offset =
static_cast<__int128_t>(next_source_frame_.raw_value()) -
clock_mono_to_frac_source_frames.subject_time();
// The calculation that would first overflow a `int128` is the partial calculation:
// `frac_source_from_offset * step_size_denominator * reference_delta`
//
// For our passed-in params, the maximal step_size_denominator that will *not* overflow is:
// `MAX_INT128 / abs(frac_source_from_offset) / reference_delta`
//
// `__int128_t` doesn't have an `abs` implementation right now so we do it manually. We add one
// fractional frame to accommodate any `source_pos_modulo_` contribution.
const __int128_t abs_frac_source_from_offset =
(frac_source_from_offset < 0 ? -frac_source_from_offset : frac_source_from_offset) + 1;
const __int128_t max_step_size_denominator = std::numeric_limits<__int128_t>::max() /
abs_frac_source_from_offset /
clock_mono_to_frac_source_frames.reference_delta();
__int128_t source_pos_modulo_128 = static_cast<__int128_t>(source_pos_modulo_);
__int128_t step_size_denominator_128 = static_cast<__int128_t>(step_size_denominator_);
// A minimum step_size_denominator of 2 allows us to round to the nearest nsec, rather than floor.
if (step_size_denominator_128 == 1) {
step_size_denominator_128 = 2;
// If step_size_denominator is 1 then `source_pos_modulo_128` is 0, so no point in doubling it.
} else {
// If step_size_denominator is large enough to cause overflow, scale it down for this
// calculation.
while (step_size_denominator_128 > max_step_size_denominator) {
step_size_denominator_128 >>= 1;
source_pos_modulo_128 >>= 1;
}
// While scaling down, don't let `source_pos_modulo_128` become equal to
// `step_size_denominator_128`.
source_pos_modulo_128 = std::min(source_pos_modulo_128, step_size_denominator_128 - 1);
}
// First portion of our `TimelineFunction::Apply`.
const __int128_t frac_src_modulo =
frac_source_from_offset * step_size_denominator_128 + source_pos_modulo_128;
// Middle portion, including rate factors.
__int128_t mono_modulo = frac_src_modulo * clock_mono_to_frac_source_frames.reference_delta();
mono_modulo /= clock_mono_to_frac_source_frames.subject_delta();
// Final portion, including adding in the mono offset.
const __int128_t mono_offset_modulo =
static_cast<__int128_t>(clock_mono_to_frac_source_frames.reference_time()) *
step_size_denominator_128;
mono_modulo += mono_offset_modulo;
// While reducing from `mono_modulo` to nsec, we add `step_size_denominator_128 / 2` in order to
// round.
const __int128_t final_mono =
(mono_modulo + step_size_denominator_128 / 2) / step_size_denominator_128;
// `final_mono` is 128-bit so we can double-check that we haven't overflowed. But we reduced
// `step_size_denominator_128` as needed to avoid all overflows.
FX_DCHECK(final_mono <= std::numeric_limits<int64_t>::max() &&
final_mono >= std::numeric_limits<int64_t>::min())
<< "0x" << std::hex << static_cast<uint64_t>(final_mono >> 64) << "'"
<< static_cast<uint64_t>(final_mono & std::numeric_limits<uint64_t>::max());
return zx::time(static_cast<zx_time_t>(final_mono));
}
void Sampler::State::AdvancePositionsBy(int64_t dest_frames, bool advance_source_pos_modulo) {
FX_DCHECK(dest_frames >= 0) << "Unexpected negative advance:" << " dest_frames=" << dest_frames
<< " denom=" << step_size_denominator_
<< " rate_mod=" << step_size_modulo_ << " "
<< " source_pos_mod=" << source_pos_modulo_;
int64_t frac_source_frame_delta = step_size_.raw_value() * dest_frames;
if constexpr (kTracePositionEvents) {
TRACE_DURATION("audio", __func__, "dest_frames", dest_frames, "advance_source_pos_modulo",
advance_source_pos_modulo, "frac_source_frame_delta", frac_source_frame_delta);
}
if (step_size_modulo_ > 0) {
// `step_size_modulo_` and `source_pos_modulo_` can be as large as `UINT64_MAX - 1`, so we use
// 128-bit to avoid overflow.
const __int128_t step_size_denominator_128 = step_size_denominator_;
__int128_t source_pos_modulo_128 = static_cast<__int128_t>(step_size_modulo_) * dest_frames;
if (advance_source_pos_modulo) {
source_pos_modulo_128 += source_pos_modulo_;
}
const uint64_t new_source_pos_modulo =
static_cast<uint64_t>(source_pos_modulo_128 % step_size_denominator_128);
if (advance_source_pos_modulo) {
source_pos_modulo_ = new_source_pos_modulo;
} else {
// `source_pos_modulo_` has already been advanced; it is already at its eventual value.
// `new_source_pos_modulo` is what `source_pos_modulo` would have become, if it had started at
// zero. Now advance `source_pos_modulo_128` by the difference (which is what its initial
// value must have been), just in case this causes `frac_source_frame_delta` to increment.
source_pos_modulo_128 += source_pos_modulo_;
source_pos_modulo_128 -= new_source_pos_modulo;
if (source_pos_modulo_ < new_source_pos_modulo) {
source_pos_modulo_128 += step_size_denominator_128;
}
}
frac_source_frame_delta +=
static_cast<int64_t>(source_pos_modulo_128 / step_size_denominator_128);
}
next_source_frame_ = Fixed::FromRaw(next_source_frame_.raw_value() + frac_source_frame_delta);
next_dest_frame_ += dest_frames;
if constexpr (kTracePositionEvents) {
TRACE_DURATION("audio", "AdvancePositionsBy End", "nest_source_frame",
next_source_frame_.Integral().Floor(), "next_source_frame_.frac",
next_source_frame_.Fraction().raw_value(), "next_dest_frame_", next_dest_frame_,
"source_pos_modulo", source_pos_modulo_);
}
}
std::shared_ptr<Sampler> Sampler::Create(const Format& source_format, const Format& dest_format,
Type type) {
TRACE_DURATION("audio", "Sampler::Create");
if (type == Type::kDefault &&
source_format.frames_per_second() == dest_format.frames_per_second()) {
return PointSampler::Create(source_format, dest_format);
}
return SincSampler::Create(source_format, dest_format);
}
} // namespace media_audio