blob: 7825e6ebae73aa1da51be40ae8dc424c7a9c1bb7 [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/lib/test/comparators.h"
#include <optional>
#include <sstream>
#include <gtest/gtest.h>
#include "src/lib/fxl/strings/string_printf.h"
#include "src/media/audio/lib/analysis/analysis.h"
namespace media::audio::test {
namespace {
template <fuchsia::media::AudioSampleFormat SampleFormat>
std::string CompareAudioBuffersShowContext(AudioBufferSlice<SampleFormat> got_slice,
AudioBufferSlice<SampleFormat> want_slice,
CompareAudioBufferOptions options, size_t frame) {
size_t raw_frame = got_slice.start_frame() + frame;
// Relative to got_slice.buf.
size_t packet = raw_frame / options.num_frames_per_packet;
size_t packet_start = packet * options.num_frames_per_packet;
size_t packet_end =
std::min(packet_start + options.num_frames_per_packet, got_slice.buf()->NumFrames());
// Display got/want side-by-side.
std::ostringstream out;
out << fxl::StringPrintf("\n\n Frames %zu to %zu (packet %zu), got vs want: ", packet_start,
packet_end, packet);
for (auto frame = packet_start; frame < packet_end; ++frame) {
if (frame % 8 == 0) {
out << fxl::StringPrintf("\n [%6lu] ", frame);
} else {
out << " | ";
}
for (auto chan = 0u; chan < got_slice.format().channels(); ++chan) {
out << SampleFormatTraits<SampleFormat>::ToString(got_slice.buf()->SampleAt(frame, chan));
}
out << " vs ";
for (auto chan = 0u; chan < got_slice.format().channels(); ++chan) {
// Translate to the equivalent offset in want_slice.buf.
size_t want_frame = frame + (static_cast<ssize_t>(want_slice.start_frame()) -
static_cast<ssize_t>(got_slice.start_frame()));
if (!want_slice.empty() && want_frame < want_slice.buf()->NumFrames()) {
out << SampleFormatTraits<SampleFormat>::ToString(
want_slice.buf()->SampleAt(want_frame, chan));
} else {
out << SampleFormatTraits<SampleFormat>::ToString(
SampleFormatTraits<SampleFormat>::kSilentValue);
}
}
}
out << "\n";
return out.str();
}
template <fuchsia::media::AudioSampleFormat SampleFormat>
std::string ExpectAudioBuffersShowContext(AudioBufferSlice<SampleFormat> slice,
ExpectAudioBufferOptions options, size_t frame) {
size_t raw_frame = slice.start_frame() + frame;
// Relative to slice.buf.
size_t packet = raw_frame / options.num_frames_per_packet;
size_t packet_start = packet * options.num_frames_per_packet;
size_t packet_end =
std::min(packet_start + options.num_frames_per_packet, slice.buf()->NumFrames());
// Display the packet of got containing frame.
std::ostringstream out;
out << fxl::StringPrintf("\n\n Frames %zu to %zu (packet %zu): ", packet_start, packet_end,
packet);
for (auto frame = packet_start; frame < packet_end; ++frame) {
if (frame % 16 == 0) {
out << fxl::StringPrintf("\n [%6lu] ", frame);
} else {
out << " | ";
}
for (auto chan = 0u; chan < slice.format().channels(); ++chan) {
out << SampleFormatTraits<SampleFormat>::ToString(slice.buf()->SampleAt(frame, chan));
}
}
out << "\n";
return out.str();
}
// Compare with bit-for-bit equality.
template <fuchsia::media::AudioSampleFormat SampleFormat>
void CompareAudioBuffersExact(AudioBufferSlice<SampleFormat> got_slice,
AudioBufferSlice<SampleFormat> want_slice,
CompareAudioBufferOptions options) {
using SampleT = typename AudioBuffer<SampleFormat>::SampleT;
// Compare sample-by-sample.
for (size_t frame = 0; frame < got_slice.NumFrames(); frame++) {
for (size_t chan = 0; chan < got_slice.format().channels(); chan++) {
SampleT got = got_slice.SampleAt(frame, chan);
SampleT want = SampleFormatTraits<SampleFormat>::kSilentValue;
if (frame < want_slice.NumFrames()) {
want = want_slice.SampleAt(frame, chan);
if (options.partial && got == SampleFormatTraits<SampleFormat>::kSilentValue &&
want != got) {
// Expect that audio data is written one complete frame at a time.
EXPECT_EQ(0u, chan);
// Found the end of the prefix.
want_slice = AudioBufferSlice<SampleFormat>();
want = SampleFormatTraits<SampleFormat>::kSilentValue;
}
}
if (want != got) {
size_t raw_frame = got_slice.start_frame() + frame;
ADD_FAILURE() << options.test_label << ": unexpected value at frame " << raw_frame
<< ", channel " << chan << ":\n got[" << raw_frame
<< "] = " << SampleFormatTraits<SampleFormat>::ToString(got) << "\n want["
<< raw_frame << "] = " << SampleFormatTraits<SampleFormat>::ToString(want)
<< CompareAudioBuffersShowContext(got_slice, want_slice, options, frame);
return;
}
}
}
}
// Compare with approximate equality.
template <fuchsia::media::AudioSampleFormat SampleFormat>
void CompareAudioBuffersApprox(AudioBufferSlice<SampleFormat> got_slice,
AudioBufferSlice<SampleFormat> want_slice, double want_slice_rms,
CompareAudioBufferOptions options) {
using SampleT = typename AudioBuffer<SampleFormat>::SampleT;
// On failure, we print the context around the first sample that differed.
struct Sample {
size_t frame, chan;
SampleT got, want;
};
std::optional<Sample> first_difference;
double diff_ss = 0; // sum((got_slice.samples[k] - want_slice.samples[k])^2)
// Compute RMS of got_slice - want_slice.
for (size_t frame = 0; frame < got_slice.NumFrames(); frame++) {
for (size_t chan = 0; chan < got_slice.format().channels(); chan++) {
SampleT got = got_slice.SampleAt(frame, chan);
SampleT want = SampleFormatTraits<SampleFormat>::kSilentValue;
if (frame < want_slice.NumFrames()) {
want = want_slice.SampleAt(frame, chan);
if (options.partial && got == SampleFormatTraits<SampleFormat>::kSilentValue &&
want != got) {
// Expect that audio data is written one complete frame at a time.
EXPECT_EQ(0u, chan);
// Found the end of the prefix.
want_slice = AudioBufferSlice<SampleFormat>();
want = SampleFormatTraits<SampleFormat>::kSilentValue;
}
}
if (want == got) {
continue;
}
if (first_difference == std::nullopt) {
first_difference = {.frame = frame, .chan = chan, .got = got, .want = want};
}
double diff = SampleFormatTraits<SampleFormat>::ToFloat(got) -
SampleFormatTraits<SampleFormat>::ToFloat(want);
diff_ss += diff * diff;
}
}
if (first_difference == std::nullopt) {
return; // bit-for-bit equal
}
double diff_rms = sqrt(diff_ss / got_slice.NumSamples());
double relative_error = diff_rms / want_slice_rms;
if (relative_error <= options.max_relative_error) {
return; // approximately equal
}
size_t raw_frame = got_slice.start_frame() + first_difference->frame;
ADD_FAILURE() << options.test_label << ": relative error " << relative_error << " > "
<< options.max_relative_error << " (diff_rms = " << diff_rms
<< ", want_slice_rms = " << want_slice_rms << ")"
<< "\nDifferences start at frame " << raw_frame << ":\n got[" << raw_frame
<< "] = " << SampleFormatTraits<SampleFormat>::ToString(first_difference->got)
<< "\n want[" << raw_frame
<< "] = " << SampleFormatTraits<SampleFormat>::ToString(first_difference->want)
<< CompareAudioBuffersShowContext(got_slice, want_slice, options,
first_difference->frame);
}
// Expect silent or not.
template <fuchsia::media::AudioSampleFormat SampleFormat>
void ExpectAudioBuffer(AudioBufferSlice<SampleFormat> slice, ExpectAudioBufferOptions options,
bool want_silent) {
FX_CHECK(!slice.empty());
using SampleT = typename AudioBuffer<SampleFormat>::SampleT;
for (size_t frame = 0; frame < slice.NumFrames(); frame++) {
for (size_t chan = 0; chan < slice.format().channels(); chan++) {
SampleT got = slice.SampleAt(frame, chan);
SampleT silent = SampleFormatTraits<SampleFormat>::kSilentValue;
if ((got == silent) != want_silent) {
size_t raw_frame = slice.start_frame() + frame;
ADD_FAILURE() << options.test_label << ": unexpected value at frame " << raw_frame
<< ", channel " << chan << ":\n got[" << raw_frame
<< "] = " << SampleFormatTraits<SampleFormat>::ToString(got) << "\n want"
<< (want_silent ? " == " : " != ")
<< SampleFormatTraits<SampleFormat>::ToString(silent)
<< (want_silent ? " (silent)" : " (not silent)")
<< ExpectAudioBuffersShowContext(slice, options, frame);
return;
}
}
}
}
} // namespace
template <fuchsia::media::AudioSampleFormat SampleFormat>
void CompareAudioBuffers(AudioBufferSlice<SampleFormat> got_slice,
AudioBufferSlice<SampleFormat> want_slice,
CompareAudioBufferOptions options) {
FX_CHECK(!got_slice.empty());
if (!want_slice.empty()) {
ASSERT_EQ(got_slice.format().channels(), want_slice.format().channels());
}
if (want_slice.NumFrames() == 0 || options.max_relative_error == 0) {
CompareAudioBuffersExact(got_slice, want_slice, options);
return;
}
FX_CHECK(options.max_relative_error > 0);
double want_slice_rms = MeasureAudioRMS(want_slice);
if (want_slice_rms == 0) {
CompareAudioBuffersExact(got_slice, want_slice, options);
return;
}
CompareAudioBuffersApprox(got_slice, want_slice, want_slice_rms, options);
}
template <fuchsia::media::AudioSampleFormat SampleFormat>
void ExpectSilentAudioBuffer(AudioBufferSlice<SampleFormat> slice,
ExpectAudioBufferOptions options) {
ExpectAudioBuffer(slice, options, true);
}
template <fuchsia::media::AudioSampleFormat SampleFormat>
void ExpectNonSilentAudioBuffer(AudioBufferSlice<SampleFormat> slice,
ExpectAudioBufferOptions options) {
ExpectAudioBuffer(slice, options, false);
}
// Explicitly instantiate all possible implementations.
#define INSTANTIATE(T) \
template void CompareAudioBuffers<T>(AudioBufferSlice<T> got_slice, \
AudioBufferSlice<T> want_slice, \
CompareAudioBufferOptions options); \
template void ExpectSilentAudioBuffer<T>(AudioBufferSlice<T> slice, \
ExpectAudioBufferOptions options); \
template void ExpectNonSilentAudioBuffer<T>(AudioBufferSlice<T> slice, \
ExpectAudioBufferOptions options);
INSTANTIATE(fuchsia::media::AudioSampleFormat::UNSIGNED_8)
INSTANTIATE(fuchsia::media::AudioSampleFormat::SIGNED_16)
INSTANTIATE(fuchsia::media::AudioSampleFormat::SIGNED_24_IN_32)
INSTANTIATE(fuchsia::media::AudioSampleFormat::FLOAT)
} // namespace media::audio::test