mirror of
https://github.com/stenzek/duckstation.git
synced 2026-02-11 17:04:33 +00:00
CubebAudioStream: Use a single shared context
Will be needed shortly.
This commit is contained in:
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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, ¶ms, &min_latency_frames);
|
||||
int rv = cubeb_get_min_latency(m_context, ¶ms, &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, ¶ms,
|
||||
output_latency_frames, &CubebAudioStream::DataCallback, StateCallback, source);
|
||||
rv =
|
||||
cubeb_stream_init(m_context, &stream, stream_name, nullptr, nullptr, selected_device, ¶ms,
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user