#include <algorithm>
#include <array>
#include <limits>
#include "media/base/audio_bus.h"
#include "services/audio/mixing_graph.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace audio {
class MixingGraphInputTest : public ::testing::Test {
protected:
void SetUp() override {
output_params_ =
media::AudioParameters(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
media::ChannelLayoutConfig::Mono(), 48000, 480);
mixing_graph_ = MixingGraph::Create(
output_params_,
base::BindRepeating(&MixingGraphInputTest::OnMoreDataCallBack,
base::Unretained(this)),
base::BindRepeating(&MixingGraphInputTest::OnErrorCallback,
base::Unretained(this)));
dest_ = media::AudioBus::Create(output_params_);
}
void PullAndVerifyData(int num_runs,
float expected_first_sample,
float expected_sample_increment,
float epsilon) {
float expected_data = expected_first_sample;
for (int i = 0; i < num_runs; i++) {
mixing_graph_->OnMoreData(base::TimeDelta(), base::TimeTicks::Now(), {},
dest_.get());
for (auto sample : dest_->channel_span(0)) {
EXPECT_NEAR(sample, expected_data, epsilon);
expected_data += expected_sample_increment;
}
}
}
void OnMoreDataCallBack(const media::AudioBus&, base::TimeDelta) {}
void OnErrorCallback(
media::AudioOutputStream::AudioSourceCallback::ErrorType) {}
media::AudioParameters output_params_;
std::unique_ptr<MixingGraph> mixing_graph_;
std::unique_ptr<media::AudioBus> dest_;
};
class SampleCounter : public media::AudioOutputStream::AudioSourceCallback {
public:
explicit SampleCounter(float counter, float increment)
: counter_(counter), increment_(increment) {}
int OnMoreData(base::TimeDelta delay,
base::TimeTicks delay_timestamp,
const media::AudioGlitchInfo& glitch_info,
media::AudioBus* dest) final {
for (int channel = 0; channel < dest->channels(); ++channel) {
auto data = dest->channel_span(channel);
for (int frame = 0; frame < dest->frames(); frame++) {
data[frame] = counter_ + increment_ * frame + increment_ * channel;
}
}
counter_ += static_cast<float>(increment_ * dest->frames());
return 0;
}
void OnError(ErrorType type) final {}
private:
float counter_ = 0.0f;
const float increment_;
};
class CallbackCounter : public media::AudioOutputStream::AudioSourceCallback {
public:
explicit CallbackCounter(float counter, float increment)
: counter_(counter), increment_(increment) {}
int OnMoreData(base::TimeDelta delay,
base::TimeTicks delay_timestamp,
const media::AudioGlitchInfo& glitch_info,
media::AudioBus* dest) final {
for (auto channel : dest->AllChannels()) {
std::ranges::fill(channel, counter_);
}
counter_ += increment_;
return 0;
}
void OnError(ErrorType type) final {}
private:
float counter_ = 0.0f;
const float increment_;
};
class ConstantInput : public media::AudioOutputStream::AudioSourceCallback {
public:
explicit ConstantInput(float value) : value_(value) {}
int OnMoreData(base::TimeDelta delay,
base::TimeTicks delay_timestamp,
const media::AudioGlitchInfo& glitch_info,
media::AudioBus* dest) final {
for (auto channel : dest->AllChannels()) {
std::ranges::fill(channel, value_);
}
return 0;
}
void OnError(ErrorType type) final {}
private:
const float value_;
};
class GlitchInfoCounter : public media::AudioOutputStream::AudioSourceCallback {
public:
int OnMoreData(base::TimeDelta delay,
base::TimeTicks delay_timestamp,
const media::AudioGlitchInfo& glitch_info,
media::AudioBus* dest) final {
cumulative_glitch_info_ += glitch_info;
return 0;
}
void OnError(ErrorType type) final {}
media::AudioGlitchInfo cumulative_glitch_info() const {
return cumulative_glitch_info_;
}
private:
media::AudioGlitchInfo cumulative_glitch_info_;
};
TEST_F(MixingGraphInputTest, NoInputs) {
PullAndVerifyData(2, 0.0f,
0.0f, 0.0f);
}
TEST_F(MixingGraphInputTest, SingleInput) {
constexpr float kInitialCounterValue = 0.0f;
constexpr float kCounterIncrement = 1e-4f;
SampleCounter source_callback(kInitialCounterValue, kCounterIncrement);
auto input = mixing_graph_->CreateInput(output_params_);
input->Start(&source_callback);
PullAndVerifyData(2,
kInitialCounterValue,
kCounterIncrement,
1e-5f);
input->Stop();
}
TEST_F(MixingGraphInputTest, MultipleInputs) {
constexpr float kInitialCounterValue1 = 0.1f;
constexpr float kInitialCounterValue2 = 0.5f;
constexpr float kInitialCounterValue3 = -0.7f;
constexpr float kCounterIncrement = 1e-4f;
SampleCounter source_callback1(kInitialCounterValue1, kCounterIncrement);
SampleCounter source_callback2(kInitialCounterValue2, kCounterIncrement);
SampleCounter source_callback3(kInitialCounterValue3, kCounterIncrement);
auto input1 = mixing_graph_->CreateInput(output_params_);
input1->Start(&source_callback1);
auto input2 = mixing_graph_->CreateInput(output_params_);
input2->Start(&source_callback2);
auto input3 = mixing_graph_->CreateInput(output_params_);
input3->Start(&source_callback3);
PullAndVerifyData(2,
kInitialCounterValue1 +
kInitialCounterValue2 + kInitialCounterValue3,
3.0f * kCounterIncrement,
1e-5f);
input1->Stop();
input2->Stop();
input3->Stop();
}
TEST_F(MixingGraphInputTest, ChannelMixing) {
media::AudioParameters input_params(
media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
media::ChannelLayoutConfig::Stereo(), 48000, 480);
constexpr float kInitialCounterValue = 0.0f;
constexpr float kCounterIncrement = 1e-4f;
SampleCounter source_callback(kInitialCounterValue, kCounterIncrement);
auto input = mixing_graph_->CreateInput(input_params);
input->Start(&source_callback);
PullAndVerifyData(
2,
kInitialCounterValue + 0.5f * kCounterIncrement,
kCounterIncrement, 1e-5f);
input->Stop();
}
TEST_F(MixingGraphInputTest, Resampling) {
media::AudioParameters input_params(
media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
media::ChannelLayoutConfig::Mono(), 24000, 480);
constexpr float kInitialCounterValue = 0.0f;
constexpr float kCounterIncrement = 1e-4f;
SampleCounter source_callback(kInitialCounterValue, kCounterIncrement);
auto input = mixing_graph_->CreateInput(input_params);
input->Start(&source_callback);
PullAndVerifyData(2,
kInitialCounterValue,
0.5f * kCounterIncrement,
1e-5f);
input->Stop();
}
TEST_F(MixingGraphInputTest, Buffering1) {
media::AudioParameters input_params(
media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
media::ChannelLayoutConfig::Mono(), 48000, 240);
constexpr float kInitialCounterValue = 0.0f;
constexpr float kCounterIncrement = 1e-4f;
SampleCounter source_callback(kInitialCounterValue, kCounterIncrement);
auto input = mixing_graph_->CreateInput(input_params);
input->Start(&source_callback);
PullAndVerifyData(2,
kInitialCounterValue,
kCounterIncrement,
1e-5f);
input->Stop();
}
TEST_F(MixingGraphInputTest, Buffering2) {
media::AudioParameters input_params(
media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
media::ChannelLayoutConfig::Mono(), 48000, 720);
constexpr float kInitialCounterValue = 0.0f;
constexpr float kCounterIncrement = 1e-4f;
SampleCounter source_callback(kInitialCounterValue, kCounterIncrement);
auto input = mixing_graph_->CreateInput(input_params);
input->Start(&source_callback);
PullAndVerifyData(2,
kInitialCounterValue,
kCounterIncrement,
1e-5f);
input->Stop();
}
TEST_F(MixingGraphInputTest, BufferClearedAtRestart) {
media::AudioParameters input_params(
media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
media::ChannelLayoutConfig::Mono(), 48000, 720);
constexpr float kInitialCounterValue = 0.0f;
constexpr float kCounterIncrement = 1e-4f;
CallbackCounter source_callback(kInitialCounterValue, kCounterIncrement);
auto input = mixing_graph_->CreateInput(input_params);
input->Start(&source_callback);
mixing_graph_->OnMoreData(base::TimeDelta(), base::TimeTicks::Now(), {},
dest_.get());
float last_sample = dest_->channel_span(0).back();
input->Stop();
input->Start(&source_callback);
mixing_graph_->OnMoreData(base::TimeDelta(), base::TimeTicks::Now(), {},
dest_.get());
float first_sample = dest_->channel_span(0).front();
EXPECT_NE(first_sample, last_sample);
input->Stop();
}
TEST_F(MixingGraphInputTest, AddingAndRemovingInputs) {
constexpr float kInitialCounterValue1 = -0.3f;
constexpr float kInitialCounterValue2 = 0.2f;
constexpr float kCounterIncrement = 1e-4f;
SampleCounter source_callback1(kInitialCounterValue1, kCounterIncrement);
SampleCounter source_callback2(kInitialCounterValue2, kCounterIncrement);
auto input1 = mixing_graph_->CreateInput(output_params_);
auto input2 = mixing_graph_->CreateInput(output_params_);
input1->Start(&source_callback1);
PullAndVerifyData(1,
kInitialCounterValue1,
kCounterIncrement,
1e-5f);
input2->Start(&source_callback2);
PullAndVerifyData(1,
kInitialCounterValue1 +
kCounterIncrement * dest_->frames() +
kInitialCounterValue2,
2.0f * kCounterIncrement,
1e-5f);
input1->Stop();
PullAndVerifyData(
1,
kInitialCounterValue2 +
kCounterIncrement * dest_->frames(),
kCounterIncrement, 1e-5f);
input2->Stop();
PullAndVerifyData(1,
0.f,
0.0f, 0.0f);
}
TEST_F(MixingGraphInputTest, SetVolume) {
constexpr float kInitialCounterValue = 0.0f;
constexpr float kCounterIncrement = 1e-4f;
constexpr float kVolume1 = 0.77f;
constexpr float kVolume2 = 0.13f;
SampleCounter source_callback(kInitialCounterValue, kCounterIncrement);
auto input = mixing_graph_->CreateInput(output_params_);
input->Start(&source_callback);
input->SetVolume(kVolume1);
PullAndVerifyData(1,
kInitialCounterValue * kVolume1,
kVolume1 * kCounterIncrement,
1e-5f);
input->SetVolume(kVolume2);
PullAndVerifyData(
1,
(kInitialCounterValue + kCounterIncrement * dest_->frames()) * kVolume2,
kVolume2 * kCounterIncrement,
1e-5f);
input->Stop();
}
TEST_F(MixingGraphInputTest, OutOfRange) {
constexpr float kInputValue1 = -0.6f;
constexpr float kInputValue2 = -0.5f;
ConstantInput source_callback1(kInputValue1);
ConstantInput source_callback2(kInputValue2);
auto input1 = mixing_graph_->CreateInput(output_params_);
input1->Start(&source_callback1);
auto input2 = mixing_graph_->CreateInput(output_params_);
input2->Start(&source_callback2);
PullAndVerifyData(1,
-1.0f,
0.0f, 0.0f);
input1->SetVolume(0.5f);
PullAndVerifyData(
1,
kInputValue1 * 0.5f + kInputValue2,
0.0f, 0.0f);
input1->Stop();
input2->Stop();
}
TEST_F(MixingGraphInputTest, InvalidInput) {
std::array<std::pair<float, float>, 9> test_values = {{
{-1.5f, -1.0f},
{2.0f, 1.0f},
{-0.8, -0.8f},
{0.3, 0.3f},
{0.0, 0.0f},
{std::numeric_limits<float>::infinity(), 1.0f},
{-std::numeric_limits<float>::infinity(), -1.0f},
{std::numeric_limits<float>::quiet_NaN(), 0.0f},
{std::numeric_limits<float>::signaling_NaN(), 0.0f},
}};
for (const auto& test_pair : test_values) {
float input_value = test_pair.first;
float expected_output_value = test_pair.second;
auto input = mixing_graph_->CreateInput(output_params_);
ConstantInput source_callback(input_value);
input->Start(&source_callback);
PullAndVerifyData(
1,
expected_output_value,
0.0f, 0.0f);
input->Stop();
}
}
TEST_F(MixingGraphInputTest, PropagatesGlitchInfo) {
GlitchInfoCounter source_callback1;
GlitchInfoCounter source_callback2;
GlitchInfoCounter source_callback3;
auto input1 = mixing_graph_->CreateInput(output_params_);
input1->Start(&source_callback1);
auto input2 = mixing_graph_->CreateInput(output_params_);
input2->Start(&source_callback2);
media::AudioParameters different_params(
media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
media::ChannelLayoutConfig::Mono(), output_params_.sample_rate() * 2,
480);
auto input3 = mixing_graph_->CreateInput(different_params);
input3->Start(&source_callback3);
mixing_graph_->OnMoreData(base::TimeDelta(), base::TimeTicks::Now(), {},
dest_.get());
EXPECT_EQ(source_callback1.cumulative_glitch_info(),
media::AudioGlitchInfo());
EXPECT_EQ(source_callback2.cumulative_glitch_info(),
media::AudioGlitchInfo());
EXPECT_EQ(source_callback3.cumulative_glitch_info(),
media::AudioGlitchInfo());
media::AudioGlitchInfo glitch_info{.duration = base::Seconds(5),
.count = 123};
mixing_graph_->OnMoreData(base::TimeDelta(), base::TimeTicks::Now(),
glitch_info, dest_.get());
EXPECT_EQ(source_callback1.cumulative_glitch_info(), glitch_info);
EXPECT_EQ(source_callback2.cumulative_glitch_info(), glitch_info);
EXPECT_EQ(source_callback3.cumulative_glitch_info(), glitch_info);
mixing_graph_->OnMoreData(base::TimeDelta(), base::TimeTicks::Now(), {},
dest_.get());
EXPECT_EQ(source_callback1.cumulative_glitch_info(), glitch_info);
EXPECT_EQ(source_callback2.cumulative_glitch_info(), glitch_info);
EXPECT_EQ(source_callback3.cumulative_glitch_info(), glitch_info);
input1->Stop();
input2->Stop();
input3->Stop();
}
}