diff options
Diffstat (limited to 'src/3rdparty/resonance-audio/resonance_audio/node/audio_nodes_test.cc')
-rw-r--r-- | src/3rdparty/resonance-audio/resonance_audio/node/audio_nodes_test.cc | 407 |
1 files changed, 407 insertions, 0 deletions
diff --git a/src/3rdparty/resonance-audio/resonance_audio/node/audio_nodes_test.cc b/src/3rdparty/resonance-audio/resonance_audio/node/audio_nodes_test.cc new file mode 100644 index 000000000..a623f5d1c --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/node/audio_nodes_test.cc @@ -0,0 +1,407 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include <algorithm> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/audio_buffer.h" +#include "base/logging.h" +#include "node/processing_node.h" +#include "node/sink_node.h" +#include "node/source_node.h" + +namespace vraudio { + +namespace { + +// Number of channels in test buffers. +static const size_t kNumChannels = 5; + +// Number of frames in test buffers. +static const size_t kNumFrames = 7; + +// Helper method to compare two audio buffers. +bool CompareAudioBuffer(const AudioBuffer& buffer_a, + const AudioBuffer& buffer_b) { + if (buffer_a.num_channels() != buffer_b.num_channels() || + buffer_a.num_frames() != buffer_b.num_frames()) { + return false; + } + for (size_t channel = 0; channel < buffer_a.num_channels(); ++channel) { + const AudioBuffer::Channel& channel_a = buffer_a[channel]; + const AudioBuffer::Channel& channel_b = buffer_b[channel]; + for (size_t frame = 0; frame < buffer_a.num_frames(); ++frame) { + if (channel_a[frame] != channel_b[frame]) { + return false; + } + } + } + return true; +} + +// Helper method to generate a test AudioBuffer. +std::unique_ptr<AudioBuffer> GenerateTestAudioBuffer(float factor) { + std::unique_ptr<AudioBuffer> new_buffer( + new AudioBuffer(kNumChannels, kNumFrames)); + for (size_t channel = 0; channel < kNumChannels; ++channel) { + std::fill((*new_buffer)[channel].begin(), (*new_buffer)[channel].end(), + static_cast<float>(channel) * factor); + } + return new_buffer; +} + +// Simple audio source node that generates AudioData buffers. +class MySourceNode : public SourceNode { + public: + MySourceNode(bool output_empty_buffer, bool* node_deletion_flag) + : output_empty_buffer_(output_empty_buffer), + node_deletion_flag_(node_deletion_flag), + audio_buffer_(GenerateTestAudioBuffer(1.0f)) {} + ~MySourceNode() final { + if (node_deletion_flag_ != nullptr) { + *node_deletion_flag_ = true; + } + } + + protected: + // AudioProcess methods outputs kTestAudioData data. + const AudioBuffer* AudioProcess() override { + if (output_empty_buffer_) { + return nullptr; + } + return audio_buffer_.get(); + } + + private: + const bool output_empty_buffer_; + bool* node_deletion_flag_; + + std::unique_ptr<AudioBuffer> audio_buffer_; +}; + +// Simple audio processing node that reads from a single input and passes the +// data to the output. +class MyProcessingNode : public ProcessingNode { + public: + MyProcessingNode(bool process_on_empty_input, bool* audio_process_called_flag, + bool* node_deletion_flag) + : process_on_empty_input_(process_on_empty_input), + audio_process_called_flag_(audio_process_called_flag), + node_deletion_flag_(node_deletion_flag) { + EnableProcessOnEmptyInput(process_on_empty_input_); + } + + ~MyProcessingNode() final { + if (node_deletion_flag_ != nullptr) { + *node_deletion_flag_ = true; + } + } + + protected: + const AudioBuffer* AudioProcess(const NodeInput& input) override { + if (audio_process_called_flag_ != nullptr) { + *audio_process_called_flag_ = true; + } + + if (!process_on_empty_input_) { + EXPECT_GT(input.GetInputBuffers().size(), 0U); + } else { + if (input.GetInputBuffers().empty()) { + return nullptr; + } + } + + return input.GetInputBuffers()[0]; + } + + private: + const bool process_on_empty_input_; + bool* const audio_process_called_flag_; + bool* const node_deletion_flag_; +}; + +// Simple audio mixer node that connects to multiple nodes and outputs the sum +// of all inputs. +class MyAudioMixerNode : public ProcessingNode { + public: + explicit MyAudioMixerNode(bool* node_deletion_flag) + : node_deletion_flag_(node_deletion_flag) {} + ~MyAudioMixerNode() final { + if (node_deletion_flag_ != nullptr) { + *node_deletion_flag_ = true; + } + } + + protected: + // AudioProcess performs an element-wise sum from all inputs and outputs a new + // AudioData buffer. + const AudioBuffer* AudioProcess(const NodeInput& input) override { + const auto& input_buffers = input.GetInputBuffers(); + output_data_ = *input_buffers[0]; + + // Iterate over all inputs and add its data to |output_data|. + for (size_t buffer = 1; buffer < input_buffers.size(); ++buffer) { + const AudioBuffer* accumulate_data = input_buffers[buffer]; + for (size_t channel = 0; channel < accumulate_data->num_channels(); + ++channel) { + const AudioBuffer::Channel& accumulate_channel = + (*accumulate_data)[channel]; + AudioBuffer::Channel* output_channel = &output_data_[channel]; + EXPECT_EQ(accumulate_channel.size(), output_channel->size()); + for (size_t frame = 0; frame < accumulate_channel.size(); ++frame) { + (*output_channel)[frame] += accumulate_channel[frame]; + } + } + } + return &output_data_; + } + + private: + bool* node_deletion_flag_; + AudioBuffer output_data_; +}; + +// Simple audio sink node that expects a single input. +class MySinkNode : public SinkNode { + public: + explicit MySinkNode(bool* node_deletion_flag) + : node_deletion_flag_(node_deletion_flag) {} + + ~MySinkNode() final { + if (node_deletion_flag_ != nullptr) { + *node_deletion_flag_ = true; + } + } + + private: + bool* node_deletion_flag_; +}; + +// Tests a chain of an |SinkNode|, |ProcessingNode| and +// |SourceNode|. +TEST(AudioNodesTest, SourceProcessingSinkConnectionTest) { + static const bool kOutputEmptyBuffer = false; + static const bool kEnableProcessOnEmptyInput = true; + + auto source_node = + std::make_shared<MySourceNode>(kOutputEmptyBuffer, nullptr); + auto processing_node = std::make_shared<MyProcessingNode>( + kEnableProcessOnEmptyInput, nullptr, nullptr); + auto sink_node = std::make_shared<MySinkNode>(nullptr); + + // Create chain of nodes. + sink_node->Connect(processing_node); + processing_node->Connect(source_node); + + // Test output data. + const auto& data = sink_node->ReadInputs(); + EXPECT_GT(data.size(), 0U); + EXPECT_TRUE(CompareAudioBuffer(*data[0], *GenerateTestAudioBuffer(1.0f))); +} + +// Tests a chain of an |SinkNode| and |AudioMixerNode| connected to two +// |SourceNodes|. +TEST(AudioNodesTest, MixerProcessingConnectionTest) { + static const bool kOutputEmptyBuffer = false; + auto source_node_a = + std::make_shared<MySourceNode>(kOutputEmptyBuffer, nullptr); + auto source_node_b = + std::make_shared<MySourceNode>(kOutputEmptyBuffer, nullptr); + auto mixer_node = std::make_shared<MyAudioMixerNode>(nullptr); + auto sink_node = std::make_shared<MySinkNode>(nullptr); + + // Create chain of nodes. + sink_node->Connect(mixer_node); + mixer_node->Connect(source_node_a); + mixer_node->Connect(source_node_b); + + // Test output data. + const auto& data = sink_node->ReadInputs(); + EXPECT_GT(data.size(), 0U); + EXPECT_TRUE(CompareAudioBuffer(*data[0], *GenerateTestAudioBuffer(2.0f))); +} + +// Tests if ProcessingNode::AudioProcess() calls are skipped in case of +// empty input buffers. +TEST(AudioNodesTest, SkipProcessingOnEmptyInputTest) { + static const bool kEnableProcessOnEmptyInput = true; + + bool audio_process_called = false; + + // Tests that ProcessingNode::AudioProcess() is called in case source + // nodes do generate output. + { + static const bool kOutputEmptyBuffer = false; + auto source_node_a = + std::make_shared<MySourceNode>(kOutputEmptyBuffer, nullptr); + auto source_node_b = + std::make_shared<MySourceNode>(kOutputEmptyBuffer, nullptr); + auto processing_node = std::make_shared<MyProcessingNode>( + kEnableProcessOnEmptyInput, &audio_process_called, nullptr); + auto sink_node = std::make_shared<MySinkNode>(nullptr); + + // Create chain of nodes. + sink_node->Connect(processing_node); + processing_node->Connect(source_node_a); + processing_node->Connect(source_node_b); + + EXPECT_FALSE(audio_process_called); + const auto& data = sink_node->ReadInputs(); + EXPECT_TRUE(audio_process_called); + EXPECT_GT(data.size(), 0U); + } + + audio_process_called = false; + + // Tests that ProcessingNode::AudioProcess() is *not* called in case + // source nodes do *not* generate output. + { + static const bool kOutputEmptyBuffer = true; + auto source_node_a = + std::make_shared<MySourceNode>(kOutputEmptyBuffer, nullptr); + auto source_node_b = + std::make_shared<MySourceNode>(kOutputEmptyBuffer, nullptr); + auto processing_node = std::make_shared<MyProcessingNode>( + false, &audio_process_called, nullptr); + auto sink_node = std::make_shared<MySinkNode>(nullptr); + + // Create chain of nodes. + sink_node->Connect(processing_node); + processing_node->Connect(source_node_a); + processing_node->Connect(source_node_b); + + EXPECT_FALSE(audio_process_called); + const auto& data = sink_node->ReadInputs(); + EXPECT_FALSE(audio_process_called); + EXPECT_EQ(data.size(), 0U); + } +} + +// Tests a chain of an |SinkNode|, |ProcessingNode| and +// |SourceNode| and runs the node clean-up procedure with sources being +// *not* marked with end-of-stream. +TEST(AudioNodesTest, NodeCleanUpWithoutMarkEndOfStreamCallTest) { + static const bool kEnableProcessOnEmptyInput = true; + + bool source_node_deleted = false; + bool processing_node_deleted = false; + bool sink_node_deleted = false; + + auto sink_node = std::make_shared<MySinkNode>(&sink_node_deleted); + + { + // Create a source and processing node and connect it to sink node. + auto source_node = std::make_shared<MySourceNode>( + /*output_empty_buffer=*/false, &source_node_deleted); + auto processing_node = std::make_shared<MyProcessingNode>( + kEnableProcessOnEmptyInput, nullptr, &processing_node_deleted); + + // Connect nodes. + sink_node->Connect(processing_node); + processing_node->Connect(source_node); + + // End-of-stream is not marked in source node. + // source_node->MarkEndOfStream(); + } + + EXPECT_FALSE(source_node_deleted); + EXPECT_FALSE(processing_node_deleted); + EXPECT_FALSE(sink_node_deleted); + + sink_node->CleanUp(); + + EXPECT_FALSE(source_node_deleted); + EXPECT_FALSE(processing_node_deleted); + EXPECT_FALSE(sink_node_deleted); +} + +// Tests a chain of an SinkNode, ProcessingNode and SourceNode +// and runs the node clean-up procedure with sources being marked with +// end-of-stream. +TEST(AudioNodesTest, NodeCleanUpTest) { + static const bool kEnableProcessOnEmptyInput = true; + + bool source_node_deleted = false; + bool processing_node_deleted = false; + bool sink_node_deleted = false; + + auto sink_node = std::make_shared<MySinkNode>(&sink_node_deleted); + + { + // Create a source and processing node and connect it to sink node. + auto source_node = std::make_shared<MySourceNode>( + /*output_empty_buffer=*/false, &source_node_deleted); + auto processing_node = std::make_shared<MyProcessingNode>( + kEnableProcessOnEmptyInput, nullptr, &processing_node_deleted); + + // Connect nodes. + sink_node->Connect(processing_node); + processing_node->Connect(source_node); + + // End of stream is marked in source node. Do not expect any data anymore. + source_node->MarkEndOfStream(); + } + + EXPECT_FALSE(source_node_deleted); + EXPECT_FALSE(processing_node_deleted); + EXPECT_FALSE(sink_node_deleted); + + sink_node->CleanUp(); + + EXPECT_TRUE(source_node_deleted); + EXPECT_TRUE(processing_node_deleted); + EXPECT_FALSE(sink_node_deleted); +} + +// Tests ProcessingNode::EnableProcessOnEmptyInput(). +TEST(AudioNodesTest, ProcessOnEmptyInputFlagTest) { + bool audio_process_called = false; + + static const bool kEnableProcessOnEmptyInput = true; + static const bool kOutputEmptyBuffer = true; + + auto source_node = + std::make_shared<MySourceNode>(kOutputEmptyBuffer, nullptr); + auto processing_node = std::make_shared<MyProcessingNode>( + kEnableProcessOnEmptyInput, &audio_process_called, nullptr); + auto sink_node = std::make_shared<MySinkNode>(nullptr); + + // Create chain of nodes. + sink_node->Connect(processing_node); + processing_node->Connect(source_node); + + EXPECT_FALSE(audio_process_called); + const auto& data = sink_node->ReadInputs(); + EXPECT_TRUE(audio_process_called); + EXPECT_EQ(data.size(), 0U); +} + +// Tests a chain of an |SinkNode| and |AudioMixerNode| without a |SourceNode|. +TEST(AudioNodesTest, MissingSourceConnectionTest) { + auto mixer_node = std::make_shared<MyAudioMixerNode>(nullptr); + auto sink_node = std::make_shared<MySinkNode>(nullptr); + + // Create chain of nodes. + sink_node->Connect(mixer_node); + + // Test output data. + const auto& data = sink_node->ReadInputs(); + EXPECT_EQ(data.size(), 0U); +} + +} // namespace + +} // namespace vraudio |