mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-09-09 13:36:35 +00:00
[audio] Nuke cubeb and use SDL3 exclusively
Signed-off-by: lizzie <lizzie@eden-emu.dev>
This commit is contained in:
@@ -58,13 +58,6 @@ class LicensesFragment : Fragment() {
|
||||
R.string.license_fidelityfx_fsr_copyright,
|
||||
R.string.license_fidelityfx_fsr_text
|
||||
),
|
||||
License(
|
||||
R.string.license_cubeb,
|
||||
R.string.license_cubeb_description,
|
||||
R.string.license_cubeb_link,
|
||||
R.string.license_cubeb_copyright,
|
||||
R.string.license_cubeb_text
|
||||
),
|
||||
License(
|
||||
R.string.license_dynarmic,
|
||||
R.string.license_dynarmic_description,
|
||||
|
||||
@@ -466,13 +466,11 @@
|
||||
<string-array name="outputEngineEntries">
|
||||
<item>@string/auto</item>
|
||||
<item>@string/sdl3</item>
|
||||
<item>@string/cubeb</item>
|
||||
<item>@string/string_null</item>
|
||||
</string-array>
|
||||
<integer-array name="outputEngineValues">
|
||||
<item>0</item>
|
||||
<item>2</item>
|
||||
<item>1</item>
|
||||
<item>3</item>
|
||||
</integer-array>
|
||||
|
||||
|
||||
@@ -1243,7 +1243,6 @@
|
||||
|
||||
<!-- Audio output engines -->
|
||||
<string name="sdl3" translatable="false">SDL3</string>
|
||||
<string name="cubeb" translatable="false">cubeb</string>
|
||||
|
||||
<!-- Anisotropic filtering options -->
|
||||
<string name="multiplier_x2" translatable="false">x2</string>
|
||||
@@ -1383,23 +1382,6 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
</string>
|
||||
<string name="license_cubeb" translatable="false">cubeb</string>
|
||||
<string name="license_cubeb_description" translatable="false">Cross platform audio library</string>
|
||||
<string name="license_cubeb_link" translatable="false">https://github.com/mozilla/cubeb</string>
|
||||
<string name="license_cubeb_copyright" translatable="false">Copyright © 2011 Mozilla Foundation</string>
|
||||
<string name="license_cubeb_text" translatable="false">
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.\n\n
|
||||
|
||||
THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
</string>
|
||||
<string name="license_dynarmic" translatable="false">Dynarmic</string>
|
||||
<string name="license_dynarmic_description" translatable="false">An ARM dynamic recompiler</string>
|
||||
|
||||
@@ -229,15 +229,6 @@ endif()
|
||||
target_include_directories(audio_core PRIVATE ${OPUS_INCLUDE_DIRS})
|
||||
target_link_libraries(audio_core PUBLIC common core Opus::opus)
|
||||
|
||||
if (ENABLE_CUBEB)
|
||||
target_sources(audio_core PRIVATE
|
||||
sink/cubeb_sink.cpp
|
||||
sink/cubeb_sink.h)
|
||||
|
||||
target_link_libraries(audio_core PRIVATE cubeb::cubeb)
|
||||
target_compile_definitions(audio_core PRIVATE HAVE_CUBEB=1)
|
||||
endif()
|
||||
|
||||
target_sources(audio_core PRIVATE
|
||||
sink/sdl3_sink.cpp
|
||||
sink/sdl3_sink.h)
|
||||
|
||||
@@ -1,450 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/sink/cubeb_sink.h"
|
||||
#include "audio_core/sink/sink_stream.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "core/core.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <objbase.h>
|
||||
#undef CreateEvent
|
||||
#endif
|
||||
|
||||
namespace AudioCore::Sink {
|
||||
/**
|
||||
* Cubeb sink stream, responsible for sinking samples to hardware.
|
||||
*/
|
||||
class CubebSinkStream final : public SinkStream {
|
||||
public:
|
||||
/**
|
||||
* Create a new sink stream.
|
||||
*
|
||||
* @param ctx_ - Cubeb context to create this stream with.
|
||||
* @param device_channels_ - Number of channels supported by the hardware.
|
||||
* @param system_channels_ - Number of channels the audio systems expect.
|
||||
* @param output_device - Cubeb output device id.
|
||||
* @param input_device - Cubeb input device id.
|
||||
* @param name_ - Name of this stream.
|
||||
* @param type_ - Type of this stream.
|
||||
* @param system_ - Core system.
|
||||
* @param event - Event used only for audio renderer, signalled on buffer consume.
|
||||
*/
|
||||
CubebSinkStream(cubeb* ctx_, u32 device_channels_, u32 system_channels_,
|
||||
cubeb_devid output_device, cubeb_devid input_device, const std::string& name_,
|
||||
StreamType type_, Core::System& system_)
|
||||
: SinkStream(system_, type_), ctx{ctx_} {
|
||||
#ifdef _WIN32
|
||||
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
#endif
|
||||
name = name_;
|
||||
device_channels = device_channels_;
|
||||
system_channels = system_channels_;
|
||||
|
||||
cubeb_stream_params params{};
|
||||
params.rate = TargetSampleRate;
|
||||
params.channels = device_channels;
|
||||
params.format = CUBEB_SAMPLE_S16LE;
|
||||
params.prefs = CUBEB_STREAM_PREF_NONE;
|
||||
switch (params.channels) {
|
||||
case 1:
|
||||
params.layout = CUBEB_LAYOUT_MONO;
|
||||
break;
|
||||
case 2:
|
||||
params.layout = CUBEB_LAYOUT_STEREO;
|
||||
break;
|
||||
case 6:
|
||||
params.layout = CUBEB_LAYOUT_3F2_LFE;
|
||||
break;
|
||||
}
|
||||
|
||||
u32 minimum_latency{0};
|
||||
const auto latency_error = cubeb_get_min_latency(ctx, ¶ms, &minimum_latency);
|
||||
if (latency_error != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "Error getting minimum latency, error: {}", latency_error);
|
||||
minimum_latency = TargetSampleCount * 2;
|
||||
}
|
||||
|
||||
minimum_latency = (std::max)(minimum_latency, TargetSampleCount * 2);
|
||||
|
||||
LOG_INFO(Service_Audio,
|
||||
"Opening cubeb stream {} type {} with: rate {} channels {} (system channels {}) "
|
||||
"latency {}",
|
||||
name, type, params.rate, params.channels, system_channels, minimum_latency);
|
||||
|
||||
auto init_error{0};
|
||||
if (type == StreamType::In) {
|
||||
init_error = cubeb_stream_init(ctx, &stream_backend, name.c_str(), input_device,
|
||||
¶ms, output_device, nullptr, minimum_latency,
|
||||
&CubebSinkStream::DataCallback,
|
||||
&CubebSinkStream::StateCallback, this);
|
||||
} else {
|
||||
init_error = cubeb_stream_init(ctx, &stream_backend, name.c_str(), input_device,
|
||||
nullptr, output_device, ¶ms, minimum_latency,
|
||||
&CubebSinkStream::DataCallback,
|
||||
&CubebSinkStream::StateCallback, this);
|
||||
}
|
||||
|
||||
if (init_error != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "Error initializing cubeb stream, error: {}", init_error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the sink stream.
|
||||
*/
|
||||
~CubebSinkStream() override {
|
||||
LOG_DEBUG(Service_Audio, "Destructing cubeb stream {}", name);
|
||||
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
Finalize();
|
||||
|
||||
#ifdef _WIN32
|
||||
CoUninitialize();
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalize the sink stream.
|
||||
*/
|
||||
void Finalize() override {
|
||||
Stop();
|
||||
cubeb_stream_destroy(stream_backend);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (!ctx || !paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
paused = false;
|
||||
if (cubeb_stream_start(stream_backend) != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the sink stream.
|
||||
*/
|
||||
void Stop() override {
|
||||
if (!ctx || paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
SignalPause();
|
||||
if (cubeb_stream_stop(stream_backend) != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream");
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* Main callback from Cubeb. Either expects samples from us (audio render/audio out), or will
|
||||
* provide samples to be copied (audio in).
|
||||
*
|
||||
* @param stream - Cubeb-specific data about the stream.
|
||||
* @param user_data - Custom data pointer passed along, points to a CubebSinkStream.
|
||||
* @param in_buff - Input buffer to be used if the stream is an input type.
|
||||
* @param out_buff - Output buffer to be used if the stream is an output type.
|
||||
* @param num_frames_ - Number of frames of audio in the buffers. Note: Not number of samples.
|
||||
*/
|
||||
static long DataCallback([[maybe_unused]] cubeb_stream* stream, void* user_data,
|
||||
[[maybe_unused]] const void* in_buff, void* out_buff,
|
||||
long num_frames_) {
|
||||
auto* impl = static_cast<CubebSinkStream*>(user_data);
|
||||
if (!impl) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const std::size_t num_channels = impl->GetDeviceChannels();
|
||||
const std::size_t frame_size = num_channels;
|
||||
const std::size_t num_frames{static_cast<size_t>(num_frames_)};
|
||||
|
||||
if (impl->type == StreamType::In) {
|
||||
std::span<const s16> input_buffer{reinterpret_cast<const s16*>(in_buff),
|
||||
num_frames * frame_size};
|
||||
impl->ProcessAudioIn(input_buffer, num_frames);
|
||||
} else {
|
||||
std::span<s16> output_buffer{reinterpret_cast<s16*>(out_buff), num_frames * frame_size};
|
||||
impl->ProcessAudioOutAndRender(output_buffer, num_frames);
|
||||
}
|
||||
|
||||
return num_frames_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cubeb callback for if a device state changes. Unused currently.
|
||||
*
|
||||
* @param stream - Cubeb-specific data about the stream.
|
||||
* @param user_data - Custom data pointer passed along, points to a CubebSinkStream.
|
||||
* @param state - New state of the device.
|
||||
*/
|
||||
static void StateCallback(cubeb_stream*, void*, cubeb_state) {}
|
||||
|
||||
/// Main Cubeb context
|
||||
cubeb* ctx{};
|
||||
/// Cubeb stream backend
|
||||
cubeb_stream* stream_backend{};
|
||||
};
|
||||
|
||||
CubebSink::CubebSink(std::string_view target_device_name) {
|
||||
// Cubeb requires COM to be initialized on the thread calling cubeb_init on Windows
|
||||
#ifdef _WIN32
|
||||
com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
#endif
|
||||
|
||||
if (cubeb_init(&ctx, "Eden", nullptr) != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (target_device_name != auto_device_name && !target_device_name.empty()) {
|
||||
cubeb_device_collection collection;
|
||||
if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) {
|
||||
LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported");
|
||||
} else {
|
||||
const auto collection_end{collection.device + collection.count};
|
||||
const auto device{
|
||||
std::find_if(collection.device, collection_end, [&](const cubeb_device_info& info) {
|
||||
return info.friendly_name != nullptr &&
|
||||
target_device_name == std::string(info.friendly_name);
|
||||
})};
|
||||
if (device != collection_end) {
|
||||
output_device = device->devid;
|
||||
}
|
||||
cubeb_device_collection_destroy(ctx, &collection);
|
||||
}
|
||||
}
|
||||
|
||||
cubeb_get_max_channel_count(ctx, &device_channels);
|
||||
device_channels = device_channels >= 6U ? 6U : 2U;
|
||||
}
|
||||
|
||||
CubebSink::~CubebSink() {
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& sink_stream : sink_streams) {
|
||||
sink_stream.reset();
|
||||
}
|
||||
|
||||
cubeb_destroy(ctx);
|
||||
|
||||
#ifdef _WIN32
|
||||
if (SUCCEEDED(com_init_result)) {
|
||||
CoUninitialize();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
SinkStream* CubebSink::AcquireSinkStream(Core::System& system, u32 system_channels_,
|
||||
const std::string& name, StreamType type) {
|
||||
system_channels = system_channels_;
|
||||
SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<CubebSinkStream>(
|
||||
ctx, device_channels, system_channels, output_device, input_device, name, type, system));
|
||||
|
||||
return stream.get();
|
||||
}
|
||||
|
||||
void CubebSink::CloseStream(SinkStream* stream) {
|
||||
for (size_t i = 0; i < sink_streams.size(); i++) {
|
||||
if (sink_streams[i].get() == stream) {
|
||||
sink_streams[i].reset();
|
||||
sink_streams.erase(sink_streams.begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CubebSink::CloseStreams() {
|
||||
sink_streams.clear();
|
||||
}
|
||||
|
||||
f32 CubebSink::GetDeviceVolume() const {
|
||||
if (sink_streams.empty()) {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
return sink_streams[0]->GetDeviceVolume();
|
||||
}
|
||||
|
||||
void CubebSink::SetDeviceVolume(f32 volume) {
|
||||
for (auto& stream : sink_streams) {
|
||||
stream->SetDeviceVolume(volume);
|
||||
}
|
||||
}
|
||||
|
||||
void CubebSink::SetSystemVolume(f32 volume) {
|
||||
for (auto& stream : sink_streams) {
|
||||
stream->SetSystemVolume(volume);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> ListCubebSinkDevices(bool capture) {
|
||||
std::vector<std::string> device_list;
|
||||
cubeb* ctx;
|
||||
|
||||
#ifdef _WIN32
|
||||
auto com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
#endif
|
||||
|
||||
if (cubeb_init(&ctx, "Eden Device Enumerator", nullptr) != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
|
||||
return {};
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
if (SUCCEEDED(com_init_result)) {
|
||||
CoUninitialize();
|
||||
}
|
||||
#endif
|
||||
|
||||
auto type{capture ? CUBEB_DEVICE_TYPE_INPUT : CUBEB_DEVICE_TYPE_OUTPUT};
|
||||
cubeb_device_collection collection;
|
||||
if (cubeb_enumerate_devices(ctx, type, &collection) != CUBEB_OK) {
|
||||
LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported");
|
||||
} else {
|
||||
for (std::size_t i = 0; i < collection.count; i++) {
|
||||
const cubeb_device_info& device = collection.device[i];
|
||||
if (device.friendly_name && device.friendly_name[0] != '\0' &&
|
||||
device.state == CUBEB_DEVICE_STATE_ENABLED) {
|
||||
device_list.emplace_back(device.friendly_name);
|
||||
}
|
||||
}
|
||||
cubeb_device_collection_destroy(ctx, &collection);
|
||||
}
|
||||
|
||||
cubeb_destroy(ctx);
|
||||
return device_list;
|
||||
}
|
||||
|
||||
/* REVERSION TO 3833 - function GetCubebLatency REINTRODUCED FROM 3833 - DIABLO 3 FIX */
|
||||
u32 GetCubebLatency() {
|
||||
cubeb* ctx;
|
||||
|
||||
#ifdef _WIN32
|
||||
auto com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
#endif
|
||||
|
||||
// Init cubeb
|
||||
if (cubeb_init(&ctx, "yuzu Latency Getter", nullptr) != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
|
||||
// Return a large latency so we choose SDL instead.
|
||||
return 10000u;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
if (SUCCEEDED(com_init_result)) {
|
||||
CoUninitialize();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Get min latency
|
||||
cubeb_stream_params params{};
|
||||
params.rate = TargetSampleRate;
|
||||
params.channels = 2;
|
||||
params.format = CUBEB_SAMPLE_S16LE;
|
||||
params.prefs = CUBEB_STREAM_PREF_NONE;
|
||||
params.layout = CUBEB_LAYOUT_STEREO;
|
||||
|
||||
u32 latency{0};
|
||||
const auto latency_error = cubeb_get_min_latency(ctx, ¶ms, &latency);
|
||||
if (latency_error != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "Error getting minimum latency, error: {}", latency_error);
|
||||
latency = TargetSampleCount * 2;
|
||||
}
|
||||
latency = (std::max)(latency, TargetSampleCount * 2);
|
||||
cubeb_destroy(ctx);
|
||||
return latency;
|
||||
}
|
||||
|
||||
// REVERTED back to 3833 - Below namespace section and function IsCubebSuitable() removed, reverting to GetCubebLatency() above. - DIABLO 3 FIX
|
||||
/*
|
||||
namespace {
|
||||
static long TmpDataCallback(cubeb_stream*, void*, const void*, void*, long) {
|
||||
return TargetSampleCount;
|
||||
}
|
||||
static void TmpStateCallback(cubeb_stream*, void*, cubeb_state) {}
|
||||
} // namespace
|
||||
|
||||
bool IsCubebSuitable() {
|
||||
#if !defined(HAVE_CUBEB)
|
||||
return false;
|
||||
#else
|
||||
cubeb* ctx{nullptr};
|
||||
|
||||
#ifdef _WIN32
|
||||
auto com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
#endif
|
||||
|
||||
// Init cubeb
|
||||
if (cubeb_init(&ctx, "Eden Latency Getter", nullptr) != CUBEB_OK) {
|
||||
LOG_ERROR(Audio_Sink, "Cubeb failed to init, it is not suitable.");
|
||||
return false;
|
||||
}
|
||||
|
||||
SCOPE_EXIT {
|
||||
cubeb_destroy(ctx);
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
if (SUCCEEDED(com_init_result)) {
|
||||
CoUninitialize();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Get min latency
|
||||
cubeb_stream_params params{};
|
||||
params.rate = TargetSampleRate;
|
||||
params.channels = 2;
|
||||
params.format = CUBEB_SAMPLE_S16LE;
|
||||
params.prefs = CUBEB_STREAM_PREF_NONE;
|
||||
params.layout = CUBEB_LAYOUT_STEREO;
|
||||
|
||||
u32 latency{0};
|
||||
const auto latency_error = cubeb_get_min_latency(ctx, ¶ms, &latency);
|
||||
if (latency_error != CUBEB_OK) {
|
||||
LOG_ERROR(Audio_Sink, "Cubeb could not get min latency, it is not suitable.");
|
||||
return false;
|
||||
}
|
||||
latency = (std::max)(latency, TargetSampleCount * 2);
|
||||
|
||||
// Test opening a device with standard parameters
|
||||
cubeb_devid output_device{0};
|
||||
cubeb_devid input_device{0};
|
||||
std::string name{"Eden test"};
|
||||
cubeb_stream* stream{nullptr};
|
||||
|
||||
if (cubeb_stream_init(ctx, &stream, name.c_str(), input_device, nullptr, output_device, ¶ms,
|
||||
latency, &TmpDataCallback, &TmpStateCallback, nullptr) != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "Cubeb could not open a device, it is not suitable.");
|
||||
return false;
|
||||
}
|
||||
|
||||
cubeb_stream_stop(stream);
|
||||
cubeb_stream_destroy(stream);
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
*/
|
||||
|
||||
} // namespace AudioCore::Sink
|
||||
@@ -1,118 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <cubeb/cubeb.h>
|
||||
|
||||
#include "audio_core/sink/sink.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace AudioCore::Sink {
|
||||
class SinkStream;
|
||||
|
||||
/**
|
||||
* Cubeb backend sink, holds multiple output streams and is responsible for sinking samples to
|
||||
* hardware. Used by Audio Render, Audio In and Audio Out.
|
||||
*/
|
||||
class CubebSink final : public Sink {
|
||||
public:
|
||||
explicit CubebSink(std::string_view device_id);
|
||||
~CubebSink() override;
|
||||
|
||||
/**
|
||||
* Create a new sink stream.
|
||||
*
|
||||
* @param system - Core system.
|
||||
* @param system_channels - Number of channels the audio system expects.
|
||||
* May differ from the device's channel count.
|
||||
* @param name - Name of this stream.
|
||||
* @param type - Type of this stream, render/in/out.
|
||||
*
|
||||
* @return A pointer to the created SinkStream
|
||||
*/
|
||||
SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels,
|
||||
const std::string& name, StreamType type) override;
|
||||
|
||||
/**
|
||||
* Close a given stream.
|
||||
*
|
||||
* @param stream - The stream to close.
|
||||
*/
|
||||
void CloseStream(SinkStream* stream) override;
|
||||
|
||||
/**
|
||||
* Close all streams.
|
||||
*/
|
||||
void CloseStreams() override;
|
||||
|
||||
/**
|
||||
* Get the device volume. Set from calls to the IAudioDevice service.
|
||||
*
|
||||
* @return Volume of the device.
|
||||
*/
|
||||
f32 GetDeviceVolume() const override;
|
||||
|
||||
/**
|
||||
* Set the device volume. Set from calls to the IAudioDevice service.
|
||||
*
|
||||
* @param volume - New volume of the device.
|
||||
*/
|
||||
void SetDeviceVolume(f32 volume) override;
|
||||
|
||||
/**
|
||||
* Set the system volume. Comes from the audio system using this stream.
|
||||
*
|
||||
* @param volume - New volume of the system.
|
||||
*/
|
||||
void SetSystemVolume(f32 volume) override;
|
||||
|
||||
private:
|
||||
/// Backend Cubeb context
|
||||
cubeb* ctx{};
|
||||
/// Cubeb id of the actual hardware output device
|
||||
cubeb_devid output_device{};
|
||||
/// Cubeb id of the actual hardware input device
|
||||
cubeb_devid input_device{};
|
||||
/// Vector of streams managed by this sink
|
||||
std::vector<SinkStreamPtr> sink_streams{};
|
||||
|
||||
#ifdef _WIN32
|
||||
/// Cubeb required COM to be initialized multi-threaded on Windows
|
||||
u32 com_init_result = 0;
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a list of connected devices from Cubeb.
|
||||
*
|
||||
* @param capture - Return input (capture) devices if true, otherwise output devices.
|
||||
*/
|
||||
std::vector<std::string> ListCubebSinkDevices(bool capture);
|
||||
|
||||
// REVERSION - function GetCubebLatency() reintroduced from EA-3833 - DIABLO 3 FIX
|
||||
/**
|
||||
* Get the reported latency for this sink.
|
||||
*
|
||||
* @return Minimum latency for this sink.
|
||||
*/
|
||||
u32 GetCubebLatency();
|
||||
|
||||
/**
|
||||
* Check if this backend is suitable for use.
|
||||
* Checks if enabled, its latency, whether it opens successfully, etc.
|
||||
*
|
||||
* @return True is this backend is suitable, false otherwise.
|
||||
*/
|
||||
// bool IsCubebSuitable(); // REVERTED BACK TO GetCubebLatency() FROM 3833
|
||||
|
||||
} // namespace AudioCore::Sink
|
||||
@@ -25,9 +25,10 @@ namespace {
|
||||
// See https://github.com/PCSX2/pcsx2/pull/12312
|
||||
// "SDL and cubeb backends previously resulted in different names for the output which
|
||||
// caused them be identified as different applications by the OS."
|
||||
//
|
||||
// Keep in sync with cubeb_sink.cpp name.
|
||||
SDL_SetHint("SDL_AUDIO_DEVICE_APP_NAME", "yuzu Latency Getter");
|
||||
SDL_SetHint("SDL_HINT_AUDIO_DEVICE_STREAM_ROLE", "Game");
|
||||
// We do our own processing, so just have SDL copy our audio
|
||||
SDL_SetHint("SDL_HINT_AUDIO_DEVICE_RAW_STREAM", "1");
|
||||
if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) {
|
||||
LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError());
|
||||
return false;
|
||||
@@ -82,23 +83,18 @@ public:
|
||||
|
||||
SDL_AudioSpec spec{};
|
||||
spec.freq = TargetSampleRate;
|
||||
spec.channels = static_cast<u8>(device_channels);
|
||||
spec.format = SDL_AUDIO_S16;
|
||||
spec.channels = u8(device_channels);
|
||||
spec.format = SDL_AUDIO_S16LE;
|
||||
|
||||
std::string device_name{output_device};
|
||||
bool capture{false};
|
||||
if (type == StreamType::In) {
|
||||
device_name = input_device;
|
||||
capture = true;
|
||||
}
|
||||
auto const is_capture = (type == StreamType::In);
|
||||
std::string device_name = is_capture ? input_device : output_device;
|
||||
const SDL_AudioDeviceID audio_device = device_name.empty()
|
||||
? (is_capture
|
||||
? SDL_AUDIO_DEVICE_DEFAULT_RECORDING
|
||||
: SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK)
|
||||
: FindAudioDeviceByName(device_name, is_capture);
|
||||
|
||||
const SDL_AudioDeviceID audio_device =
|
||||
device_name.empty() ? (capture ? SDL_AUDIO_DEVICE_DEFAULT_RECORDING
|
||||
: SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK)
|
||||
: FindAudioDeviceByName(device_name, capture);
|
||||
|
||||
stream = SDL_OpenAudioDeviceStream(audio_device, &spec, &SDLSinkStream::DataCallback,
|
||||
this);
|
||||
stream = SDL_OpenAudioDeviceStream(audio_device, &spec, &SDLSinkStream::DataCallback, this);
|
||||
|
||||
if (stream == nullptr) {
|
||||
LOG_CRITICAL(Audio_Sink, "Error opening SDL audio device: {}", SDL_GetError());
|
||||
@@ -107,13 +103,12 @@ public:
|
||||
|
||||
SDL_AudioSpec stream_in{};
|
||||
SDL_AudioSpec stream_out{};
|
||||
static_cast<void>(SDL_GetAudioStreamFormat(stream, &stream_in, &stream_out));
|
||||
|
||||
void(SDL_GetAudioStreamFormat(stream, &stream_in, &stream_out));
|
||||
LOG_INFO(Service_Audio,
|
||||
"Opening SDL stream {} with: rate {} channels {} (system channels {}) "
|
||||
" format {}",
|
||||
static_cast<const void*>(stream), stream_out.freq, stream_out.channels,
|
||||
system_channels, static_cast<int>(stream_out.format));
|
||||
system_channels, int(stream_out.format));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,14 +123,12 @@ public:
|
||||
* Finalize the sink stream.
|
||||
*/
|
||||
void Finalize() override {
|
||||
if (stream == nullptr) {
|
||||
return;
|
||||
if (stream != nullptr) {
|
||||
Stop();
|
||||
SDL_ClearAudioStream(stream);
|
||||
SDL_DestroyAudioStream(stream);
|
||||
stream = nullptr;
|
||||
}
|
||||
|
||||
Stop();
|
||||
SDL_ClearAudioStream(stream);
|
||||
SDL_DestroyAudioStream(stream);
|
||||
stream = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -148,9 +141,8 @@ public:
|
||||
if (stream == nullptr || !paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
paused = false;
|
||||
static_cast<void>(SDL_ResumeAudioStreamDevice(stream));
|
||||
void(SDL_ResumeAudioStreamDevice(stream));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,7 +153,7 @@ public:
|
||||
return;
|
||||
}
|
||||
SignalPause();
|
||||
static_cast<void>(SDL_PauseAudioStreamDevice(stream));
|
||||
void(SDL_PauseAudioStreamDevice(stream));
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -173,51 +165,34 @@ private:
|
||||
* @param stream - Buffer of samples to be filled or read.
|
||||
* @param len - Length of the stream in bytes.
|
||||
*/
|
||||
static void DataCallback(void* userdata, SDL_AudioStream* stream, int additional_amount,
|
||||
int total_amount) {
|
||||
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;
|
||||
|
||||
if (impl->type == StreamType::In) {
|
||||
const int bytes_available = SDL_GetAudioStreamAvailable(stream);
|
||||
if (bytes_available <= 0) {
|
||||
return;
|
||||
static void DataCallback(void* userdata, SDL_AudioStream* stream, int additional_amount, int total_amount) {
|
||||
if (auto* impl = static_cast<SDLSinkStream*>(userdata); impl) {
|
||||
auto const num_channels = impl->GetDeviceChannels();
|
||||
auto const frame_size = num_channels;
|
||||
if (impl->type == StreamType::In) {
|
||||
auto const bytes_available = SDL_GetAudioStreamAvailable(stream);
|
||||
if (bytes_available > 0) {
|
||||
std::vector<s16> input(bytes_available / int(sizeof(s16)));
|
||||
auto const bytes_read = SDL_GetAudioStreamData(stream, input.data(), bytes_available);
|
||||
if (bytes_read > 0) {
|
||||
auto const num_frames = std::size_t(bytes_read) / sizeof(s16) / frame_size;
|
||||
std::span<const s16> input_buffer{input.data(), std::size_t(bytes_read) / sizeof(s16)};
|
||||
impl->ProcessAudioIn(input_buffer, num_frames);
|
||||
}
|
||||
}
|
||||
} else if (additional_amount > 0 || total_amount > 0) {
|
||||
auto const bytes_requested = additional_amount > 0 ? additional_amount : total_amount;
|
||||
std::vector<s16> output(bytes_requested / int(sizeof(s16)));
|
||||
auto const num_frames = std::size_t(bytes_requested) / sizeof(s16) / frame_size;
|
||||
std::span<s16> output_buffer{output.data(), output.size()};
|
||||
impl->ProcessAudioOutAndRender(output_buffer, num_frames);
|
||||
void(SDL_PutAudioStreamData(stream, output.data(), bytes_requested));
|
||||
}
|
||||
|
||||
std::vector<s16> input(bytes_available / static_cast<int>(sizeof(s16)));
|
||||
const int bytes_read = SDL_GetAudioStreamData(stream, input.data(), bytes_available);
|
||||
if (bytes_read <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::size_t num_frames =
|
||||
static_cast<std::size_t>(bytes_read) / sizeof(s16) / frame_size;
|
||||
std::span<const s16> input_buffer{input.data(),
|
||||
static_cast<std::size_t>(bytes_read) / sizeof(s16)};
|
||||
impl->ProcessAudioIn(input_buffer, num_frames);
|
||||
} else {
|
||||
if (additional_amount <= 0 && total_amount <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int bytes_requested = additional_amount > 0 ? additional_amount : total_amount;
|
||||
std::vector<s16> output(bytes_requested / static_cast<int>(sizeof(s16)));
|
||||
const std::size_t num_frames =
|
||||
static_cast<std::size_t>(bytes_requested) / sizeof(s16) / frame_size;
|
||||
std::span<s16> output_buffer{output.data(), output.size()};
|
||||
impl->ProcessAudioOutAndRender(output_buffer, num_frames);
|
||||
static_cast<void>(SDL_PutAudioStreamData(stream, output.data(), bytes_requested));
|
||||
}
|
||||
}
|
||||
|
||||
/// SDL stream attached to an opened input/output device
|
||||
SDL_AudioStream* stream{};
|
||||
SDL_AudioStream* stream = nullptr;
|
||||
};
|
||||
|
||||
SDLSink::SDLSink(std::string_view target_device_name) {
|
||||
@@ -233,11 +208,9 @@ SDLSink::SDLSink(std::string_view target_device_name) {
|
||||
|
||||
SDLSink::~SDLSink() = default;
|
||||
|
||||
SinkStream* SDLSink::AcquireSinkStream(Core::System& system, u32 system_channels_,
|
||||
const std::string&, StreamType type) {
|
||||
SinkStream* SDLSink::AcquireSinkStream(Core::System& system, u32 system_channels_, const std::string&, StreamType type) {
|
||||
system_channels = system_channels_;
|
||||
SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>(
|
||||
device_channels, system_channels, output_device, input_device, type, system));
|
||||
SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>(device_channels, system_channels, output_device, input_device, type, system));
|
||||
return stream.get();
|
||||
}
|
||||
|
||||
@@ -256,11 +229,7 @@ void SDLSink::CloseStreams() {
|
||||
}
|
||||
|
||||
f32 SDLSink::GetDeviceVolume() const {
|
||||
if (sink_streams.empty()) {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
return sink_streams[0]->GetDeviceVolume();
|
||||
return sink_streams.empty() ? 1.0f : sink_streams[0]->GetDeviceVolume();
|
||||
}
|
||||
|
||||
void SDLSink::SetDeviceVolume(f32 volume) {
|
||||
@@ -275,26 +244,20 @@ void SDLSink::SetSystemVolume(f32 volume) {
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> ListSDLSinkDevices(bool capture) {
|
||||
if (!InitializeAudio())
|
||||
return {}; //no devices
|
||||
|
||||
std::vector<std::string> device_list;
|
||||
int device_count = 0;
|
||||
SDL_AudioDeviceID* devices =
|
||||
capture ? SDL_GetAudioRecordingDevices(&device_count)
|
||||
: SDL_GetAudioPlaybackDevices(&device_count);
|
||||
if (devices == nullptr) {
|
||||
return device_list;
|
||||
}
|
||||
|
||||
for (int i = 0; i < device_count; ++i) {
|
||||
if (const char* name = SDL_GetAudioDeviceName(devices[i])) {
|
||||
device_list.emplace_back(name);
|
||||
std::vector<std::string> ListSDLSinkDevices(bool is_capture) {
|
||||
std::vector<std::string> device_list{};
|
||||
if (InitializeAudio()) {
|
||||
int device_count = 0;
|
||||
SDL_AudioDeviceID* devices = is_capture ? SDL_GetAudioRecordingDevices(&device_count) : SDL_GetAudioPlaybackDevices(&device_count);
|
||||
if (devices != nullptr) {
|
||||
for (int i = 0; i < device_count; ++i) {
|
||||
if (const char* name = SDL_GetAudioDeviceName(devices[i]); name) {
|
||||
device_list.emplace_back(name);
|
||||
}
|
||||
}
|
||||
SDL_free(devices);
|
||||
}
|
||||
}
|
||||
SDL_free(devices);
|
||||
|
||||
return device_list;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,9 +10,6 @@
|
||||
#include <vector>
|
||||
|
||||
#include "audio_core/sink/sink_details.h"
|
||||
#ifdef HAVE_CUBEB
|
||||
#include "audio_core/sink/cubeb_sink.h"
|
||||
#endif
|
||||
#ifdef HAVE_SDL3
|
||||
#include "audio_core/sink/sdl3_sink.h"
|
||||
#endif
|
||||
@@ -41,17 +38,7 @@ struct SinkDetails {
|
||||
};
|
||||
|
||||
// sink_details is ordered in terms of desirability, with the best choice at the top.
|
||||
constexpr SinkDetails sink_details[] = {
|
||||
#ifdef HAVE_CUBEB
|
||||
SinkDetails{
|
||||
Settings::AudioEngine::Cubeb,
|
||||
[](std::string_view device_id) -> std::unique_ptr<Sink> {
|
||||
return std::make_unique<CubebSink>(device_id);
|
||||
},
|
||||
&ListCubebSinkDevices,
|
||||
&GetCubebLatency,
|
||||
},
|
||||
#endif
|
||||
static constexpr SinkDetails sink_details[] = {
|
||||
#ifdef HAVE_SDL3
|
||||
SinkDetails{
|
||||
Settings::AudioEngine::Sdl3,
|
||||
@@ -74,46 +61,20 @@ constexpr SinkDetails sink_details[] = {
|
||||
|
||||
const SinkDetails& GetOutputSinkDetails(Settings::AudioEngine sink_id) {
|
||||
const auto find_backend{[](Settings::AudioEngine id) {
|
||||
return std::find_if(std::begin(sink_details), std::end(sink_details),
|
||||
[&id](const auto& sink_detail) { return sink_detail.id == id; });
|
||||
return std::ranges::find_if(std::begin(sink_details), std::end(sink_details), [&id](const auto& e) {
|
||||
return e.id == id;
|
||||
});
|
||||
}};
|
||||
|
||||
auto iter = find_backend(sink_id);
|
||||
|
||||
if (sink_id == Settings::AudioEngine::Auto) {
|
||||
// REVERTED TO 3833 BELOW - DIABLO 3 FIX
|
||||
/*
|
||||
// Auto-select a backend. Use the sink details ordering, preferring cubeb first, checking
|
||||
// that the backend is available and suitable to use.
|
||||
for (auto& details : sink_details) {
|
||||
if (details.is_suitable()) {
|
||||
iter = &details;
|
||||
break;
|
||||
}
|
||||
}
|
||||
*/ // END REVERTED CODE - DIABLO 3 FIX
|
||||
|
||||
// BEGIN REINTRODUCED FROM 3833 - REPLACED CODE BLOCK ABOVE - DIABLO 3 FIX
|
||||
// Auto-select a backend. Prefer CubeB, but it may report a large minimum latency which
|
||||
// causes audio issues, in that case go with SDL.
|
||||
#if defined(HAVE_CUBEB) && defined(HAVE_SDL3)
|
||||
iter = find_backend(Settings::AudioEngine::Cubeb);
|
||||
if (iter->latency() > TargetSampleCount * 3) {
|
||||
#if defined(HAVE_SDL3)
|
||||
iter = find_backend(Settings::AudioEngine::Sdl3);
|
||||
}
|
||||
#else
|
||||
iter = std::begin(sink_details);
|
||||
#endif
|
||||
// END REINTRODUCED SECTION FROM 3833 - DIABLO 3 FIX
|
||||
LOG_INFO(Service_Audio, "Auto-selecting the {} backend",
|
||||
Settings::CanonicalizeEnum(iter->id));
|
||||
/* BEGIN REMOVED - REVERTING BACK TO 3833, this didn't exist at all. - DIABLO 3 FIX
|
||||
} else {
|
||||
if (iter != std::end(sink_details) && !iter->is_suitable()) {
|
||||
LOG_ERROR(Service_Audio, "Selected backend {} is not suitable, falling back to null",
|
||||
Settings::CanonicalizeEnum(iter->id));
|
||||
iter = find_backend(Settings::AudioEngine::Null);
|
||||
} */ // END REMOVED REVERT - DIABLO 3 FIX
|
||||
LOG_INFO(Service_Audio, "Auto-selecting the {} backend", Settings::CanonicalizeEnum(iter->id));
|
||||
}
|
||||
|
||||
if (iter == std::end(sink_details)) {
|
||||
@@ -127,10 +88,7 @@ const SinkDetails& GetOutputSinkDetails(Settings::AudioEngine sink_id) {
|
||||
|
||||
std::vector<Settings::AudioEngine> GetSinkIDs() {
|
||||
std::vector<Settings::AudioEngine> sink_ids(std::size(sink_details));
|
||||
|
||||
std::transform(std::begin(sink_details), std::end(sink_details), std::begin(sink_ids),
|
||||
[](const auto& sink) { return sink.id; });
|
||||
|
||||
std::transform(std::begin(sink_details), std::end(sink_details), std::begin(sink_ids), [](const auto& sink) { return sink.id; });
|
||||
return sink_ids;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user