mirror of
https://git.citron-emu.org/Citron/Citron.git
synced 2025-03-15 06:44:48 +00:00
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:
parent
7b6495aced
commit
3aa9c0d151
2 changed files with 114 additions and 132 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue