Explorar el Código

Merge pull request #855 from bunnei/cubeb

Audio output backend based on cubeb
bunnei hace 8 años
padre
commit
bf9c62bc76

+ 3 - 0
.gitmodules

@@ -7,6 +7,9 @@
 [submodule "catch"]
 [submodule "catch"]
     path = externals/catch
     path = externals/catch
     url = https://github.com/philsquared/Catch.git
     url = https://github.com/philsquared/Catch.git
+[submodule "cubeb"]
+    path = externals/cubeb
+    url = https://github.com/kinetiknz/cubeb.git
 [submodule "dynarmic"]
 [submodule "dynarmic"]
     path = externals/dynarmic
     path = externals/dynarmic
     url = https://github.com/MerryMage/dynarmic.git
     url = https://github.com/MerryMage/dynarmic.git

+ 2 - 0
CMakeLists.txt

@@ -17,6 +17,8 @@ CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" ON "EN
 
 
 option(YUZU_USE_BUNDLED_UNICORN "Build/Download bundled Unicorn" ON)
 option(YUZU_USE_BUNDLED_UNICORN "Build/Download bundled Unicorn" ON)
 
 
+option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
+
 if(NOT EXISTS ${CMAKE_SOURCE_DIR}/.git/hooks/pre-commit)
 if(NOT EXISTS ${CMAKE_SOURCE_DIR}/.git/hooks/pre-commit)
     message(STATUS "Copying pre-commit hook")
     message(STATUS "Copying pre-commit hook")
     file(COPY hooks/pre-commit
     file(COPY hooks/pre-commit

+ 6 - 0
externals/CMakeLists.txt

@@ -54,3 +54,9 @@ endif()
 # Opus
 # Opus
 add_subdirectory(opus)
 add_subdirectory(opus)
 target_include_directories(opus INTERFACE ./opus/include)
 target_include_directories(opus INTERFACE ./opus/include)
+
+# Cubeb
+if(ENABLE_CUBEB)
+    set(BUILD_TESTS OFF CACHE BOOL "")
+    add_subdirectory(cubeb)
+endif()

+ 1 - 0
externals/cubeb

@@ -0,0 +1 @@
+Subproject commit 12b78c0edfa40007e41dbdcd9dfe367fbb98d018

+ 12 - 0
src/audio_core/CMakeLists.txt

@@ -2,10 +2,22 @@ add_library(audio_core STATIC
     audio_out.cpp
     audio_out.cpp
     audio_out.h
     audio_out.h
     buffer.h
     buffer.h
+    cubeb_sink.cpp
+    cubeb_sink.h
+    null_sink.h
     stream.cpp
     stream.cpp
     stream.h
     stream.h
+    sink.h
+    sink_details.cpp
+    sink_details.h
+    sink_stream.h
 )
 )
 
 
 create_target_directory_groups(audio_core)
 create_target_directory_groups(audio_core)
 
 
 target_link_libraries(audio_core PUBLIC common core)
 target_link_libraries(audio_core PUBLIC common core)
+
+if(ENABLE_CUBEB)
+    target_link_libraries(audio_core PRIVATE cubeb)
+    target_compile_definitions(audio_core PRIVATE -DHAVE_CUBEB=1)
+endif()

+ 12 - 5
src/audio_core/audio_out.cpp

@@ -3,13 +3,15 @@
 // Refer to the license.txt file included.
 // Refer to the license.txt file included.
 
 
 #include "audio_core/audio_out.h"
 #include "audio_core/audio_out.h"
+#include "audio_core/sink.h"
+#include "audio_core/sink_details.h"
 #include "common/assert.h"
 #include "common/assert.h"
 #include "common/logging/log.h"
 #include "common/logging/log.h"
 
 
 namespace AudioCore {
 namespace AudioCore {
 
 
 /// Returns the stream format from the specified number of channels
 /// Returns the stream format from the specified number of channels
-static Stream::Format ChannelsToStreamFormat(int num_channels) {
+static Stream::Format ChannelsToStreamFormat(u32 num_channels) {
     switch (num_channels) {
     switch (num_channels) {
     case 1:
     case 1:
         return Stream::Format::Mono16;
         return Stream::Format::Mono16;
@@ -24,11 +26,16 @@ static Stream::Format ChannelsToStreamFormat(int num_channels) {
     return {};
     return {};
 }
 }
 
 
-StreamPtr AudioOut::OpenStream(int sample_rate, int num_channels,
+StreamPtr AudioOut::OpenStream(u32 sample_rate, u32 num_channels,
                                Stream::ReleaseCallback&& release_callback) {
                                Stream::ReleaseCallback&& release_callback) {
-    streams.push_back(std::make_shared<Stream>(sample_rate, ChannelsToStreamFormat(num_channels),
-                                               std::move(release_callback)));
-    return streams.back();
+    if (!sink) {
+        const SinkDetails& sink_details = GetSinkDetails("auto");
+        sink = sink_details.factory("");
+    }
+
+    return std::make_shared<Stream>(sample_rate, ChannelsToStreamFormat(num_channels),
+                                    std::move(release_callback),
+                                    sink->AcquireSinkStream(sample_rate, num_channels));
 }
 }
 
 
 std::vector<u64> AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream, size_t max_count) {
 std::vector<u64> AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream, size_t max_count) {

+ 3 - 5
src/audio_core/audio_out.h

@@ -8,20 +8,19 @@
 #include <vector>
 #include <vector>
 
 
 #include "audio_core/buffer.h"
 #include "audio_core/buffer.h"
+#include "audio_core/sink.h"
 #include "audio_core/stream.h"
 #include "audio_core/stream.h"
 #include "common/common_types.h"
 #include "common/common_types.h"
 
 
 namespace AudioCore {
 namespace AudioCore {
 
 
-using StreamPtr = std::shared_ptr<Stream>;
-
 /**
 /**
  * Represents an audio playback interface, used to open and play audio streams
  * Represents an audio playback interface, used to open and play audio streams
  */
  */
 class AudioOut {
 class AudioOut {
 public:
 public:
     /// Opens a new audio stream
     /// Opens a new audio stream
-    StreamPtr OpenStream(int sample_rate, int num_channels,
+    StreamPtr OpenStream(u32 sample_rate, u32 num_channels,
                          Stream::ReleaseCallback&& release_callback);
                          Stream::ReleaseCallback&& release_callback);
 
 
     /// Returns a vector of recently released buffers specified by tag for the specified stream
     /// Returns a vector of recently released buffers specified by tag for the specified stream
@@ -37,8 +36,7 @@ public:
     bool QueueBuffer(StreamPtr stream, Buffer::Tag tag, std::vector<u8>&& data);
     bool QueueBuffer(StreamPtr stream, Buffer::Tag tag, std::vector<u8>&& data);
 
 
 private:
 private:
-    /// Active audio streams on the interface
-    std::vector<StreamPtr> streams;
+    SinkPtr sink;
 };
 };
 
 
 } // namespace AudioCore
 } // namespace AudioCore

+ 3 - 0
src/audio_core/buffer.h

@@ -4,6 +4,7 @@
 
 
 #pragma once
 #pragma once
 
 
+#include <memory>
 #include <vector>
 #include <vector>
 
 
 #include "common/common_types.h"
 #include "common/common_types.h"
@@ -34,4 +35,6 @@ private:
     std::vector<u8> data;
     std::vector<u8> data;
 };
 };
 
 
+using BufferPtr = std::shared_ptr<Buffer>;
+
 } // namespace AudioCore
 } // namespace AudioCore

+ 190 - 0
src/audio_core/cubeb_sink.cpp

@@ -0,0 +1,190 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <cstring>
+
+#include "audio_core/cubeb_sink.h"
+#include "audio_core/stream.h"
+#include "common/logging/log.h"
+
+namespace AudioCore {
+
+class SinkStreamImpl final : public SinkStream {
+public:
+    SinkStreamImpl(cubeb* ctx, cubeb_devid output_device) : ctx{ctx} {
+        cubeb_stream_params params;
+        params.rate = 48000;
+        params.channels = GetNumChannels();
+        params.format = CUBEB_SAMPLE_S16NE;
+        params.layout = CUBEB_LAYOUT_STEREO;
+
+        u32 minimum_latency = 0;
+        if (cubeb_get_min_latency(ctx, &params, &minimum_latency) != CUBEB_OK) {
+            LOG_CRITICAL(Audio_Sink, "Error getting minimum latency");
+        }
+
+        if (cubeb_stream_init(ctx, &stream_backend, "yuzu Audio Output", nullptr, nullptr,
+                              output_device, &params, std::max(512u, minimum_latency),
+                              &SinkStreamImpl::DataCallback, &SinkStreamImpl::StateCallback,
+                              this) != CUBEB_OK) {
+            LOG_CRITICAL(Audio_Sink, "Error initializing cubeb stream");
+            return;
+        }
+
+        if (cubeb_stream_start(stream_backend) != CUBEB_OK) {
+            LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream");
+            return;
+        }
+    }
+
+    ~SinkStreamImpl() {
+        if (!ctx) {
+            return;
+        }
+
+        if (cubeb_stream_stop(stream_backend) != CUBEB_OK) {
+            LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream");
+        }
+
+        cubeb_stream_destroy(stream_backend);
+    }
+
+    void EnqueueSamples(u32 num_channels, const s16* samples, size_t sample_count) override {
+        if (!ctx) {
+            return;
+        }
+
+        queue.reserve(queue.size() + sample_count * GetNumChannels());
+
+        if (num_channels == 2) {
+            // Copy as-is
+            std::copy(samples, samples + sample_count * GetNumChannels(),
+                      std::back_inserter(queue));
+        } else if (num_channels == 6) {
+            // Downsample 6 channels to 2
+            const size_t sample_count_copy_size = sample_count * num_channels * 2;
+            queue.reserve(sample_count_copy_size);
+            for (size_t i = 0; i < sample_count * num_channels; i += num_channels) {
+                queue.push_back(samples[i]);
+                queue.push_back(samples[i + 1]);
+            }
+        } else {
+            ASSERT_MSG(false, "Unimplemented");
+        }
+    }
+
+    u32 GetNumChannels() const {
+        // Only support 2-channel stereo output for now
+        return 2;
+    }
+
+private:
+    std::vector<std::string> device_list;
+
+    cubeb* ctx{};
+    cubeb_stream* stream_backend{};
+
+    std::vector<s16> 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(std::string target_device_name) {
+    if (cubeb_init(&ctx, "yuzu", 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& device) {
+                                               return target_device_name == device.friendly_name;
+                                           })};
+            if (device != collection_end) {
+                output_device = device->devid;
+            }
+            cubeb_device_collection_destroy(ctx, &collection);
+        }
+    }
+}
+
+CubebSink::~CubebSink() {
+    if (!ctx) {
+        return;
+    }
+
+    for (auto& sink_stream : sink_streams) {
+        sink_stream.reset();
+    }
+
+    cubeb_destroy(ctx);
+}
+
+SinkStream& CubebSink::AcquireSinkStream(u32 sample_rate, u32 num_channels) {
+    sink_streams.push_back(std::make_unique<SinkStreamImpl>(ctx, output_device));
+    return *sink_streams.back();
+}
+
+long SinkStreamImpl::DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer,
+                                  void* output_buffer, long num_frames) {
+    SinkStreamImpl* impl = static_cast<SinkStreamImpl*>(user_data);
+    u8* buffer = reinterpret_cast<u8*>(output_buffer);
+
+    if (!impl) {
+        return {};
+    }
+
+    const size_t frames_to_write{
+        std::min(impl->queue.size() / impl->GetNumChannels(), static_cast<size_t>(num_frames))};
+
+    memcpy(buffer, impl->queue.data(), frames_to_write * sizeof(s16) * impl->GetNumChannels());
+    impl->queue.erase(impl->queue.begin(),
+                      impl->queue.begin() + frames_to_write * impl->GetNumChannels());
+
+    if (frames_to_write < num_frames) {
+        // Fill the rest of the frames with silence
+        memset(buffer + frames_to_write * sizeof(s16) * impl->GetNumChannels(), 0,
+               (num_frames - frames_to_write) * sizeof(s16) * impl->GetNumChannels());
+    }
+
+    return num_frames;
+}
+
+void SinkStreamImpl::StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state) {}
+
+std::vector<std::string> ListCubebSinkDevices() {
+    std::vector<std::string> device_list;
+    cubeb* ctx;
+
+    if (cubeb_init(&ctx, "Citra Device Enumerator", nullptr) != CUBEB_OK) {
+        LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
+        return {};
+    }
+
+    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 {
+        for (size_t i = 0; i < collection.count; i++) {
+            const cubeb_device_info& device = collection.device[i];
+            if (device.friendly_name) {
+                device_list.emplace_back(device.friendly_name);
+            }
+        }
+        cubeb_device_collection_destroy(ctx, &collection);
+    }
+
+    cubeb_destroy(ctx);
+    return device_list;
+}
+
+} // namespace AudioCore

+ 31 - 0
src/audio_core/cubeb_sink.h

@@ -0,0 +1,31 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include <cubeb/cubeb.h>
+
+#include "audio_core/sink.h"
+
+namespace AudioCore {
+
+class CubebSink final : public Sink {
+public:
+    explicit CubebSink(std::string device_id);
+    ~CubebSink() override;
+
+    SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels) override;
+
+private:
+    cubeb* ctx{};
+    cubeb_devid output_device{};
+    std::vector<SinkStreamPtr> sink_streams;
+};
+
+std::vector<std::string> ListCubebSinkDevices();
+
+} // namespace AudioCore

+ 27 - 0
src/audio_core/null_sink.h

@@ -0,0 +1,27 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "audio_core/sink.h"
+
+namespace AudioCore {
+
+class NullSink final : public Sink {
+public:
+    explicit NullSink(std::string){};
+    ~NullSink() override = default;
+
+    SinkStream& AcquireSinkStream(u32 /*sample_rate*/, u32 /*num_channels*/) override {
+        return null_sink_stream;
+    }
+
+private:
+    struct NullSinkStreamImpl final : SinkStream {
+        void EnqueueSamples(u32 /*num_channels*/, const s16* /*samples*/,
+                            size_t /*sample_count*/) override {}
+    } null_sink_stream;
+};
+
+} // namespace AudioCore

+ 29 - 0
src/audio_core/sink.h

@@ -0,0 +1,29 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+
+#include "audio_core/sink_stream.h"
+#include "common/common_types.h"
+
+namespace AudioCore {
+
+constexpr char auto_device_name[] = "auto";
+
+/**
+ * This class is an interface for an audio sink. An audio sink accepts samples in stereo signed
+ * PCM16 format to be output. Sinks *do not* handle resampling and expect the correct sample rate.
+ * They are dumb outputs.
+ */
+class Sink {
+public:
+    virtual ~Sink() = default;
+    virtual SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels) = 0;
+};
+
+using SinkPtr = std::unique_ptr<Sink>;
+
+} // namespace AudioCore

+ 44 - 0
src/audio_core/sink_details.cpp

@@ -0,0 +1,44 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <vector>
+#include "audio_core/null_sink.h"
+#include "audio_core/sink_details.h"
+#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<SinkDetails> g_sink_details = {
+#ifdef HAVE_CUBEB
+    SinkDetails{"cubeb", &std::make_unique<CubebSink, std::string>, &ListCubebSinkDevices},
+#endif
+    SinkDetails{"null", &std::make_unique<NullSink, std::string>,
+                [] { return std::vector<std::string>{"null"}; }},
+};
+
+const SinkDetails& GetSinkDetails(std::string sink_id) {
+    auto iter =
+        std::find_if(g_sink_details.begin(), g_sink_details.end(),
+                     [sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; });
+
+    if (sink_id == "auto" || iter == g_sink_details.end()) {
+        if (sink_id != "auto") {
+            LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id {}", sink_id);
+        }
+        // Auto-select.
+        // g_sink_details is ordered in terms of desirability, with the best choice at the front.
+        iter = g_sink_details.begin();
+    }
+
+    return *iter;
+}
+
+} // namespace AudioCore

+ 32 - 0
src/audio_core/sink_details.h

@@ -0,0 +1,32 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+#include <memory>
+#include <vector>
+
+namespace AudioCore {
+
+class Sink;
+
+struct SinkDetails {
+    SinkDetails(const char* id_, std::function<std::unique_ptr<Sink>(std::string)> factory_,
+                std::function<std::vector<std::string>()> list_devices_)
+        : id(id_), factory(factory_), list_devices(list_devices_) {}
+
+    /// Name for this sink.
+    const char* id;
+    /// A method to call to construct an instance of this type of sink.
+    std::function<std::unique_ptr<Sink>(std::string device_id)> factory;
+    /// A method to call to list available devices.
+    std::function<std::vector<std::string>()> list_devices;
+};
+
+extern const std::vector<SinkDetails> g_sink_details;
+
+const SinkDetails& GetSinkDetails(std::string sink_id);
+
+} // namespace AudioCore

+ 32 - 0
src/audio_core/sink_stream.h

@@ -0,0 +1,32 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+
+#include "common/common_types.h"
+
+namespace AudioCore {
+
+/**
+ * Accepts samples in stereo signed PCM16 format to be output. Sinks *do not* handle resampling and
+ * expect the correct sample rate. They are dumb outputs.
+ */
+class SinkStream {
+public:
+    virtual ~SinkStream() = default;
+
+    /**
+     * Feed stereo samples to sink.
+     * @param num_channels Number of channels used.
+     * @param samples Samples in interleaved stereo PCM16 format.
+     * @param sample_count Number of samples.
+     */
+    virtual void EnqueueSamples(u32 num_channels, const s16* samples, size_t sample_count) = 0;
+};
+
+using SinkStreamPtr = std::unique_ptr<SinkStream>;
+
+} // namespace AudioCore

+ 23 - 12
src/audio_core/stream.cpp

@@ -7,30 +7,37 @@
 #include "core/core_timing.h"
 #include "core/core_timing.h"
 #include "core/core_timing_util.h"
 #include "core/core_timing_util.h"
 
 
+#include "audio_core/sink.h"
+#include "audio_core/sink_details.h"
 #include "audio_core/stream.h"
 #include "audio_core/stream.h"
 
 
 namespace AudioCore {
 namespace AudioCore {
 
 
 constexpr size_t MaxAudioBufferCount{32};
 constexpr size_t MaxAudioBufferCount{32};
 
 
-/// Returns the sample size for the specified audio stream format
-static size_t SampleSizeFromFormat(Stream::Format format) {
+u32 Stream::GetNumChannels() const {
     switch (format) {
     switch (format) {
-    case Stream::Format::Mono16:
+    case Format::Mono16:
+        return 1;
+    case Format::Stereo16:
         return 2;
         return 2;
-    case Stream::Format::Stereo16:
-        return 4;
-    case Stream::Format::Multi51Channel16:
-        return 12;
-    };
-
+    case Format::Multi51Channel16:
+        return 6;
+    }
     LOG_CRITICAL(Audio, "Unimplemented format={}", static_cast<u32>(format));
     LOG_CRITICAL(Audio, "Unimplemented format={}", static_cast<u32>(format));
     UNREACHABLE();
     UNREACHABLE();
     return {};
     return {};
 }
 }
 
 
-Stream::Stream(int sample_rate, Format format, ReleaseCallback&& release_callback)
-    : sample_rate{sample_rate}, format{format}, release_callback{std::move(release_callback)} {
+u32 Stream::GetSampleSize() const {
+    return GetNumChannels() * 2;
+}
+
+Stream::Stream(u32 sample_rate, Format format, ReleaseCallback&& release_callback,
+               SinkStream& sink_stream)
+    : sample_rate{sample_rate}, format{format}, release_callback{std::move(release_callback)},
+      sink_stream{sink_stream} {
+
     release_event = CoreTiming::RegisterEvent(
     release_event = CoreTiming::RegisterEvent(
         "Stream::Release", [this](u64 userdata, int cycles_late) { ReleaseActiveBuffer(); });
         "Stream::Release", [this](u64 userdata, int cycles_late) { ReleaseActiveBuffer(); });
 }
 }
@@ -45,7 +52,7 @@ void Stream::Stop() {
 }
 }
 
 
 s64 Stream::GetBufferReleaseCycles(const Buffer& buffer) const {
 s64 Stream::GetBufferReleaseCycles(const Buffer& buffer) const {
-    const size_t num_samples{buffer.GetData().size() / SampleSizeFromFormat(format)};
+    const size_t num_samples{buffer.GetData().size() / GetSampleSize()};
     return CoreTiming::usToCycles((static_cast<u64>(num_samples) * 1000000) / sample_rate);
     return CoreTiming::usToCycles((static_cast<u64>(num_samples) * 1000000) / sample_rate);
 }
 }
 
 
@@ -68,6 +75,10 @@ void Stream::PlayNextBuffer() {
     active_buffer = queued_buffers.front();
     active_buffer = queued_buffers.front();
     queued_buffers.pop();
     queued_buffers.pop();
 
 
+    sink_stream.EnqueueSamples(GetNumChannels(),
+                               reinterpret_cast<const s16*>(active_buffer->GetData().data()),
+                               active_buffer->GetData().size() / GetSampleSize());
+
     CoreTiming::ScheduleEventThreadsafe(GetBufferReleaseCycles(*active_buffer), release_event, {});
     CoreTiming::ScheduleEventThreadsafe(GetBufferReleaseCycles(*active_buffer), release_event, {});
 }
 }
 
 

+ 18 - 4
src/audio_core/stream.h

@@ -10,14 +10,13 @@
 #include <queue>
 #include <queue>
 
 
 #include "audio_core/buffer.h"
 #include "audio_core/buffer.h"
+#include "audio_core/sink_stream.h"
 #include "common/assert.h"
 #include "common/assert.h"
 #include "common/common_types.h"
 #include "common/common_types.h"
 #include "core/core_timing.h"
 #include "core/core_timing.h"
 
 
 namespace AudioCore {
 namespace AudioCore {
 
 
-using BufferPtr = std::shared_ptr<Buffer>;
-
 /**
 /**
  * Represents an audio stream, which is a sequence of queued buffers, to be outputed by AudioOut
  * Represents an audio stream, which is a sequence of queued buffers, to be outputed by AudioOut
  */
  */
@@ -33,7 +32,8 @@ public:
     /// Callback function type, used to change guest state on a buffer being released
     /// Callback function type, used to change guest state on a buffer being released
     using ReleaseCallback = std::function<void()>;
     using ReleaseCallback = std::function<void()>;
 
 
-    Stream(int sample_rate, Format format, ReleaseCallback&& release_callback);
+    Stream(u32 sample_rate, Format format, ReleaseCallback&& release_callback,
+           SinkStream& sink_stream);
 
 
     /// Plays the audio stream
     /// Plays the audio stream
     void Play();
     void Play();
@@ -60,6 +60,17 @@ public:
         return queued_buffers.size();
         return queued_buffers.size();
     }
     }
 
 
+    /// Gets the sample rate
+    u32 GetSampleRate() const {
+        return sample_rate;
+    }
+
+    /// Gets the number of channels
+    u32 GetNumChannels() const;
+
+    /// Gets the sample size in bytes
+    u32 GetSampleSize() const;
+
 private:
 private:
     /// Current state of the stream
     /// Current state of the stream
     enum class State {
     enum class State {
@@ -76,7 +87,7 @@ private:
     /// Gets the number of core cycles when the specified buffer will be released
     /// Gets the number of core cycles when the specified buffer will be released
     s64 GetBufferReleaseCycles(const Buffer& buffer) const;
     s64 GetBufferReleaseCycles(const Buffer& buffer) const;
 
 
-    int sample_rate;                        ///< Sample rate of the stream
+    u32 sample_rate;                        ///< Sample rate of the stream
     Format format;                          ///< Format of the stream
     Format format;                          ///< Format of the stream
     ReleaseCallback release_callback;       ///< Buffer release callback for the stream
     ReleaseCallback release_callback;       ///< Buffer release callback for the stream
     State state{State::Stopped};            ///< Playback state of the stream
     State state{State::Stopped};            ///< Playback state of the stream
@@ -84,6 +95,9 @@ private:
     BufferPtr active_buffer;                ///< Actively playing buffer in the stream
     BufferPtr active_buffer;                ///< Actively playing buffer in the stream
     std::queue<BufferPtr> queued_buffers;   ///< Buffers queued to be played in the stream
     std::queue<BufferPtr> queued_buffers;   ///< Buffers queued to be played in the stream
     std::queue<BufferPtr> released_buffers; ///< Buffers recently released from the stream
     std::queue<BufferPtr> released_buffers; ///< Buffers recently released from the stream
+    SinkStream& sink_stream;                ///< Output sink for the stream
 };
 };
 
 
+using StreamPtr = std::shared_ptr<Stream>;
+
 } // namespace AudioCore
 } // namespace AudioCore

+ 0 - 2
src/core/core.cpp

@@ -177,7 +177,6 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
     }
     }
 
 
     gpu_core = std::make_unique<Tegra::GPU>();
     gpu_core = std::make_unique<Tegra::GPU>();
-    audio_core = std::make_unique<AudioCore::AudioOut>();
     telemetry_session = std::make_unique<Core::TelemetrySession>();
     telemetry_session = std::make_unique<Core::TelemetrySession>();
     service_manager = std::make_shared<Service::SM::ServiceManager>();
     service_manager = std::make_shared<Service::SM::ServiceManager>();
 
 
@@ -229,7 +228,6 @@ void System::Shutdown() {
     service_manager.reset();
     service_manager.reset();
     telemetry_session.reset();
     telemetry_session.reset();
     gpu_core.reset();
     gpu_core.reset();
-    audio_core.reset();
 
 
     // Close all CPU/threading state
     // Close all CPU/threading state
     cpu_barrier->NotifyEnd();
     cpu_barrier->NotifyEnd();

+ 0 - 7
src/core/core.h

@@ -8,7 +8,6 @@
 #include <memory>
 #include <memory>
 #include <string>
 #include <string>
 #include <thread>
 #include <thread>
-#include "audio_core/audio_out.h"
 #include "common/common_types.h"
 #include "common/common_types.h"
 #include "core/arm/exclusive_monitor.h"
 #include "core/arm/exclusive_monitor.h"
 #include "core/core_cpu.h"
 #include "core/core_cpu.h"
@@ -132,11 +131,6 @@ public:
         return *gpu_core;
         return *gpu_core;
     }
     }
 
 
-    /// Gets the AudioCore interface
-    AudioCore::AudioOut& AudioCore() {
-        return *audio_core;
-    }
-
     /// Gets the scheduler for the CPU core that is currently running
     /// Gets the scheduler for the CPU core that is currently running
     Kernel::Scheduler& CurrentScheduler() {
     Kernel::Scheduler& CurrentScheduler() {
         return *CurrentCpuCore().Scheduler();
         return *CurrentCpuCore().Scheduler();
@@ -201,7 +195,6 @@ private:
     /// AppLoader used to load the current executing application
     /// AppLoader used to load the current executing application
     std::unique_ptr<Loader::AppLoader> app_loader;
     std::unique_ptr<Loader::AppLoader> app_loader;
     std::unique_ptr<Tegra::GPU> gpu_core;
     std::unique_ptr<Tegra::GPU> gpu_core;
-    std::unique_ptr<AudioCore::AudioOut> audio_core;
     std::shared_ptr<Tegra::DebugContext> debug_context;
     std::shared_ptr<Tegra::DebugContext> debug_context;
     Kernel::SharedPtr<Kernel::Process> current_process;
     Kernel::SharedPtr<Kernel::Process> current_process;
     std::shared_ptr<ExclusiveMonitor> cpu_exclusive_monitor;
     std::shared_ptr<ExclusiveMonitor> cpu_exclusive_monitor;

+ 4 - 4
src/core/hle/service/audio/audout_u.cpp

@@ -25,9 +25,8 @@ constexpr int DefaultSampleRate{48000};
 
 
 class IAudioOut final : public ServiceFramework<IAudioOut> {
 class IAudioOut final : public ServiceFramework<IAudioOut> {
 public:
 public:
-    IAudioOut(AudoutParams audio_params)
-        : ServiceFramework("IAudioOut"), audio_params(audio_params),
-          audio_core(Core::System::GetInstance().AudioCore()) {
+    IAudioOut(AudoutParams audio_params, AudioCore::AudioOut& audio_core)
+        : ServiceFramework("IAudioOut"), audio_params(audio_params), audio_core(audio_core) {
 
 
         static const FunctionInfo functions[] = {
         static const FunctionInfo functions[] = {
             {0, &IAudioOut::GetAudioOutState, "GetAudioOutState"},
             {0, &IAudioOut::GetAudioOutState, "GetAudioOutState"},
@@ -195,7 +194,7 @@ void AudOutU::OpenAudioOutImpl(Kernel::HLERequestContext& ctx) {
     // TODO(bunnei): Support more than one IAudioOut interface. When we add this, ListAudioOutsImpl
     // TODO(bunnei): Support more than one IAudioOut interface. When we add this, ListAudioOutsImpl
     // will likely need to be updated as well.
     // will likely need to be updated as well.
     ASSERT_MSG(!audio_out_interface, "Unimplemented");
     ASSERT_MSG(!audio_out_interface, "Unimplemented");
-    audio_out_interface = std::make_shared<IAudioOut>(std::move(params));
+    audio_out_interface = std::make_shared<IAudioOut>(std::move(params), *audio_core);
 
 
     IPC::ResponseBuilder rb{ctx, 6, 0, 1};
     IPC::ResponseBuilder rb{ctx, 6, 0, 1};
     rb.Push(RESULT_SUCCESS);
     rb.Push(RESULT_SUCCESS);
@@ -212,6 +211,7 @@ AudOutU::AudOutU() : ServiceFramework("audout:u") {
                                              {2, &AudOutU::ListAudioOutsImpl, "ListAudioOutsAuto"},
                                              {2, &AudOutU::ListAudioOutsImpl, "ListAudioOutsAuto"},
                                              {3, &AudOutU::OpenAudioOutImpl, "OpenAudioOutAuto"}};
                                              {3, &AudOutU::OpenAudioOutImpl, "OpenAudioOutAuto"}};
     RegisterHandlers(functions);
     RegisterHandlers(functions);
+    audio_core = std::make_unique<AudioCore::AudioOut>();
 }
 }
 
 
 } // namespace Service::Audio
 } // namespace Service::Audio

+ 2 - 0
src/core/hle/service/audio/audout_u.h

@@ -4,6 +4,7 @@
 
 
 #pragma once
 #pragma once
 
 
+#include "audio_core/audio_out.h"
 #include "core/hle/service/service.h"
 #include "core/hle/service/service.h"
 
 
 namespace Kernel {
 namespace Kernel {
@@ -33,6 +34,7 @@ public:
 
 
 private:
 private:
     std::shared_ptr<IAudioOut> audio_out_interface;
     std::shared_ptr<IAudioOut> audio_out_interface;
+    std::unique_ptr<AudioCore::AudioOut> audio_core;
 
 
     void ListAudioOutsImpl(Kernel::HLERequestContext& ctx);
     void ListAudioOutsImpl(Kernel::HLERequestContext& ctx);
     void OpenAudioOutImpl(Kernel::HLERequestContext& ctx);
     void OpenAudioOutImpl(Kernel::HLERequestContext& ctx);