2026-04-14 16:39:29 +08:00
|
|
|
#include <gtest/gtest.h>
|
|
|
|
|
|
|
|
|
|
#include <XCEngine/Audio/AudioSystem.h>
|
|
|
|
|
#include <XCEngine/Audio/IAudioBackend.h>
|
|
|
|
|
#include <XCEngine/Audio/AudioMixer.h>
|
|
|
|
|
#include <XCEngine/Components/AudioListenerComponent.h>
|
|
|
|
|
#include <XCEngine/Components/GameObject.h>
|
|
|
|
|
#include <XCEngine/Components/AudioSourceComponent.h>
|
|
|
|
|
#include <XCEngine/Core/Math/Quaternion.h>
|
|
|
|
|
#include <XCEngine/Core/Math/Vector3.h>
|
|
|
|
|
#include <XCEngine/Resources/AudioClip/AudioClip.h>
|
|
|
|
|
#include <sstream>
|
|
|
|
|
|
|
|
|
|
using namespace XCEngine::Audio;
|
|
|
|
|
using namespace XCEngine::Components;
|
|
|
|
|
using namespace XCEngine::Resources;
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
AudioClip CreateMono16Clip(std::initializer_list<int16_t> samples, XCEngine::Core::uint32 sampleRate = 4) {
|
|
|
|
|
AudioClip clip;
|
|
|
|
|
XCEngine::Containers::Array<XCEngine::Core::uint8> pcmData;
|
|
|
|
|
pcmData.ResizeUninitialized(samples.size() * sizeof(int16_t));
|
|
|
|
|
|
|
|
|
|
size_t byteOffset = 0;
|
|
|
|
|
for (const int16_t sample : samples) {
|
|
|
|
|
const uint16_t encoded = static_cast<uint16_t>(sample);
|
|
|
|
|
pcmData[byteOffset++] = static_cast<XCEngine::Core::uint8>(encoded & 0xFFu);
|
|
|
|
|
pcmData[byteOffset++] = static_cast<XCEngine::Core::uint8>((encoded >> 8) & 0xFFu);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
clip.SetSampleRate(sampleRate);
|
|
|
|
|
clip.SetChannels(1);
|
|
|
|
|
clip.SetBitsPerSample(16);
|
|
|
|
|
clip.SetAudioFormat(XCEngine::Resources::AudioFormat::WAV);
|
|
|
|
|
clip.SetPCMData(pcmData);
|
|
|
|
|
clip.m_isValid = true;
|
|
|
|
|
return clip;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class CaptureBackend final : public IAudioBackend {
|
|
|
|
|
public:
|
|
|
|
|
explicit CaptureBackend(const AudioConfig& inConfig) : config(inConfig) {}
|
|
|
|
|
|
|
|
|
|
bool Initialize(const AudioConfig& inConfig) override {
|
|
|
|
|
config = inConfig;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Shutdown() override {}
|
|
|
|
|
|
|
|
|
|
std::string GetDeviceName() const override { return "CaptureBackend"; }
|
|
|
|
|
void GetAvailableDevices(std::vector<std::string>& devices) override {
|
|
|
|
|
devices = {"CaptureBackend"};
|
|
|
|
|
}
|
|
|
|
|
bool SetDevice(const std::string& deviceName) override { return deviceName == "CaptureBackend"; }
|
|
|
|
|
|
|
|
|
|
void Start() override { running = true; }
|
|
|
|
|
void Stop() override { running = false; }
|
|
|
|
|
void Suspend() override {}
|
|
|
|
|
void Resume() override {}
|
|
|
|
|
|
|
|
|
|
void ProcessAudio(float* buffer, uint32 frameCount, uint32 channels, uint32 sampleRate) override {
|
|
|
|
|
lastFrameCount = frameCount;
|
|
|
|
|
lastChannels = channels;
|
|
|
|
|
lastSampleRate = sampleRate;
|
|
|
|
|
captured.assign(buffer, buffer + static_cast<size_t>(frameCount) * channels);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool IsRunning() const override { return running; }
|
|
|
|
|
AudioConfig GetConfig() const override { return config; }
|
|
|
|
|
|
|
|
|
|
AudioConfig config{};
|
|
|
|
|
std::vector<float> captured;
|
|
|
|
|
bool running = true;
|
|
|
|
|
uint32 lastFrameCount = 0;
|
|
|
|
|
uint32 lastChannels = 0;
|
|
|
|
|
uint32 lastSampleRate = 0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
TEST(AudioSystem, MasterMixerProcessesDirectSources) {
|
|
|
|
|
AudioSystem& system = AudioSystem::Get();
|
|
|
|
|
system.Shutdown();
|
|
|
|
|
|
|
|
|
|
AudioConfig config;
|
|
|
|
|
config.sampleRate = 4;
|
|
|
|
|
config.channels = 1;
|
|
|
|
|
config.bufferSize = 1;
|
|
|
|
|
|
|
|
|
|
auto backend = std::make_unique<CaptureBackend>(config);
|
|
|
|
|
CaptureBackend* backendPtr = backend.get();
|
|
|
|
|
system.SetBackend(std::move(backend));
|
|
|
|
|
system.GetMasterMixer().SetVolume(0.5f);
|
|
|
|
|
system.GetMasterMixer().SetMute(false);
|
|
|
|
|
system.GetMasterMixer().ClearEffects();
|
|
|
|
|
|
|
|
|
|
AudioClip clip = CreateMono16Clip({16384}, 4);
|
|
|
|
|
AudioSourceComponent source;
|
|
|
|
|
source.SetSpatialize(false);
|
|
|
|
|
source.SetClip(&clip);
|
|
|
|
|
source.Play();
|
|
|
|
|
|
|
|
|
|
system.Update(0.0f);
|
|
|
|
|
|
|
|
|
|
ASSERT_EQ(backendPtr->captured.size(), 1u);
|
|
|
|
|
EXPECT_NEAR(backendPtr->captured[0], 0.25f, 1e-5f);
|
|
|
|
|
|
|
|
|
|
source.Stop();
|
|
|
|
|
system.GetMasterMixer().SetVolume(1.0f);
|
|
|
|
|
system.Shutdown();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(AudioSystem, SourceOutputMixerRoutesIntoBackend) {
|
|
|
|
|
AudioSystem& system = AudioSystem::Get();
|
|
|
|
|
system.Shutdown();
|
|
|
|
|
|
|
|
|
|
AudioConfig config;
|
|
|
|
|
config.sampleRate = 4;
|
|
|
|
|
config.channels = 1;
|
|
|
|
|
config.bufferSize = 1;
|
|
|
|
|
|
|
|
|
|
auto backend = std::make_unique<CaptureBackend>(config);
|
|
|
|
|
CaptureBackend* backendPtr = backend.get();
|
|
|
|
|
system.SetBackend(std::move(backend));
|
|
|
|
|
system.GetMasterMixer().SetVolume(1.0f);
|
|
|
|
|
system.GetMasterMixer().SetMute(false);
|
|
|
|
|
system.GetMasterMixer().ClearEffects();
|
|
|
|
|
|
|
|
|
|
AudioMixer mixer;
|
|
|
|
|
mixer.SetVolume(0.5f);
|
|
|
|
|
|
|
|
|
|
AudioClip clip = CreateMono16Clip({16384}, 4);
|
|
|
|
|
AudioSourceComponent source;
|
|
|
|
|
source.SetSpatialize(false);
|
|
|
|
|
source.SetOutputMixer(&mixer);
|
|
|
|
|
source.SetClip(&clip);
|
|
|
|
|
source.Play();
|
|
|
|
|
|
|
|
|
|
system.Update(0.0f);
|
|
|
|
|
|
|
|
|
|
ASSERT_EQ(backendPtr->captured.size(), 1u);
|
|
|
|
|
EXPECT_NEAR(backendPtr->captured[0], 0.25f, 1e-5f);
|
|
|
|
|
|
|
|
|
|
source.Stop();
|
|
|
|
|
system.Shutdown();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(AudioSystem, SetMasterVolumeControlsMasterMixerOutput) {
|
|
|
|
|
AudioSystem& system = AudioSystem::Get();
|
|
|
|
|
system.Shutdown();
|
|
|
|
|
|
|
|
|
|
AudioConfig config;
|
|
|
|
|
config.sampleRate = 4;
|
|
|
|
|
config.channels = 1;
|
|
|
|
|
config.bufferSize = 1;
|
|
|
|
|
|
|
|
|
|
auto backend = std::make_unique<CaptureBackend>(config);
|
|
|
|
|
CaptureBackend* backendPtr = backend.get();
|
|
|
|
|
system.SetBackend(std::move(backend));
|
|
|
|
|
system.SetMasterVolume(0.25f);
|
|
|
|
|
system.SetMuted(false);
|
|
|
|
|
system.GetMasterMixer().ClearEffects();
|
|
|
|
|
|
|
|
|
|
AudioClip clip = CreateMono16Clip({16384}, 4);
|
|
|
|
|
AudioSourceComponent source;
|
|
|
|
|
source.SetSpatialize(false);
|
|
|
|
|
source.SetClip(&clip);
|
|
|
|
|
source.Play();
|
|
|
|
|
|
|
|
|
|
system.Update(0.0f);
|
|
|
|
|
|
|
|
|
|
ASSERT_EQ(backendPtr->captured.size(), 1u);
|
|
|
|
|
EXPECT_NEAR(backendPtr->captured[0], 0.125f, 1e-5f);
|
|
|
|
|
EXPECT_FLOAT_EQ(system.GetMasterVolume(), 0.25f);
|
|
|
|
|
EXPECT_FALSE(system.IsMuted());
|
|
|
|
|
|
|
|
|
|
source.Stop();
|
|
|
|
|
system.SetMasterVolume(1.0f);
|
|
|
|
|
system.Shutdown();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(AudioSystem, ListenerReverbMixerReceivesSend) {
|
|
|
|
|
AudioSystem& system = AudioSystem::Get();
|
|
|
|
|
system.Shutdown();
|
|
|
|
|
|
|
|
|
|
AudioConfig config;
|
|
|
|
|
config.sampleRate = 4;
|
|
|
|
|
config.channels = 1;
|
|
|
|
|
config.bufferSize = 1;
|
|
|
|
|
|
|
|
|
|
auto backend = std::make_unique<CaptureBackend>(config);
|
|
|
|
|
CaptureBackend* backendPtr = backend.get();
|
|
|
|
|
system.SetBackend(std::move(backend));
|
|
|
|
|
system.SetMasterVolume(1.0f);
|
|
|
|
|
system.SetMuted(false);
|
|
|
|
|
system.GetMasterMixer().ClearEffects();
|
|
|
|
|
|
|
|
|
|
AudioMixer reverbMixer;
|
|
|
|
|
reverbMixer.SetVolume(1.0f);
|
|
|
|
|
system.SetListenerReverbMixer(&reverbMixer);
|
|
|
|
|
system.SetListenerReverbLevel(0.5f);
|
|
|
|
|
|
|
|
|
|
AudioClip clip = CreateMono16Clip({16384}, 4);
|
|
|
|
|
AudioSourceComponent source;
|
|
|
|
|
source.SetSpatialize(false);
|
|
|
|
|
source.SetReverbZoneMix(0.5f);
|
|
|
|
|
source.SetClip(&clip);
|
|
|
|
|
source.Play();
|
|
|
|
|
|
|
|
|
|
system.Update(0.0f);
|
|
|
|
|
|
|
|
|
|
ASSERT_EQ(backendPtr->captured.size(), 1u);
|
|
|
|
|
EXPECT_NEAR(backendPtr->captured[0], 0.625f, 1e-5f);
|
|
|
|
|
|
|
|
|
|
source.Stop();
|
|
|
|
|
system.SetListenerReverbMixer(nullptr);
|
|
|
|
|
system.SetListenerReverbLevel(1.0f);
|
|
|
|
|
system.Shutdown();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(AudioSystem, ListenerComponentPublishesVelocityAndDopplerSettings) {
|
|
|
|
|
AudioSystem& system = AudioSystem::Get();
|
|
|
|
|
system.Shutdown();
|
|
|
|
|
|
|
|
|
|
GameObject listenerObject("Listener");
|
|
|
|
|
auto* listener = listenerObject.AddComponent<AudioListenerComponent>();
|
|
|
|
|
|
|
|
|
|
listener->SetDopplerLevel(2.0f);
|
|
|
|
|
listener->SetSpeedOfSound(200.0f);
|
|
|
|
|
|
|
|
|
|
listenerObject.GetTransform()->SetPosition(XCEngine::Math::Vector3::Zero());
|
|
|
|
|
listener->Update(0.0f);
|
|
|
|
|
|
|
|
|
|
listenerObject.GetTransform()->SetPosition(XCEngine::Math::Vector3(10.0f, 0.0f, 0.0f));
|
|
|
|
|
listener->Update(0.5f);
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(system.GetListenerDopplerLevel(), 2.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(system.GetSpeedOfSound(), 200.0f);
|
|
|
|
|
EXPECT_EQ(system.GetListenerPosition(), XCEngine::Math::Vector3(10.0f, 0.0f, 0.0f));
|
|
|
|
|
EXPECT_EQ(system.GetListenerVelocity(), XCEngine::Math::Vector3(20.0f, 0.0f, 0.0f));
|
|
|
|
|
EXPECT_FLOAT_EQ(system.GetListenerRotation().x, 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(system.GetListenerRotation().y, 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(system.GetListenerRotation().z, 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(system.GetListenerRotation().w, 1.0f);
|
|
|
|
|
|
|
|
|
|
system.Shutdown();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(AudioSystem, ProcessAudioUsesBackendConfigSampleRateForDirectSubmission) {
|
|
|
|
|
AudioSystem& system = AudioSystem::Get();
|
|
|
|
|
system.Shutdown();
|
|
|
|
|
|
|
|
|
|
AudioConfig config;
|
|
|
|
|
config.sampleRate = 22050;
|
|
|
|
|
config.channels = 1;
|
|
|
|
|
config.bufferSize = 1;
|
|
|
|
|
|
|
|
|
|
auto backend = std::make_unique<CaptureBackend>(config);
|
|
|
|
|
CaptureBackend* backendPtr = backend.get();
|
|
|
|
|
system.SetBackend(std::move(backend));
|
|
|
|
|
|
|
|
|
|
float buffer[1] = {0.5f};
|
|
|
|
|
system.ProcessAudio(buffer, 1, 1);
|
|
|
|
|
|
|
|
|
|
ASSERT_EQ(backendPtr->captured.size(), 1u);
|
|
|
|
|
EXPECT_FLOAT_EQ(backendPtr->captured[0], 0.5f);
|
|
|
|
|
EXPECT_EQ(backendPtr->lastSampleRate, 22050u);
|
|
|
|
|
|
|
|
|
|
system.Shutdown();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 16:42:25 +08:00
|
|
|
TEST(AudioSystem, RenderAudioProducesMixedBlockWithoutBackendSubmission) {
|
|
|
|
|
AudioSystem& system = AudioSystem::Get();
|
|
|
|
|
system.Shutdown();
|
|
|
|
|
|
|
|
|
|
system.SetMasterVolume(0.5f);
|
|
|
|
|
system.SetMuted(false);
|
|
|
|
|
system.GetMasterMixer().ClearEffects();
|
|
|
|
|
|
|
|
|
|
AudioClip clip = CreateMono16Clip({16384}, 4);
|
|
|
|
|
AudioSourceComponent source;
|
|
|
|
|
source.SetSpatialize(false);
|
|
|
|
|
source.SetClip(&clip);
|
|
|
|
|
source.Play();
|
|
|
|
|
|
|
|
|
|
float buffer[1] = {};
|
|
|
|
|
system.RenderAudio(buffer, 1, 1, 4);
|
|
|
|
|
|
|
|
|
|
EXPECT_NEAR(buffer[0], 0.25f, 1e-5f);
|
|
|
|
|
EXPECT_EQ(system.GetStats().activeSources, 1u);
|
|
|
|
|
EXPECT_EQ(system.GetStats().totalSources, 1u);
|
|
|
|
|
|
|
|
|
|
source.Stop();
|
|
|
|
|
system.SetMasterVolume(1.0f);
|
|
|
|
|
system.Shutdown();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 16:39:29 +08:00
|
|
|
TEST(AudioSystem, AudioListenerComponentSerializeRoundTripPreservesSettings) {
|
|
|
|
|
AudioSystem& system = AudioSystem::Get();
|
|
|
|
|
system.Shutdown();
|
|
|
|
|
|
|
|
|
|
AudioListenerComponent source;
|
|
|
|
|
source.SetMasterVolume(0.6f);
|
|
|
|
|
source.SetMute(true);
|
|
|
|
|
source.SetDopplerLevel(2.5f);
|
|
|
|
|
source.SetSpeedOfSound(250.0f);
|
|
|
|
|
source.SetReverbLevel(0.35f);
|
|
|
|
|
|
|
|
|
|
std::stringstream stream;
|
|
|
|
|
source.Serialize(stream);
|
|
|
|
|
|
|
|
|
|
AudioListenerComponent target;
|
|
|
|
|
target.Deserialize(stream);
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(target.GetMasterVolume(), 0.6f);
|
|
|
|
|
EXPECT_TRUE(target.IsMute());
|
|
|
|
|
EXPECT_FLOAT_EQ(target.GetDopplerLevel(), 2.5f);
|
|
|
|
|
EXPECT_FLOAT_EQ(target.GetSpeedOfSound(), 250.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(target.GetReverbLevel(), 0.35f);
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(system.GetMasterVolume(), 0.6f);
|
|
|
|
|
EXPECT_TRUE(system.IsMuted());
|
|
|
|
|
EXPECT_FLOAT_EQ(system.GetListenerDopplerLevel(), 2.5f);
|
|
|
|
|
EXPECT_FLOAT_EQ(system.GetSpeedOfSound(), 250.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(system.GetListenerReverbLevel(), 0.35f);
|
|
|
|
|
|
|
|
|
|
system.SetMasterVolume(1.0f);
|
|
|
|
|
system.SetMuted(false);
|
|
|
|
|
system.SetListenerDopplerLevel(1.0f);
|
|
|
|
|
system.SetSpeedOfSound(343.0f);
|
|
|
|
|
system.SetListenerReverbLevel(1.0f);
|
|
|
|
|
system.Shutdown();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|