Files
wiicompiled/runtime/src/audio_backend.cpp
T
patchzyy ec226e8348 init
2026-08-23 17:10:50 +02:00

204 lines
6.3 KiB
C++

#include "audio_backend.h"
#include "runtime_log.h"
#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <iostream>
#include <SDL3/SDL_init.h>
AudioBackend& AudioBackend::Instance() {
static AudioBackend instance;
return instance;
}
float AudioBackend::EffectiveGainLocked() const {
return m_muted ? 0.0f : m_masterVolume;
}
void AudioBackend::ApplyGainLocked() {
if (m_stream && !SDL_SetAudioStreamGain(m_stream, EffectiveGainLocked())) {
RT_LOG(RT_TAG_AUDIO) << "SDL_SetAudioStreamGain failed: " << SDL_GetError() << std::endl;
}
}
void AudioBackend::SetMasterVolume(float volume) {
std::lock_guard<std::mutex> lock(m_mutex);
m_masterVolume = std::clamp(volume, 0.0f, 1.0f);
ApplyGainLocked();
}
void AudioBackend::SetMuted(bool muted) {
std::lock_guard<std::mutex> lock(m_mutex);
m_muted = muted;
ApplyGainLocked();
}
bool AudioBackend::EnsureInitializedLocked(uint32_t sampleRate, uint32_t channels) {
if (m_initialized && m_sampleRate == sampleRate && m_channels == channels) {
return true;
}
if (m_stream) {
SDL_DestroyAudioStream(m_stream);
m_stream = nullptr;
}
if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) {
RT_LOG(RT_TAG_AUDIO) << "SDL_InitSubSystem(SDL_INIT_AUDIO) failed: " << SDL_GetError() << std::endl;
return false;
}
SDL_AudioSpec spec{};
spec.format = SDL_AUDIO_S16LE;
spec.channels = static_cast<int>(channels);
spec.freq = static_cast<int>(sampleRate);
SDL_AudioStream* stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, nullptr, nullptr);
if (!stream) {
RT_LOG(RT_TAG_AUDIO) << "SDL_OpenAudioDeviceStream failed: " << SDL_GetError() << std::endl;
return false;
}
if (!SDL_ResumeAudioStreamDevice(stream)) {
RT_LOG(RT_TAG_AUDIO) << "SDL_ResumeAudioStreamDevice failed: " << SDL_GetError() << std::endl;
SDL_DestroyAudioStream(stream);
return false;
}
if (!SDL_SetAudioStreamGain(stream, EffectiveGainLocked())) {
RT_LOG(RT_TAG_AUDIO) << "SDL_SetAudioStreamGain failed: " << SDL_GetError() << std::endl;
SDL_DestroyAudioStream(stream);
return false;
}
m_stream = stream;
m_spec = spec;
m_sampleRate = sampleRate;
m_channels = channels;
m_initialized = true;
return true;
}
bool AudioBackend::Init(uint32_t sampleRate, uint32_t channels) {
std::lock_guard<std::mutex> lock(m_mutex);
return EnsureInitializedLocked(sampleRate, channels);
}
void AudioBackend::Shutdown() {
std::lock_guard<std::mutex> lock(m_mutex);
if (m_stream) {
SDL_DestroyAudioStream(m_stream);
m_stream = nullptr;
}
if (m_initialized) {
SDL_QuitSubSystem(SDL_INIT_AUDIO);
}
m_initialized = false;
m_sampleRate = 0;
m_channels = 0;
m_reportedDroppedBlock = false;
m_convertBuffer.clear();
}
uint32_t AudioBackend::QueueLimitBytesLocked() const {
constexpr uint32_t queueMs = 120;
const uint64_t bytesPerSecond = static_cast<uint64_t>(m_spec.freq) *
static_cast<uint64_t>(m_spec.channels) *
sizeof(int16_t);
return static_cast<uint32_t>((bytesPerSecond * queueMs) / 1000u);
}
bool AudioBackend::QueueHasCapacityLocked(int incomingBytes) {
if (!m_stream) {
return false;
}
const int queued = SDL_GetAudioStreamQueued(m_stream);
if (queued < 0) {
return true;
}
const uint32_t maxQueued = QueueLimitBytesLocked();
if (static_cast<uint64_t>(queued) + static_cast<uint64_t>(std::max(incomingBytes, 0)) > maxQueued) {
// Match Dolphin's FIFO overflow behavior: preserve the continuous audio
// already queued and discard the new block. Clearing SDL's entire
// stream creates an audible discontinuity (the severe crackle seen when
// VI-batched DMA briefly outran playback).
// A drop is audible; report the first one so it is not invisible.
if (!m_reportedDroppedBlock) {
m_reportedDroppedBlock = true;
RT_LOG(RT_TAG_AUDIO) << "output queue full (" << queued << "/" << maxQueued
<< " bytes); dropping blocks to preserve continuity" << std::endl;
}
return false;
}
return true;
}
bool AudioBackend::PushWiiAiSamplesBE16(const uint8_t* data, size_t bytes) {
if (!data || bytes == 0) {
return false;
}
std::lock_guard<std::mutex> lock(m_mutex);
if (!m_initialized || !m_stream) {
return false;
}
const size_t sampleCount = bytes / sizeof(int16_t);
if (sampleCount == 0) {
return false;
}
if (m_convertBuffer.size() < sampleCount) {
m_convertBuffer.resize(sampleCount);
}
const size_t frameCount = sampleCount / 2;
for (size_t frame = 0; frame < frameCount; ++frame) {
const size_t rightOffset = frame * 4;
const size_t leftOffset = rightOffset + 2;
const uint16_t right = static_cast<uint16_t>(data[rightOffset]) << 8 |
static_cast<uint16_t>(data[rightOffset + 1]);
const uint16_t left = static_cast<uint16_t>(data[leftOffset]) << 8 |
static_cast<uint16_t>(data[leftOffset + 1]);
m_convertBuffer[frame * 2] = static_cast<int16_t>(left);
m_convertBuffer[frame * 2 + 1] = static_cast<int16_t>(right);
}
const int lenBytes = static_cast<int>(frameCount * 2 * sizeof(int16_t));
if (!QueueHasCapacityLocked(lenBytes)) {
return true;
}
if (!SDL_PutAudioStreamData(m_stream, m_convertBuffer.data(), lenBytes)) {
RT_LOG(RT_TAG_AUDIO) << "SDL_PutAudioStreamData failed: " << SDL_GetError() << std::endl;
return false;
}
return true;
}
bool AudioBackend::PushSamplesLE16(const int16_t* samples, size_t sampleCount) {
if (!samples || sampleCount == 0) {
return false;
}
std::lock_guard<std::mutex> lock(m_mutex);
if (!m_initialized || !m_stream) {
return false;
}
const int lenBytes = static_cast<int>(sampleCount * sizeof(int16_t));
if (!QueueHasCapacityLocked(lenBytes)) {
return true;
}
if (!SDL_PutAudioStreamData(m_stream, samples, lenBytes)) {
RT_LOG(RT_TAG_AUDIO) << "SDL_PutAudioStreamData failed: " << SDL_GetError() << std::endl;
return false;
}
return true;
}