blob: 58ad734da752adae5ad4cfc7996a4941510deb7e [file] [log] [blame]
// Copyright 2020 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 <limits>
#include <ffl/string.h>
#include <gtest/gtest.h>
#include "src/media/audio/audio_core/mixer/mixer.h"
#include "src/media/audio/lib/timeline/timeline_function.h"
namespace media::audio::mixer {
namespace {
class StubMixer : public Mixer {
public:
StubMixer() : Mixer(Fixed(0), Fixed(0), Gain::Limits{}) {}
void Mix(float*, int64_t, int64_t*, const void*, int64_t, Fixed*, bool) final {}
};
class SourceInfoTest : public testing::Test {
protected:
void TestPositionAdvanceNoRateModulo(bool advance_source_pos_modulo);
void TestPositionAdvanceWithRateModulo(bool advance_source_pos_modulo);
void TestPositionAdvanceNegative(bool advance_source_pos_modulo);
StubMixer mixer_;
};
TEST_F(SourceInfoTest, Defaults) {
auto& info = mixer_.source_info();
EXPECT_EQ(info.next_dest_frame, 0);
EXPECT_EQ(info.next_source_frame, 0);
EXPECT_EQ(info.source_pos_error, zx::duration(0));
EXPECT_EQ(info.dest_frames_to_frac_source_frames.subject_time(), 0);
EXPECT_EQ(info.dest_frames_to_frac_source_frames.reference_time(), 0);
EXPECT_EQ(info.dest_frames_to_frac_source_frames.subject_delta(), 0u);
EXPECT_EQ(info.dest_frames_to_frac_source_frames.reference_delta(), 1u);
EXPECT_EQ(info.clock_mono_to_frac_source_frames.subject_time(), 0);
EXPECT_EQ(info.clock_mono_to_frac_source_frames.reference_time(), 0);
EXPECT_EQ(info.clock_mono_to_frac_source_frames.subject_delta(), 0u);
EXPECT_EQ(info.clock_mono_to_frac_source_frames.reference_delta(), 1u);
}
// Reset with dest_frame: sets the running dest and frac_src position counters appropriately.
// next_source_frame is set according to dest_to_frac_source transform, source_pos_modulo
// according to rate_modulo and denominator.
TEST_F(SourceInfoTest, ResetPositions) {
auto& bookkeeping = mixer_.bookkeeping();
bookkeeping.SetRateModuloAndDenominator(5, 7);
auto& info = mixer_.source_info();
info.dest_frames_to_frac_source_frames = TimelineFunction(TimelineRate(17u, 1u));
// All these values will be overwritten
bookkeeping.source_pos_modulo = 1u;
info.next_dest_frame = -97;
info.next_source_frame = Fixed(7);
info.source_pos_error = zx::duration(-777);
info.ResetPositions(100, bookkeeping);
EXPECT_EQ(info.next_dest_frame, 100);
// Calculated directly from the TimelineFunction
EXPECT_EQ(info.next_source_frame, Fixed::FromRaw(1700));
// cleared by ResetPositions
EXPECT_EQ(bookkeeping.source_pos_modulo, 0ull);
EXPECT_EQ(info.source_pos_error, zx::duration(0));
}
// From current values, AdvanceAllPositionsTo advances running positions for dest, source and
// source_modulo to a given dest frame, based on the step_size, rate_modulo and denominator.
void SourceInfoTest::TestPositionAdvanceNoRateModulo(bool advance_source_pos_modulo) {
auto& bookkeeping = mixer_.bookkeeping();
bookkeeping.step_size = kOneFrame + Fixed::FromRaw(2);
bookkeeping.SetRateModuloAndDenominator(0, 1);
bookkeeping.source_pos_modulo = 1;
auto& info = mixer_.source_info();
info.next_source_frame = Fixed(3);
info.source_pos_error = zx::duration(-17);
info.next_dest_frame = 2;
if (advance_source_pos_modulo) {
info.AdvanceAllPositionsTo(11, bookkeeping);
} else {
info.UpdateRunningPositionsBy(9, bookkeeping);
}
// These should be unchanged
EXPECT_EQ(info.source_pos_error, zx::duration(-17));
EXPECT_EQ(bookkeeping.source_pos_modulo, 1u);
// These should be updated
EXPECT_EQ(info.next_dest_frame, 11u); // starts at 2, advance 9
// Source starts at 3, step_size "1.002", advance by 9 dest, adds 9 frames 18 subframes
// We expect new source_pos to be 12 frames, 18 subframes.
EXPECT_EQ(info.next_source_frame, Fixed(12) + Fixed::FromRaw(18))
<< "next_source_frame " << ffl::String::DecRational << info.next_source_frame;
}
TEST_F(SourceInfoTest, AdvanceAllPositions_NoRateModulo) {
TestPositionAdvanceNoRateModulo(/* advance_source_pos_modulo = */ true);
}
TEST_F(SourceInfoTest, UpdateRunningPositions_NoRateModulo) {
TestPositionAdvanceNoRateModulo(/* advance_source_pos_modulo = */ false);
}
void SourceInfoTest::TestPositionAdvanceWithRateModulo(bool advance_source_pos_modulo) {
auto& bookkeeping = mixer_.bookkeeping();
bookkeeping.step_size = kOneFrame + Fixed::FromRaw(2);
bookkeeping.SetRateModuloAndDenominator(2, 5);
bookkeeping.source_pos_modulo = 2;
auto& info = mixer_.source_info();
info.next_dest_frame = 2;
info.next_source_frame = Fixed(3);
info.source_pos_error = zx::duration(-17);
if (advance_source_pos_modulo) {
info.AdvanceAllPositionsTo(11, bookkeeping);
} else {
info.UpdateRunningPositionsBy(9, bookkeeping);
}
// This should be unchanged
EXPECT_EQ(info.source_pos_error, zx::duration(-17));
// These should be updated
// Source starts at 3 with position modulo 1/5, step_size "1.002" with rate_modulo 2/5.
// Advancing by 9 dest frames will add 9 * (1frame + 2subframes) to source_pos
// (9 frames + 18 subframes), plus any source_pos_modulo effects.
if (advance_source_pos_modulo) {
// rate_mod/denom is 2/5, so source_pos_modulo should increase by (9 * 2), from 2 to 20.
// source_pos_modulo / denominator (20 / 5) is 4, so source_pos adds 4 subframes.
// The remaining source_pos_modulo (20 % 5) is 0.
// Thus new source_pos should be 12 frames (3+9), 22 subframes (18+4), modulo 0/5.
EXPECT_EQ(bookkeeping.source_pos_modulo, 0ull);
} else {
// rate_mod/denom is 2/5, so source_pos_modulo increased by (9 * 2) and ended up as 2 (22).
// source_pos_modulo / denominator (22 / 5) is 4, so source_pos adds 4 subframes.
// The remaining source_pos_modulo (22 % 5) is 2.
// Thus new source_pos should be 12 frames (3+9), 22 subframes (18+4), modulo 2/5.
EXPECT_EQ(bookkeeping.source_pos_modulo, 2ull);
}
EXPECT_EQ(info.next_dest_frame, 11u);
EXPECT_EQ(info.next_source_frame, Fixed(Fixed(12) + Fixed::FromRaw(22)))
<< "next_source_frame " << ffl::String::DecRational << info.next_source_frame;
}
TEST_F(SourceInfoTest, AdvanceAllPositions_WithRateModulo) {
TestPositionAdvanceWithRateModulo(/* advance_source_pos_modulo = */ true);
}
TEST_F(SourceInfoTest, UpdateRunningPositions_WithRateModulo) {
TestPositionAdvanceWithRateModulo(/* advance_source_pos_modulo = */ false);
}
// Validate correct translation to nsec, from running source position including source_pos_modulo
// Test inputs were captured during real-world clock synchronization.
TEST_F(SourceInfoTest, MonotonicNsecFromRunningSource) {
Mixer::SourceInfo info;
// 44100 Hz stream with +987ppm clock adjustment, started at ~59 sec after bootup.
//
info.next_source_frame = Fixed(296) + Fixed::FromRaw(306);
info.clock_mono_to_frac_source_frames =
TimelineFunction(0, 59'468'459'010, {441'435'267, 1'220'703'125});
EXPECT_EQ(Mixer::SourceInfo::MonotonicNsecFromRunningSource(info, 26574, 78125),
zx::time(59'475'165'257));
// 48000 Hz stream with +4ppm clock adjustment +4ppm, started at ~319 sec after bootup.
//
info.next_source_frame = Fixed(-743) + Fixed::FromRaw(-1286);
info.clock_mono_to_frac_source_frames =
TimelineFunction(0, 319'214'380'550, {96'000'384, 244'140'625});
EXPECT_EQ(Mixer::SourceInfo::MonotonicNsecFromRunningSource(info, 5627, 15625),
zx::time(319'198'898'176));
// 6000 Hz stream with -3ppm clock adjustment, started at ~134 sec after bootup.
//
info.next_source_frame = Fixed(-143) + Fixed::FromRaw(-3293);
info.clock_mono_to_frac_source_frames =
TimelineFunction(0, 134'260'312'077, {11'999'964, 244'140'625});
EXPECT_EQ(Mixer::SourceInfo::MonotonicNsecFromRunningSource(info, 0, 1),
zx::time(134'236'411'676));
// same stream, from a mix 32 millisecs later
info.next_source_frame = Fixed(48) + Fixed::FromRaw(4892);
EXPECT_EQ(Mixer::SourceInfo::MonotonicNsecFromRunningSource(info, 15167, 15625),
zx::time(134'268'411'649));
// Synthetic example that overflows a int128 if we don't prevent it
//
// 191999 Hz (prime) stream with -997 (prime) clock adjustment, stream-start 1 year after bootup,
// seeked to a running source position of 6e12 frames: about 1 year also.
//
// We expect a zx::time that is roughly 2 yrs (now + stream position), more than 6.2e16 nsec.
// If this particular calculation overflows, the result is positive but approx half the magnitude.
info.next_source_frame = Fixed(6'000'000'000'000) + Fixed::FromRaw(8191);
info.clock_mono_to_frac_source_frames =
TimelineFunction(0, 31'556'736'000'000'000, {191'807'576'997, 122'070'312'500});
EXPECT_GT(
Mixer::SourceInfo::MonotonicNsecFromRunningSource(
info, std::numeric_limits<uint64_t>::max() - 1, std::numeric_limits<uint64_t>::max()),
zx::time(62'838'086'000'000'000));
}
} // namespace
} // namespace media::audio::mixer