mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-09-10 17:16:47 -04:00
204 lines
6.3 KiB
C++
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;
|
|
}
|