blob: 41bb39383a64899f1160d2913891800e7cbb0ee9 [file] [log] [blame]
// Copyright 2021 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/analysis/dropout.h"
#include <cmath>
#include <limits>
#include <gtest/gtest.h>
#include "src/media/audio/lib/analysis/generators.h"
using ASF = fuchsia::media::AudioSampleFormat;
namespace media::audio {
// SilentPacketChecker tests
// All samples in a buffer must be silent, for SilentPacketChecker to qualify a buffer as silent.
// These methods return [false] if the buffer contains even one non-silent sample.
//
TEST(SilentPacketChecker, Uint8) {
uint8_t source_data[4] = {0, 0x80, 0x80, 0x80};
EXPECT_FALSE(SilentPacketChecker::IsSilenceUint8(source_data, 4));
EXPECT_TRUE(SilentPacketChecker::IsSilenceUint8(source_data + 1, 3));
}
TEST(SilentPacketChecker, Int16) {
int16_t source_data[6] = {0, 0, 0, 0, 0, 0x0001};
EXPECT_TRUE(SilentPacketChecker::IsSilenceInt16(source_data, 5));
EXPECT_FALSE(SilentPacketChecker::IsSilenceInt16(source_data + 3, 3));
}
// We ignore the fourth, least-significant byte in padded-24 frames.
TEST(SilentPacketChecker, Int24Padded) {
int32_t source_data[6] = {
0, 0, 0x000000FF, 0, 0, static_cast<int32_t>(0xFFFFFFFF),
};
EXPECT_TRUE(SilentPacketChecker::IsSilenceInt24In32(source_data, 5));
EXPECT_FALSE(SilentPacketChecker::IsSilenceInt24In32(source_data, 6));
}
TEST(SilentPacketChecker, Float32) {
float source_data[8] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f};
EXPECT_TRUE(SilentPacketChecker::IsSilenceFloat32(source_data, 6));
EXPECT_TRUE(SilentPacketChecker::IsSilenceFloat32(source_data + 1, 6));
EXPECT_FALSE(SilentPacketChecker::IsSilenceFloat32(source_data + 2, 6));
}
// The numeric range [-numeric_limits<float>::epsilon(),numeric_limits<float>::epsilon()] is
// considered equivalent to +/-0.0f (absolute silence).
TEST(SilentPacketChecker, Epsilon) {
float bad_vals[] = {
2.0f * std::numeric_limits<float>::epsilon(),
std::numeric_limits<float>::epsilon(),
-std::numeric_limits<float>::epsilon(),
-2.0f * std::numeric_limits<float>::epsilon(),
};
EXPECT_FALSE(SilentPacketChecker::IsSilenceFloat32(bad_vals, 4));
EXPECT_TRUE(SilentPacketChecker::IsSilenceFloat32(bad_vals + 1, 2));
EXPECT_TRUE(SilentPacketChecker::IsSilenceFloat32(bad_vals + 2, 1));
EXPECT_FALSE(SilentPacketChecker::IsSilenceFloat32(bad_vals + 3, 1));
}
// DropoutPowerChecker tests
//
TEST(DropoutPowerChecker, Constant) {
constexpr int32_t kSamplesPerSecond = 48000;
constexpr float kConstValue = 0.12345f;
const auto format = Format::Create<ASF::FLOAT>(1, kSamplesPerSecond).take_value();
auto buf = GenerateConstantAudio(format, 8, kConstValue);
PowerChecker checker(4, 1, kConstValue);
for (size_t i = 0; i <= 4; ++i) {
EXPECT_TRUE(checker.Check(&buf.samples()[i], i, 4, true))
<< "samples [" << i << "] to [" << i + 3 << "]";
}
for (size_t i = 0; i < 8; ++i) {
EXPECT_TRUE(checker.Check(&buf.samples()[i], i, 1, true)) << "sample[" << i << "]";
}
// Check a good signal, then inject a glitch and check that it is detected.
buf.samples()[3] = 0.0f;
for (size_t i = 0; i < 4; ++i) {
EXPECT_FALSE(checker.Check(&buf.samples()[i], i, 4, false))
<< "samples [" << i << "] to [" << i + 3 << "]";
}
EXPECT_TRUE(checker.Check(&buf.samples()[4], 4, 4, true)) << "samples [4] to [7]";
for (size_t i = 0; i < 8; ++i) {
if (i == 3) {
EXPECT_FALSE(checker.Check(&buf.samples()[i], i, 1, false)) << "sample[" << i << "]";
} else {
EXPECT_TRUE(checker.Check(&buf.samples()[i], i, 1, true)) << "sample[" << i << "]";
}
}
}
TEST(DropoutPowerChecker, Sine) {
constexpr int32_t kSamplesPerSecond = 48000;
constexpr double kRelativeFreq = 1.0;
const auto format = Format::Create<ASF::FLOAT>(1, kSamplesPerSecond).take_value();
auto buf = GenerateCosineAudio(format, 8, kRelativeFreq);
PowerChecker checker(4, 1, M_SQRT1_2);
for (size_t i = 0; i <= 4; ++i) {
EXPECT_TRUE(checker.Check(&buf.samples()[i], i, 4, true))
<< "samples [" << i << "] to [" << i + 3 << "]";
}
for (size_t i = 0; i < 8; ++i) {
EXPECT_TRUE(checker.Check(&buf.samples()[i], i, 1, true)) << "sample[" << i << "]";
}
// Check a good signal, then inject a glitch and check that's detected.
buf.samples()[3] = 0.0f;
for (size_t i = 0; i < 4; ++i) {
EXPECT_FALSE(checker.Check(&buf.samples()[i], i, 4, false))
<< "samples [" << i << "] to [" << i + 3 << "]";
}
EXPECT_TRUE(checker.Check(&buf.samples()[4], 4, 4, true)) << "samples [4] to [7]";
}
TEST(DropoutPowerChecker, Reset) {
constexpr float kConstValue = 0.12345f;
constexpr float kBadValue = 0.01234f;
PowerChecker checker(3, 1, kConstValue);
// We haven't finished the next RMS window, so we won't fail yet.
EXPECT_TRUE(checker.Check(&kBadValue, 0, 1, true));
EXPECT_TRUE(checker.Check(&kBadValue, 1, 1, true));
EXPECT_FALSE(checker.Check(&kBadValue, 2, 1, false));
EXPECT_TRUE(checker.Check(&kBadValue, 3, 1, true));
EXPECT_TRUE(checker.Check(&kBadValue, 4, 1, true));
// Without Reset(), this Check() should fail.
checker.Reset();
EXPECT_TRUE(checker.Check(&kBadValue, 5, 1, true));
EXPECT_TRUE(checker.Check(&kBadValue, 6, 1, true));
// Because of position discontinuity, these Check()s will pass.
EXPECT_TRUE(checker.Check(&kBadValue, 8, 1, true));
EXPECT_TRUE(checker.Check(&kBadValue, 9, 1, true));
EXPECT_FALSE(checker.Check(&kBadValue, 10, 1, false));
}
// These test cases must be carefully written, because SilenceChecker::kOnlyLogFailureOnEnd might be
// set, which means we don't log our ERROR message (even if Check returns false) until the silent
// range of frames ends (even if that particular Check returns true).
TEST(DropoutSilenceChecker, Reset) {
// Allow two consecutive silent frames, but not three.
SilenceChecker checker(2, 1);
float bad_vals[] = {0.0f, 0.0f, 0.0f, 0.5f};
EXPECT_FALSE(checker.Check(bad_vals, 0, 3, false));
// Ensure that the counter is NOT reset after the above failure (increments from 3 to 4).
EXPECT_FALSE(checker.Check(bad_vals, 3, 4, false));
// Ensure that the counter is reset (from 2 silent frames to 0) by position discontinuity.
EXPECT_TRUE(checker.Check(bad_vals, 6, 2, true));
EXPECT_TRUE(checker.Check(bad_vals, 0, 2, true));
// Ensure that the counter is reset by a good value.
auto non_silent_val = bad_vals[3];
EXPECT_TRUE(checker.Check(&non_silent_val, 2, 1, true));
EXPECT_TRUE(checker.Check(bad_vals, 3, 2, true));
// Ensure that the counter is reset by an explicit Reset().
checker.Reset(0, true);
EXPECT_TRUE(checker.Check(bad_vals, 0, 2, true));
}
// All samples in a frame must be silent, to qualify as a silent frame for this checker.
TEST(DropoutSilenceChecker, EntireFrame) {
// Allow one silent frame but not two.
SilenceChecker checker(1, 2);
float source_data[12] = {
0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
};
EXPECT_TRUE(checker.Check(source_data, 0, 6, true));
EXPECT_FALSE(checker.Check(source_data + 1, 1, 5, false));
}
TEST(DropoutSilenceChecker, Sine) {
constexpr int32_t kSamplesPerSecond = 48000;
constexpr double kRelativeFreq = 1.0;
const auto format = Format::Create<ASF::FLOAT>(1, kSamplesPerSecond).take_value();
auto buf = GenerateCosineAudio(format, 8, kRelativeFreq);
// Allow a silent frame, but not two consecutive ones.
SilenceChecker checker(1, 1);
EXPECT_TRUE(checker.Check(buf.samples().data(), 0, 8, true));
// Now inject a glitch and ensure it is detected.
// Exactly one wavelength of our cosine signal fits into the 8-sample buffer. This means that the
// values at indices [2] and [6] will be zero. Thus, setting [5] to zero should cause a failure.
buf.samples()[5] = 0.0f;
EXPECT_FALSE(checker.Check(buf.samples().data(), 0, 8, false));
}
// Values as far from zero as +/-numeric_limits<float>::epsilon() are still considered silent.
TEST(DropoutSilenceChecker, Epsilon) {
SilenceChecker checker(1, 1);
float bad_vals[] = {
std::numeric_limits<float>::epsilon(), -1.0f * std::numeric_limits<float>::epsilon(),
2.0f * std::numeric_limits<float>::epsilon(), -2.0f * std::numeric_limits<float>::epsilon()};
EXPECT_TRUE(checker.Check(&bad_vals[0], 0, 1, true));
EXPECT_FALSE(checker.Check(&bad_vals[1], 1, 2, false));
EXPECT_TRUE(checker.Check(&bad_vals[2], 2, 2, true));
}
class DropoutBasicTimestampChecker : public testing::Test {
protected:
static std::string SeparatedPts(int64_t pts) { return BasicTimestampChecker::separated_pts(pts); }
};
TEST_F(DropoutBasicTimestampChecker, BasicCheck) {
BasicTimestampChecker checker;
EXPECT_TRUE(checker.Check(0, 16, true));
EXPECT_TRUE(checker.Check(16, 0, true));
EXPECT_TRUE(checker.Check(16, 2, true));
EXPECT_FALSE(checker.Check(0, 16, false));
}
// Reset() sets the previous pts-range end to 0.
TEST_F(DropoutBasicTimestampChecker, Reset) {
BasicTimestampChecker checker;
EXPECT_TRUE(checker.Check(0, 4, true));
EXPECT_FALSE(checker.Check(3, 4, false));
EXPECT_TRUE(checker.Check(7, 4, true));
checker.Reset();
EXPECT_TRUE(checker.Check(14, 16, true));
checker.Reset(42);
EXPECT_TRUE(checker.Check(42, 68, true));
}
TEST_F(DropoutBasicTimestampChecker, SeparatedPts) {
EXPECT_EQ(SeparatedPts(0), "000");
EXPECT_EQ(SeparatedPts(1234), "001'234");
EXPECT_EQ(SeparatedPts(7654321), "007'654'321");
EXPECT_EQ(SeparatedPts(-1), "-001");
EXPECT_EQ(SeparatedPts(-4321), "-004'321");
}
} // namespace media::audio