blob: b82eac650576d316e3ed898921dbf35bb845dbfa [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/position_manager.h"
#include <iterator>
#include <gtest/gtest.h>
#include "src/media/audio/lib/format2/fixed.h"
namespace media_audio {
namespace {
TEST(PositionManagerTest, CurrentSourceFrame) {
constexpr auto source_chans = 2u;
constexpr auto dest_chans = 1u;
PositionManager pos_mgr(source_chans, dest_chans, 1, kFracOneFrame);
constexpr int64_t source_frame_count = 2;
int16_t source[source_frame_count * source_chans];
auto source_void = static_cast<void*>(source);
auto source_start = 1;
auto source_offset = Fixed(source_start);
pos_mgr.SetSourceValues(source_void, source_frame_count, &source_offset);
auto expected_source_frame = source + (source_start * source_chans);
EXPECT_EQ(pos_mgr.CurrentSourceFrame<int16_t>(), expected_source_frame);
EXPECT_FALSE(pos_mgr.IsSourceConsumed());
const auto expected_source_frame_u8 =
reinterpret_cast<uint8_t*>(source_void) + (source_start * source_chans);
EXPECT_EQ(pos_mgr.CurrentSourceFrame<uint8_t>(), expected_source_frame_u8);
const auto expected_source_frame_32 =
reinterpret_cast<int32_t*>(source_void) + (source_start * source_chans);
EXPECT_EQ(pos_mgr.CurrentSourceFrame<int32_t>(), expected_source_frame_32);
const auto expected_source_frame_float =
reinterpret_cast<float*>(source_void) + (source_start * source_chans);
EXPECT_EQ(pos_mgr.CurrentSourceFrame<float>(), expected_source_frame_float);
source_start = 0;
source_offset = Fixed(source_start);
pos_mgr.SetSourceValues(source_void, source_frame_count, &source_offset);
expected_source_frame = source + (source_start * source_chans);
EXPECT_EQ(pos_mgr.CurrentSourceFrame<int16_t>(), expected_source_frame);
EXPECT_FALSE(pos_mgr.IsSourceConsumed());
}
TEST(PositionManagerTest, CurrentDestFrame) {
constexpr auto source_chans = 1u;
constexpr auto dest_chans = 4u;
PositionManager pos_mgr(source_chans, dest_chans, 1, kFracOneFrame);
constexpr auto dest_frame_count = 2;
float dest[dest_frame_count * dest_chans];
int64_t dest_offset = 1;
auto expected_dest_frame = dest + (dest_offset * dest_chans);
pos_mgr.SetDestValues(dest, dest_frame_count, &dest_offset);
EXPECT_EQ(pos_mgr.CurrentDestFrame(), expected_dest_frame);
dest_offset = 0;
expected_dest_frame = dest + (dest_offset * dest_chans);
pos_mgr.SetDestValues(dest, dest_frame_count, &dest_offset);
EXPECT_EQ(pos_mgr.CurrentDestFrame(), expected_dest_frame);
}
TEST(PositionManagerTest, UpdateOffsets) {
PositionManager pos_mgr(1, 1, 1, kFracOneFrame);
float input;
void* source_void_ptr = static_cast<void*>(&input);
constexpr int64_t source_frame_count = 1;
auto source_offset = Fixed(0);
pos_mgr.SetSourceValues(source_void_ptr, source_frame_count, &source_offset);
float data;
float* dest = &data;
auto dest_frame_count = 1;
int64_t dest_offset = 0;
pos_mgr.SetDestValues(dest, dest_frame_count, &dest_offset);
auto step_size = kFracOneFrame;
auto rate_modulo = 0ul;
auto denominator = 1ul;
auto source_position_modulo = 0ul;
pos_mgr.SetRateValues(step_size, rate_modulo, denominator, &source_position_modulo);
source_offset = Fixed::FromRaw(27);
dest_offset = 42;
source_position_modulo = 72;
pos_mgr.UpdateOffsets();
EXPECT_EQ(source_offset, Fixed(0));
EXPECT_EQ(dest_offset, 0);
EXPECT_EQ(source_position_modulo, 72ul);
// Now that `rate_modulo` and `denominator` are non-zero, `source_position_modulo` should be
// updated.
rate_modulo = 1ul;
denominator = 2ul;
source_position_modulo = 0;
pos_mgr.SetRateValues(step_size, rate_modulo, denominator, &source_position_modulo);
source_offset = Fixed::FromRaw(27);
dest_offset = 42;
source_position_modulo = 72;
pos_mgr.UpdateOffsets();
EXPECT_EQ(source_offset, Fixed(0));
EXPECT_EQ(dest_offset, 0);
EXPECT_EQ(source_position_modulo, 0ul);
}
TEST(PositionManagerTest, CanFrameBeMixed) {
constexpr auto source_chans = 1u;
constexpr auto dest_chans = 1u;
auto half = Fixed(ffl::FromRatio(1, 2)).raw_value();
PositionManager pos_mgr(source_chans, dest_chans, half + 1, half + 1);
int16_t source[2 * source_chans];
const auto source_void = static_cast<void*>(source);
Fixed source_offset = ffl::FromRatio(3, 2) - Fixed::FromRaw(1);
pos_mgr.SetSourceValues(source_void, std::size(source), &source_offset);
float dest[2u * dest_chans];
int64_t dest_offset = 1;
pos_mgr.SetDestValues(dest, 2, &dest_offset);
EXPECT_TRUE(pos_mgr.CanFrameBeMixed());
EXPECT_FALSE(pos_mgr.IsSourceConsumed());
source_offset += Fixed::FromRaw(1);
pos_mgr.SetSourceValues(source_void, std::size(source), &source_offset);
EXPECT_FALSE(pos_mgr.CanFrameBeMixed());
EXPECT_TRUE(pos_mgr.IsSourceConsumed());
}
TEST(PositionManagerTest, AdvanceFrameBasic) {
PositionManager pos_mgr(1, 1, 1, kFracOneFrame);
uint8_t source[3];
auto source_offset = Fixed(1);
pos_mgr.SetSourceValues(static_cast<void*>(source), std::size(source), &source_offset);
float dest[3];
int64_t dest_offset = 1;
pos_mgr.SetDestValues(dest, 3, &dest_offset);
auto source_position_modulo = 0ul;
pos_mgr.SetRateValues(kFracOneFrame, 0, 1, &source_position_modulo);
Fixed expected_source_offset = source_offset + Fixed(1);
auto received_source_offset = Fixed::FromRaw(pos_mgr.AdvanceFrame());
EXPECT_EQ(received_source_offset, expected_source_offset);
EXPECT_TRUE(pos_mgr.CanFrameBeMixed());
EXPECT_FALSE(pos_mgr.IsSourceConsumed());
}
TEST(PositionManagerTest, AdvanceFrameSourceReachesEnd) {
PositionManager pos_mgr(1, 1, 1, kFracOneFrame);
int32_t source[2];
auto source_offset = Fixed(1);
pos_mgr.SetSourceValues(static_cast<void*>(source), std::size(source), &source_offset);
float dest[3u];
int64_t dest_offset = 2;
pos_mgr.SetDestValues(dest, 3, &dest_offset);
auto source_position_modulo = 0ul;
pos_mgr.SetRateValues(kFracOneFrame, 0, 1, &source_position_modulo);
Fixed expected_source_offset = source_offset + Fixed(1);
auto received_source_offset = Fixed::FromRaw(pos_mgr.AdvanceFrame());
EXPECT_EQ(received_source_offset, expected_source_offset);
EXPECT_FALSE(pos_mgr.CanFrameBeMixed());
EXPECT_TRUE(pos_mgr.IsSourceConsumed());
}
TEST(PositionManagerTest, AdvanceFrame_SourceModuloReachesEnd) {
PositionManager pos_mgr(1, 1, 1, kFracOneFrame);
int16_t source[3];
Fixed source_offset = Fixed(2) - Fixed::FromRaw(1);
pos_mgr.SetSourceValues(static_cast<void*>(source), std::size(source), &source_offset);
float dest[3];
int64_t dest_offset = 1;
pos_mgr.SetDestValues(dest, 3, &dest_offset);
// Source modulo starts just one shy of incrementing `source_offset`, and `rate_modulo` increments
// it. This is the boundary case, exactly where source modulo affects `source_offset`.
constexpr auto step_size = kFracOneFrame;
auto rate_modulo = 1ul;
auto denominator = 243ul;
auto source_position_modulo = 242ul;
pos_mgr.SetRateValues(step_size, rate_modulo, denominator, &source_position_modulo);
Fixed expected_source_offset = Fixed(2) - Fixed::FromRaw(1);
EXPECT_TRUE(pos_mgr.CanFrameBeMixed());
EXPECT_FALSE(pos_mgr.IsSourceConsumed());
EXPECT_EQ(pos_mgr.CurrentSourceFrame<int16_t>(), &source[1]);
EXPECT_EQ(pos_mgr.source_offset(), expected_source_offset);
expected_source_offset = Fixed(3);
auto received_source_offset = Fixed::FromRaw(pos_mgr.AdvanceFrame());
EXPECT_EQ(received_source_offset, expected_source_offset);
EXPECT_EQ(pos_mgr.source_offset(), received_source_offset);
EXPECT_FALSE(pos_mgr.CanFrameBeMixed());
EXPECT_TRUE(pos_mgr.IsSourceConsumed());
EXPECT_EQ(received_source_offset, Fixed(3));
}
TEST(PositionManagerTest, AdvanceFrameSourceModuloAlmostReachesEnd) {
PositionManager pos_mgr(1, 1, 1, kFracOneFrame);
float source[3];
Fixed source_offset = Fixed(2) - Fixed::FromRaw(1);
pos_mgr.SetSourceValues(static_cast<void*>(source), std::size(source), &source_offset);
float dest[3];
int64_t dest_offset = 1;
pos_mgr.SetDestValues(dest, 3, &dest_offset);
constexpr auto step_size = kFracOneFrame;
auto rate_modulo = 1ul;
auto denominator = 243ul;
auto source_position_modulo = 241ul;
pos_mgr.SetRateValues(step_size, rate_modulo, denominator, &source_position_modulo);
// Source modulo starts just two shy of incrementing `source_offset`, and `rate_modulo` increments
// it by one. This is the boundary case, one less than where source modulo would affect
// `source_offset`.
Fixed expected_source_offset = Fixed(2) - Fixed::FromRaw(1);
EXPECT_TRUE(pos_mgr.CanFrameBeMixed());
EXPECT_EQ(pos_mgr.CurrentSourceFrame<float>(), &source[1]);
EXPECT_EQ(pos_mgr.source_offset(), expected_source_offset);
expected_source_offset = source_offset + Fixed(1);
auto received_source_offset = Fixed::FromRaw(pos_mgr.AdvanceFrame());
EXPECT_EQ(received_source_offset, expected_source_offset);
EXPECT_EQ(pos_mgr.source_offset(), received_source_offset);
EXPECT_TRUE(pos_mgr.CanFrameBeMixed());
EXPECT_FALSE(pos_mgr.IsSourceConsumed());
EXPECT_EQ(pos_mgr.CurrentSourceFrame<float>(), &source[2]);
EXPECT_EQ(received_source_offset, Fixed(3) - Fixed::FromRaw(1));
}
TEST(PositionManagerTest, AdvanceFrameDestReachesEnd) {
PositionManager pos_mgr(1, 1, 1, kFracOneFrame);
int16_t source[3];
auto source_offset = Fixed(1);
const Fixed expected_source_offset = source_offset + Fixed(1);
pos_mgr.SetSourceValues(static_cast<void*>(source), std::size(source), &source_offset);
float dest[2];
const int64_t dest_frame_count = std::size(dest);
int64_t dest_offset = 1;
pos_mgr.SetDestValues(dest, dest_frame_count, &dest_offset);
constexpr auto step_size = kFracOneFrame;
auto source_position_modulo = 0ul;
pos_mgr.SetRateValues(step_size, 0, 1, &source_position_modulo);
// When `dest_offset` reaches `dest_frame_count`, we can no longer mix a frame, but `source` is
// not consumed.
auto received_source_offset = Fixed::FromRaw(pos_mgr.AdvanceFrame());
EXPECT_EQ(received_source_offset, expected_source_offset);
EXPECT_EQ(pos_mgr.source_offset(), received_source_offset);
EXPECT_FALSE(pos_mgr.CanFrameBeMixed());
EXPECT_FALSE(pos_mgr.IsSourceConsumed());
}
TEST(PositionManagerTest, AdvanceFrameNoRateValues) {
PositionManager pos_mgr(1, 1, 1, kFracOneFrame);
int16_t source[3];
Fixed source_offset = Fixed(2) - Fixed::FromRaw(1);
pos_mgr.SetSourceValues(static_cast<void*>(source), std::size(source), &source_offset);
float dest[3];
int64_t dest_offset = 1;
pos_mgr.SetDestValues(dest, 3, &dest_offset);
// Unity `step_size` (no rate_modulo) should be the default if `SetRateValues` is never called.
Fixed expected_source_offset = Fixed(2) - Fixed::FromRaw(1);
EXPECT_TRUE(pos_mgr.CanFrameBeMixed());
EXPECT_FALSE(pos_mgr.IsSourceConsumed());
EXPECT_EQ(pos_mgr.CurrentSourceFrame<int16_t>(), &source[1]);
EXPECT_EQ(pos_mgr.source_offset(), expected_source_offset);
expected_source_offset = Fixed(3) - Fixed::FromRaw(1);
auto received_source_offset = Fixed::FromRaw(pos_mgr.AdvanceFrame());
pos_mgr.UpdateOffsets();
EXPECT_EQ(received_source_offset, expected_source_offset);
EXPECT_EQ(pos_mgr.source_offset(), received_source_offset);
EXPECT_TRUE(pos_mgr.CanFrameBeMixed());
EXPECT_FALSE(pos_mgr.IsSourceConsumed());
}
TEST(PositionManagerTest, AdvanceToEndDest) {
PositionManager pos_mgr(1, 1, 1, kFracOneFrame);
int16_t source[12];
Fixed source_offset = Fixed(1) - Fixed::FromRaw(1);
pos_mgr.SetSourceValues(static_cast<void*>(source), std::size(source), &source_offset);
float dest[5];
int64_t dest_offset = 0;
pos_mgr.SetDestValues(dest, std::size(dest), &dest_offset);
constexpr auto step_size = kFracOneFrame * 2 - 1;
auto source_position_modulo = 1ul;
auto denominator = 2ul;
pos_mgr.SetRateValues(step_size, 0, denominator, &source_position_modulo);
// `AdvanceToEnd` should be limited by `dest`.
auto num_source_frame_count_skipped = pos_mgr.AdvanceToEnd();
EXPECT_EQ(num_source_frame_count_skipped, 10);
pos_mgr.UpdateOffsets();
EXPECT_EQ(source_offset, Fixed(11) - Fixed::FromRaw(6));
EXPECT_EQ(dest_offset, 5);
EXPECT_EQ(source_position_modulo, 1ul);
EXPECT_FALSE(pos_mgr.CanFrameBeMixed());
EXPECT_FALSE(pos_mgr.IsSourceConsumed());
}
TEST(PositionManagerTest, AdvanceToEndSourceBasic) {
PositionManager pos_mgr(1, 1, kFracHalfFrame + 1, kFracHalfFrame + 1);
int16_t source[6];
auto source_offset = Fixed(1);
pos_mgr.SetSourceValues(static_cast<void*>(source), std::size(source), &source_offset);
float dest[13];
int64_t dest_offset = 0;
pos_mgr.SetDestValues(dest, std::size(dest), &dest_offset);
auto source_position_modulo = 0ul;
pos_mgr.SetRateValues(kFracHalfFrame, 0, 1, &source_position_modulo);
// `AdvanceToEnd` should be limited by `source`.
auto num_source_frame_count_skipped = pos_mgr.AdvanceToEnd();
EXPECT_EQ(num_source_frame_count_skipped, 5);
Fixed expect_source_offset = Fixed(ffl::FromRatio(11, 2));
pos_mgr.UpdateOffsets();
EXPECT_EQ(source_offset, expect_source_offset)
<< std::hex << source_offset.raw_value() << " != " << expect_source_offset.raw_value();
EXPECT_EQ(dest_offset, 9);
EXPECT_EQ(source_position_modulo, 0u);
EXPECT_FALSE(pos_mgr.CanFrameBeMixed());
EXPECT_TRUE(pos_mgr.IsSourceConsumed());
}
TEST(PositionManagerTest, AdvanceToEndSourceExactModulo) {
PositionManager pos_mgr(1, 1, 1, kFracHalfFrame + 1);
int16_t source[11];
Fixed source_offset = Fixed(1) - Fixed::FromRaw(1);
pos_mgr.SetSourceValues(static_cast<void*>(source), std::size(source), &source_offset);
float dest[6];
int64_t dest_offset = 0;
pos_mgr.SetDestValues(dest, std::size(dest), &dest_offset);
constexpr auto step_size = 2 * kFracOneFrame;
auto rate_modulo = 1ul;
auto denominator = 25ul;
auto source_position_modulo = 20ul;
pos_mgr.SetRateValues(step_size, rate_modulo, denominator, &source_position_modulo);
// `AdvanceToEnd` should be limited by `source`, where `rate_module` contributes EXACTLY what
// consumes `source`.
auto num_source_frame_count_skipped = pos_mgr.AdvanceToEnd();
EXPECT_EQ(num_source_frame_count_skipped, 11);
pos_mgr.UpdateOffsets();
EXPECT_EQ(source_offset, Fixed(11));
EXPECT_EQ(dest_offset, 5);
EXPECT_EQ(source_position_modulo, 0ul);
EXPECT_FALSE(pos_mgr.CanFrameBeMixed());
EXPECT_TRUE(pos_mgr.IsSourceConsumed());
}
// AdvanceToEnd is limited by source; source_position modulo flows beyond source but by < 1 frame.
TEST(PositionManagerTest, AdvanceToEndSourceExtraModulo) {
PositionManager pos_mgr(1, 1, 1, kFracHalfFrame + 1);
int16_t source[11];
Fixed source_offset = Fixed(1) - Fixed::FromRaw(1);
pos_mgr.SetSourceValues(static_cast<void*>(source), std::size(source), &source_offset);
float dest[6];
int64_t dest_offset = 0;
pos_mgr.SetDestValues(dest, std::size(dest), &dest_offset);
constexpr auto step_size = kFracOneFrame * 2;
auto rate_modulo = 1ul;
auto denominator = 25ul;
auto source_position_modulo = 24ul;
pos_mgr.SetRateValues(step_size, rate_modulo, denominator, &source_position_modulo);
// `AdvanceToEnd` should be limited by `source`, where `source_position_modulo` flows beyond
// `source` by <1 frame.
auto num_source_frame_count_skipped = pos_mgr.AdvanceToEnd();
EXPECT_EQ(num_source_frame_count_skipped, 11);
pos_mgr.UpdateOffsets();
EXPECT_EQ(source_offset, Fixed(11));
EXPECT_EQ(dest_offset, 5);
EXPECT_EQ(source_position_modulo, 4ul);
EXPECT_FALSE(pos_mgr.CanFrameBeMixed());
EXPECT_TRUE(pos_mgr.IsSourceConsumed());
}
TEST(PositionManagerTest, AdvanceToEndExtremeRatesAndWidths) {
PositionManager pos_mgr(1, 1, (336 << Fixed::Format::FractionalBits) + 1,
(336 << Fixed::Format::FractionalBits) + 1);
float dest[10];
int64_t dest_offset = 0;
pos_mgr.SetDestValues(dest, std::size(dest), &dest_offset);
auto step_size = (24 << Fixed::Format::FractionalBits);
auto rate_modulo = 0ul;
auto denominator = 1ul;
auto source_position_modulo = 0ul;
pos_mgr.SetRateValues(step_size, rate_modulo, denominator, &source_position_modulo);
int16_t source[360];
auto source_offset = Fixed::FromRaw(-1);
pos_mgr.SetSourceValues(static_cast<void*>(source), 336, &source_offset);
// `AdvanceToEnd` should be limited by `source`.
auto num_source_frame_count_skipped = pos_mgr.AdvanceToEnd();
EXPECT_EQ(num_source_frame_count_skipped, 24);
pos_mgr.UpdateOffsets();
EXPECT_EQ(source_offset.raw_value(), (24 << Fixed::Format::FractionalBits) - 1);
EXPECT_EQ(dest_offset, 1);
EXPECT_FALSE(pos_mgr.CanFrameBeMixed());
EXPECT_TRUE(pos_mgr.IsSourceConsumed());
// Now try starting from just one subframe further. This should not advance.
source_offset = Fixed(0);
pos_mgr.SetSourceValues(static_cast<void*>(source), 336, &source_offset);
num_source_frame_count_skipped = pos_mgr.AdvanceToEnd();
EXPECT_EQ(num_source_frame_count_skipped, 0);
pos_mgr.UpdateOffsets();
EXPECT_EQ(source_offset, Fixed(0));
EXPECT_EQ(dest_offset, 1);
EXPECT_FALSE(pos_mgr.CanFrameBeMixed());
EXPECT_TRUE(pos_mgr.IsSourceConsumed());
// Back up position but subtract a frame of source supply: we should not advance.
source_offset = Fixed::FromRaw(-1);
pos_mgr.SetSourceValues(static_cast<void*>(source), 335, &source_offset);
num_source_frame_count_skipped = pos_mgr.AdvanceToEnd();
EXPECT_EQ(num_source_frame_count_skipped, 0);
pos_mgr.UpdateOffsets();
EXPECT_EQ(source_offset, Fixed::FromRaw(-1));
EXPECT_EQ(dest_offset, 1);
EXPECT_FALSE(pos_mgr.CanFrameBeMixed());
EXPECT_TRUE(pos_mgr.IsSourceConsumed());
}
} // namespace
} // namespace media_audio