blob: 9887526369b4478e156698c9c99dcc3375d9e942 [file] [log] [blame]
// Copyright 2018 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 <dlfcn.h>
#include <cmath>
#include "garnet/bin/media/audio_core/mixer/fx_loader.h"
#include "garnet/bin/media/audio_core/mixer/fx_processor.h"
#include "garnet/public/lib/media/audio_dfx/audio_device_fx.h"
#include "garnet/public/lib/media/audio_dfx/lib/dfx_base.h"
#include "garnet/public/lib/media/audio_dfx/lib/dfx_delay.h"
#include "garnet/public/lib/media/audio_dfx/lib/dfx_rechannel.h"
#include "garnet/public/lib/media/audio_dfx/lib/dfx_swap.h"
#include "gtest/gtest.h"
// By directly including the fx_loader module from audio_mixer_lib, we eliminate
// a lot of duplicate SO-loading and export-checking code, plus we can now
// directly use/test the fx_loader itself. Otherwise, we would be able to test
// it only within the context of the audio_core process itself, which would
// prevent us from using a test binary, or using a audio_dfx.so library that
// was built outside the audio_core package.
namespace media {
namespace audio {
// We override this method so that we can name our test library differently than
// the hard-coded "audiofx.so" that fx_loader always loads into audio_core.
class TestFxLoader : public FxLoader {
public:
void* OpenLoadableModuleBinary() override {
return dlopen("audio_dfx.so", RTLD_LAZY | RTLD_GLOBAL);
}
};
} // namespace audio
namespace audio_dfx_test {
//
// Tests FxLoader, which directly calls the shared library interface.
//
class FxLoaderTest : public testing::Test {
protected:
audio::TestFxLoader fx_loader_;
void SetUp() override { ASSERT_EQ(fx_loader_.LoadLibrary(), ZX_OK); }
void TearDown() override { fx_loader_.UnloadLibrary(); }
};
//
// These child classes may not differentiate, but we use different classes for
// Delay/Rechannel/Swap in order to group related test results accordingly.
//
class FxDelayTest : public FxLoaderTest {
protected:
void TestDelayBounds(uint32_t frame_rate, uint32_t channels,
uint32_t delay_frames);
};
class FxRechannelTest : public FxLoaderTest {};
class FxSwapTest : public FxLoaderTest {};
class FxProcessorTest : public FxLoaderTest {
protected:
audio::FxProcessor* fx_processor_;
void SetUp() override {
FxLoaderTest::SetUp();
fx_processor_ = new ::media::audio::FxProcessor(&fx_loader_, 48000);
}
void TearDown() override {
delete fx_processor_;
FxLoaderTest::TearDown();
}
};
// When validating controls, we make certain assumptions about the test effects.
static_assert(DfxDelay::kNumControls > 0, "DfxDelay must have controls");
static_assert(DfxRechannel::kNumControls == 0,
"DfxRechannel must have no controls");
static_assert(DfxSwap::kNumControls == 0, "DfxSwap must have no controls");
// We test the delay effect with certain control values, making assumptions
// about how those values relate to the allowed range for this DFX.
constexpr float kTestDelay1 = 1.0f;
constexpr float kTestDelay2 = 2.0f;
static_assert(DfxDelay::kMaxDelayFrames >= kTestDelay2, "Test value too high");
static_assert(DfxDelay::kMinDelayFrames <= kTestDelay1, "Test value too low");
static_assert(DfxDelay::kInitialDelayFrames != kTestDelay1,
"kTestDelay1 must not equal kInitialDelayFrames");
static_assert(DfxDelay::kInitialDelayFrames != kTestDelay2,
"kTestDelay2 must not equal kInitialDelayFrames");
// For the most part, the below tests use a specific channel_count.
constexpr uint16_t kTestChans = 2;
// When testing or using the delay effect, we make certain channel assumptions.
static_assert(DfxDelay::kNumChannelsIn == kTestChans ||
DfxDelay::kNumChannelsIn == FUCHSIA_AUDIO_DFX_CHANNELS_ANY,
"DfxDelay::kNumChannelsIn must match kTestChans");
static_assert(DfxDelay::kNumChannelsOut == kTestChans ||
DfxDelay::kNumChannelsOut == FUCHSIA_AUDIO_DFX_CHANNELS_ANY ||
DfxDelay::kNumChannelsOut ==
FUCHSIA_AUDIO_DFX_CHANNELS_SAME_AS_IN,
"DfxDelay::kNumChannelsOut must match kTestChans");
// When testing or using rechannel effect, we make certain channel assumptions.
static_assert(DfxRechannel::kNumChannelsIn != 2 ||
DfxRechannel::kNumChannelsOut != 2,
"DfxRechannel must not be stereo-in/-out");
static_assert(DfxRechannel::kNumChannelsIn != DfxRechannel::kNumChannelsOut &&
DfxRechannel::kNumChannelsOut !=
FUCHSIA_AUDIO_DFX_CHANNELS_ANY &&
DfxRechannel::kNumChannelsOut !=
FUCHSIA_AUDIO_DFX_CHANNELS_SAME_AS_IN,
"DfxRechannel must not be in-place");
// When testing or using the swap effect, we make certain channel assumptions.
static_assert(DfxSwap::kNumChannelsIn == kTestChans ||
DfxSwap::kNumChannelsIn == FUCHSIA_AUDIO_DFX_CHANNELS_ANY,
"DfxSwap::kNumChannelsIn must match kTestChans");
static_assert(DfxSwap::kNumChannelsOut == kTestChans ||
DfxSwap::kNumChannelsOut == FUCHSIA_AUDIO_DFX_CHANNELS_ANY ||
DfxSwap::kNumChannelsOut ==
FUCHSIA_AUDIO_DFX_CHANNELS_SAME_AS_IN,
"DfxSwap::kNumChannelsOut must match kTestChans");
// Tests the get_num_effects ABI, and that the test library behaves as expected.
TEST_F(FxLoaderTest, GetNumEffects) {
uint32_t num_effects;
EXPECT_EQ(fx_loader_.GetNumFx(&num_effects), ZX_OK);
EXPECT_TRUE(num_effects == DfxBase::kNumTestEffects);
// Verify null out_param
EXPECT_NE(fx_loader_.GetNumFx(nullptr), ZX_OK);
}
// Tests the get_info ABI, and that the test DFXs behave as expected.
TEST_F(FxLoaderTest, GetInfo) {
fuchsia_audio_dfx_description dfx_desc;
EXPECT_EQ(fx_loader_.GetFxInfo(Effect::Delay, &dfx_desc), ZX_OK);
EXPECT_TRUE(dfx_desc.num_controls == DfxDelay::kNumControls);
EXPECT_TRUE(dfx_desc.incoming_channels == DfxDelay::kNumChannelsIn);
EXPECT_TRUE(dfx_desc.outgoing_channels == DfxDelay::kNumChannelsOut);
EXPECT_EQ(fx_loader_.GetFxInfo(Effect::Swap, &dfx_desc), ZX_OK);
EXPECT_TRUE(dfx_desc.num_controls == DfxSwap::kNumControls);
EXPECT_TRUE(dfx_desc.incoming_channels == DfxSwap::kNumChannelsIn);
EXPECT_TRUE(dfx_desc.outgoing_channels == DfxSwap::kNumChannelsOut);
EXPECT_EQ(fx_loader_.GetFxInfo(Effect::Rechannel, &dfx_desc), ZX_OK);
EXPECT_TRUE(dfx_desc.num_controls == DfxRechannel::kNumControls);
EXPECT_TRUE(dfx_desc.incoming_channels == DfxRechannel::kNumChannelsIn);
EXPECT_TRUE(dfx_desc.outgoing_channels == DfxRechannel::kNumChannelsOut);
// Verify effect beyond range
EXPECT_NE(fx_loader_.GetFxInfo(Effect::Count, &dfx_desc), ZX_OK);
// Verify null struct*
EXPECT_NE(fx_loader_.GetFxInfo(Effect::Rechannel, nullptr), ZX_OK);
}
// Tests the get_control_info ABI, and that the test DFXs behave as expected.
TEST_F(FxLoaderTest, GetControlInfo) {
fuchsia_audio_dfx_control_description dfx_control_desc;
EXPECT_EQ(fx_loader_.GetFxControlInfo(Effect::Delay, 0, &dfx_control_desc),
ZX_OK);
EXPECT_LE(dfx_control_desc.initial_val, dfx_control_desc.max_val);
EXPECT_GE(dfx_control_desc.initial_val, dfx_control_desc.min_val);
EXPECT_TRUE(dfx_control_desc.max_val == DfxDelay::kMaxDelayFrames);
EXPECT_TRUE(dfx_control_desc.min_val == DfxDelay::kMinDelayFrames);
EXPECT_TRUE(dfx_control_desc.initial_val == DfxDelay::kInitialDelayFrames);
// Verify control beyond range
EXPECT_NE(fx_loader_.GetFxControlInfo(Effect::Delay, DfxDelay::kNumControls,
&dfx_control_desc),
ZX_OK);
// Verify null struct*
EXPECT_NE(fx_loader_.GetFxControlInfo(Effect::Delay, 0, nullptr), ZX_OK);
// Verify effects with no controls
EXPECT_NE(
fx_loader_.GetFxControlInfo(Effect::Rechannel, 0, &dfx_control_desc),
ZX_OK);
EXPECT_NE(fx_loader_.GetFxControlInfo(Effect::Swap, 0, &dfx_control_desc),
ZX_OK);
EXPECT_NE(fx_loader_.GetFxControlInfo(Effect::Count, 0, &dfx_control_desc),
ZX_OK);
}
// Tests the create ABI.
TEST_F(FxLoaderTest, Create) {
uint32_t frame_rate = 0;
fx_token_t dfx_token =
fx_loader_.CreateFx(Effect::Delay, frame_rate, kTestChans, kTestChans);
EXPECT_NE(dfx_token, FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
dfx_token =
fx_loader_.CreateFx(Effect::Swap, frame_rate, kTestChans, kTestChans);
EXPECT_NE(dfx_token, FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
dfx_token = fx_loader_.CreateFx(Effect::Rechannel, frame_rate,
DfxRechannel::kNumChannelsIn,
DfxRechannel::kNumChannelsOut);
EXPECT_NE(dfx_token, FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
// Verify num_channels mismatch (is not equal, should be)
EXPECT_EQ(fx_loader_.CreateFx(Effect::Delay, frame_rate, kTestChans,
kTestChans - 1),
FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
// Verify num_channels mismatch (is equal, should not be)
EXPECT_EQ(fx_loader_.CreateFx(Effect::Rechannel, frame_rate, kTestChans,
kTestChans),
FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
// Verify effect out of range
EXPECT_EQ(
fx_loader_.CreateFx(Effect::Count, frame_rate, kTestChans, kTestChans),
FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
// Verify channels out of range
EXPECT_EQ(fx_loader_.CreateFx(Effect::Delay, frame_rate,
FUCHSIA_AUDIO_DFX_CHANNELS_MAX + 1,
FUCHSIA_AUDIO_DFX_CHANNELS_MAX + 1),
FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
EXPECT_EQ(fx_loader_.CreateFx(Effect::Delay, frame_rate,
FUCHSIA_AUDIO_DFX_CHANNELS_SAME_AS_IN,
FUCHSIA_AUDIO_DFX_CHANNELS_SAME_AS_IN),
FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
}
// Tests the delete ABI.
TEST_F(FxLoaderTest, Delete) {
fx_token_t dfx_token =
fx_loader_.CreateFx(Effect::Delay, 48000, kTestChans, kTestChans);
ASSERT_NE(dfx_token, FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
EXPECT_EQ(fx_loader_.DeleteFx(dfx_token), ZX_OK);
EXPECT_NE(fx_loader_.DeleteFx(FUCHSIA_AUDIO_DFX_INVALID_TOKEN), ZX_OK);
}
// Tests the get_parameters ABI, and that the test DFX behaves as expected.
TEST_F(FxDelayTest, GetParameters) {
fuchsia_audio_dfx_parameters device_fx_params;
uint32_t frame_rate = 48000;
fx_token_t dfx_token =
fx_loader_.CreateFx(Effect::Delay, frame_rate, kTestChans, kTestChans);
EXPECT_EQ(fx_loader_.FxGetParameters(dfx_token, &device_fx_params), ZX_OK);
EXPECT_EQ(device_fx_params.frame_rate, frame_rate);
EXPECT_EQ(device_fx_params.channels_in, kTestChans);
EXPECT_EQ(device_fx_params.channels_out, kTestChans);
EXPECT_TRUE(device_fx_params.signal_latency_frames ==
DfxDelay::kLatencyFrames);
EXPECT_TRUE(device_fx_params.suggested_frames_per_buffer ==
DfxDelay::kLatencyFrames);
// Verify invalid device token
EXPECT_NE(fx_loader_.FxGetParameters(FUCHSIA_AUDIO_DFX_INVALID_TOKEN,
&device_fx_params),
ZX_OK);
// Verify null struct*
EXPECT_NE(fx_loader_.FxGetParameters(dfx_token, nullptr), ZX_OK);
EXPECT_EQ(fx_loader_.DeleteFx(dfx_token), ZX_OK);
}
// Tests the get_parameters ABI, and that the test DFX behaves as expected.
TEST_F(FxRechannelTest, GetParameters) {
fuchsia_audio_dfx_parameters device_fx_params;
uint32_t frame_rate = 48000;
fx_token_t dfx_token = fx_loader_.CreateFx(Effect::Rechannel, frame_rate,
DfxRechannel::kNumChannelsIn,
DfxRechannel::kNumChannelsOut);
device_fx_params.frame_rate = 44100; // should be overwritten
EXPECT_EQ(fx_loader_.FxGetParameters(dfx_token, &device_fx_params), ZX_OK);
EXPECT_EQ(device_fx_params.frame_rate, frame_rate);
EXPECT_TRUE(device_fx_params.channels_in == DfxRechannel::kNumChannelsIn);
EXPECT_TRUE(device_fx_params.channels_out == DfxRechannel::kNumChannelsOut);
EXPECT_TRUE(device_fx_params.signal_latency_frames ==
DfxRechannel::kLatencyFrames);
EXPECT_TRUE(device_fx_params.suggested_frames_per_buffer ==
DfxRechannel::kLatencyFrames);
EXPECT_EQ(fx_loader_.DeleteFx(dfx_token), ZX_OK);
}
// Tests the get_parameters ABI, and that the test DFX behaves as expected.
TEST_F(FxSwapTest, GetParameters) {
fuchsia_audio_dfx_parameters device_fx_params;
uint32_t frame_rate = 44100;
fx_token_t dfx_token =
fx_loader_.CreateFx(Effect::Swap, frame_rate, kTestChans, kTestChans);
device_fx_params.frame_rate = 48000; // should be overwritten
EXPECT_EQ(fx_loader_.FxGetParameters(dfx_token, &device_fx_params), ZX_OK);
EXPECT_EQ(device_fx_params.frame_rate, frame_rate);
EXPECT_EQ(device_fx_params.channels_in, kTestChans);
EXPECT_EQ(device_fx_params.channels_out, kTestChans);
EXPECT_TRUE(device_fx_params.signal_latency_frames ==
DfxSwap::kLatencyFrames);
EXPECT_TRUE(device_fx_params.suggested_frames_per_buffer ==
DfxSwap::kLatencyFrames);
EXPECT_EQ(fx_loader_.DeleteFx(dfx_token), ZX_OK);
}
// Tests the get_control_value ABI, and that the test DFX behaves as expected.
TEST_F(FxDelayTest, GetControlValue) {
uint16_t control_num = 0;
fuchsia_audio_dfx_description dfx_desc;
fuchsia_audio_dfx_control_description dfx_control_desc;
ASSERT_EQ(fx_loader_.GetFxInfo(Effect::Delay, &dfx_desc), ZX_OK);
ASSERT_GT(dfx_desc.num_controls, control_num);
ASSERT_EQ(fx_loader_.GetFxControlInfo(Effect::Delay, control_num,
&dfx_control_desc),
ZX_OK);
fx_token_t dfx_token =
fx_loader_.CreateFx(Effect::Delay, 48000, kTestChans, kTestChans);
ASSERT_NE(dfx_token, FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
float val;
EXPECT_EQ(fx_loader_.FxGetControlValue(dfx_token, control_num, &val), ZX_OK);
EXPECT_GE(val, dfx_control_desc.min_val);
EXPECT_LE(val, dfx_control_desc.max_val);
EXPECT_EQ(val, dfx_control_desc.initial_val);
// Verify invalid effect token
EXPECT_NE(fx_loader_.FxGetControlValue(FUCHSIA_AUDIO_DFX_INVALID_TOKEN,
control_num, &val),
ZX_OK);
// Verify control out of range
EXPECT_NE(
fx_loader_.FxGetControlValue(dfx_token, dfx_desc.num_controls, &val),
ZX_OK);
// Verify null out_param
EXPECT_NE(fx_loader_.FxGetControlValue(dfx_token, control_num, nullptr),
ZX_OK);
EXPECT_EQ(fx_loader_.DeleteFx(dfx_token), ZX_OK);
}
// Tests cases in which we expect get_control_value to fail.
TEST_F(FxRechannelTest, GetControlValue) {
float val;
fx_token_t dfx_token = fx_loader_.CreateFx(Effect::Rechannel, 48000,
DfxRechannel::kNumChannelsIn,
DfxRechannel::kNumChannelsOut);
ASSERT_NE(dfx_token, FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
EXPECT_NE(fx_loader_.FxGetControlValue(dfx_token, 0, &val), ZX_OK);
EXPECT_EQ(fx_loader_.DeleteFx(dfx_token), ZX_OK);
}
// Tests cases in which we expect get_control_value to fail.
TEST_F(FxSwapTest, GetControlValue) {
float val;
fx_token_t dfx_token =
fx_loader_.CreateFx(Effect::Swap, 48000, kTestChans, kTestChans);
ASSERT_NE(dfx_token, FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
EXPECT_NE(fx_loader_.FxGetControlValue(dfx_token, 0, &val), ZX_OK);
EXPECT_EQ(fx_loader_.DeleteFx(dfx_token), ZX_OK);
}
// Tests the set_control_value ABI, and that the test DFX behaves as expected.
TEST_F(FxDelayTest, SetControlValue) {
uint16_t control_num = 0;
fuchsia_audio_dfx_description dfx_desc;
fuchsia_audio_dfx_control_description dfx_control_desc;
ASSERT_EQ(fx_loader_.GetFxInfo(Effect::Delay, &dfx_desc), ZX_OK);
ASSERT_GT(dfx_desc.num_controls, control_num);
ASSERT_EQ(fx_loader_.GetFxControlInfo(Effect::Delay, control_num,
&dfx_control_desc),
ZX_OK);
fx_token_t dfx_token =
fx_loader_.CreateFx(Effect::Delay, 48000, kTestChans, kTestChans);
EXPECT_EQ(fx_loader_.FxSetControlValue(dfx_token, control_num, kTestDelay1),
ZX_OK);
float new_value;
EXPECT_EQ(fx_loader_.FxGetControlValue(dfx_token, control_num, &new_value),
ZX_OK);
EXPECT_EQ(new_value, kTestDelay1);
// Verify invalid effect token
EXPECT_NE(fx_loader_.FxSetControlValue(FUCHSIA_AUDIO_DFX_INVALID_TOKEN,
control_num, kTestDelay1),
ZX_OK);
// Verify control out of range
EXPECT_NE(fx_loader_.FxSetControlValue(dfx_token, dfx_desc.num_controls,
kTestDelay1),
ZX_OK);
// Verify value out of range
EXPECT_NE(fx_loader_.FxSetControlValue(dfx_token, control_num,
dfx_control_desc.max_val + 1),
ZX_OK);
EXPECT_EQ(fx_loader_.DeleteFx(dfx_token), ZX_OK);
}
// Tests cases in which we expect set_control_value to fail.
TEST_F(FxRechannelTest, SetControlValue) {
fx_token_t dfx_token = fx_loader_.CreateFx(Effect::Rechannel, 48000,
DfxRechannel::kNumChannelsIn,
DfxRechannel::kNumChannelsOut);
ASSERT_NE(dfx_token, FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
EXPECT_NE(fx_loader_.FxSetControlValue(dfx_token, 0, 0), ZX_OK);
EXPECT_EQ(fx_loader_.DeleteFx(dfx_token), ZX_OK);
}
// Tests cases in which we expect set_control_value to fail.
TEST_F(FxSwapTest, SetControlValue) {
fx_token_t dfx_token =
fx_loader_.CreateFx(Effect::Swap, 48000, kTestChans, kTestChans);
ASSERT_NE(dfx_token, FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
EXPECT_NE(fx_loader_.FxSetControlValue(dfx_token, 0, 0), ZX_OK);
EXPECT_EQ(fx_loader_.DeleteFx(dfx_token), ZX_OK);
}
// Tests the reset ABI, and that DFX discards state and control values.
TEST_F(FxDelayTest, Reset) {
uint16_t control_num = 0;
fuchsia_audio_dfx_description dfx_desc;
fuchsia_audio_dfx_control_description dfx_control_desc;
ASSERT_EQ(fx_loader_.GetFxInfo(Effect::Delay, &dfx_desc), ZX_OK);
ASSERT_GT(dfx_desc.num_controls, control_num);
ASSERT_EQ(fx_loader_.GetFxControlInfo(Effect::Delay, control_num,
&dfx_control_desc),
ZX_OK);
fx_token_t dfx_token =
fx_loader_.CreateFx(Effect::Delay, 48000, kTestChans, kTestChans);
float new_value;
ASSERT_EQ(fx_loader_.FxGetControlValue(dfx_token, control_num, &new_value),
ZX_OK);
EXPECT_NE(new_value, kTestDelay1);
ASSERT_EQ(fx_loader_.FxSetControlValue(dfx_token, control_num, kTestDelay1),
ZX_OK);
ASSERT_EQ(fx_loader_.FxGetControlValue(dfx_token, control_num, &new_value),
ZX_OK);
ASSERT_EQ(new_value, kTestDelay1);
EXPECT_EQ(fx_loader_.FxReset(dfx_token), ZX_OK);
EXPECT_EQ(fx_loader_.FxGetControlValue(dfx_token, control_num, &new_value),
ZX_OK);
EXPECT_NE(new_value, kTestDelay1);
EXPECT_TRUE(new_value == DfxDelay::kInitialDelayFrames);
EXPECT_EQ(fx_loader_.DeleteFx(dfx_token), ZX_OK);
// Verify invalid effect token
EXPECT_NE(fx_loader_.FxReset(FUCHSIA_AUDIO_DFX_INVALID_TOKEN), ZX_OK);
}
// Tests the process_inplace ABI, and that the test DFX behaves as expected.
TEST_F(FxDelayTest, ProcessInPlace) {
uint32_t num_samples = 12 * kTestChans;
uint32_t delay_samples = 6 * kTestChans;
float delay_buff_in_out[num_samples];
float expect[num_samples];
for (uint32_t i = 0; i < delay_samples; ++i) {
delay_buff_in_out[i] = static_cast<float>(i + 1);
expect[i] = 0.0f;
}
for (uint32_t i = delay_samples; i < num_samples; ++i) {
delay_buff_in_out[i] = static_cast<float>(i + 1);
expect[i] = delay_buff_in_out[i - delay_samples];
}
fx_token_t dfx_token =
fx_loader_.CreateFx(Effect::Delay, 48000, kTestChans, kTestChans);
ASSERT_NE(dfx_token, FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
uint16_t control_num = 0;
ASSERT_EQ(fx_loader_.FxSetControlValue(
dfx_token, control_num,
static_cast<float>(delay_samples) / kTestChans),
ZX_OK);
EXPECT_EQ(fx_loader_.FxProcessInPlace(dfx_token, 4, delay_buff_in_out),
ZX_OK);
EXPECT_EQ(fx_loader_.FxProcessInPlace(dfx_token, 4,
delay_buff_in_out + (4 * kTestChans)),
ZX_OK);
EXPECT_EQ(fx_loader_.FxProcessInPlace(dfx_token, 4,
delay_buff_in_out + (8 * kTestChans)),
ZX_OK);
for (uint32_t sample = 0; sample < num_samples; ++sample) {
EXPECT_EQ(delay_buff_in_out[sample], expect[sample]) << sample;
}
EXPECT_EQ(fx_loader_.FxProcessInPlace(dfx_token, 0, delay_buff_in_out),
ZX_OK);
EXPECT_EQ(fx_loader_.DeleteFx(dfx_token), ZX_OK);
}
// Tests cases in which we expect process_inplace to fail.
TEST_F(FxRechannelTest, ProcessInPlace) {
constexpr uint32_t kNumFrames = 1;
float buff_in_out[kNumFrames * DfxRechannel::kNumChannelsIn] = {0};
// Effects that change the channelization should not process in-place.
fx_token_t dfx_token = fx_loader_.CreateFx(Effect::Rechannel, 48000,
DfxRechannel::kNumChannelsIn,
DfxRechannel::kNumChannelsOut);
ASSERT_NE(dfx_token, FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
EXPECT_NE(fx_loader_.FxProcessInPlace(dfx_token, kNumFrames, buff_in_out),
ZX_OK);
EXPECT_EQ(fx_loader_.DeleteFx(dfx_token), ZX_OK);
}
// Tests the process_inplace ABI, and that the test DFX behaves as expected.
TEST_F(FxSwapTest, ProcessInPlace) {
constexpr uint32_t kNumFrames = 4;
float swap_buff_in_out[kNumFrames * kTestChans] = {1.0f, -1.0f, 1.0f, -1.0f,
1.0f, -1.0f, 1.0f, -1.0f};
fx_token_t dfx_token =
fx_loader_.CreateFx(Effect::Swap, 48000, kTestChans, kTestChans);
ASSERT_NE(dfx_token, FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
EXPECT_EQ(
fx_loader_.FxProcessInPlace(dfx_token, kNumFrames, swap_buff_in_out),
ZX_OK);
for (uint32_t sample_num = 0; sample_num < kNumFrames * kTestChans;
++sample_num) {
EXPECT_EQ(swap_buff_in_out[sample_num], (sample_num % 2 ? 1.0f : -1.0f));
}
EXPECT_EQ(fx_loader_.FxProcessInPlace(dfx_token, 0, swap_buff_in_out), ZX_OK);
// Calls with invalid token or null buff_ptr should fail.
EXPECT_NE(fx_loader_.FxProcessInPlace(FUCHSIA_AUDIO_DFX_INVALID_TOKEN,
kNumFrames, swap_buff_in_out),
ZX_OK);
EXPECT_NE(fx_loader_.FxProcessInPlace(dfx_token, kNumFrames, nullptr), ZX_OK);
EXPECT_NE(fx_loader_.FxProcessInPlace(dfx_token, 0, nullptr), ZX_OK);
EXPECT_EQ(fx_loader_.DeleteFx(dfx_token), ZX_OK);
}
// Tests cases in which we expect process to fail.
TEST_F(FxDelayTest, Process) {
constexpr uint32_t kNumFrames = 1;
float audio_buff_in[kNumFrames * kTestChans] = {0.0f};
float audio_buff_out[kNumFrames * kTestChans] = {0.0f};
// These stereo-to-stereo effects should ONLY process in-place
fx_token_t dfx_token =
fx_loader_.CreateFx(Effect::Delay, 48000, kTestChans, kTestChans);
ASSERT_NE(dfx_token, FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
EXPECT_NE(fx_loader_.FxProcess(dfx_token, kNumFrames, audio_buff_in,
audio_buff_out),
ZX_OK);
EXPECT_EQ(fx_loader_.DeleteFx(dfx_token), ZX_OK);
}
// Tests the process ABI, and that the test DFX behaves as expected.
TEST_F(FxRechannelTest, Process) {
constexpr uint32_t kNumFrames = 1;
float audio_buff_in[kNumFrames * DfxRechannel::kNumChannelsIn] = {
1.0f, -1.0f, 0.25f, -1.0f, 0.98765432f, -0.09876544f};
float audio_buff_out[kNumFrames * DfxRechannel::kNumChannelsOut] = {0.0f};
float expected[kNumFrames * DfxRechannel::kNumChannelsOut] = {0.799536645f,
-0.340580851f};
fx_token_t dfx_token = fx_loader_.CreateFx(Effect::Rechannel, 48000,
DfxRechannel::kNumChannelsIn,
DfxRechannel::kNumChannelsOut);
ASSERT_NE(dfx_token, FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
EXPECT_EQ(fx_loader_.FxProcess(dfx_token, kNumFrames, audio_buff_in,
audio_buff_out),
ZX_OK);
EXPECT_EQ(audio_buff_out[0], expected[0])
<< std::setprecision(9) << audio_buff_out[0];
EXPECT_EQ(audio_buff_out[1], expected[1])
<< std::setprecision(9) << audio_buff_out[1];
EXPECT_EQ(fx_loader_.FxProcess(dfx_token, 0, audio_buff_in, audio_buff_out),
ZX_OK);
// Test null token, buffer_in, buffer_out
EXPECT_NE(fx_loader_.FxProcess(FUCHSIA_AUDIO_DFX_INVALID_TOKEN, kNumFrames,
audio_buff_in, audio_buff_out),
ZX_OK);
EXPECT_NE(
fx_loader_.FxProcess(dfx_token, kNumFrames, nullptr, audio_buff_out),
ZX_OK);
EXPECT_NE(fx_loader_.FxProcess(dfx_token, kNumFrames, audio_buff_in, nullptr),
ZX_OK);
EXPECT_NE(fx_loader_.FxProcess(dfx_token, 0, nullptr, audio_buff_out), ZX_OK);
EXPECT_NE(fx_loader_.FxProcess(dfx_token, 0, audio_buff_in, nullptr), ZX_OK);
EXPECT_EQ(fx_loader_.DeleteFx(dfx_token), ZX_OK);
}
// Tests cases in which we expect process to fail.
TEST_F(FxSwapTest, Process) {
constexpr uint32_t kNumFrames = 1;
float audio_buff_in[kNumFrames * kTestChans] = {0.0f};
float audio_buff_out[kNumFrames * kTestChans] = {0.0f};
// These stereo-to-stereo effects should ONLY process in-place
fx_token_t dfx_token =
fx_loader_.CreateFx(Effect::Swap, 48000, kTestChans, kTestChans);
ASSERT_NE(dfx_token, FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
EXPECT_NE(fx_loader_.FxProcess(dfx_token, kNumFrames, audio_buff_in,
audio_buff_out),
ZX_OK);
EXPECT_EQ(fx_loader_.DeleteFx(dfx_token), ZX_OK);
}
// Tests the process_inplace ABI thru successive in-place calls.
TEST_F(FxDelayTest, ProcessInPlace_Chain) {
constexpr uint32_t kNumFrames = 6;
float buff_in_out[kNumFrames * kTestChans] = {1.0f, -0.1f, -0.2f, 2.0f,
0.3f, -3.0f, -4.0f, 0.4f,
5.0f, -0.5f, -0.6f, 6.0f};
float expected[kNumFrames * kTestChans] = {0.0f, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, -0.1f, 1.0f,
2.0f, -0.2f, -3.0f, 0.3f};
fx_token_t delay1_token, swap_token, delay2_token;
delay1_token =
fx_loader_.CreateFx(Effect::Delay, 44100, kTestChans, kTestChans);
swap_token = fx_loader_.CreateFx(Effect::Swap, 44100, kTestChans, kTestChans);
delay2_token =
fx_loader_.CreateFx(Effect::Delay, 44100, kTestChans, kTestChans);
ASSERT_NE(delay1_token, FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
ASSERT_NE(swap_token, FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
ASSERT_NE(delay2_token, FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
uint16_t control_num = 0;
ASSERT_EQ(
fx_loader_.FxSetControlValue(delay1_token, control_num, kTestDelay1),
ZX_OK);
ASSERT_EQ(
fx_loader_.FxSetControlValue(delay2_token, control_num, kTestDelay2),
ZX_OK);
EXPECT_EQ(fx_loader_.FxProcessInPlace(delay1_token, kNumFrames, buff_in_out),
ZX_OK);
EXPECT_EQ(fx_loader_.FxProcessInPlace(swap_token, kNumFrames, buff_in_out),
ZX_OK);
EXPECT_EQ(fx_loader_.FxProcessInPlace(delay2_token, kNumFrames, buff_in_out),
ZX_OK);
for (uint32_t sample_num = 0; sample_num < kNumFrames * kTestChans;
++sample_num) {
EXPECT_EQ(buff_in_out[sample_num], expected[sample_num]) << sample_num;
}
EXPECT_EQ(fx_loader_.FxProcessInPlace(delay2_token, 0, buff_in_out), ZX_OK);
EXPECT_EQ(fx_loader_.FxProcessInPlace(swap_token, 0, buff_in_out), ZX_OK);
EXPECT_EQ(fx_loader_.FxProcessInPlace(delay1_token, 0, buff_in_out), ZX_OK);
EXPECT_EQ(fx_loader_.DeleteFx(delay2_token), ZX_OK);
EXPECT_EQ(fx_loader_.DeleteFx(swap_token), ZX_OK);
EXPECT_EQ(fx_loader_.DeleteFx(delay1_token), ZX_OK);
}
// Tests the flush ABI, and that DFX discards state but retains control values.
TEST_F(FxDelayTest, Flush) {
constexpr uint32_t kNumFrames = 1;
float buff_in_out[kTestChans] = {1.0f, -1.0f};
fx_token_t dfx_token =
fx_loader_.CreateFx(Effect::Delay, 44100, kTestChans, kTestChans);
float new_value;
ASSERT_EQ(fx_loader_.FxGetControlValue(dfx_token, 0, &new_value), ZX_OK);
EXPECT_NE(new_value, kTestDelay1);
ASSERT_EQ(fx_loader_.FxSetControlValue(dfx_token, 0, kTestDelay1), ZX_OK);
ASSERT_EQ(fx_loader_.FxGetControlValue(dfx_token, 0, &new_value), ZX_OK);
ASSERT_EQ(new_value, kTestDelay1);
ASSERT_EQ(fx_loader_.FxProcessInPlace(dfx_token, kNumFrames, buff_in_out),
ZX_OK);
ASSERT_EQ(buff_in_out[0], 0.0f);
EXPECT_EQ(fx_loader_.FxFlush(dfx_token), ZX_OK);
// Validate that settings are retained after Flush.
EXPECT_EQ(fx_loader_.FxGetControlValue(dfx_token, 0, &new_value), ZX_OK);
EXPECT_EQ(new_value, kTestDelay1);
// Validate that cached samples are flushed.
EXPECT_EQ(fx_loader_.FxProcessInPlace(dfx_token, kNumFrames, buff_in_out),
ZX_OK);
EXPECT_EQ(buff_in_out[0], 0.0f);
// Verify invalid effect token
EXPECT_NE(fx_loader_.FxFlush(FUCHSIA_AUDIO_DFX_INVALID_TOKEN), ZX_OK);
EXPECT_EQ(fx_loader_.DeleteFx(dfx_token), ZX_OK);
}
//
// We use this subfunction to test the outer limits allowed by ProcessInPlace.
void FxDelayTest::TestDelayBounds(uint32_t frame_rate, uint32_t channels,
uint32_t delay_frames) {
uint32_t delay_samples = delay_frames * channels;
uint32_t num_frames = frame_rate;
uint32_t num_samples = num_frames * channels;
std::unique_ptr<float[]> delay_buff_in_out =
std::make_unique<float[]>(num_samples);
std::unique_ptr<float[]> expect = std::make_unique<float[]>(num_samples);
fx_token_t dfx_token =
fx_loader_.CreateFx(Effect::Delay, frame_rate, channels, channels);
ASSERT_NE(dfx_token, FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
ASSERT_EQ(fx_loader_.FxSetControlValue(dfx_token, 0,
static_cast<float>(delay_frames)),
ZX_OK);
for (uint32_t pass = 0; pass < 2; ++pass) {
for (uint32_t i = 0; i < num_samples; ++i) {
delay_buff_in_out[i] = static_cast<float>(i + pass * num_samples + 1);
expect[i] = fmax(delay_buff_in_out[i] - delay_samples, 0.0f);
}
EXPECT_EQ(fx_loader_.FxProcessInPlace(dfx_token, num_frames,
delay_buff_in_out.get()),
ZX_OK);
for (uint32_t sample = 0; sample < num_samples; ++sample) {
EXPECT_EQ(delay_buff_in_out[sample], expect[sample]) << sample;
}
}
EXPECT_EQ(fx_loader_.DeleteFx(dfx_token), ZX_OK);
}
// Verifies DfxDelay at the outer allowed bounds (largest delays and buffers).
TEST_F(FxDelayTest, ProcessInPlace_Bounds) {
TestDelayBounds(192000, 2, DfxDelay::kMaxDelayFrames);
TestDelayBounds(2000, FUCHSIA_AUDIO_DFX_CHANNELS_MAX,
DfxDelay::kMaxDelayFrames);
}
//
// The following tests validates the FxProcessor class itself.
//
// Verify the creation, uniqueness, quantity and deletion of effect instances.
TEST_F(FxProcessorTest, CreateDelete) {
fx_token_t token3 = fx_processor_->CreateFx(0, 1, 1, 0);
fx_token_t token1 = fx_processor_->CreateFx(0, 1, 1, 0);
fx_token_t token2 = fx_processor_->CreateFx(0, 1, 1, 1);
fx_token_t token4 = fx_processor_->CreateFx(0, 1, 1, 3);
ASSERT_TRUE(token1 != FUCHSIA_AUDIO_DFX_INVALID_TOKEN &&
token2 != FUCHSIA_AUDIO_DFX_INVALID_TOKEN &&
token3 != FUCHSIA_AUDIO_DFX_INVALID_TOKEN &&
token4 != FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
EXPECT_TRUE(token1 != token2 && token1 != token3 && token1 != token4 &&
token2 != token3 && token2 != token4 && token3 != token4);
EXPECT_EQ(fx_processor_->GetNumFx(), 4);
fx_token_t token5 = fx_processor_->CreateFx(0, 1, 1, 5);
EXPECT_EQ(token5, FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
// Remove one of the four instances.
EXPECT_EQ(fx_processor_->DeleteFx(token3), ZX_OK);
EXPECT_EQ(fx_processor_->GetNumFx(), 3);
// Remove a second instance.
EXPECT_EQ(fx_processor_->DeleteFx(token4), ZX_OK);
EXPECT_EQ(fx_processor_->GetNumFx(), 2);
// This token has already been removed.
EXPECT_NE(fx_processor_->DeleteFx(token3), ZX_OK);
EXPECT_EQ(fx_processor_->GetNumFx(), 2);
// Remove a third instance -- only one should remain.
EXPECT_EQ(fx_processor_->DeleteFx(token1), ZX_OK);
EXPECT_EQ(fx_processor_->GetNumFx(), 1);
// Invalid token cannot be removed/deleted.
EXPECT_NE(fx_processor_->DeleteFx(FUCHSIA_AUDIO_DFX_INVALID_TOKEN), ZX_OK);
EXPECT_EQ(fx_processor_->GetNumFx(), 1);
// Remove fourth and last instance.
EXPECT_EQ(fx_processor_->DeleteFx(token2), ZX_OK);
EXPECT_EQ(fx_processor_->GetNumFx(), 0);
// This token has already been removed -- also empty chain.
EXPECT_NE(fx_processor_->DeleteFx(token4), ZX_OK);
EXPECT_EQ(fx_processor_->GetNumFx(), 0);
// Inserting an instance into a chain that has been populated, then emptied.
EXPECT_NE(fx_processor_->CreateFx(0, 1, 1, 0),
FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
EXPECT_EQ(fx_processor_->GetNumFx(), 1);
// Leave an active instance, to exercise the destructor cleanup.
}
// Verify the chain's positioning -- during insertion, reorder, deletion.
TEST_F(FxProcessorTest, Reorder) {
fx_token_t token2 = fx_processor_->CreateFx(0, 1, 1, 0);
fx_token_t token1 = fx_processor_->CreateFx(0, 1, 1, 0);
fx_token_t token4 = fx_processor_->CreateFx(0, 1, 1, 2);
fx_token_t token3 = fx_processor_->CreateFx(0, 1, 1, 2);
// Chain is [2], then [1,2], then [1,2,4], then [1,2,3,4].
ASSERT_TRUE(token1 != FUCHSIA_AUDIO_DFX_INVALID_TOKEN &&
token2 != FUCHSIA_AUDIO_DFX_INVALID_TOKEN &&
token3 != FUCHSIA_AUDIO_DFX_INVALID_TOKEN &&
token4 != FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
// Chain is [1,2,3,4].
EXPECT_EQ(fx_processor_->GetFxAt(0), token1);
EXPECT_EQ(fx_processor_->GetFxAt(1), token2);
EXPECT_EQ(fx_processor_->GetFxAt(2), token3);
EXPECT_EQ(fx_processor_->GetFxAt(3), token4);
EXPECT_EQ(fx_processor_->GetFxAt(4), FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
// Moving token4 to position 2: [1,2,3,4] becomes [1,2,4,3].
EXPECT_EQ(fx_processor_->ReorderFx(token4, 2), ZX_OK);
EXPECT_EQ(fx_processor_->GetFxAt(0), token1);
EXPECT_EQ(fx_processor_->GetFxAt(1), token2);
EXPECT_EQ(fx_processor_->GetFxAt(2), token4);
EXPECT_EQ(fx_processor_->GetFxAt(3), token3);
EXPECT_EQ(fx_processor_->GetFxAt(4), FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
// Moving token1 to position 2: [1,2,4,3] becomes [2,4,1,3].
EXPECT_EQ(fx_processor_->ReorderFx(token1, 2), ZX_OK);
EXPECT_EQ(fx_processor_->GetFxAt(0), token2);
EXPECT_EQ(fx_processor_->GetFxAt(1), token4);
EXPECT_EQ(fx_processor_->GetFxAt(2), token1);
EXPECT_EQ(fx_processor_->GetFxAt(3), token3);
EXPECT_EQ(fx_processor_->GetFxAt(4), FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
// Position 4 is outside the chain. No change: chain is still [2,4,1,3].
EXPECT_NE(fx_processor_->ReorderFx(token2, 4), ZX_OK);
EXPECT_EQ(fx_processor_->GetFxAt(0), token2);
EXPECT_EQ(fx_processor_->GetFxAt(1), token4);
EXPECT_EQ(fx_processor_->GetFxAt(2), token1);
EXPECT_EQ(fx_processor_->GetFxAt(3), token3);
EXPECT_EQ(fx_processor_->GetFxAt(4), FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
// Removing token1: [2,4,1,3] becomes [2,4,3].
EXPECT_EQ(fx_processor_->DeleteFx(token1), ZX_OK);
EXPECT_EQ(fx_processor_->GetFxAt(0), token2);
EXPECT_EQ(fx_processor_->GetFxAt(1), token4);
EXPECT_EQ(fx_processor_->GetFxAt(2), token3);
EXPECT_EQ(fx_processor_->GetFxAt(3), FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
// Removing token2 (from front): [2,4,3] becomes [4,3].
EXPECT_EQ(fx_processor_->DeleteFx(token2), ZX_OK);
EXPECT_EQ(fx_processor_->GetFxAt(0), token4);
EXPECT_EQ(fx_processor_->GetFxAt(1), token3);
EXPECT_EQ(fx_processor_->GetFxAt(2), FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
// Removing token3 (from end): [4,3] becomes [4].
EXPECT_EQ(fx_processor_->DeleteFx(token3), ZX_OK);
EXPECT_EQ(fx_processor_->GetFxAt(0), token4);
EXPECT_EQ(fx_processor_->GetFxAt(1), FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
// Removing token4: [4] becomes [].
EXPECT_EQ(fx_processor_->DeleteFx(token4), ZX_OK);
EXPECT_EQ(fx_processor_->GetFxAt(0), FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
}
// Verify (at a VERY Basic level) the methods that handle data flow.
TEST_F(FxProcessorTest, ProcessInPlaceFlush) {
float buff[4] = {0, 0, 0, 0};
// Before instances added, ProcessInPlace and Flush should succeed.
EXPECT_EQ(fx_processor_->ProcessInPlace(4, buff), ZX_OK);
EXPECT_EQ(fx_processor_->Flush(), ZX_OK);
// Chaining four instances together, ProcessInPlace and flush should succeed.
fx_token_t token1 = fx_processor_->CreateFx(0, 1, 1, 0);
fx_token_t token2 = fx_processor_->CreateFx(0, 1, 1, 1);
fx_token_t token3 = fx_processor_->CreateFx(0, 1, 1, 2);
fx_token_t token4 = fx_processor_->CreateFx(0, 1, 1, 3);
ASSERT_TRUE(token1 != FUCHSIA_AUDIO_DFX_INVALID_TOKEN &&
token2 != FUCHSIA_AUDIO_DFX_INVALID_TOKEN &&
token3 != FUCHSIA_AUDIO_DFX_INVALID_TOKEN &&
token4 != FUCHSIA_AUDIO_DFX_INVALID_TOKEN);
EXPECT_EQ(fx_processor_->ProcessInPlace(4, buff), ZX_OK);
EXPECT_EQ(fx_processor_->Flush(), ZX_OK);
EXPECT_EQ(fx_processor_->ProcessInPlace(4, buff), ZX_OK);
// Zero num_frames is valid and should succeed.
EXPECT_EQ(fx_processor_->ProcessInPlace(0, buff), ZX_OK);
// If no buffer provided, ProcessInPlace should fail (even if 0 num_frames).
EXPECT_NE(fx_processor_->ProcessInPlace(0, nullptr), ZX_OK);
// With all instances removed, ProcessInPlace and Flush should still succeed.
EXPECT_EQ(fx_processor_->DeleteFx(token1), ZX_OK);
EXPECT_EQ(fx_processor_->DeleteFx(token2), ZX_OK);
EXPECT_EQ(fx_processor_->DeleteFx(token3), ZX_OK);
EXPECT_EQ(fx_processor_->DeleteFx(token4), ZX_OK);
EXPECT_EQ(fx_processor_->ProcessInPlace(4, buff), ZX_OK);
EXPECT_EQ(fx_processor_->Flush(), ZX_OK);
}
} // namespace audio_dfx_test
} // namespace media