From 83ee80666fbdf7829d741698425b7b547609165b Mon Sep 17 00:00:00 2001 From: darkf Date: Fri, 25 May 2018 00:18:13 -0500 Subject: [PATCH 1/5] Import cubeb as an external --- .gitmodules | 3 +++ externals/cubeb | 1 + 2 files changed, 4 insertions(+) create mode 160000 externals/cubeb diff --git a/.gitmodules b/.gitmodules index bce7d2220..ace938e19 100644 --- a/.gitmodules +++ b/.gitmodules @@ -31,3 +31,6 @@ [submodule "libressl"] path = externals/libressl url = https://github.com/citra-emu/ext-libressl-portable.git +[submodule "cubeb"] + path = externals/cubeb + url = https://github.com/kinetiknz/cubeb.git diff --git a/externals/cubeb b/externals/cubeb new file mode 160000 index 000000000..44341a1e0 --- /dev/null +++ b/externals/cubeb @@ -0,0 +1 @@ +Subproject commit 44341a1e0658a3939d29c0b5a230ca095ae63dd3 From af73dd45f00cfed38d9f623641e631b2bbcdedf1 Mon Sep 17 00:00:00 2001 From: darkf Date: Fri, 25 May 2018 00:50:37 -0500 Subject: [PATCH 2/5] audio_core: Implement a cubeb audio sink --- CMakeLists.txt | 2 + externals/CMakeLists.txt | 5 ++ src/audio_core/CMakeLists.txt | 5 ++ src/audio_core/cubeb_sink.cpp | 146 ++++++++++++++++++++++++++++++++ src/audio_core/cubeb_sink.h | 34 ++++++++ src/audio_core/sink_details.cpp | 2 + 6 files changed, 194 insertions(+) create mode 100644 src/audio_core/cubeb_sink.cpp create mode 100644 src/audio_core/cubeb_sink.h diff --git a/CMakeLists.txt b/CMakeLists.txt index af8f63f2d..6170d9df9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,8 @@ CMAKE_DEPENDENT_OPTION(CITRA_USE_BUNDLED_QT "Download bundled Qt binaries" ON "E option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON) +option(ENABLE_CUBEB "Enables the cubeb audio backend" ON) + if(NOT EXISTS ${CMAKE_SOURCE_DIR}/.git/hooks/pre-commit) message(STATUS "Copying pre-commit hook") file(COPY hooks/pre-commit diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index e0f42cb08..8687da427 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -77,3 +77,8 @@ if (ENABLE_WEB_SERVICE) add_library(json-headers INTERFACE) target_include_directories(json-headers INTERFACE ./json) endif() + +# Cubeb +if(ENABLE_CUBEB) + add_subdirectory(cubeb) +endif() diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index 00b31f972..6c0befab0 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -24,6 +24,7 @@ add_library(audio_core STATIC time_stretch.h $<$:sdl2_sink.cpp sdl2_sink.h> + $<$:cubeb_sink.cpp cubeb_sink.h> ) create_target_directory_groups(audio_core) @@ -35,3 +36,7 @@ if(SDL2_FOUND) target_link_libraries(audio_core PRIVATE SDL2) target_compile_definitions(audio_core PRIVATE HAVE_SDL2) endif() + +if(ENABLE_CUBEB) + target_link_libraries(audio_core PRIVATE cubeb) +endif() \ No newline at end of file diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp new file mode 100644 index 000000000..75a82d329 --- /dev/null +++ b/src/audio_core/cubeb_sink.cpp @@ -0,0 +1,146 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include "audio_core/audio_types.h" +#include "audio_core/cubeb_sink.h" +#include "common/logging/log.h" +#include "core/settings.h" + +namespace AudioCore { + +struct CubebSink::Impl { + unsigned int sample_rate = 0; + + cubeb* ctx = nullptr; + cubeb_stream* stream = nullptr; + + std::vector queue; + + static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, + void* output_buffer, long num_frames); + static void StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state); +}; + +CubebSink::CubebSink() : impl(std::make_unique()) { + if (cubeb_init(&impl->ctx, "Citra", nullptr) != CUBEB_OK) { + NGLOG_CRITICAL(Audio_Sink, "cubeb_init failed"); + return; + } + + const char* target_device_name = nullptr; + cubeb_devid output_device = nullptr; + + cubeb_stream_params params; + params.rate = native_sample_rate; + params.channels = 2; + params.format = CUBEB_SAMPLE_S16NE; + params.layout = CUBEB_LAYOUT_STEREO; + + impl->sample_rate = native_sample_rate; + + u32 minimum_latency = 0; + if (cubeb_get_min_latency(impl->ctx, ¶ms, &minimum_latency) != CUBEB_OK) + NGLOG_CRITICAL(Audio_Sink, "Error getting minimum latency"); + + cubeb_device_collection collection; + if (cubeb_enumerate_devices(impl->ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) { + NGLOG_WARNING(Audio_Sink, "Audio output device enumeration not supported"); + } else { + if (collection.count >= 1 && Settings::values.audio_device_id != "auto" && + !Settings::values.audio_device_id.empty()) { + target_device_name = Settings::values.audio_device_id.c_str(); + } + + for (size_t i = 0; i < collection.count; i++) { + const cubeb_device_info& device = collection.device[i]; + device_list.emplace_back(device.friendly_name); + + if (target_device_name && strcmp(target_device_name, device.friendly_name) == 0) { + output_device = device.devid; + } + } + + cubeb_device_collection_destroy(impl->ctx, &collection); + } + + if (cubeb_stream_init(impl->ctx, &impl->stream, "Citra Audio Output", nullptr, nullptr, + output_device, ¶ms, std::max(512u, minimum_latency), + &Impl::DataCallback, &Impl::StateCallback, impl.get()) != CUBEB_OK) { + NGLOG_CRITICAL(Audio_Sink, "Error initializing cubeb stream"); + return; + } + + if (cubeb_stream_start(impl->stream) != CUBEB_OK) { + NGLOG_CRITICAL(Audio_Sink, "Error starting cubeb stream"); + return; + } +} + +CubebSink::~CubebSink() { + if (!impl->ctx) + return; + + if (cubeb_stream_stop(impl->stream) != CUBEB_OK) { + NGLOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream"); + } + + cubeb_stream_destroy(impl->stream); + cubeb_destroy(impl->ctx); +} + +unsigned int CubebSink::GetNativeSampleRate() const { + if (!impl->ctx) + return native_sample_rate; + + return impl->sample_rate; +} + +std::vector CubebSink::GetDeviceList() const { + return device_list; +} + +void CubebSink::EnqueueSamples(const s16* samples, size_t sample_count) { + if (!impl->ctx) + return; + + impl->queue.reserve(impl->queue.size() + sample_count * 2); + std::copy(samples, samples + sample_count * 2, std::back_inserter(impl->queue)); +} + +size_t CubebSink::SamplesInQueue() const { + if (!impl->ctx) + return 0; + + return impl->queue.size() / 2; +} + +void CubebSink::SetDevice(int device_id) {} + +long CubebSink::Impl::DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, + void* output_buffer, long num_frames) { + Impl* impl = static_cast(user_data); + u8* buffer = reinterpret_cast(output_buffer); + + if (!impl) + return 0; + + size_t frames_to_write = std::min(impl->queue.size() / 2, static_cast(num_frames)); + + memcpy(buffer, impl->queue.data(), frames_to_write * sizeof(s16) * 2); + impl->queue.erase(impl->queue.begin(), impl->queue.begin() + frames_to_write * 2); + + if (frames_to_write < num_frames) { + // Fill the rest of the frames with silence + memset(buffer + frames_to_write * sizeof(s16) * 2, 0, + (num_frames - frames_to_write) * sizeof(s16) * 2); + } + + return num_frames; +} + +void CubebSink::Impl::StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state) {} + +} // namespace AudioCore diff --git a/src/audio_core/cubeb_sink.h b/src/audio_core/cubeb_sink.h new file mode 100644 index 000000000..192c76d9e --- /dev/null +++ b/src/audio_core/cubeb_sink.h @@ -0,0 +1,34 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "audio_core/sink.h" + +namespace AudioCore { + +class CubebSink final : public Sink { +public: + CubebSink(); + ~CubebSink() override; + + unsigned int GetNativeSampleRate() const override; + + void EnqueueSamples(const s16* samples, size_t sample_count) override; + + size_t SamplesInQueue() const override; + + std::vector GetDeviceList() const override; + void SetDevice(int device_id) override; + +private: + struct Impl; + std::unique_ptr impl; + int device_id; + std::vector device_list; +}; + +} // namespace AudioCore diff --git a/src/audio_core/sink_details.cpp b/src/audio_core/sink_details.cpp index 167b32b64..b526ed953 100644 --- a/src/audio_core/sink_details.cpp +++ b/src/audio_core/sink_details.cpp @@ -11,12 +11,14 @@ #ifdef HAVE_SDL2 #include "audio_core/sdl2_sink.h" #endif +#include "audio_core/cubeb_sink.h" #include "common/logging/log.h" namespace AudioCore { // g_sink_details is ordered in terms of desirability, with the best choice at the top. const std::vector g_sink_details = { + {"cubeb", []() { return std::make_unique(); }}, #ifdef HAVE_SDL2 {"sdl2", []() { return std::make_unique(); }}, #endif From 35c43aa2932e4291ae29e0c0d30991dc8ddfd6f7 Mon Sep 17 00:00:00 2001 From: darkf Date: Fri, 25 May 2018 00:58:53 -0500 Subject: [PATCH 3/5] audio_core: Only include cubeb if it's available --- src/audio_core/CMakeLists.txt | 1 + src/audio_core/sink_details.cpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index 6c0befab0..9f704c751 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -39,4 +39,5 @@ endif() if(ENABLE_CUBEB) target_link_libraries(audio_core PRIVATE cubeb) + add_definitions(-DHAVE_CUBEB=1) endif() \ No newline at end of file diff --git a/src/audio_core/sink_details.cpp b/src/audio_core/sink_details.cpp index b526ed953..a7e51e5f3 100644 --- a/src/audio_core/sink_details.cpp +++ b/src/audio_core/sink_details.cpp @@ -11,14 +11,18 @@ #ifdef HAVE_SDL2 #include "audio_core/sdl2_sink.h" #endif +#ifdef HAVE_CUBEB #include "audio_core/cubeb_sink.h" +#endif #include "common/logging/log.h" namespace AudioCore { // g_sink_details is ordered in terms of desirability, with the best choice at the top. const std::vector g_sink_details = { +#ifdef HAVE_CUBEB {"cubeb", []() { return std::make_unique(); }}, +#endif #ifdef HAVE_SDL2 {"sdl2", []() { return std::make_unique(); }}, #endif From ce6d9e2e2849e2a751e8cbc50d2a59f691b8fc54 Mon Sep 17 00:00:00 2001 From: darkf Date: Fri, 25 May 2018 02:05:38 -0500 Subject: [PATCH 4/5] externals: Don't build cubeb with tests --- externals/CMakeLists.txt | 1 + src/audio_core/CMakeLists.txt | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 8687da427..cb7530f2d 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -80,5 +80,6 @@ endif() # Cubeb if(ENABLE_CUBEB) + set(BUILD_TESTS OFF CACHE BOOL "") add_subdirectory(cubeb) endif() diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index 9f704c751..de6079edc 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -40,4 +40,5 @@ endif() if(ENABLE_CUBEB) target_link_libraries(audio_core PRIVATE cubeb) add_definitions(-DHAVE_CUBEB=1) -endif() \ No newline at end of file +endif() + From 04139e26bdcf8e6a181a9d9487954fdfc99bfe2c Mon Sep 17 00:00:00 2001 From: darkf Date: Fri, 25 May 2018 03:39:07 -0500 Subject: [PATCH 5/5] cubeb_sink: Skip devices without a name Previously this would crash. --- src/audio_core/cubeb_sink.cpp | 11 +++++++---- src/audio_core/cubeb_sink.h | 2 -- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp index 75a82d329..1e13e177d 100644 --- a/src/audio_core/cubeb_sink.cpp +++ b/src/audio_core/cubeb_sink.cpp @@ -13,6 +13,7 @@ namespace AudioCore { struct CubebSink::Impl { unsigned int sample_rate = 0; + std::vector device_list; cubeb* ctx = nullptr; cubeb_stream* stream = nullptr; @@ -56,10 +57,12 @@ CubebSink::CubebSink() : impl(std::make_unique()) { for (size_t i = 0; i < collection.count; i++) { const cubeb_device_info& device = collection.device[i]; - device_list.emplace_back(device.friendly_name); + if (device.friendly_name) { + impl->device_list.emplace_back(device.friendly_name); - if (target_device_name && strcmp(target_device_name, device.friendly_name) == 0) { - output_device = device.devid; + if (target_device_name && strcmp(target_device_name, device.friendly_name) == 0) { + output_device = device.devid; + } } } @@ -99,7 +102,7 @@ unsigned int CubebSink::GetNativeSampleRate() const { } std::vector CubebSink::GetDeviceList() const { - return device_list; + return impl->device_list; } void CubebSink::EnqueueSamples(const s16* samples, size_t sample_count) { diff --git a/src/audio_core/cubeb_sink.h b/src/audio_core/cubeb_sink.h index 192c76d9e..1fb521834 100644 --- a/src/audio_core/cubeb_sink.h +++ b/src/audio_core/cubeb_sink.h @@ -27,8 +27,6 @@ public: private: struct Impl; std::unique_ptr impl; - int device_id; - std::vector device_list; }; } // namespace AudioCore