blob: ce58a68a56015a2184e913328f7f7b0cb8f60787 [file] [log] [blame]
// Copyright 2018 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 "garnet/bin/media/audio_core/mixer/test/audio_result.h"
#include "garnet/bin/media/audio_core/mixer/test/mixer_tests_shared.h"
#include "lib/fxl/logging.h"
namespace media::audio::test {
// Convenience abbreviation within this source file to shorten names
using Resampler = ::media::audio::Mixer::Resampler;
// Ideal dynamic range measurement is exactly equal to the reduction in gain.
// Ideal accompanying noise is ideal noise floor, minus the reduction in gain.
void MeasureSummaryDynamicRange(float gain_db, double* level_db,
double* sinad_db) {
MixerPtr mixer = SelectMixer(fuchsia::media::AudioSampleFormat::FLOAT, 1,
48000, 1, 48000, Resampler::SampleAndHold);
std::vector<float> source(kFreqTestBufSize);
std::vector<float> accum(kFreqTestBufSize);
// Populate source buffer; mix it (pass-thru) to accumulation buffer
OverwriteCosine(source.data(), kFreqTestBufSize,
FrequencySet::kReferenceFreq);
uint32_t dest_offset = 0;
int32_t frac_src_offset = 0;
Bookkeeping info;
info.gain.SetSourceGain(gain_db);
mixer->Mix(accum.data(), kFreqTestBufSize, &dest_offset, source.data(),
kFreqTestBufSize << kPtsFractionalBits, &frac_src_offset, false,
&info);
EXPECT_EQ(kFreqTestBufSize, dest_offset);
EXPECT_EQ(static_cast<int32_t>(kFreqTestBufSize << kPtsFractionalBits),
frac_src_offset);
// Copy result to double-float buffer, FFT (freq-analyze) it at high-res
double magn_signal, magn_other;
MeasureAudioFreq(accum.data(), kFreqTestBufSize, FrequencySet::kReferenceFreq,
&magn_signal, &magn_other);
*level_db = Gain::DoubleToDb(magn_signal);
*sinad_db = Gain::DoubleToDb(magn_signal / magn_other);
}
// Measure dynamic range at two gain settings: less than 1.0 by the smallest
// increment possible, as well as the smallest increment detectable (the
// closest-to-1.0 gain that actually causes incoming data values to change).
TEST(DynamicRange, Epsilon) {
double unity_level_db, unity_sinad_db, level_db, sinad_db;
MeasureSummaryDynamicRange(0.0f, &unity_level_db, &unity_sinad_db);
EXPECT_NEAR(unity_level_db, 0.0, AudioResult::kPrevLevelToleranceSourceFloat);
EXPECT_GE(unity_sinad_db, AudioResult::kPrevFloorSourceFloat);
AudioResult::LevelToleranceSourceFloat =
fmax(AudioResult::LevelToleranceSourceFloat, abs(unity_level_db));
// kMinGainDbUnity is the lowest (furthest-from-Unity) with no observable
// attenuation on float32 (i.e. the smallest indistinguishable from Unity).
// Just above the 'first detectable reduction' scale; should be same as unity.
MeasureSummaryDynamicRange(AudioResult::kMinGainDbUnity, &level_db,
&sinad_db);
EXPECT_EQ(level_db, unity_level_db);
EXPECT_EQ(sinad_db, unity_sinad_db);
// kMaxGainDbNonUnity is the highest (closest-to-Unity) with observable effect
// on full-scale (i.e. largest sub-Unity AScale distinguishable from Unity).
// At this 'detectable reduction' scale, level and noise floor are reduced.
MeasureSummaryDynamicRange(AudioResult::kMaxGainDbNonUnity,
&AudioResult::LevelEpsilonDown,
&AudioResult::SinadEpsilonDown);
EXPECT_NEAR(AudioResult::LevelEpsilonDown, AudioResult::kPrevLevelEpsilonDown,
AudioResult::kPrevDynRangeTolerance);
AudioResult::DynRangeTolerance = fmax(
AudioResult::DynRangeTolerance,
abs(AudioResult::LevelEpsilonDown - AudioResult::kPrevLevelEpsilonDown));
EXPECT_LT(AudioResult::LevelEpsilonDown, unity_level_db);
EXPECT_GE(AudioResult::SinadEpsilonDown, AudioResult::kPrevSinadEpsilonDown);
}
// Measure dynamic range (signal level, noise floor) when gain is -30dB.
TEST(DynamicRange, 30Down) {
MeasureSummaryDynamicRange(-30.0f, &AudioResult::Level30Down,
&AudioResult::Sinad30Down);
AudioResult::DynRangeTolerance = fmax(AudioResult::DynRangeTolerance,
abs(AudioResult::Level30Down + 30.0));
EXPECT_NEAR(AudioResult::Level30Down, -30.0,
AudioResult::kPrevDynRangeTolerance);
EXPECT_GE(AudioResult::Sinad30Down, AudioResult::kPrevSinad30Down);
}
// Measure dynamic range (signal level, noise floor) when gain is -60dB.
TEST(DynamicRange, 60Down) {
MeasureSummaryDynamicRange(-60.0f, &AudioResult::Level60Down,
&AudioResult::Sinad60Down);
AudioResult::DynRangeTolerance = fmax(AudioResult::DynRangeTolerance,
abs(AudioResult::Level60Down + 60.0));
EXPECT_NEAR(AudioResult::Level60Down, -60.0,
AudioResult::kPrevDynRangeTolerance);
EXPECT_GE(AudioResult::Sinad60Down, AudioResult::kPrevSinad60Down);
}
// Measure dynamic range (signal level, noise floor) when gain is -90dB.
TEST(DynamicRange, 90Down) {
MeasureSummaryDynamicRange(-90.0f, &AudioResult::Level90Down,
&AudioResult::Sinad90Down);
AudioResult::DynRangeTolerance = fmax(AudioResult::DynRangeTolerance,
abs(AudioResult::Level90Down + 90.0));
EXPECT_NEAR(AudioResult::Level90Down, -90.0,
AudioResult::kPrevDynRangeTolerance);
EXPECT_GE(AudioResult::Sinad90Down, AudioResult::kPrevSinad90Down);
}
// Test our mix level and noise floor, when rechannelizing mono into stereo.
TEST(DynamicRange, MonoToStereo) {
MixerPtr mixer = SelectMixer(fuchsia::media::AudioSampleFormat::FLOAT, 1,
48000, 2, 48000, Resampler::SampleAndHold);
std::vector<float> source(kFreqTestBufSize);
std::vector<float> accum(kFreqTestBufSize * 2);
std::vector<float> left(kFreqTestBufSize);
// Populate mono source buffer; mix it (no SRC/gain) to stereo accumulator
OverwriteCosine(source.data(), kFreqTestBufSize,
FrequencySet::kReferenceFreq);
uint32_t dest_offset = 0;
int32_t frac_src_offset = 0;
Bookkeeping info;
mixer->Mix(accum.data(), kFreqTestBufSize, &dest_offset, source.data(),
kFreqTestBufSize << kPtsFractionalBits, &frac_src_offset, false,
&info);
EXPECT_EQ(kFreqTestBufSize, dest_offset);
EXPECT_EQ(static_cast<int32_t>(kFreqTestBufSize << kPtsFractionalBits),
frac_src_offset);
// Copy left result to double-float buffer, FFT (freq-analyze) it at high-res
for (uint32_t idx = 0; idx < kFreqTestBufSize; ++idx) {
EXPECT_EQ(accum[idx * 2], accum[(idx * 2) + 1]);
left[idx] = accum[idx * 2];
}
// Only need to analyze left side, since we verified that right is identical.
double magn_left_signal, magn_left_other, level_left_db, sinad_left_db;
MeasureAudioFreq(left.data(), kFreqTestBufSize, FrequencySet::kReferenceFreq,
&magn_left_signal, &magn_left_other);
level_left_db = Gain::DoubleToDb(magn_left_signal);
sinad_left_db = Gain::DoubleToDb(magn_left_signal / magn_left_other);
EXPECT_NEAR(level_left_db, 0.0, AudioResult::kPrevLevelToleranceSourceFloat);
AudioResult::LevelToleranceSourceFloat =
fmax(AudioResult::LevelToleranceSourceFloat, abs(level_left_db));
EXPECT_GE(sinad_left_db, AudioResult::kPrevFloorSourceFloat);
}
// Test our mix level and noise floor, when rechannelizing stereo into mono.
TEST(DynamicRange, StereoToMono) {
MixerPtr mixer = SelectMixer(fuchsia::media::AudioSampleFormat::FLOAT, 2,
48000, 1, 48000, Resampler::SampleAndHold);
std::vector<float> mono(kFreqTestBufSize);
std::vector<float> source(kFreqTestBufSize * 2);
std::vector<float> accum(kFreqTestBufSize);
// Populate a mono source buffer; copy it into left side of stereo buffer.
OverwriteCosine(mono.data(), kFreqTestBufSize, FrequencySet::kReferenceFreq,
kFullScaleFloatInputAmplitude);
for (uint32_t idx = 0; idx < kFreqTestBufSize; ++idx) {
source[idx * 2] = mono[idx];
}
// Populate a mono source buffer with same frequency and amplitude, phase-
// shifted by PI/2 (1/4 of a cycle); copy it into right side of stereo buffer.
OverwriteCosine(mono.data(), kFreqTestBufSize, FrequencySet::kReferenceFreq,
kFullScaleFloatInputAmplitude, M_PI / 2);
for (uint32_t idx = 0; idx < kFreqTestBufSize; ++idx) {
source[(idx * 2) + 1] = mono[idx];
}
uint32_t dest_offset = 0;
int32_t frac_src_offset = 0;
Bookkeeping info;
mixer->Mix(accum.data(), kFreqTestBufSize, &dest_offset, source.data(),
kFreqTestBufSize << kPtsFractionalBits, &frac_src_offset, false,
&info);
EXPECT_EQ(kFreqTestBufSize, dest_offset);
EXPECT_EQ(static_cast<int32_t>(kFreqTestBufSize << kPtsFractionalBits),
frac_src_offset);
// Copy result to double-float buffer, FFT (freq-analyze) it at high-res
double magn_signal, magn_other;
MeasureAudioFreq(accum.data(), kFreqTestBufSize, FrequencySet::kReferenceFreq,
&magn_signal, &magn_other);
AudioResult::LevelStereoMono = Gain::DoubleToDb(magn_signal);
AudioResult::FloorStereoMono =
Gain::DoubleToDb(kFullScaleFloatAccumAmplitude / magn_other);
// We added identical signals, so accuracy should be high. However, noise
// floor is doubled as well, so we expect 6dB reduction in sinad.
EXPECT_NEAR(AudioResult::LevelStereoMono, AudioResult::kPrevLevelStereoMono,
AudioResult::kPrevLevelToleranceStereoMono);
AudioResult::LevelToleranceStereoMono = fmax(
AudioResult::LevelToleranceStereoMono,
abs(AudioResult::LevelStereoMono - AudioResult::kPrevLevelStereoMono));
EXPECT_GE(AudioResult::FloorStereoMono, AudioResult::kPrevFloorStereoMono);
}
// Test mix level and noise floor, when accumulating sources.
//
// Mix 2 full-scale streams with gain exactly 50% (audio out 100%, master 50%),
// then measure level and sinad. On systems with robust gain processing, a
// post-SUM master gain stage reduces noise along with level, for the same noise
// floor as a single FS signal with 100% gain (98,49 dB for 16,8 respectively).
//
// When summing two full-scale streams, signal should be approx +6dBFS, and
// noise floor should be related to the bitwidth of source and accumulator
// (whichever is more narrow). Because our accumulator is still normalized to
// 16 bits, we expect the single-stream noise floor to be approx. 98 dB. This
// test emulates the mixing of two streams, along with the application of a
// master gain which reduces the mixed result to 50%, which should result in a
// signal which is exactly full-scale. Summing the two streams will sum the
// inherent noise as well, leading to a noise floor of 91-92 dB before taking
// gain into account. Once our architecture contains a post-SUM master gain,
// after applying a 0.5 master gain scaling we would expect this 91-92 dB
// SINAD to be reduced to perhaps 98 dB. Today master gain is combined with
// audio out gain, so it is pre-Sum.
template <typename T>
void MeasureMixFloor(double* level_mix_db, double* sinad_mix_db) {
MixerPtr mixer;
double amplitude, expected_amplitude;
// Why isn't expected_amplitude 1.0? int16 and int8 have more negative values
// than positive ones. To be linear without clipping, a full-scale signal
// reaches the max (such as 0x7FFF) but not the min (such as -0x8000). Thus,
// this magnitude is slightly less than the 1.0 we expect for float signals.
if (std::is_same<T, uint8_t>::value) {
mixer = SelectMixer(fuchsia::media::AudioSampleFormat::UNSIGNED_8, 1, 48000,
1, 48000, Resampler::SampleAndHold);
amplitude = kFullScaleInt8InputAmplitude;
expected_amplitude = kFullScaleInt8AccumAmplitude;
} else if (std::is_same<T, int16_t>::value) {
mixer = SelectMixer(fuchsia::media::AudioSampleFormat::SIGNED_16, 1, 48000,
1, 48000, Resampler::SampleAndHold);
amplitude = kFullScaleInt16InputAmplitude;
expected_amplitude = kFullScaleInt16AccumAmplitude;
} else if (std::is_same<T, int32_t>::value) {
mixer = SelectMixer(fuchsia::media::AudioSampleFormat::SIGNED_24_IN_32, 1,
48000, 1, 48000, Resampler::SampleAndHold);
amplitude = kFullScaleInt24In32InputAmplitude;
expected_amplitude = kFullScaleInt24In32AccumAmplitude;
} else {
mixer = SelectMixer(fuchsia::media::AudioSampleFormat::FLOAT, 1, 48000, 1,
48000, Resampler::SampleAndHold);
amplitude = kFullScaleFloatInputAmplitude;
expected_amplitude = kFullScaleFloatAccumAmplitude;
}
std::vector<T> source(kFreqTestBufSize);
std::vector<float> accum(kFreqTestBufSize);
OverwriteCosine(source.data(), kFreqTestBufSize, FrequencySet::kReferenceFreq,
amplitude);
uint32_t dest_offset = 0;
int32_t frac_src_offset = 0;
// -6.0206 dB leads to 0.500 scale (exactly 50%), to be mixed with itself
Bookkeeping info;
info.gain.SetSourceGain(-6.0205999f);
EXPECT_TRUE(mixer->Mix(accum.data(), kFreqTestBufSize, &dest_offset,
source.data(), kFreqTestBufSize << kPtsFractionalBits,
&frac_src_offset, false, &info));
// Accumulate the same (reference-frequency) wave.
dest_offset = 0;
frac_src_offset = 0;
EXPECT_TRUE(mixer->Mix(accum.data(), kFreqTestBufSize, &dest_offset,
source.data(), kFreqTestBufSize << kPtsFractionalBits,
&frac_src_offset, true, &info));
EXPECT_EQ(kFreqTestBufSize, dest_offset);
EXPECT_EQ(dest_offset << kPtsFractionalBits,
static_cast<uint32_t>(frac_src_offset));
// Copy result to double-float buffer, FFT (freq-analyze) it at high-res
double magn_signal, magn_other;
MeasureAudioFreq(accum.data(), kFreqTestBufSize, FrequencySet::kReferenceFreq,
&magn_signal, &magn_other);
*level_mix_db = Gain::DoubleToDb(magn_signal / expected_amplitude);
*sinad_mix_db = Gain::DoubleToDb(expected_amplitude / magn_other);
}
// Test our mix level and noise floor, when accumulating 8-bit sources.
TEST(DynamicRange, Mix_8) {
MeasureMixFloor<uint8_t>(&AudioResult::LevelMix8, &AudioResult::FloorMix8);
EXPECT_NEAR(AudioResult::LevelMix8, 0.0,
AudioResult::kPrevLevelToleranceMix8);
AudioResult::LevelToleranceMix8 =
fmax(AudioResult::LevelToleranceMix8, abs(AudioResult::LevelMix8));
// 8-bit noise floor should be approx -48dBFS. Because 8-bit sources are
// normalized up to 16-bit level, they can take advantage of fractional
// "footroom"; hence we still expect sinad of ~48dB.
EXPECT_GE(AudioResult::FloorMix8, AudioResult::kPrevFloorMix8)
<< std::setprecision(10) << AudioResult::FloorMix8;
}
// Test our mix level and noise floor, when accumulating 16-bit sources.
TEST(DynamicRange, Mix_16) {
MeasureMixFloor<int16_t>(&AudioResult::LevelMix16, &AudioResult::FloorMix16);
EXPECT_NEAR(AudioResult::LevelMix16, 0.0,
AudioResult::kPrevLevelToleranceMix16);
AudioResult::LevelToleranceMix16 =
fmax(AudioResult::LevelToleranceMix16, abs(AudioResult::LevelMix16));
// 16-bit noise floor should be approx -96dBFS. Noise is summed along with
// signal; therefore we expect sinad of ~90 dB.
EXPECT_GE(AudioResult::FloorMix16, AudioResult::kPrevFloorMix16)
<< std::setprecision(10) << AudioResult::FloorMix16;
}
// Test our mix level and noise floor, when accumulating 24-bit sources.
TEST(DynamicRange, Mix_24) {
MeasureMixFloor<int32_t>(&AudioResult::LevelMix24, &AudioResult::FloorMix24);
EXPECT_NEAR(AudioResult::LevelMix24, 0.0,
AudioResult::kPrevLevelToleranceMix24);
AudioResult::LevelToleranceMix24 =
fmax(AudioResult::LevelToleranceMix24, abs(AudioResult::LevelMix24));
// 24-bit noise floor should be approx -144dBFS. Noise is summed along with
// signal; therefore we expect sinad of ~138 dB.
EXPECT_GE(AudioResult::FloorMix24, AudioResult::kPrevFloorMix24)
<< std::setprecision(10) << AudioResult::FloorMix24;
}
// Test our mix level and noise floor, when accumulating float sources.
TEST(DynamicRange, Mix_Float) {
MeasureMixFloor<float>(&AudioResult::LevelMixFloat,
&AudioResult::FloorMixFloat);
EXPECT_NEAR(AudioResult::LevelMixFloat, 0.0,
AudioResult::kPrevLevelToleranceMixFloat);
AudioResult::LevelToleranceMixFloat = fmax(
AudioResult::LevelToleranceMixFloat, abs(AudioResult::LevelMixFloat));
// This should be same as 16-bit (~91dB), per accumulator precision. Once we
// increase accumulator precision, we expect this to improve, while Mix_16
// would not, as precision will still be limited by its 16-bit source.
EXPECT_GE(AudioResult::FloorMixFloat, AudioResult::kPrevFloorMixFloat)
<< std::setprecision(10) << AudioResult::FloorMixFloat;
}
} // namespace media::audio::test