audio: switch waveout backend to pull rendering
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
#include <XCEngine/Core/Math/Vector3.h>
|
||||
#include <XCEngine/Core/Math/Quaternion.h>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
@@ -22,6 +23,7 @@ namespace Audio {
|
||||
class AudioSystem {
|
||||
public:
|
||||
static AudioSystem& Get();
|
||||
static AudioSystem* TryGetExisting();
|
||||
|
||||
void Initialize(const AudioConfig& config);
|
||||
void Shutdown();
|
||||
@@ -30,6 +32,9 @@ public:
|
||||
|
||||
void SetBackend(std::unique_ptr<IAudioBackend> backend);
|
||||
IAudioBackend* GetBackend() const { return m_backend.get(); }
|
||||
[[nodiscard]] std::unique_lock<std::recursive_mutex> AcquireStateLock() {
|
||||
return std::unique_lock<std::recursive_mutex>(m_stateMutex);
|
||||
}
|
||||
AudioMixer& GetMasterMixer() { return m_masterMixer; }
|
||||
const AudioMixer& GetMasterMixer() const { return m_masterMixer; }
|
||||
|
||||
@@ -80,8 +85,8 @@ public:
|
||||
const Stats& GetStats() const { return m_stats; }
|
||||
|
||||
private:
|
||||
AudioSystem() = default;
|
||||
~AudioSystem() = default;
|
||||
AudioSystem();
|
||||
~AudioSystem();
|
||||
|
||||
AudioSystem(const AudioSystem&) = delete;
|
||||
AudioSystem& operator=(const AudioSystem&) = delete;
|
||||
@@ -89,8 +94,10 @@ private:
|
||||
void ProcessSource(Components::AudioSourceComponent* source, float* buffer, uint32 frameCount, uint32 channels, uint32 sampleRate);
|
||||
void RenderAudioBlock(uint32 frameCount, uint32 channels, uint32 sampleRate);
|
||||
void DetachMixerReferences(AudioMixer* mixer);
|
||||
void UpdateStats();
|
||||
|
||||
private:
|
||||
mutable std::recursive_mutex m_stateMutex;
|
||||
std::unique_ptr<IAudioBackend> m_backend;
|
||||
AudioMixer m_masterMixer;
|
||||
AudioMixer* m_listenerReverbMixer = nullptr;
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include "AudioConfig.h"
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Audio {
|
||||
|
||||
class IAudioBackend {
|
||||
public:
|
||||
using RenderCallback = std::function<void(float*, uint32, uint32, uint32)>;
|
||||
|
||||
virtual ~IAudioBackend() = default;
|
||||
|
||||
virtual bool Initialize(const AudioConfig& config) = 0;
|
||||
@@ -26,6 +30,8 @@ public:
|
||||
|
||||
virtual void ProcessAudio(float* buffer, uint32 frameCount,
|
||||
uint32 channels, uint32 sampleRate) = 0;
|
||||
virtual bool UsesPullModel() const { return false; }
|
||||
virtual void SetRenderCallback(RenderCallback callback) { (void)callback; }
|
||||
|
||||
virtual bool IsRunning() const = 0;
|
||||
virtual AudioConfig GetConfig() const = 0;
|
||||
|
||||
@@ -39,6 +39,8 @@ public:
|
||||
|
||||
void ProcessAudio(float* buffer, uint32 frameCount,
|
||||
uint32 channels, uint32 sampleRate) override;
|
||||
bool UsesPullModel() const override { return true; }
|
||||
void SetRenderCallback(RenderCallback callback) override;
|
||||
|
||||
bool IsRunning() const override { return m_isRunning.load(); }
|
||||
AudioConfig GetConfig() const override { return m_config; }
|
||||
@@ -76,9 +78,6 @@ private:
|
||||
WAVEFORMATEX m_waveFormat = {};
|
||||
HWAVEOUT m_hWaveOut = nullptr;
|
||||
|
||||
std::vector<float> m_mixBuffer;
|
||||
uint32 m_mixBufferSize = 0;
|
||||
|
||||
std::vector<WAVEOUTCAPS> m_waveOutCaps;
|
||||
std::string m_deviceName;
|
||||
std::string m_requestedDeviceName;
|
||||
@@ -86,13 +85,13 @@ private:
|
||||
static constexpr size_t BufferSize = 8192;
|
||||
std::vector<int16_t> m_audioBuffer1;
|
||||
std::vector<int16_t> m_audioBuffer2;
|
||||
std::vector<float> m_pendingMixBuffer;
|
||||
bool m_hasPendingMix = false;
|
||||
std::vector<float> m_renderBuffer;
|
||||
bool m_buffer1Available = true;
|
||||
bool m_buffer2Available = true;
|
||||
RenderCallback m_renderCallback;
|
||||
|
||||
std::mutex m_bufferMutex;
|
||||
std::condition_variable m_dataReadyCond;
|
||||
std::condition_variable m_bufferReadyCond;
|
||||
|
||||
WAVEHDR m_waveHeader1 = {};
|
||||
WAVEHDR m_waveHeader2 = {};
|
||||
|
||||
@@ -9,6 +9,13 @@ namespace Audio {
|
||||
|
||||
namespace {
|
||||
|
||||
std::unique_lock<std::recursive_mutex> LockAudioState() {
|
||||
if (AudioSystem* audioSystem = AudioSystem::TryGetExisting()) {
|
||||
return audioSystem->AcquireStateLock();
|
||||
}
|
||||
return std::unique_lock<std::recursive_mutex>();
|
||||
}
|
||||
|
||||
AudioChannel ResolveChannel(uint32 channelIndex) {
|
||||
switch (channelIndex) {
|
||||
case 0: return AudioChannel::FrontLeft;
|
||||
@@ -27,31 +34,39 @@ AudioChannel ResolveChannel(uint32 channelIndex) {
|
||||
|
||||
AudioMixer::AudioMixer()
|
||||
{
|
||||
AudioSystem::Get().RegisterMixer(this);
|
||||
if (AudioSystem* audioSystem = AudioSystem::TryGetExisting()) {
|
||||
audioSystem->RegisterMixer(this);
|
||||
}
|
||||
for (int i = 0; i < static_cast<int>(AudioChannel::SideRight) + 1; ++i) {
|
||||
m_channelVolumes[static_cast<AudioChannel>(i)] = ChannelVolume{1.0f, false};
|
||||
}
|
||||
}
|
||||
|
||||
AudioMixer::~AudioMixer() {
|
||||
AudioSystem::Get().UnregisterMixer(this);
|
||||
if (AudioSystem* audioSystem = AudioSystem::TryGetExisting()) {
|
||||
audioSystem->UnregisterMixer(this);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioMixer::SetVolume(float volume) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
m_volume = std::max(0.0f, std::min(1.0f, volume));
|
||||
}
|
||||
|
||||
void AudioMixer::SetMute(bool mute) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
m_mute = mute;
|
||||
}
|
||||
|
||||
void AudioMixer::AddEffect(IAudioEffect* effect) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
if (effect && std::find(m_effects.begin(), m_effects.end(), effect) == m_effects.end()) {
|
||||
m_effects.push_back(effect);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioMixer::RemoveEffect(IAudioEffect* effect) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
if (!effect) return;
|
||||
auto it = std::find(m_effects.begin(), m_effects.end(), effect);
|
||||
if (it != m_effects.end()) {
|
||||
@@ -60,10 +75,12 @@ void AudioMixer::RemoveEffect(IAudioEffect* effect) {
|
||||
}
|
||||
|
||||
void AudioMixer::ClearEffects() {
|
||||
auto audioStateLock = LockAudioState();
|
||||
m_effects.clear();
|
||||
}
|
||||
|
||||
void AudioMixer::ProcessAudio(float* buffer, uint32 frameCount, uint32 channels, uint32 sampleRate) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
if (!buffer || frameCount == 0 || channels == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -98,14 +115,17 @@ void AudioMixer::ProcessAudio(float* buffer, uint32 frameCount, uint32 channels,
|
||||
}
|
||||
|
||||
void AudioMixer::SetOutputMixer(AudioMixer* mixer) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
m_outputMixer = mixer;
|
||||
}
|
||||
|
||||
void AudioMixer::Set3DParams(const Audio3DParams& params) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
m_3DParams = params;
|
||||
}
|
||||
|
||||
void AudioMixer::SetChannelVolume(AudioChannel channel, float volume) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
auto it = m_channelVolumes.find(channel);
|
||||
if (it != m_channelVolumes.end()) {
|
||||
it->second.volume = std::max(0.0f, std::min(1.0f, volume));
|
||||
@@ -113,6 +133,7 @@ void AudioMixer::SetChannelVolume(AudioChannel channel, float volume) {
|
||||
}
|
||||
|
||||
float AudioMixer::GetChannelVolume(AudioChannel channel) const {
|
||||
auto audioStateLock = LockAudioState();
|
||||
auto it = m_channelVolumes.find(channel);
|
||||
if (it != m_channelVolumes.end()) {
|
||||
return it->second.volume;
|
||||
|
||||
@@ -12,6 +12,8 @@ namespace Audio {
|
||||
|
||||
namespace {
|
||||
|
||||
AudioSystem* g_audioSystemInstance = nullptr;
|
||||
|
||||
void MixBufferInto(const std::vector<float>& source, std::vector<float>& destination) {
|
||||
if (destination.size() < source.size()) {
|
||||
destination.resize(source.size(), 0.0f);
|
||||
@@ -93,79 +95,128 @@ AudioSystem& AudioSystem::Get() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
void AudioSystem::Initialize(const AudioConfig& config) {
|
||||
if (m_backend) {
|
||||
Shutdown();
|
||||
}
|
||||
AudioSystem* AudioSystem::TryGetExisting() {
|
||||
return g_audioSystemInstance;
|
||||
}
|
||||
|
||||
m_backend = std::make_unique<Audio::WaveOut::WaveOutBackend>();
|
||||
if (m_backend->Initialize(config)) {
|
||||
m_backend->Start();
|
||||
AudioSystem::AudioSystem() {
|
||||
g_audioSystemInstance = this;
|
||||
RegisterMixer(&m_masterMixer);
|
||||
}
|
||||
|
||||
AudioSystem::~AudioSystem() {
|
||||
g_audioSystemInstance = nullptr;
|
||||
}
|
||||
|
||||
void AudioSystem::Initialize(const AudioConfig& config) {
|
||||
Shutdown();
|
||||
|
||||
auto backend = std::make_unique<Audio::WaveOut::WaveOutBackend>();
|
||||
backend->SetRenderCallback(
|
||||
[this](float* buffer, uint32 frameCount, uint32 channels, uint32 sampleRate) {
|
||||
RenderAudio(buffer, frameCount, channels, sampleRate);
|
||||
});
|
||||
|
||||
if (backend->Initialize(config)) {
|
||||
IAudioBackend* backendPtr = backend.get();
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
m_backend = std::move(backend);
|
||||
}
|
||||
backendPtr->Start();
|
||||
std::cout << "AudioSystem initialized successfully" << std::endl;
|
||||
} else {
|
||||
std::cout << "Failed to initialize AudioSystem" << std::endl;
|
||||
m_backend.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioSystem::Shutdown() {
|
||||
if (m_backend) {
|
||||
m_backend->Stop();
|
||||
m_backend->Shutdown();
|
||||
m_backend.reset();
|
||||
std::unique_ptr<IAudioBackend> backend;
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
backend = std::move(m_backend);
|
||||
}
|
||||
|
||||
if (backend) {
|
||||
if (backend->IsRunning()) {
|
||||
backend->Stop();
|
||||
}
|
||||
backend->Shutdown();
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
m_registeredMixers.clear();
|
||||
m_registeredSourceComponents.clear();
|
||||
m_registeredListenerComponents.clear();
|
||||
m_activeSources.clear();
|
||||
m_listenerReverbMixer = nullptr;
|
||||
m_listenerReverbLevel = 1.0f;
|
||||
m_listenerDopplerLevel = 1.0f;
|
||||
m_speedOfSound = 343.0f;
|
||||
m_listenerPosition = Math::Vector3::Zero();
|
||||
m_listenerRotation = Math::Quaternion::Identity();
|
||||
m_listenerVelocity = Math::Vector3::Zero();
|
||||
m_activeSourceSnapshot.clear();
|
||||
m_mixScratchBuffer.clear();
|
||||
m_sourceScratchBuffer.clear();
|
||||
m_mixerScratchBuffers.clear();
|
||||
m_mixerScratchChildren.clear();
|
||||
m_mixerScratchActiveMixers.clear();
|
||||
m_mixerScratchVisiting.clear();
|
||||
m_mixerScratchRendered.clear();
|
||||
m_deltaTime = 0.0f;
|
||||
m_stats = {};
|
||||
}
|
||||
m_registeredMixers.clear();
|
||||
m_registeredSourceComponents.clear();
|
||||
m_registeredListenerComponents.clear();
|
||||
m_activeSources.clear();
|
||||
m_listenerReverbMixer = nullptr;
|
||||
m_listenerReverbLevel = 1.0f;
|
||||
m_listenerDopplerLevel = 1.0f;
|
||||
m_speedOfSound = 343.0f;
|
||||
m_listenerPosition = Math::Vector3::Zero();
|
||||
m_listenerRotation = Math::Quaternion::Identity();
|
||||
m_listenerVelocity = Math::Vector3::Zero();
|
||||
m_activeSourceSnapshot.clear();
|
||||
m_mixScratchBuffer.clear();
|
||||
m_sourceScratchBuffer.clear();
|
||||
m_mixerScratchBuffers.clear();
|
||||
m_mixerScratchChildren.clear();
|
||||
m_mixerScratchActiveMixers.clear();
|
||||
m_mixerScratchVisiting.clear();
|
||||
m_mixerScratchRendered.clear();
|
||||
}
|
||||
|
||||
void AudioSystem::Update(float deltaTime) {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
m_deltaTime = deltaTime;
|
||||
|
||||
if (!m_backend || !m_backend->IsRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& config = m_backend->GetConfig();
|
||||
const uint32 frameCount = config.bufferSize;
|
||||
RenderAudioBlock(frameCount, config.channels, config.sampleRate);
|
||||
m_backend->ProcessAudio(
|
||||
m_mixScratchBuffer.data(),
|
||||
frameCount,
|
||||
config.channels,
|
||||
config.sampleRate);
|
||||
|
||||
uint32 activeCount = 0;
|
||||
for (auto* source : m_activeSources) {
|
||||
if (source && source->IsPlaying()) {
|
||||
activeCount++;
|
||||
}
|
||||
if (!m_backend->UsesPullModel()) {
|
||||
const auto& config = m_backend->GetConfig();
|
||||
const uint32 frameCount = config.bufferSize;
|
||||
RenderAudioBlock(frameCount, config.channels, config.sampleRate);
|
||||
m_backend->ProcessAudio(
|
||||
m_mixScratchBuffer.data(),
|
||||
frameCount,
|
||||
config.channels,
|
||||
config.sampleRate);
|
||||
} else {
|
||||
UpdateStats();
|
||||
}
|
||||
m_stats.activeSources = activeCount;
|
||||
m_stats.totalSources = static_cast<uint32>(m_activeSources.size());
|
||||
}
|
||||
|
||||
void AudioSystem::SetBackend(std::unique_ptr<IAudioBackend> backend) {
|
||||
m_backend = std::move(backend);
|
||||
std::unique_ptr<IAudioBackend> previousBackend;
|
||||
if (backend) {
|
||||
backend->SetRenderCallback(
|
||||
[this](float* buffer, uint32 frameCount, uint32 channels, uint32 sampleRate) {
|
||||
RenderAudio(buffer, frameCount, channels, sampleRate);
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
previousBackend = std::move(m_backend);
|
||||
m_backend = std::move(backend);
|
||||
}
|
||||
|
||||
if (previousBackend) {
|
||||
if (previousBackend->IsRunning()) {
|
||||
previousBackend->Stop();
|
||||
}
|
||||
previousBackend->Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
std::string AudioSystem::GetCurrentDevice() const {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
if (m_backend) {
|
||||
return m_backend->GetDeviceName();
|
||||
}
|
||||
@@ -173,50 +224,61 @@ std::string AudioSystem::GetCurrentDevice() const {
|
||||
}
|
||||
|
||||
void AudioSystem::SetDevice(const std::string& deviceName) {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
if (m_backend) {
|
||||
m_backend->SetDevice(deviceName);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioSystem::GetAvailableDevices(std::vector<std::string>& devices) {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
if (m_backend) {
|
||||
m_backend->GetAvailableDevices(devices);
|
||||
}
|
||||
}
|
||||
|
||||
float AudioSystem::GetMasterVolume() const {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
return m_masterMixer.GetVolume();
|
||||
}
|
||||
|
||||
void AudioSystem::SetMasterVolume(float volume) {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
m_masterMixer.SetVolume(volume);
|
||||
}
|
||||
|
||||
bool AudioSystem::IsMuted() const {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
return m_masterMixer.IsMute();
|
||||
}
|
||||
|
||||
void AudioSystem::SetMuted(bool muted) {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
m_masterMixer.SetMute(muted);
|
||||
}
|
||||
|
||||
void AudioSystem::SetListenerReverbMixer(AudioMixer* mixer) {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
m_listenerReverbMixer = mixer;
|
||||
}
|
||||
|
||||
void AudioSystem::SetListenerReverbLevel(float level) {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
m_listenerReverbLevel = std::clamp(level, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
void AudioSystem::SetListenerDopplerLevel(float level) {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
m_listenerDopplerLevel = std::clamp(level, 0.0f, 5.0f);
|
||||
}
|
||||
|
||||
void AudioSystem::SetSpeedOfSound(float metersPerSecond) {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
m_speedOfSound = std::max(1.0f, metersPerSecond);
|
||||
}
|
||||
|
||||
void AudioSystem::RenderAudio(float* buffer, uint32 frameCount, uint32 channels, uint32 sampleRate) {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
if (buffer == nullptr || frameCount == 0 || channels == 0 || sampleRate == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -227,6 +289,7 @@ void AudioSystem::RenderAudio(float* buffer, uint32 frameCount, uint32 channels,
|
||||
}
|
||||
|
||||
void AudioSystem::ProcessAudio(float* buffer, uint32 frameCount, uint32 channels) {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
if (m_backend) {
|
||||
const uint32 sampleRate =
|
||||
m_backend->GetConfig().sampleRate != 0 ? m_backend->GetConfig().sampleRate : 48000;
|
||||
@@ -235,15 +298,18 @@ void AudioSystem::ProcessAudio(float* buffer, uint32 frameCount, uint32 channels
|
||||
}
|
||||
|
||||
void AudioSystem::SetListenerTransform(const Math::Vector3& position, const Math::Quaternion& rotation) {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
m_listenerPosition = position;
|
||||
m_listenerRotation = rotation;
|
||||
}
|
||||
|
||||
void AudioSystem::SetListenerVelocity(const Math::Vector3& velocity) {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
m_listenerVelocity = velocity;
|
||||
}
|
||||
|
||||
void AudioSystem::RegisterSource(Components::AudioSourceComponent* source) {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
if (!source) {
|
||||
return;
|
||||
}
|
||||
@@ -254,6 +320,7 @@ void AudioSystem::RegisterSource(Components::AudioSourceComponent* source) {
|
||||
}
|
||||
|
||||
void AudioSystem::RegisterSourceComponent(Components::AudioSourceComponent* source) {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
if (!source) {
|
||||
return;
|
||||
}
|
||||
@@ -265,6 +332,7 @@ void AudioSystem::RegisterSourceComponent(Components::AudioSourceComponent* sour
|
||||
}
|
||||
|
||||
void AudioSystem::UnregisterSourceComponent(Components::AudioSourceComponent* source) {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
if (!source) {
|
||||
return;
|
||||
}
|
||||
@@ -276,6 +344,7 @@ void AudioSystem::UnregisterSourceComponent(Components::AudioSourceComponent* so
|
||||
}
|
||||
|
||||
void AudioSystem::RegisterListenerComponent(Components::AudioListenerComponent* listener) {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
if (!listener) {
|
||||
return;
|
||||
}
|
||||
@@ -287,6 +356,7 @@ void AudioSystem::RegisterListenerComponent(Components::AudioListenerComponent*
|
||||
}
|
||||
|
||||
void AudioSystem::UnregisterListenerComponent(Components::AudioListenerComponent* listener) {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
if (!listener) {
|
||||
return;
|
||||
}
|
||||
@@ -297,6 +367,7 @@ void AudioSystem::UnregisterListenerComponent(Components::AudioListenerComponent
|
||||
}
|
||||
|
||||
void AudioSystem::RegisterMixer(AudioMixer* mixer) {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
if (!mixer) {
|
||||
return;
|
||||
}
|
||||
@@ -307,6 +378,7 @@ void AudioSystem::RegisterMixer(AudioMixer* mixer) {
|
||||
}
|
||||
|
||||
void AudioSystem::UnregisterMixer(AudioMixer* mixer) {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
if (!mixer) {
|
||||
return;
|
||||
}
|
||||
@@ -318,6 +390,7 @@ void AudioSystem::UnregisterMixer(AudioMixer* mixer) {
|
||||
}
|
||||
|
||||
void AudioSystem::UnregisterSource(Components::AudioSourceComponent* source) {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
if (!source) {
|
||||
return;
|
||||
}
|
||||
@@ -377,8 +450,7 @@ void AudioSystem::RenderAudioBlock(uint32 frameCount, uint32 channels, uint32 sa
|
||||
if (sampleCount == 0 || sampleRate == 0) {
|
||||
m_mixScratchBuffer.clear();
|
||||
m_sourceScratchBuffer.clear();
|
||||
m_stats.activeSources = 0;
|
||||
m_stats.totalSources = static_cast<uint32>(m_activeSources.size());
|
||||
UpdateStats();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -481,6 +553,10 @@ void AudioSystem::RenderAudioBlock(uint32 frameCount, uint32 channels, uint32 sa
|
||||
channels,
|
||||
sampleRate);
|
||||
|
||||
UpdateStats();
|
||||
}
|
||||
|
||||
void AudioSystem::UpdateStats() {
|
||||
uint32 activeCount = 0;
|
||||
for (auto* source : m_activeSources) {
|
||||
if (source && source->IsPlaying()) {
|
||||
|
||||
@@ -64,8 +64,7 @@ bool WaveOutBackend::Initialize(const AudioConfig& config) {
|
||||
const size_t bufferSampleCount = static_cast<size_t>(config.bufferSize) * config.channels;
|
||||
m_audioBuffer1.assign(bufferSampleCount, 0);
|
||||
m_audioBuffer2.assign(bufferSampleCount, 0);
|
||||
m_pendingMixBuffer.assign(bufferSampleCount, 0.0f);
|
||||
m_hasPendingMix = false;
|
||||
m_renderBuffer.assign(bufferSampleCount, 0.0f);
|
||||
m_buffer1Available = true;
|
||||
m_buffer2Available = true;
|
||||
|
||||
@@ -152,7 +151,7 @@ void WaveOutBackend::Start() {
|
||||
|
||||
void WaveOutBackend::Stop() {
|
||||
m_isRunning = false;
|
||||
m_dataReadyCond.notify_all();
|
||||
m_bufferReadyCond.notify_all();
|
||||
|
||||
if (m_hWaveOut != nullptr) {
|
||||
waveOutReset(m_hWaveOut);
|
||||
@@ -180,20 +179,15 @@ void WaveOutBackend::Resume() {
|
||||
|
||||
void WaveOutBackend::ProcessAudio(float* buffer, uint32 frameCount,
|
||||
uint32 channels, uint32 sampleRate) {
|
||||
(void)buffer;
|
||||
(void)frameCount;
|
||||
(void)channels;
|
||||
(void)sampleRate;
|
||||
}
|
||||
|
||||
if (buffer == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32 sampleCount = frameCount * channels;
|
||||
void WaveOutBackend::SetRenderCallback(RenderCallback callback) {
|
||||
std::lock_guard<std::mutex> lock(m_bufferMutex);
|
||||
m_pendingMixBuffer.assign(m_pendingMixBuffer.size(), 0.0f);
|
||||
for (uint32 i = 0; i < sampleCount && i < m_pendingMixBuffer.size(); ++i) {
|
||||
m_pendingMixBuffer[i] = buffer[i];
|
||||
}
|
||||
m_hasPendingMix = true;
|
||||
m_dataReadyCond.notify_one();
|
||||
m_renderCallback = std::move(callback);
|
||||
}
|
||||
|
||||
MMRESULT WaveOutBackend::InitDevice() {
|
||||
@@ -269,9 +263,8 @@ DWORD WINAPI WaveOutBackend::AudioThreadProc(LPVOID lpParameter) {
|
||||
void WaveOutBackend::AudioThread() {
|
||||
while (m_isRunning.load()) {
|
||||
std::unique_lock<std::mutex> lock(m_bufferMutex);
|
||||
m_dataReadyCond.wait(lock, [this] {
|
||||
return !m_isRunning.load() ||
|
||||
(m_hasPendingMix && (m_buffer1Available || m_buffer2Available));
|
||||
m_bufferReadyCond.wait(lock, [this] {
|
||||
return !m_isRunning.load() || m_buffer1Available || m_buffer2Available;
|
||||
});
|
||||
|
||||
if (!m_isRunning.load()) {
|
||||
@@ -293,10 +286,31 @@ void WaveOutBackend::AudioThread() {
|
||||
continue;
|
||||
}
|
||||
|
||||
FillPcm16Buffer(*targetBuffer, m_pendingMixBuffer);
|
||||
m_hasPendingMix = false;
|
||||
RenderCallback renderCallback;
|
||||
const uint32 frameCount = m_config.bufferSize;
|
||||
const uint32 channels = m_config.channels;
|
||||
const uint32 sampleRate = m_config.sampleRate;
|
||||
const size_t sampleCount = static_cast<size_t>(frameCount) * channels;
|
||||
|
||||
if (targetBuffer->size() != sampleCount) {
|
||||
targetBuffer->assign(sampleCount, 0);
|
||||
} else {
|
||||
std::fill(targetBuffer->begin(), targetBuffer->end(), 0);
|
||||
}
|
||||
|
||||
if (m_renderBuffer.size() != sampleCount) {
|
||||
m_renderBuffer.assign(sampleCount, 0.0f);
|
||||
} else {
|
||||
std::fill(m_renderBuffer.begin(), m_renderBuffer.end(), 0.0f);
|
||||
}
|
||||
renderCallback = m_renderCallback;
|
||||
lock.unlock();
|
||||
|
||||
if (renderCallback && !m_renderBuffer.empty()) {
|
||||
renderCallback(m_renderBuffer.data(), frameCount, channels, sampleRate);
|
||||
}
|
||||
FillPcm16Buffer(*targetBuffer, m_renderBuffer);
|
||||
|
||||
if (SubmitBuffer(*targetHeader, *targetBuffer) != MMSYSERR_NOERROR) {
|
||||
std::lock_guard<std::mutex> restoreLock(m_bufferMutex);
|
||||
if (targetHeader == &m_waveHeader1) {
|
||||
@@ -304,6 +318,7 @@ void WaveOutBackend::AudioThread() {
|
||||
} else {
|
||||
m_buffer2Available = true;
|
||||
}
|
||||
m_bufferReadyCond.notify_one();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -322,7 +337,7 @@ void WaveOutBackend::OnAudioCallback(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstan
|
||||
} else if (completedHeader == &m_waveHeader2) {
|
||||
m_buffer2Available = true;
|
||||
}
|
||||
m_dataReadyCond.notify_one();
|
||||
m_bufferReadyCond.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,17 @@
|
||||
namespace XCEngine {
|
||||
namespace Components {
|
||||
|
||||
namespace {
|
||||
|
||||
std::unique_lock<std::recursive_mutex> LockAudioState() {
|
||||
if (Audio::AudioSystem* audioSystem = Audio::AudioSystem::TryGetExisting()) {
|
||||
return audioSystem->AcquireStateLock();
|
||||
}
|
||||
return std::unique_lock<std::recursive_mutex>();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
AudioListenerComponent::AudioListenerComponent()
|
||||
{
|
||||
Audio::AudioSystem::Get().RegisterListenerComponent(this);
|
||||
@@ -17,36 +28,43 @@ AudioListenerComponent::~AudioListenerComponent() {
|
||||
}
|
||||
|
||||
void AudioListenerComponent::SetMasterVolume(float volume) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
m_masterVolume = std::max(0.0f, std::min(1.0f, volume));
|
||||
Audio::AudioSystem::Get().SetMasterVolume(m_masterVolume);
|
||||
}
|
||||
|
||||
void AudioListenerComponent::SetMute(bool mute) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
m_mute = mute;
|
||||
Audio::AudioSystem::Get().SetMuted(m_mute);
|
||||
}
|
||||
|
||||
void AudioListenerComponent::SetDopplerLevel(float level) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
m_dopplerLevel = std::max(0.0f, std::min(5.0f, level));
|
||||
Audio::AudioSystem::Get().SetListenerDopplerLevel(m_dopplerLevel);
|
||||
}
|
||||
|
||||
void AudioListenerComponent::SetSpeedOfSound(float metersPerSecond) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
m_speedOfSound = std::max(1.0f, metersPerSecond);
|
||||
Audio::AudioSystem::Get().SetSpeedOfSound(m_speedOfSound);
|
||||
}
|
||||
|
||||
void AudioListenerComponent::SetReverbLevel(float level) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
m_reverbLevel = std::max(0.0f, std::min(1.0f, level));
|
||||
Audio::AudioSystem::Get().SetListenerReverbLevel(m_reverbLevel);
|
||||
}
|
||||
|
||||
void AudioListenerComponent::SetReverb(Audio::AudioMixer* reverb) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
m_reverb = reverb;
|
||||
Audio::AudioSystem::Get().SetListenerReverbMixer(m_reverb);
|
||||
}
|
||||
|
||||
void AudioListenerComponent::Update(float deltaTime) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
if (!m_gameObject) {
|
||||
return;
|
||||
}
|
||||
@@ -67,6 +85,7 @@ void AudioListenerComponent::Update(float deltaTime) {
|
||||
}
|
||||
|
||||
void AudioListenerComponent::Serialize(std::ostream& os) const {
|
||||
auto audioStateLock = LockAudioState();
|
||||
os << "masterVolume=" << m_masterVolume << ";";
|
||||
os << "mute=" << (m_mute ? 1 : 0) << ";";
|
||||
os << "dopplerLevel=" << m_dopplerLevel << ";";
|
||||
@@ -75,6 +94,7 @@ void AudioListenerComponent::Serialize(std::ostream& os) const {
|
||||
}
|
||||
|
||||
void AudioListenerComponent::Deserialize(std::istream& is) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
SetMasterVolume(1.0f);
|
||||
SetMute(false);
|
||||
SetDopplerLevel(1.0f);
|
||||
|
||||
@@ -144,6 +144,13 @@ void ApplyPanToBuffer(float* buffer, Audio::uint32 frameCount, Audio::uint32 cha
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_lock<std::recursive_mutex> LockAudioState() {
|
||||
if (Audio::AudioSystem* audioSystem = Audio::AudioSystem::TryGetExisting()) {
|
||||
return audioSystem->AcquireStateLock();
|
||||
}
|
||||
return std::unique_lock<std::recursive_mutex>();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
AudioSourceComponent::AudioSourceComponent() {
|
||||
@@ -158,6 +165,7 @@ AudioSourceComponent::~AudioSourceComponent() {
|
||||
}
|
||||
|
||||
void AudioSourceComponent::Play() {
|
||||
auto audioStateLock = LockAudioState();
|
||||
if (!m_clip || !m_clip->IsValid()) {
|
||||
return;
|
||||
}
|
||||
@@ -189,6 +197,7 @@ void AudioSourceComponent::Play() {
|
||||
}
|
||||
|
||||
void AudioSourceComponent::Pause() {
|
||||
auto audioStateLock = LockAudioState();
|
||||
if (m_playState == Audio::PlayState::Playing) {
|
||||
m_playState = Audio::PlayState::Paused;
|
||||
Audio::AudioSystem::Get().UnregisterSource(this);
|
||||
@@ -196,6 +205,7 @@ void AudioSourceComponent::Pause() {
|
||||
}
|
||||
|
||||
void AudioSourceComponent::Stop(Audio::StopMode mode) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
if (m_playState != Audio::PlayState::Stopped) {
|
||||
m_playState = Audio::PlayState::Stopped;
|
||||
m_samplePosition = 0;
|
||||
@@ -209,6 +219,7 @@ void AudioSourceComponent::Stop(Audio::StopMode mode) {
|
||||
}
|
||||
|
||||
void AudioSourceComponent::SetClip(Resources::AudioClip* clip) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
m_clipHandle = Resources::ResourceHandle<Resources::AudioClip>(clip);
|
||||
m_clip = clip;
|
||||
m_clipPath.clear();
|
||||
@@ -235,6 +246,7 @@ void AudioSourceComponent::SetClip(Resources::AudioClip* clip) {
|
||||
}
|
||||
|
||||
void AudioSourceComponent::SetClipPath(const std::string& clipPath) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
m_clipRef.Reset();
|
||||
m_clipPath = clipPath;
|
||||
if (!m_clipPath.empty() &&
|
||||
@@ -266,24 +278,29 @@ void AudioSourceComponent::SetClipPath(const std::string& clipPath) {
|
||||
}
|
||||
|
||||
void AudioSourceComponent::ClearClip() {
|
||||
auto audioStateLock = LockAudioState();
|
||||
m_clipRef.Reset();
|
||||
m_clipPath.clear();
|
||||
SetClip(nullptr);
|
||||
}
|
||||
|
||||
void AudioSourceComponent::SetVolume(float volume) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
m_volume = std::max(0.0f, std::min(1.0f, volume));
|
||||
}
|
||||
|
||||
void AudioSourceComponent::SetPitch(float pitch) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
m_pitch = std::max(0.0f, std::min(3.0f, pitch));
|
||||
}
|
||||
|
||||
void AudioSourceComponent::SetPan(float pan) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
m_pan = std::max(-1.0f, std::min(1.0f, pan));
|
||||
}
|
||||
|
||||
void AudioSourceComponent::SetHRTFEnabled(bool enabled) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
m_useHRTF = enabled;
|
||||
if (!enabled) {
|
||||
m_hrtf.ResetState();
|
||||
@@ -291,22 +308,27 @@ void AudioSourceComponent::SetHRTFEnabled(bool enabled) {
|
||||
}
|
||||
|
||||
void AudioSourceComponent::SetHRTFCrossFeed(float crossFeed) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
m_hrtf.SetCrossFeed(crossFeed);
|
||||
}
|
||||
|
||||
void AudioSourceComponent::SetHRTFQuality(Audio::uint32 level) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
m_hrtf.SetQualityLevel(level);
|
||||
}
|
||||
|
||||
void AudioSourceComponent::SetLooping(bool loop) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
m_isLooping = loop;
|
||||
}
|
||||
|
||||
void AudioSourceComponent::SetSpatialize(bool spatialize) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
m_spatialize = spatialize;
|
||||
}
|
||||
|
||||
void AudioSourceComponent::Set3DParams(const Audio::Audio3DParams& params) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
m_3DParams = params;
|
||||
m_3DParams.dopplerLevel = std::max(0.0f, m_3DParams.dopplerLevel);
|
||||
m_3DParams.speedOfSound = std::max(1.0f, m_3DParams.speedOfSound);
|
||||
@@ -318,22 +340,27 @@ void AudioSourceComponent::Set3DParams(const Audio::Audio3DParams& params) {
|
||||
}
|
||||
|
||||
void AudioSourceComponent::SetDopplerLevel(float level) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
m_3DParams.dopplerLevel = std::max(0.0f, level);
|
||||
}
|
||||
|
||||
void AudioSourceComponent::SetSpread(float spread) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
m_3DParams.spread = std::max(0.0f, std::min(1.0f, spread));
|
||||
}
|
||||
|
||||
void AudioSourceComponent::SetReverbZoneMix(float mix) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
m_3DParams.reverbZoneMix = std::max(0.0f, std::min(1.0f, mix));
|
||||
}
|
||||
|
||||
void AudioSourceComponent::SetOutputMixer(Audio::AudioMixer* mixer) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
m_outputMixer = mixer;
|
||||
}
|
||||
|
||||
void AudioSourceComponent::SetTime(float seconds) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
if (!m_clip || !m_clip->IsValid()) {
|
||||
return;
|
||||
}
|
||||
@@ -370,15 +397,18 @@ float AudioSourceComponent::GetDuration() const {
|
||||
}
|
||||
|
||||
void AudioSourceComponent::StartEnergyDetect() {
|
||||
auto audioStateLock = LockAudioState();
|
||||
m_isEnergyDetecting = true;
|
||||
m_energyHistory.clear();
|
||||
}
|
||||
|
||||
void AudioSourceComponent::StopEnergyDetect() {
|
||||
auto audioStateLock = LockAudioState();
|
||||
m_isEnergyDetecting = false;
|
||||
}
|
||||
|
||||
void AudioSourceComponent::Update(float deltaTime) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
if (m_gameObject) {
|
||||
const Math::Vector3 position = transform().GetPosition();
|
||||
if (m_hasLastPosition && deltaTime > 0.0f) {
|
||||
@@ -403,12 +433,14 @@ void AudioSourceComponent::Update(float deltaTime) {
|
||||
}
|
||||
|
||||
void AudioSourceComponent::OnEnable() {
|
||||
auto audioStateLock = LockAudioState();
|
||||
if (m_playState == Audio::PlayState::Playing) {
|
||||
Audio::AudioSystem::Get().RegisterSource(this);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioSourceComponent::OnDisable() {
|
||||
auto audioStateLock = LockAudioState();
|
||||
if (m_playState == Audio::PlayState::Playing) {
|
||||
Audio::AudioSystem::Get().UnregisterSource(this);
|
||||
}
|
||||
@@ -419,6 +451,7 @@ void AudioSourceComponent::OnDestroy() {
|
||||
}
|
||||
|
||||
void AudioSourceComponent::Serialize(std::ostream& os) const {
|
||||
auto audioStateLock = LockAudioState();
|
||||
Resources::AssetRef serializedClipRef = m_clipRef;
|
||||
std::string serializedClipPath = m_clipPath;
|
||||
if (serializedClipPath.empty() && m_clip != nullptr) {
|
||||
@@ -458,6 +491,7 @@ void AudioSourceComponent::Serialize(std::ostream& os) const {
|
||||
}
|
||||
|
||||
void AudioSourceComponent::Deserialize(std::istream& is) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
ClearClip();
|
||||
SetVolume(1.0f);
|
||||
SetPitch(1.0f);
|
||||
@@ -555,6 +589,7 @@ void AudioSourceComponent::ProcessAudio(float* buffer, Audio::uint32 frameCount,
|
||||
float listenerDopplerLevel,
|
||||
float speedOfSound,
|
||||
Audio::uint32 outputSampleRate) {
|
||||
auto audioStateLock = LockAudioState();
|
||||
if (m_playState != Audio::PlayState::Playing || !m_clip) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -78,6 +78,60 @@ public:
|
||||
uint32 lastSampleRate = 0;
|
||||
};
|
||||
|
||||
class PullCaptureBackend final : public IAudioBackend {
|
||||
public:
|
||||
explicit PullCaptureBackend(const AudioConfig& inConfig) : config(inConfig) {}
|
||||
|
||||
bool Initialize(const AudioConfig& inConfig) override {
|
||||
config = inConfig;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Shutdown() override {}
|
||||
|
||||
std::string GetDeviceName() const override { return "PullCaptureBackend"; }
|
||||
void GetAvailableDevices(std::vector<std::string>& devices) override {
|
||||
devices = {"PullCaptureBackend"};
|
||||
}
|
||||
bool SetDevice(const std::string& deviceName) override { return deviceName == "PullCaptureBackend"; }
|
||||
|
||||
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 {
|
||||
(void)buffer;
|
||||
(void)frameCount;
|
||||
(void)channels;
|
||||
(void)sampleRate;
|
||||
submissionCount++;
|
||||
}
|
||||
|
||||
bool UsesPullModel() const override { return true; }
|
||||
void SetRenderCallback(RenderCallback callback) override { renderCallback = callback; }
|
||||
|
||||
bool IsRunning() const override { return running; }
|
||||
AudioConfig GetConfig() const override { return config; }
|
||||
|
||||
bool RenderOnce() {
|
||||
if (!renderCallback) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t sampleCount = static_cast<size_t>(config.bufferSize) * config.channels;
|
||||
captured.assign(sampleCount, 0.0f);
|
||||
renderCallback(captured.data(), config.bufferSize, config.channels, config.sampleRate);
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioConfig config{};
|
||||
std::vector<float> captured;
|
||||
RenderCallback renderCallback;
|
||||
bool running = true;
|
||||
uint32 submissionCount = 0;
|
||||
};
|
||||
|
||||
TEST(AudioSystem, MasterMixerProcessesDirectSources) {
|
||||
AudioSystem& system = AudioSystem::Get();
|
||||
system.Shutdown();
|
||||
@@ -318,6 +372,40 @@ TEST(AudioSystem, RenderAudioProducesMixedBlockWithoutBackendSubmission) {
|
||||
system.Shutdown();
|
||||
}
|
||||
|
||||
TEST(AudioSystem, PullBackendRendersViaCallbackWithoutPushSubmission) {
|
||||
AudioSystem& system = AudioSystem::Get();
|
||||
system.Shutdown();
|
||||
|
||||
AudioConfig config;
|
||||
config.sampleRate = 4;
|
||||
config.channels = 1;
|
||||
config.bufferSize = 1;
|
||||
|
||||
auto backend = std::make_unique<PullCaptureBackend>(config);
|
||||
PullCaptureBackend* backendPtr = backend.get();
|
||||
system.SetBackend(std::move(backend));
|
||||
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();
|
||||
|
||||
system.Update(0.0f);
|
||||
|
||||
EXPECT_EQ(backendPtr->submissionCount, 0u);
|
||||
ASSERT_TRUE(backendPtr->RenderOnce());
|
||||
ASSERT_EQ(backendPtr->captured.size(), 1u);
|
||||
EXPECT_NEAR(backendPtr->captured[0], 0.25f, 1e-5f);
|
||||
|
||||
source.Stop();
|
||||
system.SetMasterVolume(1.0f);
|
||||
system.Shutdown();
|
||||
}
|
||||
|
||||
TEST(AudioSystem, AudioListenerComponentSerializeRoundTripPreservesSettings) {
|
||||
AudioSystem& system = AudioSystem::Get();
|
||||
system.Shutdown();
|
||||
|
||||
@@ -23,23 +23,40 @@ namespace {
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
TEST(WaveOutBackend, ProcessAudioCopiesOnlyRequestedSamplesIntoPendingMixBuffer) {
|
||||
TEST(WaveOutBackend, RenderCallbackCanDrivePcm16ConversionForPullModel) {
|
||||
WaveOut::WaveOutBackend backend;
|
||||
backend.m_pendingMixBuffer.assign(8, 1.0f);
|
||||
backend.m_config.bufferSize = 2;
|
||||
backend.m_config.channels = 2;
|
||||
backend.m_config.sampleRate = 48000;
|
||||
|
||||
float input[] = {0.25f, -0.5f, 0.75f, -1.0f};
|
||||
backend.ProcessAudio(input, 2, 2, 48000);
|
||||
bool callbackInvoked = false;
|
||||
backend.SetRenderCallback(
|
||||
[&callbackInvoked](float* buffer, uint32 frameCount, uint32 channels, uint32 sampleRate) {
|
||||
callbackInvoked = true;
|
||||
EXPECT_EQ(frameCount, 2u);
|
||||
EXPECT_EQ(channels, 2u);
|
||||
EXPECT_EQ(sampleRate, 48000u);
|
||||
|
||||
ASSERT_EQ(backend.m_pendingMixBuffer.size(), 8u);
|
||||
EXPECT_TRUE(backend.m_hasPendingMix);
|
||||
EXPECT_FLOAT_EQ(backend.m_pendingMixBuffer[0], 0.25f);
|
||||
EXPECT_FLOAT_EQ(backend.m_pendingMixBuffer[1], -0.5f);
|
||||
EXPECT_FLOAT_EQ(backend.m_pendingMixBuffer[2], 0.75f);
|
||||
EXPECT_FLOAT_EQ(backend.m_pendingMixBuffer[3], -1.0f);
|
||||
EXPECT_FLOAT_EQ(backend.m_pendingMixBuffer[4], 0.0f);
|
||||
EXPECT_FLOAT_EQ(backend.m_pendingMixBuffer[5], 0.0f);
|
||||
EXPECT_FLOAT_EQ(backend.m_pendingMixBuffer[6], 0.0f);
|
||||
EXPECT_FLOAT_EQ(backend.m_pendingMixBuffer[7], 0.0f);
|
||||
buffer[0] = 0.25f;
|
||||
buffer[1] = -0.5f;
|
||||
buffer[2] = 0.75f;
|
||||
buffer[3] = -1.0f;
|
||||
});
|
||||
|
||||
backend.m_renderBuffer.assign(4, 0.0f);
|
||||
ASSERT_TRUE(static_cast<bool>(backend.m_renderCallback));
|
||||
backend.m_renderCallback(backend.m_renderBuffer.data(), 2, 2, 48000);
|
||||
|
||||
std::vector<int16_t> output(4, 0);
|
||||
WaveOut::WaveOutBackend::FillPcm16Buffer(output, backend.m_renderBuffer);
|
||||
|
||||
EXPECT_TRUE(backend.UsesPullModel());
|
||||
EXPECT_TRUE(callbackInvoked);
|
||||
ASSERT_EQ(output.size(), 4u);
|
||||
EXPECT_EQ(output[0], 8191);
|
||||
EXPECT_EQ(output[1], -16383);
|
||||
EXPECT_EQ(output[2], 24575);
|
||||
EXPECT_EQ(output[3], -32767);
|
||||
}
|
||||
|
||||
TEST(WaveOutBackend, SetDeviceAcceptsDefaultDeviceBeforeInitialize) {
|
||||
|
||||
Reference in New Issue
Block a user