Files
SDL3CPlusPlus/src/services/impl/sdl_audio_service.cpp
johndoe6345789 147d373a5d Enhance logging across various services
- Added detailed logging to the ScriptEngineService to trace initialization status.
- Improved logging in SdlAudioService to track audio operations including initialization, shutdown, and playback functions.
- Enhanced SdlInputService with logging for key and mouse events, as well as state retrieval.
- Updated SdlWindowService to log window creation, destruction, and event polling.
- Added logging to ShaderScriptService for shader path loading and Lua state retrieval.
- Implemented logging in SwapchainService for swapchain operations, including creation, cleanup, and querying support details.
- Enhanced VulkanDeviceService with logging for device creation, shutdown, and memory type queries.
- Improved VulkanGuiService with logging for GUI initialization, frame preparation, and rendering operations.
2026-01-05 00:51:42 +00:00

478 lines
14 KiB
C++

#include "sdl_audio_service.hpp"
#include <algorithm>
#include <stdexcept>
#include <string>
namespace sdl3cpp::services::impl {
namespace {
constexpr int kDecodeChunkSize = 4096;
constexpr int kMixFrames = 1024;
} // namespace
SdlAudioService::SdlAudioService(std::shared_ptr<ILogger> logger)
: logger_(logger) {
if (logger_) {
logger_->Trace("SdlAudioService", "SdlAudioService");
}
}
SdlAudioService::~SdlAudioService() {
if (logger_) {
logger_->Trace("SdlAudioService", "~SdlAudioService");
}
if (initialized_) {
Shutdown();
}
}
void SdlAudioService::Initialize() {
if (logger_) {
logger_->Trace("SdlAudioService", "Initialize");
}
if (initialized_) {
return;
}
// Initialize SDL audio
if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) {
throw std::runtime_error("Failed to initialize SDL audio: " + std::string(SDL_GetError()));
}
// Set up desired audio spec
SDL_AudioSpec desiredSpec{};
desiredSpec.format = SDL_AUDIO_S16;
desiredSpec.channels = 2;
desiredSpec.freq = 44100;
// Open audio device stream (SDL3 way)
audioStream_ = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &desiredSpec, nullptr, nullptr);
if (!audioStream_) {
SDL_QuitSubSystem(SDL_INIT_AUDIO);
throw std::runtime_error("Failed to open audio device stream: " + std::string(SDL_GetError()));
}
mixSpec_ = desiredSpec;
SDL_AudioSpec inputSpec{};
if (SDL_GetAudioStreamFormat(audioStream_, &inputSpec, nullptr)) {
mixSpec_ = inputSpec;
}
// Start the audio stream
if (!SDL_ResumeAudioStreamDevice(audioStream_)) {
SDL_DestroyAudioStream(audioStream_);
SDL_QuitSubSystem(SDL_INIT_AUDIO);
throw std::runtime_error("Failed to resume audio stream: " + std::string(SDL_GetError()));
}
initialized_ = true;
if (logger_) {
logger_->Info("SDL audio service initialized successfully");
}
}
void SdlAudioService::Shutdown() noexcept {
if (logger_) {
logger_->Trace("SdlAudioService", "Shutdown");
}
if (!initialized_) {
return;
}
// Stop and cleanup audio
if (audioStream_) {
SDL_PauseAudioStreamDevice(audioStream_);
SDL_DestroyAudioStream(audioStream_);
audioStream_ = nullptr;
}
std::lock_guard<std::mutex> lock(audioMutex_);
if (backgroundAudio_) {
CleanupAudioData(*backgroundAudio_);
backgroundAudio_.reset();
}
for (auto& effect : effectAudio_) {
CleanupAudioData(*effect);
}
effectAudio_.clear();
mixBuffer_.clear();
tempBuffer_.clear();
outputBuffer_.clear();
SDL_QuitSubSystem(SDL_INIT_AUDIO);
initialized_ = false;
if (logger_) {
logger_->Info("SDL audio service shutdown");
}
}
void SdlAudioService::PlayBackground(const std::filesystem::path& path, bool loop) {
if (logger_) {
logger_->Trace("SdlAudioService", "PlayBackground",
"path=" + path.string() +
", loop=" + std::string(loop ? "true" : "false"));
}
if (!initialized_) {
throw std::runtime_error("Audio service not initialized");
}
std::lock_guard<std::mutex> lock(audioMutex_);
// Stop current background audio
if (backgroundAudio_) {
CleanupAudioData(*backgroundAudio_);
backgroundAudio_.reset();
}
// Load new audio file
backgroundAudio_ = std::make_unique<AudioData>();
if (!LoadAudioFile(path, *backgroundAudio_)) {
backgroundAudio_.reset();
throw std::runtime_error("Failed to load audio file: " + path.string());
}
backgroundAudio_->loop = loop;
backgroundAudio_->finished = false;
if (logger_) {
logger_->Info("Playing background audio: " + path.string() + " (loop: " + std::to_string(loop) + ")");
}
}
void SdlAudioService::PlayEffect(const std::filesystem::path& path, bool loop) {
if (logger_) {
logger_->Trace("SdlAudioService", "PlayEffect",
"path=" + path.string() +
", loop=" + std::string(loop ? "true" : "false"));
}
if (!initialized_) {
throw std::runtime_error("Audio service not initialized");
}
std::lock_guard<std::mutex> lock(audioMutex_);
auto effect = std::make_unique<AudioData>();
if (!LoadAudioFile(path, *effect)) {
throw std::runtime_error("Failed to load audio file: " + path.string());
}
effect->loop = loop;
effect->finished = false;
effectAudio_.push_back(std::move(effect));
if (logger_) {
logger_->Info("Playing effect audio: " + path.string() + " (loop: " + std::to_string(loop) + ")");
}
}
void SdlAudioService::StopBackground() {
if (logger_) {
logger_->Trace("SdlAudioService", "StopBackground");
}
if (!initialized_) {
return;
}
std::lock_guard<std::mutex> lock(audioMutex_);
if (backgroundAudio_) {
CleanupAudioData(*backgroundAudio_);
backgroundAudio_.reset();
}
if (logger_) {
logger_->Info("Stopped background audio");
}
}
void SdlAudioService::StopAll() {
if (logger_) {
logger_->Trace("SdlAudioService", "StopAll");
}
if (!initialized_) {
return;
}
std::lock_guard<std::mutex> lock(audioMutex_);
if (backgroundAudio_) {
CleanupAudioData(*backgroundAudio_);
backgroundAudio_.reset();
}
for (auto& effect : effectAudio_) {
CleanupAudioData(*effect);
}
effectAudio_.clear();
if (logger_) {
logger_->Info("Stopped all audio");
}
}
void SdlAudioService::SetVolume(float volume) {
if (logger_) {
logger_->Trace("SdlAudioService", "SetVolume",
"volume=" + std::to_string(volume));
}
std::lock_guard<std::mutex> lock(audioMutex_);
volume_ = std::clamp(volume, 0.0f, 1.0f);
if (logger_) {
logger_->TraceVariable("volume", volume_);
}
}
float SdlAudioService::GetVolume() const {
if (logger_) {
logger_->Trace("SdlAudioService", "GetVolume");
}
std::lock_guard<std::mutex> lock(const_cast<std::mutex&>(audioMutex_));
return volume_;
}
bool SdlAudioService::IsBackgroundPlaying() const {
if (logger_) {
logger_->Trace("SdlAudioService", "IsBackgroundPlaying");
}
std::lock_guard<std::mutex> lock(const_cast<std::mutex&>(audioMutex_));
return backgroundAudio_ && backgroundAudio_->isOpen && !backgroundAudio_->finished;
}
void SdlAudioService::Update() {
if (logger_) {
logger_->Trace("SdlAudioService", "Update");
}
if (!initialized_ || !audioStream_) {
return;
}
std::lock_guard<std::mutex> lock(audioMutex_);
if (!backgroundAudio_ && effectAudio_.empty()) {
return;
}
const int bytesPerFrame = SDL_AUDIO_FRAMESIZE(mixSpec_);
if (bytesPerFrame <= 0) {
return;
}
int queuedBytes = SDL_GetAudioStreamQueued(audioStream_);
if (queuedBytes < 0) {
if (logger_) {
logger_->Error("Failed to query audio queue: " + std::string(SDL_GetError()));
}
return;
}
if (queuedBytes >= bytesPerFrame * kMixFrames) {
return;
}
const int frames = kMixFrames;
const int channels = mixSpec_.channels;
const size_t sampleCount = static_cast<size_t>(frames * channels);
mixBuffer_.assign(sampleCount, 0);
bool hasAudio = false;
if (backgroundAudio_ && backgroundAudio_->isOpen) {
int bytesRead = ReadStreamSamples(*backgroundAudio_, tempBuffer_, frames);
if (bytesRead > 0) {
size_t samplesRead = static_cast<size_t>(bytesRead / static_cast<int>(sizeof(int16_t)));
for (size_t i = 0; i < samplesRead; ++i) {
mixBuffer_[i] += tempBuffer_[i];
}
hasAudio = true;
}
if (backgroundAudio_->finished &&
backgroundAudio_->convertStream &&
SDL_GetAudioStreamAvailable(backgroundAudio_->convertStream) == 0) {
CleanupAudioData(*backgroundAudio_);
backgroundAudio_.reset();
}
}
for (auto it = effectAudio_.begin(); it != effectAudio_.end(); ) {
AudioData& effect = *(*it);
int bytesRead = ReadStreamSamples(effect, tempBuffer_, frames);
if (bytesRead > 0) {
size_t samplesRead = static_cast<size_t>(bytesRead / static_cast<int>(sizeof(int16_t)));
for (size_t i = 0; i < samplesRead; ++i) {
mixBuffer_[i] += tempBuffer_[i];
}
hasAudio = true;
}
bool drained = effect.finished && effect.convertStream &&
SDL_GetAudioStreamAvailable(effect.convertStream) == 0;
if (!effect.isOpen || drained) {
CleanupAudioData(effect);
it = effectAudio_.erase(it);
} else {
++it;
}
}
if (!hasAudio) {
return;
}
outputBuffer_.assign(sampleCount, 0);
const float volume = volume_;
for (size_t i = 0; i < sampleCount; ++i) {
int32_t value = static_cast<int32_t>(mixBuffer_[i] * volume);
value = std::clamp(value, -32768, 32767);
outputBuffer_[i] = static_cast<int16_t>(value);
}
int bytesToQueue = static_cast<int>(sampleCount * sizeof(int16_t));
if (!SDL_PutAudioStreamData(audioStream_, outputBuffer_.data(), bytesToQueue)) {
if (logger_) {
logger_->Error("Failed to queue audio data: " + std::string(SDL_GetError()));
}
}
}
bool SdlAudioService::LoadAudioFile(const std::filesystem::path& path, AudioData& audioData) {
if (logger_) {
logger_->Trace("SdlAudioService", "LoadAudioFile",
"path=" + path.string() +
", audioData.isOpen=" + std::string(audioData.isOpen ? "true" : "false"));
}
std::string pathText = path.string();
FILE* file = fopen(pathText.c_str(), "rb");
if (!file) {
if (logger_) {
logger_->Error("Failed to open audio file: " + path.string());
}
return false;
}
if (ov_open(file, &audioData.vorbisFile, nullptr, 0) < 0) {
fclose(file);
if (logger_) {
logger_->Error("Failed to open vorbis file: " + path.string());
}
return false;
}
const vorbis_info* info = ov_info(&audioData.vorbisFile, -1);
if (!info) {
ov_clear(&audioData.vorbisFile);
if (logger_) {
logger_->Error("Failed to read vorbis info: " + path.string());
}
return false;
}
audioData.sourceSpec.format = SDL_AUDIO_S16;
audioData.sourceSpec.channels = info->channels;
audioData.sourceSpec.freq = info->rate;
audioData.convertStream = SDL_CreateAudioStream(&audioData.sourceSpec, &mixSpec_);
if (!audioData.convertStream) {
ov_clear(&audioData.vorbisFile);
if (logger_) {
logger_->Error("Failed to create audio stream: " + std::string(SDL_GetError()));
}
return false;
}
audioData.isOpen = true;
audioData.finished = false;
return true;
}
void SdlAudioService::CleanupAudioData(AudioData& audioData) {
if (logger_) {
logger_->Trace("SdlAudioService", "CleanupAudioData",
"audioData.isOpen=" + std::string(audioData.isOpen ? "true" : "false"));
}
if (audioData.convertStream) {
SDL_DestroyAudioStream(audioData.convertStream);
audioData.convertStream = nullptr;
}
if (audioData.isOpen) {
ov_clear(&audioData.vorbisFile);
audioData.isOpen = false;
}
audioData.finished = false;
}
int SdlAudioService::ReadStreamSamples(AudioData& audioData, std::vector<int16_t>& output, int frames) {
if (logger_) {
logger_->Trace("SdlAudioService", "ReadStreamSamples",
"frames=" + std::to_string(frames) +
", audioData.isOpen=" + std::string(audioData.isOpen ? "true" : "false"));
}
if (!audioData.isOpen || !audioData.convertStream) {
return 0;
}
const int bytesPerFrame = SDL_AUDIO_FRAMESIZE(mixSpec_);
const int bytesNeeded = frames * bytesPerFrame;
const size_t sampleCount = static_cast<size_t>(bytesNeeded / static_cast<int>(sizeof(int16_t)));
output.assign(sampleCount, 0);
while (!audioData.finished) {
int available = SDL_GetAudioStreamAvailable(audioData.convertStream);
if (available < 0) {
if (logger_) {
logger_->Error("Failed to query audio stream: " + std::string(SDL_GetError()));
}
audioData.finished = true;
break;
}
if (available >= bytesNeeded) {
break;
}
char decodeBuffer[kDecodeChunkSize];
int bytesRead = ov_read(&audioData.vorbisFile, decodeBuffer, kDecodeChunkSize, 0, 2, 1, nullptr);
if (bytesRead > 0) {
if (!SDL_PutAudioStreamData(audioData.convertStream, decodeBuffer, bytesRead)) {
if (logger_) {
logger_->Error("Failed to queue decoded audio: " + std::string(SDL_GetError()));
}
audioData.finished = true;
break;
}
} else if (bytesRead == 0) {
if (audioData.loop) {
ov_pcm_seek(&audioData.vorbisFile, 0);
continue;
}
audioData.finished = true;
SDL_FlushAudioStream(audioData.convertStream);
break;
} else {
if (logger_) {
logger_->Error("Vorbis decode error");
}
audioData.finished = true;
break;
}
}
int bytesRead = SDL_GetAudioStreamData(audioData.convertStream, output.data(), bytesNeeded);
if (bytesRead < 0) {
if (logger_) {
logger_->Error("Failed to read audio stream data: " + std::string(SDL_GetError()));
}
audioData.finished = true;
return 0;
}
if (bytesRead < bytesNeeded) {
const size_t samplesRead = static_cast<size_t>(bytesRead / static_cast<int>(sizeof(int16_t)));
if (samplesRead < output.size()) {
std::fill(output.begin() + samplesRead, output.end(), 0);
}
}
return bytesRead;
}
} // namespace sdl3cpp::services::impl