audio: refactor SDL2 sink implementation

- Move SDLSinkStream class definition to header file
- Add additional error checking for SDL audio device availability
- Limit audio channels to stereo
- Add format verification and warning messages
- Improve audio device initialization with better error handling
- Rename device variable to audio_device_id for clarity
- Add running state flag
- Update copyright header to include citron

This refactoring improves error handling and provides better debug
information when audio device initialization fails. The implementation
is now more robust and provides clearer feedback for troubleshooting
audio issues.
This commit is contained in:
Zephyron 2025-02-08 19:19:52 +10:00
parent 7b6495aced
commit 3aa9c0d151
No known key found for this signature in database
2 changed files with 114 additions and 132 deletions

View file

@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <span>
@ -16,138 +17,9 @@ namespace AudioCore::Sink {
/**
* SDL sink stream, responsible for sinking samples to hardware.
*/
class SDLSinkStream final : public SinkStream {
public:
/**
* Create a new sink stream.
*
* @param device_channels_ - Number of channels supported by the hardware.
* @param system_channels_ - Number of channels the audio systems expect.
* @param output_device - Name of the output device to use for this stream.
* @param input_device - Name of the input device to use for this stream.
* @param type_ - Type of this stream.
* @param system_ - Core system.
* @param event - Event used only for audio renderer, signalled on buffer consume.
*/
SDLSinkStream(u32 device_channels_, u32 system_channels_, const std::string& output_device,
const std::string& input_device, StreamType type_, Core::System& system_)
: SinkStream{system_, type_} {
system_channels = system_channels_;
device_channels = device_channels_;
SDL_AudioSpec spec;
spec.freq = TargetSampleRate;
spec.channels = static_cast<u8>(device_channels);
spec.format = AUDIO_S16SYS;
spec.samples = TargetSampleCount * 2;
spec.callback = &SDLSinkStream::DataCallback;
spec.userdata = this;
std::string device_name{output_device};
bool capture{false};
if (type == StreamType::In) {
device_name = input_device;
capture = true;
}
SDL_AudioSpec obtained;
if (device_name.empty()) {
device = SDL_OpenAudioDevice(nullptr, capture, &spec, &obtained, false);
} else {
device = SDL_OpenAudioDevice(device_name.c_str(), capture, &spec, &obtained, false);
}
if (device == 0) {
LOG_CRITICAL(Audio_Sink, "Error opening SDL audio device: {}", SDL_GetError());
return;
}
LOG_INFO(Service_Audio,
"Opening SDL stream {} with: rate {} channels {} (system channels {}) "
" samples {}",
device, obtained.freq, obtained.channels, system_channels, obtained.samples);
}
/**
* Destroy the sink stream.
*/
~SDLSinkStream() override {
LOG_DEBUG(Service_Audio, "Destructing SDL stream {}", name);
Finalize();
}
/**
* Finalize the sink stream.
*/
void Finalize() override {
if (device == 0) {
return;
}
Stop();
SDL_ClearQueuedAudio(device);
SDL_CloseAudioDevice(device);
}
/**
* Start the sink stream.
*
* @param resume - Set to true if this is resuming the stream a previously-active stream.
* Default false.
*/
void Start(bool resume = false) override {
if (device == 0 || !paused) {
return;
}
paused = false;
SDL_PauseAudioDevice(device, 0);
}
/**
* Stop the sink stream.
*/
void Stop() override {
if (device == 0 || paused) {
return;
}
SignalPause();
SDL_PauseAudioDevice(device, 1);
}
private:
/**
* Main callback from SDL. Either expects samples from us (audio render/audio out), or will
* provide samples to be copied (audio in).
*
* @param userdata - Custom data pointer passed along, points to a SDLSinkStream.
* @param stream - Buffer of samples to be filled or read.
* @param len - Length of the stream in bytes.
*/
static void DataCallback(void* userdata, Uint8* stream, int len) {
auto* impl = static_cast<SDLSinkStream*>(userdata);
if (!impl) {
return;
}
const std::size_t num_channels = impl->GetDeviceChannels();
const std::size_t frame_size = num_channels;
const std::size_t num_frames{len / num_channels / sizeof(s16)};
if (impl->type == StreamType::In) {
std::span<const s16> input_buffer{reinterpret_cast<const s16*>(stream),
num_frames * frame_size};
impl->ProcessAudioIn(input_buffer, num_frames);
} else {
std::span<s16> output_buffer{reinterpret_cast<s16*>(stream), num_frames * frame_size};
impl->ProcessAudioOutAndRender(output_buffer, num_frames);
}
}
/// SDL device id of the opened input/output device
SDL_AudioDeviceID device{};
};
// class SDLSinkStream final : public SinkStream {
// ...
// }
SDLSink::SDLSink(std::string_view target_device_name) {
if (!SDL_WasInit(SDL_INIT_AUDIO)) {
@ -268,4 +140,96 @@ bool IsSDLSuitable() {
#endif
}
SDLSinkStream::SDLSinkStream(u32 device_channels_, u32 system_channels_, const std::string& output_device,
const std::string& input_device, StreamType type_, Core::System& system_)
: SinkStream{system_, type_} {
system_channels = system_channels_;
device_channels = device_channels_;
// Add error checking for SDL audio device
if (SDL_GetNumAudioDevices(0) < 1) {
LOG_ERROR(Service_Audio, "No audio devices available");
return;
}
SDL_AudioSpec want{};
want.freq = TargetSampleRate;
want.format = AUDIO_S16LSB;
want.channels = static_cast<u8>(std::min(device_channels, 2u)); // Limit to stereo
want.samples = TargetSampleCount * 2; // Match the sample count from logs
want.callback = nullptr;
SDL_AudioSpec got;
audio_device_id = SDL_OpenAudioDevice(nullptr, 0, &want, &got,
SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
if (audio_device_id == 0) {
LOG_ERROR(Service_Audio, "SDL_OpenAudioDevice failed: {}", SDL_GetError());
return;
}
// Verify we got the requested format
if (got.format != want.format || got.channels != want.channels) {
LOG_WARNING(Service_Audio,
"SDL audio format mismatch - wanted: {} ch, got: {} ch",
want.channels, got.channels);
}
running = true;
SDL_PauseAudioDevice(audio_device_id, 0);
}
SDLSinkStream::~SDLSinkStream() {
LOG_DEBUG(Service_Audio, "Destructing SDL stream {}", name);
Finalize();
}
void SDLSinkStream::Finalize() {
if (audio_device_id == 0) {
return;
}
Stop();
SDL_ClearQueuedAudio(audio_device_id);
SDL_CloseAudioDevice(audio_device_id);
}
void SDLSinkStream::Start(bool resume) {
if (audio_device_id == 0 || !paused) {
return;
}
paused = false;
SDL_PauseAudioDevice(audio_device_id, 0);
}
void SDLSinkStream::Stop() {
if (audio_device_id == 0 || paused) {
return;
}
SignalPause();
SDL_PauseAudioDevice(audio_device_id, 1);
}
void SDLSinkStream::DataCallback(void* userdata, Uint8* stream, int len) {
auto* impl = static_cast<SDLSinkStream*>(userdata);
if (!impl) {
return;
}
const std::size_t num_channels = impl->GetDeviceChannels();
const std::size_t frame_size = num_channels;
const std::size_t num_frames{len / num_channels / sizeof(s16)};
if (impl->type == StreamType::In) {
std::span<const s16> input_buffer{reinterpret_cast<const s16*>(stream),
num_frames * frame_size};
impl->ProcessAudioIn(input_buffer, num_frames);
} else {
std::span<s16> output_buffer{reinterpret_cast<s16*>(stream), num_frames * frame_size};
impl->ProcessAudioOutAndRender(output_buffer, num_frames);
}
}
} // namespace AudioCore::Sink

View file

@ -5,6 +5,7 @@
#include <string>
#include <vector>
#include <SDL.h>
#include "audio_core/sink/sink.h"
@ -95,4 +96,21 @@ std::vector<std::string> ListSDLSinkDevices(bool capture);
*/
bool IsSDLSuitable();
class SDLSinkStream final : public SinkStream {
public:
SDLSinkStream(u32 sample_rate, u32 num_channels, const std::string& output_device,
const std::string& input_device, StreamType type, Core::System& system);
~SDLSinkStream();
void Start(bool resume) override;
void Stop() override;
void Finalize();
private:
void DataCallback(void* userdata, Uint8* stream, int len);
bool running{false};
SDL_AudioDeviceID audio_device_id{};
};
} // namespace AudioCore::Sink