CubebAudioStream: Use a single shared context

Will be needed shortly.
This commit is contained in:
Stenzek
2025-12-23 00:56:24 +10:00
parent 02384ac2aa
commit 132ca44f05
7 changed files with 113 additions and 61 deletions

View File

@@ -505,7 +505,7 @@ void SPU::CreateOutputStream()
Error error;
if (!s_state.audio_stream.Initialize(g_settings.audio_backend, SAMPLE_RATE, g_settings.audio_stream_parameters,
g_settings.audio_driver.c_str(), g_settings.audio_output_device.c_str(), &error))
g_settings.audio_driver, g_settings.audio_output_device, &error))
{
Host::AddIconOSDMessage(
OSDMessageType::Error, "SPUAudioStream", ICON_EMOJI_WARNING,
@@ -513,8 +513,8 @@ void SPU::CreateOutputStream()
TRANSLATE_FS("SPU",
"Failed to create or configure audio stream, falling back to null output. The error was:\n{}"),
error.GetDescription()));
s_state.audio_stream.Initialize(AudioBackend::Null, SAMPLE_RATE, g_settings.audio_stream_parameters, nullptr,
nullptr, nullptr);
s_state.audio_stream.Initialize(AudioBackend::Null, SAMPLE_RATE, g_settings.audio_stream_parameters, {}, {},
nullptr);
}
s_state.audio_stream.SetOutputVolume(System::GetAudioOutputVolume());

View File

@@ -26,10 +26,10 @@ AudioSettingsWidget::AudioSettingsWidget(SettingsWindow* dialog, QWidget* parent
SettingWidgetBinder::BindWidgetToEnumSetting(
sif, m_ui.audioBackend, "Audio", "Backend", &AudioStream::ParseBackendName, &AudioStream::GetBackendName,
&AudioStream::GetBackendDisplayName, AudioStream::DEFAULT_BACKEND, AudioBackend::Count);
SettingWidgetBinder::BindWidgetToEnumSetting(
sif, m_ui.stretchMode, "Audio", "StretchMode", &CoreAudioStream::ParseStretchMode,
&CoreAudioStream::GetStretchModeName, &CoreAudioStream::GetStretchModeDisplayName,
AudioStreamParameters::DEFAULT_STRETCH_MODE, AudioStretchMode::Count);
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.stretchMode, "Audio", "StretchMode",
&CoreAudioStream::ParseStretchMode, &CoreAudioStream::GetStretchModeName,
&CoreAudioStream::GetStretchModeDisplayName,
AudioStreamParameters::DEFAULT_STRETCH_MODE, AudioStretchMode::Count);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.bufferMS, "Audio", "BufferMS",
AudioStreamParameters::DEFAULT_BUFFER_MS);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.outputLatencyMS, "Audio", "OutputLatencyMS",
@@ -166,8 +166,7 @@ void AudioSettingsWidget::updateDeviceNames()
const AudioBackend backend = getEffectiveBackend();
const std::string driver_name = m_dialog->getEffectiveStringValue("Audio", "Driver", "");
const std::string current_device = m_dialog->getEffectiveStringValue("Audio", "Device", "");
std::vector<AudioStream::DeviceInfo> devices =
AudioStream::GetOutputDevices(backend, driver_name.c_str(), SPU::SAMPLE_RATE);
std::vector<AudioStream::DeviceInfo> devices = AudioStream::GetOutputDevices(backend, driver_name, SPU::SAMPLE_RATE);
SettingWidgetBinder::DisconnectWidget(m_ui.outputDevice);
m_ui.outputDevice->clear();

View File

@@ -85,7 +85,7 @@ std::vector<std::pair<std::string, std::string>> AudioStream::GetDriverNames(Aud
return ret;
}
std::vector<AudioStream::DeviceInfo> AudioStream::GetOutputDevices(AudioBackend backend, const char* driver,
std::vector<AudioStream::DeviceInfo> AudioStream::GetOutputDevices(AudioBackend backend, std::string_view driver,
u32 sample_rate)
{
std::vector<AudioStream::DeviceInfo> ret;
@@ -106,7 +106,7 @@ std::vector<AudioStream::DeviceInfo> AudioStream::GetOutputDevices(AudioBackend
std::unique_ptr<AudioStream> AudioStream::CreateStream(AudioBackend backend, u32 sample_rate, u32 channels,
u32 output_latency_frames, bool output_latency_minimal,
const char* driver_name, const char* device_name,
std::string_view driver_name, std::string_view device_name,
AudioStreamSource* source, bool auto_start, Error* error)
{
switch (backend)

View File

@@ -63,12 +63,12 @@ public:
static std::vector<std::pair<std::string, std::string>> GetDriverNames(AudioBackend backend);
/// Returns a list of available output devices for the specified backend and driver.
static std::vector<DeviceInfo> GetOutputDevices(AudioBackend backend, const char* driver, u32 sample_rate);
static std::vector<DeviceInfo> GetOutputDevices(AudioBackend backend, std::string_view driver, u32 sample_rate);
/// Creates an audio stream with the specified parameters.
static std::unique_ptr<AudioStream> CreateStream(AudioBackend backend, u32 sample_rate, u32 channels,
u32 output_latency_frames, bool output_latency_minimal,
const char* driver_name, const char* device_name,
std::string_view driver_name, std::string_view device_name,
AudioStreamSource* source, bool auto_start, Error* error);
/// Starts the stream, allowing it to request data.
@@ -83,10 +83,10 @@ protected:
private:
#ifndef __ANDROID__
static std::vector<std::pair<std::string, std::string>> GetCubebDriverNames();
static std::vector<DeviceInfo> GetCubebOutputDevices(const char* driver, u32 sample_rate);
static std::vector<DeviceInfo> GetCubebOutputDevices(std::string_view driver, u32 sample_rate);
static std::unique_ptr<AudioStream> CreateCubebAudioStream(u32 sample_rate, u32 channels, u32 output_latency_frames,
bool output_latency_minimal, const char* driver_name,
const char* device_name, AudioStreamSource* source,
bool output_latency_minimal, std::string_view driver_name,
std::string_view device_name, AudioStreamSource* source,
bool auto_start, Error* error);
static std::unique_ptr<AudioStream> CreateSDLAudioStream(u32 sample_rate, u32 channels, u32 output_latency_frames,
bool output_latency_minimal, AudioStreamSource* source,

View File

@@ -94,7 +94,8 @@ CoreAudioStream::~CoreAudioStream()
}
bool CoreAudioStream::Initialize(AudioBackend backend, u32 sample_rate, const AudioStreamParameters& params,
const char* driver_name, const char* device_name, Error* error /* = nullptr */)
std::string_view driver_name, std::string_view device_name,
Error* error /* = nullptr */)
{
Destroy();

View File

@@ -90,8 +90,8 @@ public:
u32 GetBufferedFramesRelaxed() const;
/// Creation/destruction.
bool Initialize(AudioBackend backend, u32 sample_rate, const AudioStreamParameters& params, const char* driver_name,
const char* device_name, Error* error);
bool Initialize(AudioBackend backend, u32 sample_rate, const AudioStreamParameters& params,
std::string_view driver_name, std::string_view device_name, Error* error);
void Destroy();
/// Temporarily pauses the stream, preventing it from requesting data.

View File

@@ -9,16 +9,26 @@
#include "common/assert.h"
#include "common/error.h"
#include "common/log.h"
#include "common/scoped_guard.h"
#include "common/string_util.h"
#include "cubeb/cubeb.h"
#include "fmt/format.h"
#include <mutex>
#include <string>
LOG_CHANNEL(AudioStream);
namespace {
struct CubebContextHolder
{
std::mutex mutex;
cubeb* context = nullptr;
u32 reference_count = 0;
std::string driver_name;
};
class CubebAudioStream final : public AudioStream
{
public:
@@ -26,14 +36,13 @@ public:
~CubebAudioStream() override;
bool Initialize(u32 sample_rate, u32 channels, u32 output_latency_frames, bool output_latency_minimal,
const char* driver_name, const char* device_name, AudioStreamSource* source, bool auto_start,
Error* error);
std::string_view driver_name, std::string_view device_name, AudioStreamSource* source,
bool auto_start, Error* error);
bool Start(Error* error) override;
bool Stop(Error* error) override;
private:
static void LogCallback(const char* fmt, ...);
static long DataCallback(cubeb_stream* stm, void* user_ptr, const void* input_buffer, void* output_buffer,
long nframes);
static void StateCallback(cubeb_stream* stream, void* user_ptr, cubeb_state state);
@@ -43,6 +52,8 @@ private:
};
} // namespace
static CubebContextHolder s_cubeb_context;
static void FormatCubebError(Error* error, const char* prefix, int rv)
{
const char* str;
@@ -69,6 +80,66 @@ static void FormatCubebError(Error* error, const char* prefix, int rv)
Error::SetStringFmt(error, "{}: {} ({})", prefix, str, rv);
}
static void CubebLogCallback(const char* fmt, ...)
{
if (!Log::IsLogVisible(Log::Level::Dev, Log::Channel::AudioStream))
return;
LargeString str;
std::va_list ap;
va_start(ap, fmt);
str.vsprintf(fmt, ap);
va_end(ap);
DEV_LOG(str);
}
static cubeb* GetCubebContext(std::string_view driver_name, Error* error)
{
std::lock_guard<std::mutex> lock(s_cubeb_context.mutex);
if (s_cubeb_context.context)
{
// Check if the requested driver/device matches the existing context.
if (driver_name != s_cubeb_context.driver_name)
ERROR_LOG("Cubeb context initialized with driver {}, but requested {}", driver_name, s_cubeb_context.driver_name);
s_cubeb_context.reference_count++;
return s_cubeb_context.context;
}
Assert(s_cubeb_context.reference_count == 0);
INFO_LOG("Creating Cubeb context with {} driver...", driver_name.empty() ? std::string_view("default") : driver_name);
cubeb_set_log_callback(CUBEB_LOG_NORMAL, CubebLogCallback);
std::string driver_name_str = std::string(driver_name);
const int rv =
cubeb_init(&s_cubeb_context.context, "DuckStation", driver_name_str.empty() ? nullptr : driver_name_str.c_str());
if (rv != CUBEB_OK)
{
FormatCubebError(error, "Could not initialize cubeb context: ", rv);
return nullptr;
}
s_cubeb_context.driver_name = std::move(driver_name_str);
s_cubeb_context.reference_count = 1;
return s_cubeb_context.context;
}
static void ReleaseCubebContext(cubeb* ctx)
{
std::lock_guard<std::mutex> lock(s_cubeb_context.mutex);
AssertMsg(s_cubeb_context.context == ctx, "Cubeb context mismatch on release.");
Assert(s_cubeb_context.reference_count > 0);
s_cubeb_context.reference_count--;
if (s_cubeb_context.reference_count > 0)
return;
VERBOSE_LOG("Destroying Cubeb context...");
cubeb_destroy(s_cubeb_context.context);
s_cubeb_context.context = nullptr;
s_cubeb_context.driver_name = {};
}
CubebAudioStream::CubebAudioStream() = default;
CubebAudioStream::~CubebAudioStream()
@@ -81,34 +152,16 @@ CubebAudioStream::~CubebAudioStream()
}
if (m_context)
{
cubeb_destroy(m_context);
m_context = nullptr;
}
}
void CubebAudioStream::LogCallback(const char* fmt, ...)
{
LargeString str;
std::va_list ap;
va_start(ap, fmt);
str.vsprintf(fmt, ap);
va_end(ap);
DEV_LOG(str);
ReleaseCubebContext(m_context);
}
bool CubebAudioStream::Initialize(u32 sample_rate, u32 channels, u32 output_latency_frames, bool output_latency_minimal,
const char* driver_name, const char* device_name, AudioStreamSource* source,
std::string_view driver_name, std::string_view device_name, AudioStreamSource* source,
bool auto_start, Error* error)
{
cubeb_set_log_callback(CUBEB_LOG_NORMAL, LogCallback);
int rv = cubeb_init(&m_context, "DuckStation", (driver_name && *driver_name != '\0') ? driver_name : nullptr);
if (rv != CUBEB_OK)
{
FormatCubebError(error, "Could not initialize cubeb context: ", rv);
m_context = GetCubebContext(driver_name, error);
if (!m_context)
return false;
}
cubeb_stream_params params = {};
params.format = CUBEB_SAMPLE_S16LE;
@@ -118,7 +171,7 @@ bool CubebAudioStream::Initialize(u32 sample_rate, u32 channels, u32 output_late
params.prefs = CUBEB_STREAM_PREF_NONE;
u32 min_latency_frames = 0;
rv = cubeb_get_min_latency(m_context, &params, &min_latency_frames);
int rv = cubeb_get_min_latency(m_context, &params, &min_latency_frames);
if (rv == CUBEB_ERROR_NOT_SUPPORTED)
{
DEV_LOG("Cubeb backend does not support latency queries, using latency of {} ms ({} frames).",
@@ -151,7 +204,7 @@ bool CubebAudioStream::Initialize(u32 sample_rate, u32 channels, u32 output_late
cubeb_devid selected_device = nullptr;
cubeb_device_collection devices;
bool devices_valid = false;
if (device_name && *device_name != '\0')
if (!device_name.empty())
{
rv = cubeb_enumerate_devices(m_context, CUBEB_DEVICE_TYPE_OUTPUT, &devices);
devices_valid = (rv == CUBEB_OK);
@@ -160,7 +213,7 @@ bool CubebAudioStream::Initialize(u32 sample_rate, u32 channels, u32 output_late
for (size_t i = 0; i < devices.count; i++)
{
const cubeb_device_info& di = devices.device[i];
if (di.device_id && std::strcmp(device_name, di.device_id) == 0)
if (di.device_id && device_name == di.device_id)
{
INFO_LOG("Using output device '{}' ({}).", di.device_id, di.friendly_name ? di.friendly_name : di.device_id);
selected_device = di.devid;
@@ -185,8 +238,9 @@ bool CubebAudioStream::Initialize(u32 sample_rate, u32 channels, u32 output_late
char stream_name[32];
std::snprintf(stream_name, sizeof(stream_name), "%p", this);
rv = cubeb_stream_init(m_context, &stream, stream_name, nullptr, nullptr, selected_device, &params,
output_latency_frames, &CubebAudioStream::DataCallback, StateCallback, source);
rv =
cubeb_stream_init(m_context, &stream, stream_name, nullptr, nullptr, selected_device, &params,
output_latency_frames, &CubebAudioStream::DataCallback, &CubebAudioStream::StateCallback, source);
if (devices_valid)
cubeb_device_collection_destroy(m_context, &devices);
@@ -246,11 +300,9 @@ bool CubebAudioStream::Stop(Error* error)
return true;
}
std::unique_ptr<AudioStream> AudioStream::CreateCubebAudioStream(u32 sample_rate, u32 channels,
u32 output_latency_frames, bool output_latency_minimal,
const char* driver_name, const char* device_name,
AudioStreamSource* source, bool auto_start,
Error* error)
std::unique_ptr<AudioStream> AudioStream::CreateCubebAudioStream(
u32 sample_rate, u32 channels, u32 output_latency_frames, bool output_latency_minimal, std::string_view driver_name,
std::string_view device_name, AudioStreamSource* source, bool auto_start, Error* error)
{
std::unique_ptr<CubebAudioStream> stream = std::make_unique<CubebAudioStream>();
if (!stream->Initialize(sample_rate, channels, output_latency_frames, output_latency_minimal, driver_name,
@@ -273,7 +325,7 @@ std::vector<std::pair<std::string, std::string>> AudioStream::GetCubebDriverName
return names;
}
std::vector<AudioStream::DeviceInfo> AudioStream::GetCubebOutputDevices(const char* driver, u32 sample_rate)
std::vector<AudioStream::DeviceInfo> AudioStream::GetCubebOutputDevices(std::string_view driver, u32 sample_rate)
{
Error error;
@@ -281,7 +333,8 @@ std::vector<AudioStream::DeviceInfo> AudioStream::GetCubebOutputDevices(const ch
ret.emplace_back(std::string(), TRANSLATE_STR("AudioStream", "Default"), 0);
cubeb* context;
int rv = cubeb_init(&context, "DuckStation", (driver && *driver) ? driver : nullptr);
TinyString driver_str(driver);
int rv = cubeb_init(&context, "DuckStation", driver_str.empty() ? nullptr : driver_str.c_str());
if (rv != CUBEB_OK)
{
FormatCubebError(&error, "cubeb_init() failed: ", rv);
@@ -289,19 +342,16 @@ std::vector<AudioStream::DeviceInfo> AudioStream::GetCubebOutputDevices(const ch
return ret;
}
ScopedGuard context_cleanup([context]() { cubeb_destroy(context); });
cubeb_device_collection devices;
rv = cubeb_enumerate_devices(context, CUBEB_DEVICE_TYPE_OUTPUT, &devices);
if (rv != CUBEB_OK)
{
FormatCubebError(&error, "cubeb_enumerate_devices() failed: ", rv);
ERROR_LOG(error.GetDescription());
cubeb_destroy(context);
return ret;
}
ScopedGuard devices_cleanup([context, &devices]() { cubeb_device_collection_destroy(context, &devices); });
// we need stream parameters to query latency
cubeb_stream_params params = {};
params.format = CUBEB_SAMPLE_S16LE;
@@ -323,5 +373,7 @@ std::vector<AudioStream::DeviceInfo> AudioStream::GetCubebOutputDevices(const ch
ret.emplace_back(di.device_id, di.friendly_name ? di.friendly_name : di.device_id, min_latency);
}
cubeb_device_collection_destroy(context, &devices);
cubeb_destroy(context);
return ret;
}