blob: cdde1f1b62779379ab6048c1284973a6a616488e [file] [log] [blame]
// Copyright 2019 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/effects_loader/effects_processor.h"
#include <gtest/gtest.h>
#include "src/media/audio/effects/test_effects/test_effects.h"
#include "src/media/audio/lib/effects_loader/testing/effects_loader_test_base.h"
namespace media::audio {
namespace {
class EffectsProcessorTest : public testing::EffectsLoaderTestBase {};
// The following tests validates the EffectsProcessor class itself.
//
// Verify the creation, uniqueness, quantity and deletion of effect instances.
TEST_F(EffectsProcessorTest, CreateDelete) {
test_effects().AddEffect("assign_to_1.0").WithAction(TEST_EFFECTS_ACTION_ASSIGN, 1.0);
Effect effect3 = effects_loader()->CreateEffect(0, "", 1, 1, 1, {});
Effect effect1 = effects_loader()->CreateEffect(0, "", 1, 1, 1, {});
Effect effect2 = effects_loader()->CreateEffect(0, "", 1, 1, 1, {});
Effect effect4 = effects_loader()->CreateEffect(0, "", 1, 1, 1, {});
ASSERT_TRUE(effect1);
ASSERT_TRUE(effect2);
ASSERT_TRUE(effect3);
ASSERT_TRUE(effect4);
EXPECT_TRUE(effect1.get() != effect2.get() && effect1.get() != effect3.get() &&
effect1.get() != effect4.get() && effect2.get() != effect3.get() &&
effect2.get() != effect4.get() && effect3.get() != effect4.get());
fuchsia_audio_effects_handle_t effects_handle1 = effect1.get();
fuchsia_audio_effects_handle_t effects_handle2 = effect2.get();
fuchsia_audio_effects_handle_t effects_handle3 = effect3.get();
fuchsia_audio_effects_handle_t effects_handle4 = effect4.get();
// Create processor
{
EffectsProcessor processor;
EXPECT_EQ(processor.AddEffect(std::move(effect3)), ZX_OK);
EXPECT_EQ(processor.AddEffect(std::move(effect1)), ZX_OK);
EXPECT_EQ(processor.AddEffect(std::move(effect2)), ZX_OK);
EXPECT_EQ(processor.AddEffect(std::move(effect4)), ZX_OK);
EXPECT_EQ(processor.size(), 4);
EXPECT_EQ(effects_handle3, processor.GetEffectAt(0).get());
EXPECT_EQ(effects_handle1, processor.GetEffectAt(1).get());
EXPECT_EQ(effects_handle2, processor.GetEffectAt(2).get());
EXPECT_EQ(effects_handle4, processor.GetEffectAt(3).get());
EXPECT_EQ(4u, test_effects().InstanceCount());
}
// All instances should be deleted when the processor is destructed.
EXPECT_EQ(0u, test_effects().InstanceCount());
}
TEST_F(EffectsProcessorTest, AddEffectWithMismatchedChannelConfig) {
test_effects().AddEffect("assign_to_1.0").WithAction(TEST_EFFECTS_ACTION_ASSIGN, 1.0);
Effect single_channel_effect1 = effects_loader()->CreateEffect(0, "", 1, 1, 1, {});
Effect single_channel_effect2 = effects_loader()->CreateEffect(0, "", 1, 1, 1, {});
Effect two_channel_effect = effects_loader()->CreateEffect(0, "", 1, 2, 2, {});
EffectsProcessor processor;
EXPECT_EQ(processor.channels_in(), 0);
EXPECT_EQ(processor.channels_out(), 0);
// Add a single channel effect (chans in == chans out == 1).
EXPECT_EQ(processor.AddEffect(std::move(single_channel_effect1)), ZX_OK);
EXPECT_EQ(processor.channels_in(), 1);
EXPECT_EQ(processor.channels_out(), 1);
// Add a second single channel effect.
EXPECT_EQ(processor.AddEffect(std::move(single_channel_effect2)), ZX_OK);
EXPECT_EQ(processor.channels_in(), 1);
EXPECT_EQ(processor.channels_out(), 1);
// Add a two channel effect. This should fail as the processor is currently producing single
// channel audio out of the last effect.
EXPECT_EQ(processor.AddEffect(std::move(two_channel_effect)), ZX_ERR_INVALID_ARGS);
}
// Verify (at a VERY Basic level) the methods that handle data flow.
TEST_F(EffectsProcessorTest, ProcessInPlaceFlush) {
test_effects().AddEffect("increment_by_1.0").WithAction(TEST_EFFECTS_ACTION_ADD, 1.0);
test_effects().AddEffect("increment_by_2.0").WithAction(TEST_EFFECTS_ACTION_ADD, 2.0);
test_effects().AddEffect("assign_to_12.0").WithAction(TEST_EFFECTS_ACTION_ASSIGN, 12.0);
test_effects().AddEffect("increment_by_4.0").WithAction(TEST_EFFECTS_ACTION_ADD, 4.0);
float buff[4] = {0, 1.0, 2.0, 3.0};
// Before instances added, ProcessInPlace and Flush should succeed.
EffectsProcessor processor;
EXPECT_EQ(processor.ProcessInPlace(4, buff), ZX_OK);
EXPECT_EQ(processor.Flush(), ZX_OK);
EXPECT_EQ(0.0, buff[0]);
EXPECT_EQ(1.0, buff[1]);
EXPECT_EQ(2.0, buff[2]);
EXPECT_EQ(3.0, buff[3]);
// Chaining four instances together, ProcessInPlace and flush should succeed.
Effect effect1 = effects_loader()->CreateEffect(0, "", 1, 1, 1, {});
Effect effect2 = effects_loader()->CreateEffect(1, "", 1, 1, 1, {});
Effect effect3 = effects_loader()->CreateEffect(2, "", 1, 1, 1, {});
Effect effect4 = effects_loader()->CreateEffect(3, "", 1, 1, 1, {});
ASSERT_TRUE(effect1 && effect2 && effect3 && effect4);
EXPECT_EQ(processor.AddEffect(std::move(effect1)), ZX_OK);
EXPECT_EQ(processor.AddEffect(std::move(effect2)), ZX_OK);
EXPECT_EQ(processor.AddEffect(std::move(effect3)), ZX_OK);
EXPECT_EQ(processor.AddEffect(std::move(effect4)), ZX_OK);
EXPECT_EQ(4u, test_effects().InstanceCount());
// The first 2 processors will mutate data, but this will be clobbered by the 3rd processor which
// just sets every sample to 12.0. The final processor will increment by 4.0 resulting in the
// expected 16.0 values.
EXPECT_EQ(processor.ProcessInPlace(4, buff), ZX_OK);
EXPECT_EQ(buff[0], 16.0);
EXPECT_EQ(buff[1], 16.0);
EXPECT_EQ(buff[2], 16.0);
EXPECT_EQ(buff[3], 16.0);
// All effects should have initial flush count 0.
test_effects_inspect_state inspect1 = {};
test_effects_inspect_state inspect2 = {};
test_effects_inspect_state inspect3 = {};
test_effects_inspect_state inspect4 = {};
EXPECT_EQ(ZX_OK, test_effects().InspectInstance(processor.GetEffectAt(0).get(), &inspect1));
EXPECT_EQ(ZX_OK, test_effects().InspectInstance(processor.GetEffectAt(1).get(), &inspect2));
EXPECT_EQ(ZX_OK, test_effects().InspectInstance(processor.GetEffectAt(2).get(), &inspect3));
EXPECT_EQ(ZX_OK, test_effects().InspectInstance(processor.GetEffectAt(3).get(), &inspect4));
EXPECT_EQ(0u, inspect1.flush_count);
EXPECT_EQ(0u, inspect2.flush_count);
EXPECT_EQ(0u, inspect3.flush_count);
EXPECT_EQ(0u, inspect4.flush_count);
// Flush, just sanity test the test_effects library has observed the flush call on each effect.
EXPECT_EQ(processor.Flush(), ZX_OK);
EXPECT_EQ(ZX_OK, test_effects().InspectInstance(processor.GetEffectAt(0).get(), &inspect1));
EXPECT_EQ(ZX_OK, test_effects().InspectInstance(processor.GetEffectAt(1).get(), &inspect2));
EXPECT_EQ(ZX_OK, test_effects().InspectInstance(processor.GetEffectAt(2).get(), &inspect3));
EXPECT_EQ(ZX_OK, test_effects().InspectInstance(processor.GetEffectAt(3).get(), &inspect4));
EXPECT_EQ(1u, inspect1.flush_count);
EXPECT_EQ(1u, inspect2.flush_count);
EXPECT_EQ(1u, inspect3.flush_count);
EXPECT_EQ(1u, inspect4.flush_count);
// Zero num_frames is valid and should succeed. We assign the buff to some random values here
// to ensure the processor does not clobber them.
buff[0] = 20.0;
buff[1] = 21.0;
buff[2] = 22.0;
buff[3] = 23.0;
EXPECT_EQ(processor.ProcessInPlace(0, buff), ZX_OK);
EXPECT_EQ(buff[0], 20.0);
EXPECT_EQ(buff[1], 21.0);
EXPECT_EQ(buff[2], 22.0);
EXPECT_EQ(buff[3], 23.0);
// If no buffer provided, ProcessInPlace should fail (even if 0 num_frames).
EXPECT_NE(processor.ProcessInPlace(0, nullptr), ZX_OK);
}
TEST_F(EffectsProcessorTest, ReportBlockSize) {
test_effects().AddEffect("block_size_3").WithBlockSize(3);
test_effects().AddEffect("block_size_5").WithBlockSize(5);
test_effects().AddEffect("block_size_any").WithBlockSize(FUCHSIA_AUDIO_EFFECTS_BLOCK_SIZE_ANY);
test_effects().AddEffect("block_size_1").WithBlockSize(1);
// Needed to use |CreateEffectByName| since the effect names are cached at loader creation time.
RecreateLoader();
// Create processor and verify default block_size.
EffectsProcessor processor;
EXPECT_EQ(1, processor.block_size());
// Add an effect and observe a change in block_size.
Effect effect1 = effects_loader()->CreateEffectByName("block_size_3", "", 1, 1, 1, {});
ASSERT_TRUE(effect1);
processor.AddEffect(std::move(effect1));
EXPECT_EQ(3, processor.block_size());
// Add another effect and observe a change in block_size (lcm(3,5)
Effect effect2 = effects_loader()->CreateEffectByName("block_size_5", "", 1, 1, 1, {});
ASSERT_TRUE(effect2);
processor.AddEffect(std::move(effect2));
EXPECT_EQ(15, processor.block_size());
// Add some final effects that should not change block_size.
Effect effect3 = effects_loader()->CreateEffectByName("block_size_any", "", 1, 1, 1, {});
ASSERT_TRUE(effect3);
processor.AddEffect(std::move(effect3));
EXPECT_EQ(15, processor.block_size());
Effect effect4 = effects_loader()->CreateEffectByName("block_size_1", "", 1, 1, 1, {});
ASSERT_TRUE(effect4);
processor.AddEffect(std::move(effect4));
EXPECT_EQ(15, processor.block_size());
}
TEST_F(EffectsProcessorTest, ReportMaxBufferSize) {
test_effects().AddEffect("max_buffer_1024").WithMaxFramesPerBuffer(1024);
test_effects().AddEffect("max_buffer_512").WithMaxFramesPerBuffer(512);
test_effects().AddEffect("max_buffer_256").WithMaxFramesPerBuffer(256);
test_effects().AddEffect("max_buffer_128").WithMaxFramesPerBuffer(128);
// Needed to use |CreateEffectByName| since the effect names are cached at loader creation time.
RecreateLoader();
// Create processor and verify default block_size.
EffectsProcessor processor;
EXPECT_EQ(0, processor.max_batch_size());
// Add an effect and observe a change in buffer size.
{
Effect effect = effects_loader()->CreateEffectByName("max_buffer_1024", "", 1, 1, 1, {});
ASSERT_TRUE(effect);
processor.AddEffect(std::move(effect));
EXPECT_EQ(1024, processor.max_batch_size());
}
{
Effect effect = effects_loader()->CreateEffectByName("max_buffer_512", "", 1, 1, 1, {});
ASSERT_TRUE(effect);
processor.AddEffect(std::move(effect));
EXPECT_EQ(512, processor.max_batch_size());
}
{
Effect effect = effects_loader()->CreateEffectByName("max_buffer_256", "", 1, 1, 1, {});
ASSERT_TRUE(effect);
processor.AddEffect(std::move(effect));
EXPECT_EQ(256, processor.max_batch_size());
}
{
Effect effect = effects_loader()->CreateEffectByName("max_buffer_128", "", 1, 1, 1, {});
ASSERT_TRUE(effect);
processor.AddEffect(std::move(effect));
EXPECT_EQ(128, processor.max_batch_size());
}
// Add a final effect with an increasing max block size to verify we don't increase the reported
// buffer size.
{
Effect effect = effects_loader()->CreateEffectByName("max_buffer_1024", "", 1, 1, 1, {});
ASSERT_TRUE(effect);
processor.AddEffect(std::move(effect));
EXPECT_EQ(128, processor.max_batch_size());
}
}
TEST_F(EffectsProcessorTest, AlignBufferWithBlockSize) {
test_effects()
.AddEffect("max_buffer_1024_any_align")
.WithMaxFramesPerBuffer(1024)
.WithBlockSize(FUCHSIA_AUDIO_EFFECTS_BLOCK_SIZE_ANY);
test_effects()
.AddEffect("any_buffer_300_align")
.WithMaxFramesPerBuffer(FUCHSIA_AUDIO_EFFECTS_FRAMES_PER_BUFFER_ANY)
.WithBlockSize(300);
test_effects()
.AddEffect("max_buffer_800_any_align")
.WithMaxFramesPerBuffer(800)
.WithBlockSize(FUCHSIA_AUDIO_EFFECTS_BLOCK_SIZE_ANY);
// Needed to use |CreateEffectByName| since the effect names are cached at loader creation time.
RecreateLoader();
// Create processor and verify default block_size.
EffectsProcessor processor;
EXPECT_EQ(0, processor.max_batch_size());
EXPECT_EQ(1, processor.block_size());
{
Effect effect =
effects_loader()->CreateEffectByName("max_buffer_1024_any_align", "", 1, 1, 1, {});
ASSERT_TRUE(effect);
processor.AddEffect(std::move(effect));
EXPECT_EQ(1024, processor.max_batch_size());
EXPECT_EQ(1, processor.block_size());
}
// Adding an effect with 300 alignment should drop our max buffer size from 1024 -> 900.
{
Effect effect = effects_loader()->CreateEffectByName("any_buffer_300_align", "", 1, 1, 1, {});
ASSERT_TRUE(effect);
processor.AddEffect(std::move(effect));
EXPECT_EQ(900, processor.max_batch_size());
EXPECT_EQ(300, processor.block_size());
}
// Adding an effect with max buffer of 800 should drop aggregate max buffer to 600.
{
Effect effect =
effects_loader()->CreateEffectByName("max_buffer_800_any_align", "", 1, 1, 1, {});
ASSERT_TRUE(effect);
processor.AddEffect(std::move(effect));
EXPECT_EQ(600, processor.max_batch_size());
EXPECT_EQ(300, processor.block_size());
}
}
TEST_F(EffectsProcessorTest, ProcessOutOfPlace) {
test_effects()
.AddEffect("increment")
.WithChannelization(FUCHSIA_AUDIO_EFFECTS_CHANNELS_ANY, FUCHSIA_AUDIO_EFFECTS_CHANNELS_ANY)
.WithAction(TEST_EFFECTS_ACTION_ADD, 1.0);
Effect effect1 = effects_loader()->CreateEffect(0, "", 1, 1, 2, {});
Effect effect2 = effects_loader()->CreateEffect(0, "", 1, 2, 2, {});
Effect effect3 = effects_loader()->CreateEffect(0, "", 1, 2, 4, {});
ASSERT_TRUE(effect1);
ASSERT_TRUE(effect2);
ASSERT_TRUE(effect3);
// Create processor
EffectsProcessor processor;
EXPECT_EQ(processor.AddEffect(std::move(effect1)), ZX_OK);
EXPECT_EQ(processor.size(), 1u);
EXPECT_EQ(processor.channels_in(), 1);
EXPECT_EQ(processor.channels_out(), 2);
EXPECT_EQ(processor.AddEffect(std::move(effect2)), ZX_OK);
EXPECT_EQ(processor.size(), 2u);
EXPECT_EQ(processor.channels_in(), 1);
EXPECT_EQ(processor.channels_out(), 2);
EXPECT_EQ(processor.AddEffect(std::move(effect3)), ZX_OK);
EXPECT_EQ(processor.size(), 3u);
EXPECT_EQ(processor.channels_in(), 1);
EXPECT_EQ(processor.channels_out(), 4);
float buff[4] = {0, 1.0, 2.0, 3.0};
float* out;
ASSERT_EQ(ZX_OK, processor.Process(4, buff, &out));
// The first effect will upchannel from 1 -> 2 channels, leaving 0.0 for the new channel and
// incrementing the current channel. The second effect will increment both, so channel 0 is
// incremented by 2 and channel 1 should be 1.0. The third effect will upchannel 2->4 channels
// and increment the existing 2 channels.
//
// So for frame N, we expect:
// out[N * 4] == N + 3.0f
// out[N * 4 + 1] == 2.0f
// out[N * 4 + 2] == 0.0f
// out[N * 4 + 3] == 0.0f
auto CheckFrame = [&out](size_t frame) {
ASSERT_FLOAT_EQ(out[4 * frame + 0], frame + 3.0f);
ASSERT_FLOAT_EQ(out[4 * frame + 1], 2.0f);
ASSERT_FLOAT_EQ(out[4 * frame + 2], 0.0f);
ASSERT_FLOAT_EQ(out[4 * frame + 3], 0.0f);
};
CheckFrame(0);
CheckFrame(1);
CheckFrame(2);
CheckFrame(3);
}
TEST_F(EffectsProcessorTest, AddEffectFailsWithInvalidChannelization) {
test_effects()
.AddEffect("effect")
.WithChannelization(FUCHSIA_AUDIO_EFFECTS_CHANNELS_ANY, FUCHSIA_AUDIO_EFFECTS_CHANNELS_ANY)
.WithAction(TEST_EFFECTS_ACTION_ADD, 1.0);
EffectsProcessor processor;
Effect effect1 = effects_loader()->CreateEffect(0, "", 1, 1, 1, {});
ASSERT_TRUE(effect1);
EXPECT_EQ(processor.AddEffect(std::move(effect1)), ZX_OK);
EXPECT_EQ(processor.size(), 1u);
EXPECT_EQ(processor.channels_in(), 1);
EXPECT_EQ(processor.channels_out(), 1);
// Create an effect with 2 chans in. This should be rejected by the processor since it's currently
// producing 1 channel audio.
Effect effect2 = effects_loader()->CreateEffect(0, "", 1, 2, 2, {});
ASSERT_TRUE(effect2);
EXPECT_EQ(processor.AddEffect(std::move(effect2)), ZX_ERR_INVALID_ARGS);
EXPECT_EQ(processor.size(), 1u);
EXPECT_EQ(processor.channels_in(), 1);
EXPECT_EQ(processor.channels_out(), 1);
}
TEST_F(EffectsProcessorTest, SetStreamInfo) {
test_effects().AddEffect("effect.0").WithAction(TEST_EFFECTS_ACTION_ASSIGN, 1.0);
EffectsProcessor processor;
constexpr int kNumEffects = 5;
for (int i = 0; i < kNumEffects; ++i) {
Effect effect = effects_loader()->CreateEffect(0, "", 1, 1, 1, {});
ASSERT_TRUE(effect);
EXPECT_EQ(processor.AddEffect(std::move(effect)), ZX_OK);
}
// SetStreamInfo
constexpr uint32_t kExpectedUsageMask =
FUCHSIA_AUDIO_EFFECTS_USAGE_MEDIA | FUCHSIA_AUDIO_EFFECTS_USAGE_COMMUNICATION;
constexpr float kExpectedGainDbfs = -20.0;
constexpr float kExpectedVolume = 0.8;
fuchsia_audio_effects_stream_info stream_info;
stream_info.usage_mask = kExpectedUsageMask;
stream_info.gain_dbfs = kExpectedGainDbfs;
stream_info.volume = kExpectedVolume;
processor.SetStreamInfo(stream_info);
// Now verify the effects received the stream info.
for (int i = 0; i < kNumEffects; ++i) {
test_effects_inspect_state inspect = {};
EXPECT_EQ(ZX_OK, test_effects().InspectInstance(processor.GetEffectAt(i).get(), &inspect));
EXPECT_EQ(kExpectedUsageMask, inspect.stream_info.usage_mask);
EXPECT_EQ(kExpectedGainDbfs, inspect.stream_info.gain_dbfs);
EXPECT_EQ(kExpectedVolume, inspect.stream_info.volume);
}
}
TEST_F(EffectsProcessorTest, FilterWidth) {
test_effects()
.AddEffect("effect1")
.WithChannelization(FUCHSIA_AUDIO_EFFECTS_CHANNELS_ANY, FUCHSIA_AUDIO_EFFECTS_CHANNELS_ANY)
.WithSignalLatencyFrames(10)
.WithRingOutFrames(4);
test_effects()
.AddEffect("effect2")
.WithChannelization(FUCHSIA_AUDIO_EFFECTS_CHANNELS_ANY, FUCHSIA_AUDIO_EFFECTS_CHANNELS_ANY)
.WithSignalLatencyFrames(50)
.WithRingOutFrames(19);
RecreateLoader();
EffectsProcessor processor;
processor.AddEffect(effects_loader()->CreateEffectByName("effect1", "", 1, 1, 1, {}));
processor.AddEffect(effects_loader()->CreateEffectByName("effect2", "", 1, 1, 1, {}));
// Sum of the inputs.
EXPECT_EQ(60, processor.delay_frames());
EXPECT_EQ(23, processor.ring_out_frames());
}
} // namespace
} // namespace media::audio