2024-04-16 06:45:15 +00:00
|
|
|
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
2022-12-04 11:03:45 +00:00
|
|
|
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
|
|
|
|
2023-08-27 06:00:06 +00:00
|
|
|
#include "host.h"
|
|
|
|
#include "imgui_manager.h"
|
|
|
|
|
|
|
|
#include "core/settings.h"
|
|
|
|
|
2020-01-11 03:28:40 +00:00
|
|
|
#include "common/assert.h"
|
2024-04-16 06:45:15 +00:00
|
|
|
#include "common/error.h"
|
2020-01-11 03:28:40 +00:00
|
|
|
#include "common/log.h"
|
2022-12-14 07:58:14 +00:00
|
|
|
#include "common/scoped_guard.h"
|
2022-07-27 14:42:41 +00:00
|
|
|
#include "common/string_util.h"
|
2023-08-27 06:00:06 +00:00
|
|
|
|
2022-07-27 14:42:41 +00:00
|
|
|
#include "cubeb/cubeb.h"
|
2022-12-14 07:58:14 +00:00
|
|
|
#include "fmt/format.h"
|
2020-01-11 03:28:40 +00:00
|
|
|
|
2021-06-28 10:16:48 +00:00
|
|
|
#ifdef _WIN32
|
2020-01-11 04:20:51 +00:00
|
|
|
#include "common/windows_headers.h"
|
|
|
|
#include <objbase.h>
|
|
|
|
#endif
|
|
|
|
|
2023-08-27 06:00:06 +00:00
|
|
|
Log_SetChannel(CubebAudioStream);
|
|
|
|
|
2023-11-18 06:21:51 +00:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
class CubebAudioStream : public AudioStream
|
|
|
|
{
|
|
|
|
public:
|
2024-04-16 06:45:15 +00:00
|
|
|
CubebAudioStream(u32 sample_rate, const AudioStreamParameters& parameters);
|
2023-11-18 06:21:51 +00:00
|
|
|
~CubebAudioStream();
|
|
|
|
|
|
|
|
void SetPaused(bool paused) override;
|
|
|
|
|
2024-04-24 15:13:51 +00:00
|
|
|
bool Initialize(const char* driver_name, const char* device_name, Error* error);
|
2023-11-18 06:21:51 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
void DestroyContextAndStream();
|
|
|
|
|
|
|
|
cubeb* m_context = nullptr;
|
|
|
|
cubeb_stream* stream = nullptr;
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
bool m_com_initialized_by_us = false;
|
|
|
|
#endif
|
|
|
|
};
|
|
|
|
} // namespace
|
2022-07-27 14:42:41 +00:00
|
|
|
|
2024-04-16 06:45:15 +00:00
|
|
|
static TinyString GetCubebErrorString(int rv)
|
|
|
|
{
|
|
|
|
TinyString ret;
|
|
|
|
switch (rv)
|
|
|
|
{
|
|
|
|
// clang-format off
|
|
|
|
#define C(e) case e: ret.assign(#e); break
|
|
|
|
// clang-format on
|
|
|
|
|
|
|
|
C(CUBEB_OK);
|
|
|
|
C(CUBEB_ERROR);
|
|
|
|
C(CUBEB_ERROR_INVALID_FORMAT);
|
|
|
|
C(CUBEB_ERROR_INVALID_PARAMETER);
|
|
|
|
C(CUBEB_ERROR_NOT_SUPPORTED);
|
|
|
|
C(CUBEB_ERROR_DEVICE_UNAVAILABLE);
|
|
|
|
|
|
|
|
default:
|
|
|
|
return "CUBEB_ERROR_UNKNOWN";
|
|
|
|
|
|
|
|
#undef C
|
|
|
|
}
|
|
|
|
|
|
|
|
ret.append_format(" ({})", rv);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
CubebAudioStream::CubebAudioStream(u32 sample_rate, const AudioStreamParameters& parameters)
|
|
|
|
: AudioStream(sample_rate, parameters)
|
2022-07-27 14:42:41 +00:00
|
|
|
{
|
|
|
|
}
|
2020-01-11 03:28:40 +00:00
|
|
|
|
|
|
|
CubebAudioStream::~CubebAudioStream()
|
|
|
|
{
|
2022-07-27 14:42:41 +00:00
|
|
|
DestroyContextAndStream();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CubebAudioStream::LogCallback(const char* fmt, ...)
|
|
|
|
{
|
2023-09-20 14:32:39 +00:00
|
|
|
LargeString str;
|
2022-07-27 14:42:41 +00:00
|
|
|
std::va_list ap;
|
|
|
|
va_start(ap, fmt);
|
2023-12-13 11:06:15 +00:00
|
|
|
str.vsprintf(fmt, ap);
|
2022-07-27 14:42:41 +00:00
|
|
|
va_end(ap);
|
2023-09-20 14:32:39 +00:00
|
|
|
Log_DevPrint(str);
|
2020-01-11 03:28:40 +00:00
|
|
|
}
|
|
|
|
|
2022-07-27 14:42:41 +00:00
|
|
|
void CubebAudioStream::DestroyContextAndStream()
|
2020-01-11 03:28:40 +00:00
|
|
|
{
|
2022-07-27 14:42:41 +00:00
|
|
|
if (stream)
|
|
|
|
{
|
|
|
|
cubeb_stream_stop(stream);
|
|
|
|
cubeb_stream_destroy(stream);
|
|
|
|
stream = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_context)
|
|
|
|
{
|
|
|
|
cubeb_destroy(m_context);
|
|
|
|
m_context = nullptr;
|
|
|
|
}
|
2020-01-11 03:28:40 +00:00
|
|
|
|
2022-07-27 14:42:41 +00:00
|
|
|
#ifdef _WIN32
|
|
|
|
if (m_com_initialized_by_us)
|
|
|
|
{
|
|
|
|
CoUninitialize();
|
|
|
|
m_com_initialized_by_us = false;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2024-04-24 15:13:51 +00:00
|
|
|
bool CubebAudioStream::Initialize(const char* driver_name, const char* device_name, Error* error)
|
2022-07-27 14:42:41 +00:00
|
|
|
{
|
|
|
|
cubeb_set_log_callback(CUBEB_LOG_NORMAL, LogCallback);
|
|
|
|
|
2022-08-05 07:50:28 +00:00
|
|
|
int rv =
|
|
|
|
cubeb_init(&m_context, "DuckStation", g_settings.audio_driver.empty() ? nullptr : g_settings.audio_driver.c_str());
|
2020-01-11 03:28:40 +00:00
|
|
|
if (rv != CUBEB_OK)
|
|
|
|
{
|
2024-04-16 06:45:15 +00:00
|
|
|
Error::SetStringFmt(error, "Could not initialize cubeb context: {}", GetCubebErrorString(rv));
|
2020-01-11 03:28:40 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-04-16 06:45:15 +00:00
|
|
|
static constexpr const std::array<std::pair<cubeb_channel_layout, SampleReader>,
|
|
|
|
static_cast<size_t>(AudioExpansionMode::Count)>
|
|
|
|
channel_setups = {{
|
|
|
|
// Disabled
|
|
|
|
{CUBEB_LAYOUT_STEREO, StereoSampleReaderImpl},
|
|
|
|
// StereoLFE
|
|
|
|
{CUBEB_LAYOUT_STEREO_LFE, &SampleReaderImpl<AudioExpansionMode::StereoLFE, READ_CHANNEL_FRONT_LEFT,
|
|
|
|
READ_CHANNEL_FRONT_RIGHT, READ_CHANNEL_LFE>},
|
|
|
|
// Quadraphonic
|
|
|
|
{CUBEB_LAYOUT_QUAD, &SampleReaderImpl<AudioExpansionMode::Quadraphonic, READ_CHANNEL_FRONT_LEFT,
|
|
|
|
READ_CHANNEL_FRONT_RIGHT, READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT>},
|
|
|
|
// QuadraphonicLFE
|
|
|
|
{CUBEB_LAYOUT_QUAD_LFE,
|
|
|
|
&SampleReaderImpl<AudioExpansionMode::QuadraphonicLFE, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
|
|
|
|
READ_CHANNEL_LFE, READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT>},
|
|
|
|
// Surround51
|
|
|
|
{CUBEB_LAYOUT_3F2_LFE_BACK,
|
|
|
|
&SampleReaderImpl<AudioExpansionMode::Surround51, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
|
|
|
|
READ_CHANNEL_FRONT_CENTER, READ_CHANNEL_LFE, READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT>},
|
|
|
|
// Surround71
|
|
|
|
{CUBEB_LAYOUT_3F4_LFE,
|
|
|
|
&SampleReaderImpl<AudioExpansionMode::Surround71, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
|
|
|
|
READ_CHANNEL_FRONT_CENTER, READ_CHANNEL_LFE, READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT,
|
|
|
|
READ_CHANNEL_SIDE_LEFT, READ_CHANNEL_SIDE_RIGHT>},
|
|
|
|
}};
|
|
|
|
|
2020-01-11 03:28:40 +00:00
|
|
|
cubeb_stream_params params = {};
|
|
|
|
params.format = CUBEB_SAMPLE_S16LE;
|
2022-07-27 14:42:41 +00:00
|
|
|
params.rate = m_sample_rate;
|
2024-04-16 06:45:15 +00:00
|
|
|
params.channels = m_output_channels;
|
|
|
|
params.layout = channel_setups[static_cast<size_t>(m_parameters.expansion_mode)].first;
|
2022-07-27 14:42:41 +00:00
|
|
|
params.prefs = CUBEB_STREAM_PREF_NONE;
|
2020-01-11 03:28:40 +00:00
|
|
|
|
2024-04-16 06:45:15 +00:00
|
|
|
u32 latency_frames = GetBufferSizeForMS(
|
|
|
|
m_sample_rate, (m_parameters.output_latency_ms == 0) ? m_parameters.buffer_ms : m_parameters.output_latency_ms);
|
2022-07-27 14:42:41 +00:00
|
|
|
u32 min_latency_frames = 0;
|
|
|
|
rv = cubeb_get_min_latency(m_context, ¶ms, &min_latency_frames);
|
2020-07-28 16:23:14 +00:00
|
|
|
if (rv == CUBEB_ERROR_NOT_SUPPORTED)
|
2020-01-11 03:28:40 +00:00
|
|
|
{
|
2024-04-16 06:45:15 +00:00
|
|
|
Log_DevFmt("Cubeb backend does not support latency queries, using latency of {} ms ({} frames).",
|
|
|
|
m_parameters.buffer_ms, latency_frames);
|
2020-01-11 03:28:40 +00:00
|
|
|
}
|
2020-07-28 16:23:14 +00:00
|
|
|
else
|
2020-06-06 04:40:20 +00:00
|
|
|
{
|
2020-07-28 16:23:14 +00:00
|
|
|
if (rv != CUBEB_OK)
|
2020-06-06 04:40:20 +00:00
|
|
|
{
|
2024-04-16 06:45:15 +00:00
|
|
|
Error::SetStringFmt(error, "cubeb_get_min_latency() failed: {}", GetCubebErrorString(rv));
|
2022-07-27 14:42:41 +00:00
|
|
|
DestroyContextAndStream();
|
2020-06-06 04:40:20 +00:00
|
|
|
return false;
|
|
|
|
}
|
2020-07-28 16:23:14 +00:00
|
|
|
|
2022-07-27 14:42:41 +00:00
|
|
|
const u32 minimum_latency_ms = GetMSForBufferSize(m_sample_rate, min_latency_frames);
|
2024-04-16 06:45:15 +00:00
|
|
|
Log_DevFmt("Minimum latency: {} ms ({} audio frames)", minimum_latency_ms, min_latency_frames);
|
2024-05-12 07:36:15 +00:00
|
|
|
if (m_parameters.output_latency_minimal)
|
2020-07-28 16:23:14 +00:00
|
|
|
{
|
2022-07-27 14:42:41 +00:00
|
|
|
// use minimum
|
|
|
|
latency_frames = min_latency_frames;
|
2020-07-28 16:23:14 +00:00
|
|
|
}
|
2024-04-16 06:45:15 +00:00
|
|
|
else if (minimum_latency_ms > m_parameters.output_latency_ms)
|
2020-07-28 16:23:14 +00:00
|
|
|
{
|
2024-04-16 06:45:15 +00:00
|
|
|
Log_WarningFmt("Minimum latency is above requested latency: {} vs {}, adjusting to compensate.",
|
|
|
|
min_latency_frames, latency_frames);
|
2022-07-27 14:42:41 +00:00
|
|
|
latency_frames = min_latency_frames;
|
2020-07-28 16:23:14 +00:00
|
|
|
}
|
2020-06-06 04:40:20 +00:00
|
|
|
}
|
2020-01-11 03:28:40 +00:00
|
|
|
|
2022-12-14 07:58:14 +00:00
|
|
|
cubeb_devid selected_device = nullptr;
|
|
|
|
const std::string& selected_device_name = g_settings.audio_output_device;
|
|
|
|
cubeb_device_collection devices;
|
|
|
|
bool devices_valid = false;
|
|
|
|
if (!selected_device_name.empty())
|
|
|
|
{
|
|
|
|
rv = cubeb_enumerate_devices(m_context, CUBEB_DEVICE_TYPE_OUTPUT, &devices);
|
|
|
|
devices_valid = (rv == CUBEB_OK);
|
|
|
|
if (rv == CUBEB_OK)
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < devices.count; i++)
|
|
|
|
{
|
|
|
|
const cubeb_device_info& di = devices.device[i];
|
|
|
|
if (di.device_id && selected_device_name == di.device_id)
|
|
|
|
{
|
2024-04-16 06:45:15 +00:00
|
|
|
Log_InfoFmt("Using output device '{}' ({}).", di.device_id,
|
|
|
|
di.friendly_name ? di.friendly_name : di.device_id);
|
2022-12-14 07:58:14 +00:00
|
|
|
selected_device = di.devid;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!selected_device)
|
|
|
|
{
|
|
|
|
Host::AddOSDMessage(
|
|
|
|
fmt::format("Requested audio output device '{}' not found, using default.", selected_device_name), 10.0f);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2024-04-16 06:45:15 +00:00
|
|
|
Log_WarningFmt("cubeb_enumerate_devices() returned {}, using default device.", GetCubebErrorString(rv));
|
2022-12-14 07:58:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-16 06:45:15 +00:00
|
|
|
BaseInitialize(channel_setups[static_cast<size_t>(m_parameters.expansion_mode)].second);
|
2022-07-27 14:42:41 +00:00
|
|
|
|
2020-01-11 03:28:40 +00:00
|
|
|
char stream_name[32];
|
2022-07-27 14:42:41 +00:00
|
|
|
std::snprintf(stream_name, sizeof(stream_name), "%p", this);
|
2020-01-11 03:28:40 +00:00
|
|
|
|
2022-12-14 07:58:14 +00:00
|
|
|
rv = cubeb_stream_init(m_context, &stream, stream_name, nullptr, nullptr, selected_device, ¶ms, latency_frames,
|
2022-07-27 14:42:41 +00:00
|
|
|
&CubebAudioStream::DataCallback, StateCallback, this);
|
2022-12-14 07:58:14 +00:00
|
|
|
|
|
|
|
if (devices_valid)
|
|
|
|
cubeb_device_collection_destroy(m_context, &devices);
|
|
|
|
|
2020-01-11 03:28:40 +00:00
|
|
|
if (rv != CUBEB_OK)
|
|
|
|
{
|
2024-04-16 06:45:15 +00:00
|
|
|
Error::SetStringFmt(error, "cubeb_stream_init() failed: {}", GetCubebErrorString(rv));
|
2022-07-27 14:42:41 +00:00
|
|
|
DestroyContextAndStream();
|
2020-01-11 03:28:40 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-07-27 14:42:41 +00:00
|
|
|
rv = cubeb_stream_start(stream);
|
2020-01-11 03:28:40 +00:00
|
|
|
if (rv != CUBEB_OK)
|
|
|
|
{
|
2024-04-16 06:45:15 +00:00
|
|
|
Error::SetStringFmt(error, "cubeb_stream_start() failed: {}", GetCubebErrorString(rv));
|
2022-07-27 14:42:41 +00:00
|
|
|
DestroyContextAndStream();
|
|
|
|
return false;
|
2020-01-11 03:28:40 +00:00
|
|
|
}
|
2021-06-13 12:30:39 +00:00
|
|
|
|
2022-07-27 14:42:41 +00:00
|
|
|
return true;
|
2020-01-11 03:28:40 +00:00
|
|
|
}
|
|
|
|
|
2023-11-18 06:21:51 +00:00
|
|
|
void CubebAudioStream::StateCallback(cubeb_stream* stream, void* user_ptr, cubeb_state state)
|
2020-01-11 03:28:40 +00:00
|
|
|
{
|
2022-07-27 14:42:41 +00:00
|
|
|
// noop
|
2020-01-11 03:28:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
long CubebAudioStream::DataCallback(cubeb_stream* stm, void* user_ptr, const void* input_buffer, void* output_buffer,
|
|
|
|
long nframes)
|
|
|
|
{
|
2022-07-27 14:42:41 +00:00
|
|
|
static_cast<CubebAudioStream*>(user_ptr)->ReadFrames(static_cast<s16*>(output_buffer), static_cast<u32>(nframes));
|
2020-01-11 03:28:40 +00:00
|
|
|
return nframes;
|
|
|
|
}
|
|
|
|
|
2022-07-27 14:42:41 +00:00
|
|
|
void CubebAudioStream::SetPaused(bool paused)
|
|
|
|
{
|
|
|
|
if (paused == m_paused || !stream)
|
|
|
|
return;
|
2020-01-11 03:28:40 +00:00
|
|
|
|
2022-07-27 14:42:41 +00:00
|
|
|
const int rv = paused ? cubeb_stream_stop(stream) : cubeb_stream_start(stream);
|
|
|
|
if (rv != CUBEB_OK)
|
|
|
|
{
|
|
|
|
Log_ErrorPrintf("Could not %s stream: %d", paused ? "pause" : "resume", rv);
|
|
|
|
return;
|
|
|
|
}
|
2020-01-11 03:28:40 +00:00
|
|
|
|
2022-07-27 14:42:41 +00:00
|
|
|
m_paused = paused;
|
2020-12-12 01:50:37 +00:00
|
|
|
}
|
|
|
|
|
2024-04-16 06:45:15 +00:00
|
|
|
std::unique_ptr<AudioStream> AudioStream::CreateCubebAudioStream(u32 sample_rate,
|
2024-04-24 15:13:51 +00:00
|
|
|
const AudioStreamParameters& parameters,
|
|
|
|
const char* driver_name, const char* device_name,
|
|
|
|
Error* error)
|
2020-01-11 03:28:40 +00:00
|
|
|
{
|
2024-04-16 06:45:15 +00:00
|
|
|
std::unique_ptr<CubebAudioStream> stream = std::make_unique<CubebAudioStream>(sample_rate, parameters);
|
2024-04-24 15:13:51 +00:00
|
|
|
if (!stream->Initialize(driver_name, device_name, error))
|
2022-07-27 14:42:41 +00:00
|
|
|
stream.reset();
|
|
|
|
return stream;
|
2020-01-11 03:28:40 +00:00
|
|
|
}
|
2022-08-05 07:50:28 +00:00
|
|
|
|
2024-05-05 02:51:31 +00:00
|
|
|
std::vector<std::pair<std::string, std::string>> AudioStream::GetCubebDriverNames()
|
2022-08-05 07:50:28 +00:00
|
|
|
{
|
2024-05-05 02:51:31 +00:00
|
|
|
std::vector<std::pair<std::string, std::string>> names;
|
|
|
|
names.emplace_back(std::string(), TRANSLATE_STR("AudioStream", "Default"));
|
|
|
|
|
2022-08-05 07:50:28 +00:00
|
|
|
const char** cubeb_names = cubeb_get_backend_names();
|
|
|
|
for (u32 i = 0; cubeb_names[i] != nullptr; i++)
|
2024-05-05 02:51:31 +00:00
|
|
|
names.emplace_back(cubeb_names[i], cubeb_names[i]);
|
2022-08-05 07:50:28 +00:00
|
|
|
return names;
|
|
|
|
}
|
2022-12-14 07:58:14 +00:00
|
|
|
|
2024-05-12 07:09:03 +00:00
|
|
|
std::vector<AudioStream::DeviceInfo> AudioStream::GetCubebOutputDevices(const char* driver, u32 sample_rate)
|
2022-12-14 07:58:14 +00:00
|
|
|
{
|
2024-04-24 15:13:51 +00:00
|
|
|
std::vector<AudioStream::DeviceInfo> ret;
|
2024-05-05 02:51:31 +00:00
|
|
|
ret.emplace_back(std::string(), TRANSLATE_STR("AudioStream", "Default"), 0);
|
2022-12-14 07:58:14 +00:00
|
|
|
|
|
|
|
cubeb* context;
|
|
|
|
int rv = cubeb_init(&context, "DuckStation", (driver && *driver) ? driver : nullptr);
|
|
|
|
if (rv != CUBEB_OK)
|
|
|
|
{
|
2024-04-24 15:13:51 +00:00
|
|
|
Log_ErrorFmt("cubeb_init() failed: {}", GetCubebErrorString(rv));
|
2022-12-14 07:58:14 +00:00
|
|
|
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)
|
|
|
|
{
|
2024-04-24 15:13:51 +00:00
|
|
|
Log_ErrorFmt("cubeb_enumerate_devices() failed: {}", GetCubebErrorString(rv));
|
2022-12-14 07:58:14 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
ScopedGuard devices_cleanup([context, &devices]() { cubeb_device_collection_destroy(context, &devices); });
|
|
|
|
|
2024-04-24 15:13:51 +00:00
|
|
|
// we need stream parameters to query latency
|
|
|
|
cubeb_stream_params params = {};
|
|
|
|
params.format = CUBEB_SAMPLE_S16LE;
|
2024-05-12 07:09:03 +00:00
|
|
|
params.rate = sample_rate;
|
2024-04-24 15:13:51 +00:00
|
|
|
params.channels = 2;
|
|
|
|
params.layout = CUBEB_LAYOUT_UNDEFINED;
|
|
|
|
params.prefs = CUBEB_STREAM_PREF_NONE;
|
|
|
|
|
|
|
|
u32 min_latency = 0;
|
|
|
|
cubeb_get_min_latency(context, ¶ms, &min_latency);
|
|
|
|
ret[0].minimum_latency_frames = min_latency;
|
|
|
|
|
2022-12-14 07:58:14 +00:00
|
|
|
for (size_t i = 0; i < devices.count; i++)
|
|
|
|
{
|
|
|
|
const cubeb_device_info& di = devices.device[i];
|
|
|
|
if (!di.device_id)
|
|
|
|
continue;
|
|
|
|
|
2024-04-24 15:13:51 +00:00
|
|
|
ret.emplace_back(di.device_id, di.friendly_name ? di.friendly_name : di.device_id, min_latency);
|
2022-12-14 07:58:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|