blob: 584b0f1bb774bd55f1f917ea645c436b44f7469a [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 "src/media/audio/audio_core/mixer/mixer_utils.h"
#include <limits>
#include <fbl/algorithm.h>
#include <gtest/gtest.h>
#include "src/media/audio/audio_core/mixer/constants.h"
namespace media::audio {
namespace {
//
// SampleNormalizer converts between uint8/int16/int24-in-32 and our internal float format.
//
// Validate uint8->float format conversion
TEST(SampleNormalizerTest, UInt8_Basic) {
const uint8_t data[] = {0x00, 0x40, 0x80, 0xE0};
const float expect[] = {-1.0, -0.5, 0, 0.75};
for (auto i = 0u; i < fbl::count_of(data); ++i) {
EXPECT_EQ(mixer::SampleNormalizer<uint8_t>::Read(data + i), expect[i]);
}
const uint8_t max_val = 0xFF;
EXPECT_LT(mixer::SampleNormalizer<uint8_t>::Read(&max_val), 1.0f);
EXPECT_GT(mixer::SampleNormalizer<uint8_t>::Read(&max_val), 0.99f);
}
// Validate int16->float format conversion
TEST(SampleNormalizerTest, Int16_Basic) {
const int16_t data[] = {std::numeric_limits<int16_t>::min(), -0x4000, 0, 0x6000};
const float expect[] = {-1.0, -0.5, 0, 0.75};
for (auto i = 0u; i < fbl::count_of(data); ++i) {
EXPECT_EQ(mixer::SampleNormalizer<int16_t>::Read(data + i), expect[i]);
}
const int16_t max_val = 0x7FFF;
EXPECT_LT(mixer::SampleNormalizer<int16_t>::Read(&max_val), 1.0f);
EXPECT_GT(mixer::SampleNormalizer<int16_t>::Read(&max_val), 0.9999f);
}
// Validate int24->float format conversion
TEST(SampleNormalizerTest, Int24_Basic) {
const int32_t data[] = {kMinInt24In32, -0x40000000, 0, 0x60000000};
const float expect[] = {-1.0, -0.5, 0, 0.75};
for (auto i = 0u; i < fbl::count_of(data); ++i) {
EXPECT_EQ(mixer::SampleNormalizer<int32_t>::Read(data + i), expect[i]);
}
const int32_t max_val = kMaxInt24In32;
EXPECT_LT(mixer::SampleNormalizer<int32_t>::Read(&max_val), 1.0f);
EXPECT_GT(mixer::SampleNormalizer<int32_t>::Read(&max_val), 0.999999f);
}
// Validate float->float format conversion
TEST(SampleNormalizerTest, Float_Basic) {
const float data[] = {-1.0, -0.5, 0, 0.75, 1.0};
for (auto i = 0u; i < fbl::count_of(data); ++i) {
EXPECT_EQ(mixer::SampleNormalizer<float>::Read(data + i), data[i]);
}
}
//
// SampleScaler tests (four scale types)
//
// Validate that with MUTED scale type, all output is silence (0).
TEST(SampleScalerTest, Mute) {
const float input[] = {-0.5f, 1.0f};
const Gain::AScale scale[] = {1.5f, 0.5f};
const float expect = 0.0f;
EXPECT_EQ(mixer::SampleScaler<mixer::ScalerType::MUTED>::Scale(input[0], scale[0]), expect);
EXPECT_EQ(mixer::SampleScaler<mixer::ScalerType::MUTED>::Scale(input[0], scale[1]), expect);
EXPECT_EQ(mixer::SampleScaler<mixer::ScalerType::MUTED>::Scale(input[1], scale[0]), expect);
}
// Validate that with NE_UNITY scale types, output is scaled appropriately.
TEST(SampleScalerTest, NotUnity) {
const float input[] = {-0.5f, 1.0f};
const Gain::AScale scale[] = {1.5f, 0.5f};
const float expect[] = {-0.75f, -0.25f, 1.5f};
EXPECT_EQ(mixer::SampleScaler<mixer::ScalerType::NE_UNITY>::Scale(input[0], scale[0]), expect[0]);
EXPECT_EQ(mixer::SampleScaler<mixer::ScalerType::NE_UNITY>::Scale(input[0], scale[1]), expect[1]);
EXPECT_EQ(mixer::SampleScaler<mixer::ScalerType::NE_UNITY>::Scale(input[1], scale[0]), expect[2]);
}
// Validate that RAMPING scale type scales appropriately and is identical to NE_UNITY.
TEST(SampleScalerTest, Ramping) {
const float input[] = {-0.5f, 1.0f};
const Gain::AScale scale[] = {1.5f, 0.5f};
const float expect[] = {-0.75f, -0.25f, 1.5f};
EXPECT_EQ(mixer::SampleScaler<mixer::ScalerType::RAMPING>::Scale(input[0], scale[0]), expect[0]);
EXPECT_EQ(mixer::SampleScaler<mixer::ScalerType::RAMPING>::Scale(input[0], scale[1]), expect[1]);
EXPECT_EQ(mixer::SampleScaler<mixer::ScalerType::RAMPING>::Scale(input[1], scale[0]), expect[2]);
EXPECT_EQ(mixer::SampleScaler<mixer::ScalerType::RAMPING>::Scale(input[0], scale[0]),
mixer::SampleScaler<mixer::ScalerType::NE_UNITY>::Scale(input[0], scale[0]));
EXPECT_EQ(mixer::SampleScaler<mixer::ScalerType::RAMPING>::Scale(input[0], scale[1]),
mixer::SampleScaler<mixer::ScalerType::NE_UNITY>::Scale(input[0], scale[1]));
EXPECT_EQ(mixer::SampleScaler<mixer::ScalerType::RAMPING>::Scale(input[1], scale[0]),
mixer::SampleScaler<mixer::ScalerType::NE_UNITY>::Scale(input[1], scale[0]));
}
// Validate that with EQ_UNITY scale type, all output is same as input.
TEST(SampleScalerTest, Unity) {
const float input[] = {-0.5f, 1.0f};
const Gain::AScale scale[] = {1.5f, 0.5f};
EXPECT_EQ(mixer::SampleScaler<mixer::ScalerType::EQ_UNITY>::Scale(input[0], scale[0]), input[0]);
EXPECT_EQ(mixer::SampleScaler<mixer::ScalerType::EQ_UNITY>::Scale(input[0], scale[1]), input[0]);
EXPECT_EQ(mixer::SampleScaler<mixer::ScalerType::EQ_UNITY>::Scale(input[1], scale[0]), input[1]);
}
//
// SrcReader tests all use float, as type conversion is handled by SamplerNormalizer
//
// Validate N->N channel mapping, including higher channel counts.
// Expectation: each source channel maps identically to that destination channel.
TEST(SrcReaderTest, Map_N_N) {
const float data[] = {-1.0, 1.0, 0.0, 0.5};
EXPECT_EQ((mixer::SrcReader<float, 1, 1>::Read(data, 0)), data[0]);
EXPECT_EQ((mixer::SrcReader<float, 1, 1>::Read(data + 3, 0)), data[3]);
EXPECT_EQ((mixer::SrcReader<float, 2, 2>::Read(data, 0)), data[0]);
EXPECT_EQ((mixer::SrcReader<float, 2, 2>::Read(data, 1)), data[1]);
EXPECT_EQ((mixer::SrcReader<float, 2, 2>::Read(data + 2, 0)), data[2]);
EXPECT_EQ((mixer::SrcReader<float, 2, 2>::Read(data + 2, 1)), data[3]);
EXPECT_EQ((mixer::SrcReader<float, 3, 3>::Read(data, 0)), data[0]);
EXPECT_EQ((mixer::SrcReader<float, 3, 3>::Read(data, 1)), data[1]);
EXPECT_EQ((mixer::SrcReader<float, 3, 3>::Read(data + 2, 0)), data[2]);
EXPECT_EQ((mixer::SrcReader<float, 3, 3>::Read(data + 1, 2)), data[3]);
EXPECT_EQ((mixer::SrcReader<float, 4, 4>::Read(data, 0)), data[0]);
EXPECT_EQ((mixer::SrcReader<float, 4, 4>::Read(data, 1)), data[1]);
EXPECT_EQ((mixer::SrcReader<float, 4, 4>::Read(data, 2)), data[2]);
EXPECT_EQ((mixer::SrcReader<float, 4, 4>::Read(data, 3)), data[3]);
EXPECT_EQ((mixer::SrcReader<float, 6, 6>::Read(data, 1)), data[1]);
EXPECT_EQ((mixer::SrcReader<float, 6, 6>::Read(data, 2)), data[2]);
EXPECT_EQ((mixer::SrcReader<float, 8, 8>::Read(data, 0)), data[0]);
EXPECT_EQ((mixer::SrcReader<float, 8, 8>::Read(data, 3)), data[3]);
}
// Validate 1->N channel mapping, including higher destination channel counts.
// Expectation: the one source channel maps to every destination channel without attenuation.
TEST(SrcReaderTest, Map_1_N) {
const float data[] = {0.76543f, 0.0};
EXPECT_EQ((mixer::SrcReader<float, 1, 1>::Read(data, 0)), *data);
EXPECT_EQ((mixer::SrcReader<float, 1, 2>::Read(data, 0)), *data);
EXPECT_EQ((mixer::SrcReader<float, 1, 2>::Read(data, 1)),
(mixer::SrcReader<float, 1, 2>::Read(data, 0)));
EXPECT_EQ((mixer::SrcReader<float, 1, 3>::Read(data, 0)), *data);
EXPECT_EQ((mixer::SrcReader<float, 1, 3>::Read(data, 1)),
(mixer::SrcReader<float, 1, 3>::Read(data, 0)));
EXPECT_EQ((mixer::SrcReader<float, 1, 3>::Read(data, 2)),
(mixer::SrcReader<float, 1, 3>::Read(data, 0)));
EXPECT_EQ((mixer::SrcReader<float, 1, 4>::Read(data, 0)), *data);
EXPECT_EQ((mixer::SrcReader<float, 1, 4>::Read(data, 1)),
(mixer::SrcReader<float, 1, 4>::Read(data, 0)));
EXPECT_EQ((mixer::SrcReader<float, 1, 4>::Read(data, 2)),
(mixer::SrcReader<float, 1, 4>::Read(data, 0)));
EXPECT_EQ((mixer::SrcReader<float, 1, 4>::Read(data, 3)),
(mixer::SrcReader<float, 1, 4>::Read(data, 0)));
EXPECT_EQ((mixer::SrcReader<float, 1, 5>::Read(data, 1)), *data);
EXPECT_EQ((mixer::SrcReader<float, 1, 5>::Read(data, 4)),
(mixer::SrcReader<float, 1, 5>::Read(data, 1)));
EXPECT_EQ((mixer::SrcReader<float, 1, 8>::Read(data, 2)), *data);
EXPECT_EQ((mixer::SrcReader<float, 1, 8>::Read(data, 7)),
(mixer::SrcReader<float, 1, 8>::Read(data, 2)));
}
// Validate 2->1 channel mapping.
// Expectation: each source channel should contribute equally to the one destination channel.
// The one destination channel is average of all source channels.
TEST(SrcReaderTest, Map_2_1) {
using SR = mixer::SrcReader<float, 2, 1>;
const float data[] = {-1.0, 1.0, 0.5};
const float expect[] = {0, 0.75};
EXPECT_EQ(SR::Read(data, 0), expect[0]);
EXPECT_EQ(SR::Read(data + 1, 0), expect[1]);
}
// Validate 2->3 channel mapping.
// Expectation: 3-channel destination is L.R.C (or some other geometry where third destination
// channel should contain an equal mix of the two source channels).
// dest[0] is source[0]; dest[1] is source[1]; dest[2] is average of source[0] and source[1].
TEST(SrcReaderTest, Map_2_3) {
using SR = mixer::SrcReader<float, 2, 3>;
const float data[] = {-1.0, 1.0, 0.5};
const float expect_chan2[] = {0.0, 0.75};
EXPECT_EQ(SR::Read(data, 0), data[0]);
EXPECT_EQ(SR::Read(data, 1), data[1]);
EXPECT_EQ(SR::Read(data, 2), expect_chan2[0]);
EXPECT_EQ(SR::Read(data + 1, 0), data[1]);
EXPECT_EQ(SR::Read(data + 1, 1), data[2]);
EXPECT_EQ(SR::Read(data + 1, 2), expect_chan2[1]);
}
// Validate 2->4 channel mapping.
// Expectation: 4-chan destination is "4 corners" FL.FR.BL.BR (or other L.R.L.R geometry).
// We map each source channel equally to the two destination channels on each side.
TEST(SrcReaderTest, Map_2_4) {
using SR = mixer::SrcReader<float, 2, 4>;
const float data[] = {-1.0, 1.0, 0.5};
EXPECT_EQ(SR::Read(data, 0), data[0]);
EXPECT_EQ(SR::Read(data, 1), data[1]);
EXPECT_EQ(SR::Read(data, 2), SR::Read(data, 0));
EXPECT_EQ(SR::Read(data, 3), SR::Read(data, 1));
EXPECT_EQ(SR::Read(data + 1, 0), data[1]);
EXPECT_EQ(SR::Read(data + 1, 1), data[2]);
EXPECT_EQ(SR::Read(data + 1, 2), SR::Read(data + 1, 0));
EXPECT_EQ(SR::Read(data + 1, 3), SR::Read(data + 1, 1));
}
// Validate 3->1 channel mapping.
// Expectation: each source channel should contribute equally to the one destination channel.
// The one destination channel is average of all source channels.
TEST(SrcReaderTest, Map_3_1) {
using SR = mixer::SrcReader<float, 3, 1>;
const float data[] = {-0.5, 1.0, 1.0, -0.8};
const float expect[] = {0.5, 0.4};
EXPECT_EQ(SR::Read(data, 0), expect[0]);
EXPECT_EQ(SR::Read(data, 1), SR::Read(data, 0));
EXPECT_EQ(SR::Read(data, 2), SR::Read(data, 0));
EXPECT_EQ(SR::Read(data + 1, 0), expect[1]);
EXPECT_EQ(SR::Read(data + 1, 1), SR::Read(data + 1, 0));
EXPECT_EQ(SR::Read(data + 1, 2), SR::Read(data + 1, 0));
}
// Validate 3->2 channel mapping.
// Expectation: 3-channel source is L.R.C (or some other geometry where third source channel should
// be distributed evenly into both destination channels).
//
// Conceptually, dest[0] becomes source[0] + source[2]/2; dest[1] becomes source[1] + source[2]/2.
// However when contributing source[2] to two destinations, we must conserve the POWER of
// source[2] relative to the other source channels -- we add sqr(0.5)*source[2] (not 0.5*source[2])
// to each side -- and then normalize the result to eliminate clipping.
//
// dest[0] = (0.585786... * source[0]) + (0.414213... * source[2])
// dest[1] = (0.585786... * source[1]) + (0.414213... * source[2])
TEST(SrcReaderTest, Map_3_2) {
using SR = mixer::SrcReader<float, 3, 2>;
const float data[] = {1, -0.5, -0.5, -1};
const float expect[] = {0.378679656f, -0.5f, -0.70710678f};
EXPECT_FLOAT_EQ(SR::Read(data, 0), expect[0]);
EXPECT_FLOAT_EQ(SR::Read(data, 1), expect[1]);
EXPECT_FLOAT_EQ(SR::Read(data + 1, 0), expect[2]);
EXPECT_EQ(SR::Read(data + 1, 1), SR::Read(data + 1, 0));
}
// No built-in 3->4 mapping is provided
// Validate 4->1 channel mapping.
// Expectation: each source channel should contribute equally to the one destination channel.
// The one destination channel is average of all source channels.
TEST(SrcReaderTest, Map_4_1) {
using SR = mixer::SrcReader<float, 4, 1>;
const float data[] = {-0.25, 0.75, 1.0, -0.5, -0.05};
const float expect[] = {0.25, 0.3};
EXPECT_EQ(SR::Read(data, 0), expect[0]);
EXPECT_EQ(SR::Read(data, 1), SR::Read(data, 0));
EXPECT_EQ(SR::Read(data, 2), SR::Read(data, 0));
EXPECT_EQ(SR::Read(data, 3), SR::Read(data, 0));
EXPECT_EQ(SR::Read(data + 1, 0), expect[1]);
EXPECT_EQ(SR::Read(data + 1, 1), SR::Read(data + 1, 0));
EXPECT_EQ(SR::Read(data + 1, 2), SR::Read(data + 1, 0));
EXPECT_EQ(SR::Read(data + 1, 3), SR::Read(data + 1, 0));
}
// Validate 4->2 channel mapping.
// Expectation: 4-chan source is "4 corners" FL.FR.BL.BR (or other L.R.L.R geometry).
// We assign equal weight to the source channels on each side.
// dest[0] is average of source[0] and [2]; dest[1] is average of source[1] and [3].
TEST(SrcReaderTest, Map_4_2) {
using SR = mixer::SrcReader<float, 4, 2>;
const float data[] = {-0.25, 0.75, 1.0, -0.5, 0.0};
const float expect[] = {0.375, 0.125, 0.5};
EXPECT_EQ(SR::Read(data, 0), expect[0]);
EXPECT_EQ(SR::Read(data, 1), expect[1]);
EXPECT_EQ(SR::Read(data + 1, 0), expect[1]);
EXPECT_EQ(SR::Read(data + 1, 1), expect[2]);
}
// No built-in 4->3 mapping is provided
// No built-in mappings are provided for configs with source channels or dest channels above 4
// (other than the "pass-thru" "N->N and "unity" 1->N mappings).
//
// DestMixer tests focus primarily on accumulate functionality, since DestMixer internally uses
// SampleScaler which is validated above.
//
// MUTED never contributes the new sample to the mix. Both accum and no-accum options are validated.
TEST(DestMixerTest, Mute) {
using DmNoAccum = mixer::DestMixer<mixer::ScalerType::MUTED, false>;
using DmAccum = mixer::DestMixer<mixer::ScalerType::MUTED, true>;
const float prev = -0.1f;
const float input[] = {-0.5f, 1.0f};
const Gain::AScale scale[] = {1.5f, 0.75f};
const float expect = 0.0;
EXPECT_EQ(DmNoAccum::Mix(prev, input[0], scale[0]), expect);
EXPECT_EQ(DmNoAccum::Mix(prev, input[0], scale[1]), expect);
EXPECT_EQ(DmNoAccum::Mix(prev, input[1], scale[0]), expect);
EXPECT_EQ(DmNoAccum::Mix(prev, input[1], scale[1]), expect);
EXPECT_EQ(DmAccum::Mix(prev, input[0], scale[0]), expect + prev);
EXPECT_EQ(DmAccum::Mix(prev, input[0], scale[1]), expect + prev);
EXPECT_EQ(DmAccum::Mix(prev, input[1], scale[0]), expect + prev);
EXPECT_EQ(DmAccum::Mix(prev, input[1], scale[1]), expect + prev);
}
// NE_UNITY scales a new sample as it is added to the mix. In this scope, RAMPING behaves
// identically to NE_UNITY. Both accumulate and no-accumulate options are validated.
TEST(DestMixerTest, NeUnity) {
const float prev = -0.1f;
const float input[] = {-0.5f, 1.0f};
const Gain::AScale scale[] = {1.5f, 0.75f};
const float expect[] = {-0.75f, -0.375f, 1.5f, 0.75f};
EXPECT_EQ((mixer::DestMixer<mixer::ScalerType::NE_UNITY, false>::Mix(prev, input[0], scale[0])),
expect[0]);
EXPECT_EQ((mixer::DestMixer<mixer::ScalerType::NE_UNITY, false>::Mix(prev, input[0], scale[1])),
expect[1]);
EXPECT_EQ((mixer::DestMixer<mixer::ScalerType::NE_UNITY, false>::Mix(prev, input[1], scale[0])),
expect[2]);
EXPECT_EQ((mixer::DestMixer<mixer::ScalerType::NE_UNITY, true>::Mix(prev, input[0], scale[0])),
expect[0] + prev);
EXPECT_EQ((mixer::DestMixer<mixer::ScalerType::NE_UNITY, true>::Mix(prev, input[0], scale[1])),
expect[1] + prev);
EXPECT_EQ((mixer::DestMixer<mixer::ScalerType::NE_UNITY, true>::Mix(prev, input[1], scale[1])),
expect[3] + prev);
EXPECT_EQ((mixer::DestMixer<mixer::ScalerType::NE_UNITY, true>::Mix(prev, input[0], scale[0])),
(mixer::DestMixer<mixer::ScalerType::RAMPING, true>::Mix(prev, input[0], scale[0])));
EXPECT_EQ((mixer::DestMixer<mixer::ScalerType::NE_UNITY, true>::Mix(prev, input[1], scale[0])),
(mixer::DestMixer<mixer::ScalerType::RAMPING, true>::Mix(prev, input[1], scale[0])));
EXPECT_EQ((mixer::DestMixer<mixer::ScalerType::NE_UNITY, true>::Mix(prev, input[1], scale[1])),
(mixer::DestMixer<mixer::ScalerType::RAMPING, true>::Mix(prev, input[1], scale[1])));
EXPECT_EQ((mixer::DestMixer<mixer::ScalerType::NE_UNITY, false>::Mix(prev, input[0], scale[1])),
(mixer::DestMixer<mixer::ScalerType::RAMPING, false>::Mix(prev, input[0], scale[1])));
EXPECT_EQ((mixer::DestMixer<mixer::ScalerType::NE_UNITY, false>::Mix(prev, input[1], scale[0])),
(mixer::DestMixer<mixer::ScalerType::RAMPING, false>::Mix(prev, input[1], scale[0])));
EXPECT_EQ((mixer::DestMixer<mixer::ScalerType::NE_UNITY, false>::Mix(prev, input[1], scale[1])),
(mixer::DestMixer<mixer::ScalerType::RAMPING, false>::Mix(prev, input[1], scale[1])));
}
// UNITY will not scale a sample as it adds it to the mix. Validate both accumulate and no-accum.
TEST(DestMixerTest, Unity) {
using DmNoAccum = mixer::DestMixer<mixer::ScalerType::EQ_UNITY, false>;
using DmAccum = mixer::DestMixer<mixer::ScalerType::EQ_UNITY, true>;
const float prev = -0.1f;
const float input[] = {-0.5f, 1.0f};
const Gain::AScale scale[] = {1.5f, 0.75f};
EXPECT_EQ(DmNoAccum::Mix(prev, input[0], scale[0]), input[0]);
EXPECT_EQ(DmNoAccum::Mix(prev, input[0], scale[1]), input[0]);
EXPECT_EQ(DmNoAccum::Mix(prev, input[1], scale[0]), input[1]);
EXPECT_EQ(DmNoAccum::Mix(prev, input[1], scale[1]), input[1]);
EXPECT_EQ(DmAccum::Mix(prev, input[0], scale[0]), input[0] + prev);
EXPECT_EQ(DmAccum::Mix(prev, input[0], scale[1]), input[0] + prev);
EXPECT_EQ(DmAccum::Mix(prev, input[1], scale[0]), input[1] + prev);
EXPECT_EQ(DmAccum::Mix(prev, input[1], scale[1]), input[1] + prev);
}
} // namespace
} // namespace media::audio