shadPS4/src/core/libraries/audio/sdl_audio.cpp
nickci2002 0d09c32df9
Ds4 Speaker Audio Rebase Fix (#3607)
* Logic update, no QT ui

* Fixing errors

* Gui boxes

* fixes

* prevent device list refreshing too fast when game not running

* Removed duplicate Socket declarations in kernel/file_system.cpp and fs.h

* Fixed clang-format and micDevice errors

* Ran clang-format and fixed rebase compiler issues

* Settings dialog fix

* Addressed squidbus' concerns

* Update config.cpp to adhere to clang-format

* Removed a space causing clang-format to complain

* Addressed squidbus' concerns and added fallbacks

Concerns:
- Changed dev_name construct to remove unnecessary cast
- Added an invalid AudioDeviceID macro to replace magic number

---------

Co-authored-by: rainmakerv2 <30595646+rainmakerv3@users.noreply.github.com>
2025-09-18 08:28:12 +03:00

157 lines
6.2 KiB
C++

// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <thread>
#include <SDL3/SDL_audio.h>
#include <SDL3/SDL_hints.h>
#include "common/config.h"
#include "common/logging/log.h"
#include "core/libraries/audio/audioout.h"
#include "core/libraries/audio/audioout_backend.h"
#define SDL_INVALID_AUDIODEVICEID 0 // Defined in SDL_audio.h but not made a macro
namespace Libraries::AudioOut {
class SDLPortBackend : public PortBackend {
public:
explicit SDLPortBackend(const PortOut& port)
: frame_size(port.format_info.FrameSize()), guest_buffer_size(port.BufferSize()) {
const SDL_AudioSpec fmt = {
.format = port.format_info.is_float ? SDL_AUDIO_F32LE : SDL_AUDIO_S16LE,
.channels = port.format_info.num_channels,
.freq = static_cast<int>(port.sample_rate),
};
// Determine port type
std::string port_name = port.type == OrbisAudioOutPort::PadSpk
? Config::getPadSpkOutputDevice()
: Config::getMainOutputDevice();
SDL_AudioDeviceID dev_id = SDL_INVALID_AUDIODEVICEID;
if (port_name == "None") {
stream = nullptr;
return;
} else if (port_name == "Default Device") {
dev_id = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;
} else {
try {
SDL_AudioDeviceID* dev_array = SDL_GetAudioPlaybackDevices(nullptr);
for (; dev_array != 0;) {
std::string dev_name(SDL_GetAudioDeviceName(*dev_array));
if (dev_name == port_name) {
dev_id = *dev_array;
break;
} else {
dev_array++;
}
}
if (dev_id == SDL_INVALID_AUDIODEVICEID) {
LOG_WARNING(Lib_AudioOut, "Audio device not found: {}", port_name);
dev_id = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;
}
} catch (const std::exception& e) {
LOG_ERROR(Lib_AudioOut, "Invalid audio output device: {}", port_name);
stream = nullptr;
return;
}
}
// Open the audio stream
stream = SDL_OpenAudioDeviceStream(dev_id, &fmt, nullptr, nullptr);
if (stream == nullptr) {
LOG_ERROR(Lib_AudioOut, "Failed to create SDL audio stream: {}", SDL_GetError());
return;
}
CalculateQueueThreshold();
if (!SDL_SetAudioStreamInputChannelMap(stream, port.format_info.channel_layout.data(),
port.format_info.num_channels)) {
LOG_ERROR(Lib_AudioOut, "Failed to configure SDL audio stream channel map: {}",
SDL_GetError());
SDL_DestroyAudioStream(stream);
stream = nullptr;
return;
}
if (!SDL_ResumeAudioStreamDevice(stream)) {
LOG_ERROR(Lib_AudioOut, "Failed to resume SDL audio stream: {}", SDL_GetError());
SDL_DestroyAudioStream(stream);
stream = nullptr;
return;
}
SDL_SetAudioStreamGain(stream, Config::getVolumeSlider() / 100.0f);
}
~SDLPortBackend() override {
if (!stream) {
return;
}
SDL_DestroyAudioStream(stream);
stream = nullptr;
}
void Output(void* ptr) override {
if (!stream) {
return;
}
// AudioOut library manages timing, but we still need to guard against the SDL
// audio queue stalling, which may happen during device changes, for example.
// Otherwise, latency may grow over time unbounded.
if (const auto queued = SDL_GetAudioStreamQueued(stream); queued >= queue_threshold) {
LOG_INFO(Lib_AudioOut, "SDL audio queue backed up ({} queued, {} threshold), clearing.",
queued, queue_threshold);
SDL_ClearAudioStream(stream);
// Recalculate the threshold in case this happened because of a device change.
CalculateQueueThreshold();
}
if (!SDL_PutAudioStreamData(stream, ptr, static_cast<int>(guest_buffer_size))) {
LOG_ERROR(Lib_AudioOut, "Failed to output to SDL audio stream: {}", SDL_GetError());
}
}
void SetVolume(const std::array<int, 8>& ch_volumes) override {
if (!stream) {
return;
}
// SDL does not have per-channel volumes, for now just take the maximum of the channels.
const auto vol = *std::ranges::max_element(ch_volumes);
if (!SDL_SetAudioStreamGain(stream, static_cast<float>(vol) / SCE_AUDIO_OUT_VOLUME_0DB *
Config::getVolumeSlider() / 100.0f)) {
LOG_WARNING(Lib_AudioOut, "Failed to change SDL audio stream volume: {}",
SDL_GetError());
}
}
private:
void CalculateQueueThreshold() {
SDL_AudioSpec discard;
int sdl_buffer_frames;
if (!SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(stream), &discard,
&sdl_buffer_frames)) {
LOG_WARNING(Lib_AudioOut, "Failed to get SDL audio stream buffer size: {}",
SDL_GetError());
sdl_buffer_frames = 0;
}
const auto sdl_buffer_size = sdl_buffer_frames * frame_size;
const auto new_threshold = std::max(guest_buffer_size, sdl_buffer_size) * 4;
if (host_buffer_size != sdl_buffer_size || queue_threshold != new_threshold) {
host_buffer_size = sdl_buffer_size;
queue_threshold = new_threshold;
LOG_INFO(Lib_AudioOut,
"SDL audio buffers: guest = {} bytes, host = {} bytes, threshold = {} bytes",
guest_buffer_size, host_buffer_size, queue_threshold);
}
}
u32 frame_size;
u32 guest_buffer_size;
u32 host_buffer_size{};
u32 queue_threshold{};
SDL_AudioStream* stream{};
};
std::unique_ptr<PortBackend> SDLAudioOut::Open(PortOut& port) {
return std::make_unique<SDLPortBackend>(port);
}
} // namespace Libraries::AudioOut