diff --git a/src/audio_core/sink/sdl2_sink.cpp b/src/audio_core/sink/sdl2_sink.cpp index 8cacd3386..7dd155ff0 100644 --- a/src/audio_core/sink/sdl2_sink.cpp +++ b/src/audio_core/sink/sdl2_sink.cpp @@ -1,5 +1,4 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -17,9 +16,138 @@ namespace AudioCore::Sink { /** * SDL sink stream, responsible for sinking samples to hardware. */ -// class SDLSinkStream final : public SinkStream { -// ... -// } +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(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(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 input_buffer{reinterpret_cast(stream), + num_frames * frame_size}; + impl->ProcessAudioIn(input_buffer, num_frames); + } else { + std::span output_buffer{reinterpret_cast(stream), num_frames * frame_size}; + impl->ProcessAudioOutAndRender(output_buffer, num_frames); + } + } + + /// SDL device id of the opened input/output device + SDL_AudioDeviceID device{}; +}; SDLSink::SDLSink(std::string_view target_device_name) { if (!SDL_WasInit(SDL_INIT_AUDIO)) { @@ -140,96 +268,4 @@ 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(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(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 input_buffer{reinterpret_cast(stream), - num_frames * frame_size}; - impl->ProcessAudioIn(input_buffer, num_frames); - } else { - std::span output_buffer{reinterpret_cast(stream), num_frames * frame_size}; - impl->ProcessAudioOutAndRender(output_buffer, num_frames); - } -} - } // namespace AudioCore::Sink diff --git a/src/audio_core/sink/sdl2_sink.h b/src/audio_core/sink/sdl2_sink.h index fef145273..9211d2e97 100644 --- a/src/audio_core/sink/sdl2_sink.h +++ b/src/audio_core/sink/sdl2_sink.h @@ -5,7 +5,6 @@ #include #include -#include #include "audio_core/sink/sink.h" @@ -96,21 +95,4 @@ std::vector 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