dolphin/Source/Core/AudioCommon/CubebUtils.cpp
Weston Heard 1301cec538 WiimoteAudio: Add individual Wiimote audio mixer
Added Platform checks, Incremented state ID

Removed semicolon from PR version comment

Riveting change

Changed Remote Names and References

Updated Wii remote Audio routing to only show
enabled when not using passthrough adapter,
enabling speaker data, and  Cubeb audio backend,
and show enable/disable per Remote when supported
2026-03-20 17:09:01 -04:00

291 lines
7.4 KiB
C++

// Copyright 2017 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "AudioCommon/CubebUtils.h"
#include <cstdarg>
#include <cstring>
#include <string_view>
#include "Common/Logging/Log.h"
#include "Common/Logging/LogManager.h"
#include "Common/StringUtil.h"
#include <cubeb/cubeb.h>
#ifdef _WIN32
#include <Objbase.h>
#endif
static void LogCallback(const char* format, ...)
{
auto* instance = Common::Log::LogManager::GetInstance();
if (instance == nullptr)
return;
constexpr auto log_type = Common::Log::LogType::AUDIO;
constexpr auto log_level = Common::Log::LogLevel::LINFO;
if (!instance->IsEnabled(log_type, log_level))
return;
va_list args;
va_start(args, format);
const char* filename = va_arg(args, const char*);
const auto last_slash = std::string_view(filename).find_last_of("/\\");
if (last_slash != std::string_view::npos)
filename = filename + last_slash + 1;
const int lineno = va_arg(args, int);
const std::string adapted_format(StripWhitespace(format + strlen("%s:%d:")));
const std::string message = StringFromFormatV(adapted_format.c_str(), args);
va_end(args);
instance->LogWithFullPath(log_level, log_type, filename, lineno, message.c_str());
}
static void DestroyContext(cubeb* ctx)
{
cubeb_destroy(ctx);
if (cubeb_set_log_callback(CUBEB_LOG_DISABLED, nullptr) != CUBEB_OK)
{
ERROR_LOG_FMT(AUDIO, "Error removing cubeb log callback");
}
}
namespace CubebUtils
{
std::shared_ptr<cubeb> GetContext()
{
static std::weak_ptr<cubeb> weak;
std::shared_ptr<cubeb> shared = weak.lock();
// Already initialized
if (shared)
return shared;
if (cubeb_set_log_callback(CUBEB_LOG_NORMAL, LogCallback) != CUBEB_OK)
{
ERROR_LOG_FMT(AUDIO, "Error setting cubeb log callback");
}
cubeb* ctx;
if (cubeb_init(&ctx, "Dolphin Emulator", nullptr) != CUBEB_OK)
{
ERROR_LOG_FMT(AUDIO, "Error initializing cubeb library");
return nullptr;
}
INFO_LOG_FMT(AUDIO, "Cubeb initialized using {} backend", cubeb_get_backend_id(ctx));
weak = shared = {ctx, DestroyContext};
return shared;
}
std::vector<std::pair<std::string, std::string>> ListInputDevices()
{
std::vector<std::pair<std::string, std::string>> devices;
cubeb_device_collection collection;
auto cubeb_ctx = GetContext();
const int r = cubeb_enumerate_devices(cubeb_ctx.get(), CUBEB_DEVICE_TYPE_INPUT, &collection);
if (r != CUBEB_OK)
{
ERROR_LOG_FMT(AUDIO, "Error listing cubeb input devices");
return devices;
}
INFO_LOG_FMT(AUDIO, "Listing cubeb input devices:");
for (uint32_t i = 0; i < collection.count; i++)
{
const auto& info = collection.device[i];
const auto device_state = info.state;
const char* state_name = [device_state] {
switch (device_state)
{
case CUBEB_DEVICE_STATE_DISABLED:
return "disabled";
case CUBEB_DEVICE_STATE_UNPLUGGED:
return "unplugged";
case CUBEB_DEVICE_STATE_ENABLED:
return "enabled";
default:
return "unknown?";
}
}();
// According to cubeb_device_info definition in cubeb.h:
// > "Optional vendor name, may be NULL."
// In practice, it seems some other fields might be NULL as well.
static constexpr auto fmt_str = [](const char* ptr) constexpr -> const char* {
return (ptr == nullptr) ? "(null)" : ptr;
};
INFO_LOG_FMT(AUDIO,
"[{}] Device ID: {}\n"
"\tName: {}\n"
"\tGroup ID: {}\n"
"\tVendor: {}\n"
"\tState: {}",
i, fmt_str(info.device_id), fmt_str(info.friendly_name), fmt_str(info.group_id),
fmt_str(info.vendor_name), state_name);
if (info.device_id == nullptr)
continue; // Shouldn't happen
if (info.state == CUBEB_DEVICE_STATE_ENABLED)
{
devices.emplace_back(info.device_id, fmt_str(info.friendly_name));
}
}
cubeb_device_collection_destroy(cubeb_ctx.get(), &collection);
return devices;
}
cubeb_devid GetInputDeviceById(std::string_view id)
{
if (id.empty())
return nullptr;
cubeb_device_collection collection;
auto cubeb_ctx = CubebUtils::GetContext();
const int r = cubeb_enumerate_devices(cubeb_ctx.get(), CUBEB_DEVICE_TYPE_INPUT, &collection);
if (r != CUBEB_OK)
{
ERROR_LOG_FMT(AUDIO, "Error enumerating cubeb input devices");
return nullptr;
}
cubeb_devid device_id = nullptr;
for (uint32_t i = 0; i < collection.count; i++)
{
const auto& info = collection.device[i];
if (id.compare(info.device_id) == 0)
{
device_id = info.devid;
break;
}
}
if (device_id == nullptr)
{
WARN_LOG_FMT(AUDIO, "Failed to find selected input device, defaulting to system preferences");
}
cubeb_device_collection_destroy(cubeb_ctx.get(), &collection);
return device_id;
}
std::vector<std::pair<std::string, std::string>> ListOutputDevices()
{
std::vector<std::pair<std::string, std::string>> devices;
cubeb_device_collection collection;
auto cubeb_ctx = GetContext();
if (!cubeb_ctx)
return devices;
const int r = cubeb_enumerate_devices(cubeb_ctx.get(), CUBEB_DEVICE_TYPE_OUTPUT, &collection);
if (r != CUBEB_OK)
{
ERROR_LOG_FMT(AUDIO, "Error listing cubeb output devices");
return devices;
}
for (uint32_t i = 0; i < collection.count; i++)
{
const auto& info = collection.device[i];
if (info.device_id == nullptr)
continue;
if (info.state == CUBEB_DEVICE_STATE_ENABLED)
{
const char* name = (info.friendly_name != nullptr) ? info.friendly_name : info.device_id;
devices.emplace_back(info.device_id, name);
}
}
cubeb_device_collection_destroy(cubeb_ctx.get(), &collection);
return devices;
}
const void* GetOutputDeviceById(std::string_view id)
{
if (id.empty())
return nullptr;
cubeb_device_collection collection;
auto cubeb_ctx = GetContext();
if (!cubeb_ctx)
return nullptr;
const int r = cubeb_enumerate_devices(cubeb_ctx.get(), CUBEB_DEVICE_TYPE_OUTPUT, &collection);
if (r != CUBEB_OK)
{
ERROR_LOG_FMT(AUDIO, "Error enumerating cubeb output devices");
return nullptr;
}
cubeb_devid device_id = nullptr;
for (uint32_t i = 0; i < collection.count; i++)
{
const auto& info = collection.device[i];
if (info.device_id && id.compare(info.device_id) == 0)
{
device_id = info.devid;
break;
}
}
if (device_id == nullptr)
{
WARN_LOG_FMT(AUDIO, "Failed to find selected output device, defaulting to system preferences");
}
cubeb_device_collection_destroy(cubeb_ctx.get(), &collection);
return device_id;
}
CoInitSyncWorker::CoInitSyncWorker([[maybe_unused]] std::string worker_name)
#ifdef _WIN32
: m_work_queue{std::move(worker_name)}
#endif
{
#ifdef _WIN32
m_work_queue.PushBlocking([this] {
const auto result = ::CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE);
m_coinit_success = result == S_OK;
m_should_couninit = m_coinit_success || result == S_FALSE;
});
#endif
}
CoInitSyncWorker::~CoInitSyncWorker()
{
#ifdef _WIN32
if (m_should_couninit)
{
m_work_queue.PushBlocking([this] {
m_should_couninit = false;
CoUninitialize();
});
}
m_coinit_success = false;
#endif
}
bool CoInitSyncWorker::Execute(FunctionType f)
{
#ifdef _WIN32
if (!m_coinit_success)
return false;
m_work_queue.PushBlocking(f);
#else
f();
#endif
return true;
}
} // namespace CubebUtils