diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/audio_core/CMakeLists.txt | 12 | ||||
| -rw-r--r-- | src/audio_core/audio_out.cpp | 17 | ||||
| -rw-r--r-- | src/audio_core/audio_out.h | 8 | ||||
| -rw-r--r-- | src/audio_core/buffer.h | 3 | ||||
| -rw-r--r-- | src/audio_core/cubeb_sink.cpp | 190 | ||||
| -rw-r--r-- | src/audio_core/cubeb_sink.h | 31 | ||||
| -rw-r--r-- | src/audio_core/null_sink.h | 27 | ||||
| -rw-r--r-- | src/audio_core/sink.h | 29 | ||||
| -rw-r--r-- | src/audio_core/sink_details.cpp | 44 | ||||
| -rw-r--r-- | src/audio_core/sink_details.h | 32 | ||||
| -rw-r--r-- | src/audio_core/sink_stream.h | 32 | ||||
| -rw-r--r-- | src/audio_core/stream.cpp | 35 | ||||
| -rw-r--r-- | src/audio_core/stream.h | 22 | ||||
| -rw-r--r-- | src/core/core.cpp | 2 | ||||
| -rw-r--r-- | src/core/core.h | 7 | ||||
| -rw-r--r-- | src/core/hle/service/audio/audout_u.cpp | 8 | ||||
| -rw-r--r-- | src/core/hle/service/audio/audout_u.h | 2 |
17 files changed, 462 insertions, 39 deletions
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index f00a55994..81121167d 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt | |||
| @@ -2,10 +2,22 @@ add_library(audio_core STATIC | |||
| 2 | audio_out.cpp | 2 | audio_out.cpp |
| 3 | audio_out.h | 3 | audio_out.h |
| 4 | buffer.h | 4 | buffer.h |
| 5 | cubeb_sink.cpp | ||
| 6 | cubeb_sink.h | ||
| 7 | null_sink.h | ||
| 5 | stream.cpp | 8 | stream.cpp |
| 6 | stream.h | 9 | stream.h |
| 10 | sink.h | ||
| 11 | sink_details.cpp | ||
| 12 | sink_details.h | ||
| 13 | sink_stream.h | ||
| 7 | ) | 14 | ) |
| 8 | 15 | ||
| 9 | create_target_directory_groups(audio_core) | 16 | create_target_directory_groups(audio_core) |
| 10 | 17 | ||
| 11 | target_link_libraries(audio_core PUBLIC common core) | 18 | target_link_libraries(audio_core PUBLIC common core) |
| 19 | |||
| 20 | if(ENABLE_CUBEB) | ||
| 21 | target_link_libraries(audio_core PRIVATE cubeb) | ||
| 22 | target_compile_definitions(audio_core PRIVATE -DHAVE_CUBEB=1) | ||
| 23 | endif() | ||
diff --git a/src/audio_core/audio_out.cpp b/src/audio_core/audio_out.cpp index 6d418a05b..77cedb6ba 100644 --- a/src/audio_core/audio_out.cpp +++ b/src/audio_core/audio_out.cpp | |||
| @@ -3,13 +3,15 @@ | |||
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include "audio_core/audio_out.h" | 5 | #include "audio_core/audio_out.h" |
| 6 | #include "audio_core/sink.h" | ||
| 7 | #include "audio_core/sink_details.h" | ||
| 6 | #include "common/assert.h" | 8 | #include "common/assert.h" |
| 7 | #include "common/logging/log.h" | 9 | #include "common/logging/log.h" |
| 8 | 10 | ||
| 9 | namespace AudioCore { | 11 | namespace AudioCore { |
| 10 | 12 | ||
| 11 | /// Returns the stream format from the specified number of channels | 13 | /// Returns the stream format from the specified number of channels |
| 12 | static Stream::Format ChannelsToStreamFormat(int num_channels) { | 14 | static Stream::Format ChannelsToStreamFormat(u32 num_channels) { |
| 13 | switch (num_channels) { | 15 | switch (num_channels) { |
| 14 | case 1: | 16 | case 1: |
| 15 | return Stream::Format::Mono16; | 17 | return Stream::Format::Mono16; |
| @@ -24,11 +26,16 @@ static Stream::Format ChannelsToStreamFormat(int num_channels) { | |||
| 24 | return {}; | 26 | return {}; |
| 25 | } | 27 | } |
| 26 | 28 | ||
| 27 | StreamPtr AudioOut::OpenStream(int sample_rate, int num_channels, | 29 | StreamPtr AudioOut::OpenStream(u32 sample_rate, u32 num_channels, |
| 28 | Stream::ReleaseCallback&& release_callback) { | 30 | Stream::ReleaseCallback&& release_callback) { |
| 29 | streams.push_back(std::make_shared<Stream>(sample_rate, ChannelsToStreamFormat(num_channels), | 31 | if (!sink) { |
| 30 | std::move(release_callback))); | 32 | const SinkDetails& sink_details = GetSinkDetails("auto"); |
| 31 | return streams.back(); | 33 | sink = sink_details.factory(""); |
| 34 | } | ||
| 35 | |||
| 36 | return std::make_shared<Stream>(sample_rate, ChannelsToStreamFormat(num_channels), | ||
| 37 | std::move(release_callback), | ||
| 38 | sink->AcquireSinkStream(sample_rate, num_channels)); | ||
| 32 | } | 39 | } |
| 33 | 40 | ||
| 34 | std::vector<u64> AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream, size_t max_count) { | 41 | std::vector<u64> AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream, size_t max_count) { |
diff --git a/src/audio_core/audio_out.h b/src/audio_core/audio_out.h index a86499d10..8d9b695d4 100644 --- a/src/audio_core/audio_out.h +++ b/src/audio_core/audio_out.h | |||
| @@ -8,20 +8,19 @@ | |||
| 8 | #include <vector> | 8 | #include <vector> |
| 9 | 9 | ||
| 10 | #include "audio_core/buffer.h" | 10 | #include "audio_core/buffer.h" |
| 11 | #include "audio_core/sink.h" | ||
| 11 | #include "audio_core/stream.h" | 12 | #include "audio_core/stream.h" |
| 12 | #include "common/common_types.h" | 13 | #include "common/common_types.h" |
| 13 | 14 | ||
| 14 | namespace AudioCore { | 15 | namespace AudioCore { |
| 15 | 16 | ||
| 16 | using StreamPtr = std::shared_ptr<Stream>; | ||
| 17 | |||
| 18 | /** | 17 | /** |
| 19 | * Represents an audio playback interface, used to open and play audio streams | 18 | * Represents an audio playback interface, used to open and play audio streams |
| 20 | */ | 19 | */ |
| 21 | class AudioOut { | 20 | class AudioOut { |
| 22 | public: | 21 | public: |
| 23 | /// Opens a new audio stream | 22 | /// Opens a new audio stream |
| 24 | StreamPtr OpenStream(int sample_rate, int num_channels, | 23 | StreamPtr OpenStream(u32 sample_rate, u32 num_channels, |
| 25 | Stream::ReleaseCallback&& release_callback); | 24 | Stream::ReleaseCallback&& release_callback); |
| 26 | 25 | ||
| 27 | /// Returns a vector of recently released buffers specified by tag for the specified stream | 26 | /// Returns a vector of recently released buffers specified by tag for the specified stream |
| @@ -37,8 +36,7 @@ public: | |||
| 37 | bool QueueBuffer(StreamPtr stream, Buffer::Tag tag, std::vector<u8>&& data); | 36 | bool QueueBuffer(StreamPtr stream, Buffer::Tag tag, std::vector<u8>&& data); |
| 38 | 37 | ||
| 39 | private: | 38 | private: |
| 40 | /// Active audio streams on the interface | 39 | SinkPtr sink; |
| 41 | std::vector<StreamPtr> streams; | ||
| 42 | }; | 40 | }; |
| 43 | 41 | ||
| 44 | } // namespace AudioCore | 42 | } // namespace AudioCore |
diff --git a/src/audio_core/buffer.h b/src/audio_core/buffer.h index 874ec787e..4bf5fd58a 100644 --- a/src/audio_core/buffer.h +++ b/src/audio_core/buffer.h | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | 4 | ||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <memory> | ||
| 7 | #include <vector> | 8 | #include <vector> |
| 8 | 9 | ||
| 9 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| @@ -34,4 +35,6 @@ private: | |||
| 34 | std::vector<u8> data; | 35 | std::vector<u8> data; |
| 35 | }; | 36 | }; |
| 36 | 37 | ||
| 38 | using BufferPtr = std::shared_ptr<Buffer>; | ||
| 39 | |||
| 37 | } // namespace AudioCore | 40 | } // namespace AudioCore |
diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp new file mode 100644 index 000000000..34ae5b062 --- /dev/null +++ b/src/audio_core/cubeb_sink.cpp | |||
| @@ -0,0 +1,190 @@ | |||
| 1 | // Copyright 2018 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <algorithm> | ||
| 6 | #include <cstring> | ||
| 7 | |||
| 8 | #include "audio_core/cubeb_sink.h" | ||
| 9 | #include "audio_core/stream.h" | ||
| 10 | #include "common/logging/log.h" | ||
| 11 | |||
| 12 | namespace AudioCore { | ||
| 13 | |||
| 14 | class SinkStreamImpl final : public SinkStream { | ||
| 15 | public: | ||
| 16 | SinkStreamImpl(cubeb* ctx, cubeb_devid output_device) : ctx{ctx} { | ||
| 17 | cubeb_stream_params params; | ||
| 18 | params.rate = 48000; | ||
| 19 | params.channels = GetNumChannels(); | ||
| 20 | params.format = CUBEB_SAMPLE_S16NE; | ||
| 21 | params.layout = CUBEB_LAYOUT_STEREO; | ||
| 22 | |||
| 23 | u32 minimum_latency = 0; | ||
| 24 | if (cubeb_get_min_latency(ctx, ¶ms, &minimum_latency) != CUBEB_OK) { | ||
| 25 | LOG_CRITICAL(Audio_Sink, "Error getting minimum latency"); | ||
| 26 | } | ||
| 27 | |||
| 28 | if (cubeb_stream_init(ctx, &stream_backend, "yuzu Audio Output", nullptr, nullptr, | ||
| 29 | output_device, ¶ms, std::max(512u, minimum_latency), | ||
| 30 | &SinkStreamImpl::DataCallback, &SinkStreamImpl::StateCallback, | ||
| 31 | this) != CUBEB_OK) { | ||
| 32 | LOG_CRITICAL(Audio_Sink, "Error initializing cubeb stream"); | ||
| 33 | return; | ||
| 34 | } | ||
| 35 | |||
| 36 | if (cubeb_stream_start(stream_backend) != CUBEB_OK) { | ||
| 37 | LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream"); | ||
| 38 | return; | ||
| 39 | } | ||
| 40 | } | ||
| 41 | |||
| 42 | ~SinkStreamImpl() { | ||
| 43 | if (!ctx) { | ||
| 44 | return; | ||
| 45 | } | ||
| 46 | |||
| 47 | if (cubeb_stream_stop(stream_backend) != CUBEB_OK) { | ||
| 48 | LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream"); | ||
| 49 | } | ||
| 50 | |||
| 51 | cubeb_stream_destroy(stream_backend); | ||
| 52 | } | ||
| 53 | |||
| 54 | void EnqueueSamples(u32 num_channels, const s16* samples, size_t sample_count) override { | ||
| 55 | if (!ctx) { | ||
| 56 | return; | ||
| 57 | } | ||
| 58 | |||
| 59 | queue.reserve(queue.size() + sample_count * GetNumChannels()); | ||
| 60 | |||
| 61 | if (num_channels == 2) { | ||
| 62 | // Copy as-is | ||
| 63 | std::copy(samples, samples + sample_count * GetNumChannels(), | ||
| 64 | std::back_inserter(queue)); | ||
| 65 | } else if (num_channels == 6) { | ||
| 66 | // Downsample 6 channels to 2 | ||
| 67 | const size_t sample_count_copy_size = sample_count * num_channels * 2; | ||
| 68 | queue.reserve(sample_count_copy_size); | ||
| 69 | for (size_t i = 0; i < sample_count * num_channels; i += num_channels) { | ||
| 70 | queue.push_back(samples[i]); | ||
| 71 | queue.push_back(samples[i + 1]); | ||
| 72 | } | ||
| 73 | } else { | ||
| 74 | ASSERT_MSG(false, "Unimplemented"); | ||
| 75 | } | ||
| 76 | } | ||
| 77 | |||
| 78 | u32 GetNumChannels() const { | ||
| 79 | // Only support 2-channel stereo output for now | ||
| 80 | return 2; | ||
| 81 | } | ||
| 82 | |||
| 83 | private: | ||
| 84 | std::vector<std::string> device_list; | ||
| 85 | |||
| 86 | cubeb* ctx{}; | ||
| 87 | cubeb_stream* stream_backend{}; | ||
| 88 | |||
| 89 | std::vector<s16> queue; | ||
| 90 | |||
| 91 | static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, | ||
| 92 | void* output_buffer, long num_frames); | ||
| 93 | static void StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state); | ||
| 94 | }; | ||
| 95 | |||
| 96 | CubebSink::CubebSink(std::string target_device_name) { | ||
| 97 | if (cubeb_init(&ctx, "yuzu", nullptr) != CUBEB_OK) { | ||
| 98 | LOG_CRITICAL(Audio_Sink, "cubeb_init failed"); | ||
| 99 | return; | ||
| 100 | } | ||
| 101 | |||
| 102 | if (target_device_name != auto_device_name && !target_device_name.empty()) { | ||
| 103 | cubeb_device_collection collection; | ||
| 104 | if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) { | ||
| 105 | LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported"); | ||
| 106 | } else { | ||
| 107 | const auto collection_end{collection.device + collection.count}; | ||
| 108 | const auto device{std::find_if(collection.device, collection_end, | ||
| 109 | [&](const cubeb_device_info& device) { | ||
| 110 | return target_device_name == device.friendly_name; | ||
| 111 | })}; | ||
| 112 | if (device != collection_end) { | ||
| 113 | output_device = device->devid; | ||
| 114 | } | ||
| 115 | cubeb_device_collection_destroy(ctx, &collection); | ||
| 116 | } | ||
| 117 | } | ||
| 118 | } | ||
| 119 | |||
| 120 | CubebSink::~CubebSink() { | ||
| 121 | if (!ctx) { | ||
| 122 | return; | ||
| 123 | } | ||
| 124 | |||
| 125 | for (auto& sink_stream : sink_streams) { | ||
| 126 | sink_stream.reset(); | ||
| 127 | } | ||
| 128 | |||
| 129 | cubeb_destroy(ctx); | ||
| 130 | } | ||
| 131 | |||
| 132 | SinkStream& CubebSink::AcquireSinkStream(u32 sample_rate, u32 num_channels) { | ||
| 133 | sink_streams.push_back(std::make_unique<SinkStreamImpl>(ctx, output_device)); | ||
| 134 | return *sink_streams.back(); | ||
| 135 | } | ||
| 136 | |||
| 137 | long SinkStreamImpl::DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, | ||
| 138 | void* output_buffer, long num_frames) { | ||
| 139 | SinkStreamImpl* impl = static_cast<SinkStreamImpl*>(user_data); | ||
| 140 | u8* buffer = reinterpret_cast<u8*>(output_buffer); | ||
| 141 | |||
| 142 | if (!impl) { | ||
| 143 | return {}; | ||
| 144 | } | ||
| 145 | |||
| 146 | const size_t frames_to_write{ | ||
| 147 | std::min(impl->queue.size() / impl->GetNumChannels(), static_cast<size_t>(num_frames))}; | ||
| 148 | |||
| 149 | memcpy(buffer, impl->queue.data(), frames_to_write * sizeof(s16) * impl->GetNumChannels()); | ||
| 150 | impl->queue.erase(impl->queue.begin(), | ||
| 151 | impl->queue.begin() + frames_to_write * impl->GetNumChannels()); | ||
| 152 | |||
| 153 | if (frames_to_write < num_frames) { | ||
| 154 | // Fill the rest of the frames with silence | ||
| 155 | memset(buffer + frames_to_write * sizeof(s16) * impl->GetNumChannels(), 0, | ||
| 156 | (num_frames - frames_to_write) * sizeof(s16) * impl->GetNumChannels()); | ||
| 157 | } | ||
| 158 | |||
| 159 | return num_frames; | ||
| 160 | } | ||
| 161 | |||
| 162 | void SinkStreamImpl::StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state) {} | ||
| 163 | |||
| 164 | std::vector<std::string> ListCubebSinkDevices() { | ||
| 165 | std::vector<std::string> device_list; | ||
| 166 | cubeb* ctx; | ||
| 167 | |||
| 168 | if (cubeb_init(&ctx, "Citra Device Enumerator", nullptr) != CUBEB_OK) { | ||
| 169 | LOG_CRITICAL(Audio_Sink, "cubeb_init failed"); | ||
| 170 | return {}; | ||
| 171 | } | ||
| 172 | |||
| 173 | cubeb_device_collection collection; | ||
| 174 | if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) { | ||
| 175 | LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported"); | ||
| 176 | } else { | ||
| 177 | for (size_t i = 0; i < collection.count; i++) { | ||
| 178 | const cubeb_device_info& device = collection.device[i]; | ||
| 179 | if (device.friendly_name) { | ||
| 180 | device_list.emplace_back(device.friendly_name); | ||
| 181 | } | ||
| 182 | } | ||
| 183 | cubeb_device_collection_destroy(ctx, &collection); | ||
| 184 | } | ||
| 185 | |||
| 186 | cubeb_destroy(ctx); | ||
| 187 | return device_list; | ||
| 188 | } | ||
| 189 | |||
| 190 | } // namespace AudioCore | ||
diff --git a/src/audio_core/cubeb_sink.h b/src/audio_core/cubeb_sink.h new file mode 100644 index 000000000..d07113f1f --- /dev/null +++ b/src/audio_core/cubeb_sink.h | |||
| @@ -0,0 +1,31 @@ | |||
| 1 | // Copyright 2018 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <string> | ||
| 8 | #include <vector> | ||
| 9 | |||
| 10 | #include <cubeb/cubeb.h> | ||
| 11 | |||
| 12 | #include "audio_core/sink.h" | ||
| 13 | |||
| 14 | namespace AudioCore { | ||
| 15 | |||
| 16 | class CubebSink final : public Sink { | ||
| 17 | public: | ||
| 18 | explicit CubebSink(std::string device_id); | ||
| 19 | ~CubebSink() override; | ||
| 20 | |||
| 21 | SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels) override; | ||
| 22 | |||
| 23 | private: | ||
| 24 | cubeb* ctx{}; | ||
| 25 | cubeb_devid output_device{}; | ||
| 26 | std::vector<SinkStreamPtr> sink_streams; | ||
| 27 | }; | ||
| 28 | |||
| 29 | std::vector<std::string> ListCubebSinkDevices(); | ||
| 30 | |||
| 31 | } // namespace AudioCore | ||
diff --git a/src/audio_core/null_sink.h b/src/audio_core/null_sink.h new file mode 100644 index 000000000..2e04438f7 --- /dev/null +++ b/src/audio_core/null_sink.h | |||
| @@ -0,0 +1,27 @@ | |||
| 1 | // Copyright 2018 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include "audio_core/sink.h" | ||
| 8 | |||
| 9 | namespace AudioCore { | ||
| 10 | |||
| 11 | class NullSink final : public Sink { | ||
| 12 | public: | ||
| 13 | explicit NullSink(std::string){}; | ||
| 14 | ~NullSink() override = default; | ||
| 15 | |||
| 16 | SinkStream& AcquireSinkStream(u32 /*sample_rate*/, u32 /*num_channels*/) override { | ||
| 17 | return null_sink_stream; | ||
| 18 | } | ||
| 19 | |||
| 20 | private: | ||
| 21 | struct NullSinkStreamImpl final : SinkStream { | ||
| 22 | void EnqueueSamples(u32 /*num_channels*/, const s16* /*samples*/, | ||
| 23 | size_t /*sample_count*/) override {} | ||
| 24 | } null_sink_stream; | ||
| 25 | }; | ||
| 26 | |||
| 27 | } // namespace AudioCore | ||
diff --git a/src/audio_core/sink.h b/src/audio_core/sink.h new file mode 100644 index 000000000..d1bb98c3d --- /dev/null +++ b/src/audio_core/sink.h | |||
| @@ -0,0 +1,29 @@ | |||
| 1 | // Copyright 2018 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <memory> | ||
| 8 | |||
| 9 | #include "audio_core/sink_stream.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | |||
| 12 | namespace AudioCore { | ||
| 13 | |||
| 14 | constexpr char auto_device_name[] = "auto"; | ||
| 15 | |||
| 16 | /** | ||
| 17 | * This class is an interface for an audio sink. An audio sink accepts samples in stereo signed | ||
| 18 | * PCM16 format to be output. Sinks *do not* handle resampling and expect the correct sample rate. | ||
| 19 | * They are dumb outputs. | ||
| 20 | */ | ||
| 21 | class Sink { | ||
| 22 | public: | ||
| 23 | virtual ~Sink() = default; | ||
| 24 | virtual SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels) = 0; | ||
| 25 | }; | ||
| 26 | |||
| 27 | using SinkPtr = std::unique_ptr<Sink>; | ||
| 28 | |||
| 29 | } // namespace AudioCore | ||
diff --git a/src/audio_core/sink_details.cpp b/src/audio_core/sink_details.cpp new file mode 100644 index 000000000..955ba20fb --- /dev/null +++ b/src/audio_core/sink_details.cpp | |||
| @@ -0,0 +1,44 @@ | |||
| 1 | // Copyright 2018 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <algorithm> | ||
| 6 | #include <memory> | ||
| 7 | #include <string> | ||
| 8 | #include <vector> | ||
| 9 | #include "audio_core/null_sink.h" | ||
| 10 | #include "audio_core/sink_details.h" | ||
| 11 | #ifdef HAVE_CUBEB | ||
| 12 | #include "audio_core/cubeb_sink.h" | ||
| 13 | #endif | ||
| 14 | #include "common/logging/log.h" | ||
| 15 | |||
| 16 | namespace AudioCore { | ||
| 17 | |||
| 18 | // g_sink_details is ordered in terms of desirability, with the best choice at the top. | ||
| 19 | const std::vector<SinkDetails> g_sink_details = { | ||
| 20 | #ifdef HAVE_CUBEB | ||
| 21 | SinkDetails{"cubeb", &std::make_unique<CubebSink, std::string>, &ListCubebSinkDevices}, | ||
| 22 | #endif | ||
| 23 | SinkDetails{"null", &std::make_unique<NullSink, std::string>, | ||
| 24 | [] { return std::vector<std::string>{"null"}; }}, | ||
| 25 | }; | ||
| 26 | |||
| 27 | const SinkDetails& GetSinkDetails(std::string sink_id) { | ||
| 28 | auto iter = | ||
| 29 | std::find_if(g_sink_details.begin(), g_sink_details.end(), | ||
| 30 | [sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; }); | ||
| 31 | |||
| 32 | if (sink_id == "auto" || iter == g_sink_details.end()) { | ||
| 33 | if (sink_id != "auto") { | ||
| 34 | LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id {}", sink_id); | ||
| 35 | } | ||
| 36 | // Auto-select. | ||
| 37 | // g_sink_details is ordered in terms of desirability, with the best choice at the front. | ||
| 38 | iter = g_sink_details.begin(); | ||
| 39 | } | ||
| 40 | |||
| 41 | return *iter; | ||
| 42 | } | ||
| 43 | |||
| 44 | } // namespace AudioCore | ||
diff --git a/src/audio_core/sink_details.h b/src/audio_core/sink_details.h new file mode 100644 index 000000000..aa8aae1a9 --- /dev/null +++ b/src/audio_core/sink_details.h | |||
| @@ -0,0 +1,32 @@ | |||
| 1 | // Copyright 2018 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <functional> | ||
| 8 | #include <memory> | ||
| 9 | #include <vector> | ||
| 10 | |||
| 11 | namespace AudioCore { | ||
| 12 | |||
| 13 | class Sink; | ||
| 14 | |||
| 15 | struct SinkDetails { | ||
| 16 | SinkDetails(const char* id_, std::function<std::unique_ptr<Sink>(std::string)> factory_, | ||
| 17 | std::function<std::vector<std::string>()> list_devices_) | ||
| 18 | : id(id_), factory(factory_), list_devices(list_devices_) {} | ||
| 19 | |||
| 20 | /// Name for this sink. | ||
| 21 | const char* id; | ||
| 22 | /// A method to call to construct an instance of this type of sink. | ||
| 23 | std::function<std::unique_ptr<Sink>(std::string device_id)> factory; | ||
| 24 | /// A method to call to list available devices. | ||
| 25 | std::function<std::vector<std::string>()> list_devices; | ||
| 26 | }; | ||
| 27 | |||
| 28 | extern const std::vector<SinkDetails> g_sink_details; | ||
| 29 | |||
| 30 | const SinkDetails& GetSinkDetails(std::string sink_id); | ||
| 31 | |||
| 32 | } // namespace AudioCore | ||
diff --git a/src/audio_core/sink_stream.h b/src/audio_core/sink_stream.h new file mode 100644 index 000000000..e7a3f01b0 --- /dev/null +++ b/src/audio_core/sink_stream.h | |||
| @@ -0,0 +1,32 @@ | |||
| 1 | // Copyright 2018 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <memory> | ||
| 8 | |||
| 9 | #include "common/common_types.h" | ||
| 10 | |||
| 11 | namespace AudioCore { | ||
| 12 | |||
| 13 | /** | ||
| 14 | * Accepts samples in stereo signed PCM16 format to be output. Sinks *do not* handle resampling and | ||
| 15 | * expect the correct sample rate. They are dumb outputs. | ||
| 16 | */ | ||
| 17 | class SinkStream { | ||
| 18 | public: | ||
| 19 | virtual ~SinkStream() = default; | ||
| 20 | |||
| 21 | /** | ||
| 22 | * Feed stereo samples to sink. | ||
| 23 | * @param num_channels Number of channels used. | ||
| 24 | * @param samples Samples in interleaved stereo PCM16 format. | ||
| 25 | * @param sample_count Number of samples. | ||
| 26 | */ | ||
| 27 | virtual void EnqueueSamples(u32 num_channels, const s16* samples, size_t sample_count) = 0; | ||
| 28 | }; | ||
| 29 | |||
| 30 | using SinkStreamPtr = std::unique_ptr<SinkStream>; | ||
| 31 | |||
| 32 | } // namespace AudioCore | ||
diff --git a/src/audio_core/stream.cpp b/src/audio_core/stream.cpp index 82bff4b9e..689f51a1d 100644 --- a/src/audio_core/stream.cpp +++ b/src/audio_core/stream.cpp | |||
| @@ -7,30 +7,37 @@ | |||
| 7 | #include "core/core_timing.h" | 7 | #include "core/core_timing.h" |
| 8 | #include "core/core_timing_util.h" | 8 | #include "core/core_timing_util.h" |
| 9 | 9 | ||
| 10 | #include "audio_core/sink.h" | ||
| 11 | #include "audio_core/sink_details.h" | ||
| 10 | #include "audio_core/stream.h" | 12 | #include "audio_core/stream.h" |
| 11 | 13 | ||
| 12 | namespace AudioCore { | 14 | namespace AudioCore { |
| 13 | 15 | ||
| 14 | constexpr size_t MaxAudioBufferCount{32}; | 16 | constexpr size_t MaxAudioBufferCount{32}; |
| 15 | 17 | ||
| 16 | /// Returns the sample size for the specified audio stream format | 18 | u32 Stream::GetNumChannels() const { |
| 17 | static size_t SampleSizeFromFormat(Stream::Format format) { | ||
| 18 | switch (format) { | 19 | switch (format) { |
| 19 | case Stream::Format::Mono16: | 20 | case Format::Mono16: |
| 21 | return 1; | ||
| 22 | case Format::Stereo16: | ||
| 20 | return 2; | 23 | return 2; |
| 21 | case Stream::Format::Stereo16: | 24 | case Format::Multi51Channel16: |
| 22 | return 4; | 25 | return 6; |
| 23 | case Stream::Format::Multi51Channel16: | 26 | } |
| 24 | return 12; | ||
| 25 | }; | ||
| 26 | |||
| 27 | LOG_CRITICAL(Audio, "Unimplemented format={}", static_cast<u32>(format)); | 27 | LOG_CRITICAL(Audio, "Unimplemented format={}", static_cast<u32>(format)); |
| 28 | UNREACHABLE(); | 28 | UNREACHABLE(); |
| 29 | return {}; | 29 | return {}; |
| 30 | } | 30 | } |
| 31 | 31 | ||
| 32 | Stream::Stream(int sample_rate, Format format, ReleaseCallback&& release_callback) | 32 | u32 Stream::GetSampleSize() const { |
| 33 | : sample_rate{sample_rate}, format{format}, release_callback{std::move(release_callback)} { | 33 | return GetNumChannels() * 2; |
| 34 | } | ||
| 35 | |||
| 36 | Stream::Stream(u32 sample_rate, Format format, ReleaseCallback&& release_callback, | ||
| 37 | SinkStream& sink_stream) | ||
| 38 | : sample_rate{sample_rate}, format{format}, release_callback{std::move(release_callback)}, | ||
| 39 | sink_stream{sink_stream} { | ||
| 40 | |||
| 34 | release_event = CoreTiming::RegisterEvent( | 41 | release_event = CoreTiming::RegisterEvent( |
| 35 | "Stream::Release", [this](u64 userdata, int cycles_late) { ReleaseActiveBuffer(); }); | 42 | "Stream::Release", [this](u64 userdata, int cycles_late) { ReleaseActiveBuffer(); }); |
| 36 | } | 43 | } |
| @@ -45,7 +52,7 @@ void Stream::Stop() { | |||
| 45 | } | 52 | } |
| 46 | 53 | ||
| 47 | s64 Stream::GetBufferReleaseCycles(const Buffer& buffer) const { | 54 | s64 Stream::GetBufferReleaseCycles(const Buffer& buffer) const { |
| 48 | const size_t num_samples{buffer.GetData().size() / SampleSizeFromFormat(format)}; | 55 | const size_t num_samples{buffer.GetData().size() / GetSampleSize()}; |
| 49 | return CoreTiming::usToCycles((static_cast<u64>(num_samples) * 1000000) / sample_rate); | 56 | return CoreTiming::usToCycles((static_cast<u64>(num_samples) * 1000000) / sample_rate); |
| 50 | } | 57 | } |
| 51 | 58 | ||
| @@ -68,6 +75,10 @@ void Stream::PlayNextBuffer() { | |||
| 68 | active_buffer = queued_buffers.front(); | 75 | active_buffer = queued_buffers.front(); |
| 69 | queued_buffers.pop(); | 76 | queued_buffers.pop(); |
| 70 | 77 | ||
| 78 | sink_stream.EnqueueSamples(GetNumChannels(), | ||
| 79 | reinterpret_cast<const s16*>(active_buffer->GetData().data()), | ||
| 80 | active_buffer->GetData().size() / GetSampleSize()); | ||
| 81 | |||
| 71 | CoreTiming::ScheduleEventThreadsafe(GetBufferReleaseCycles(*active_buffer), release_event, {}); | 82 | CoreTiming::ScheduleEventThreadsafe(GetBufferReleaseCycles(*active_buffer), release_event, {}); |
| 72 | } | 83 | } |
| 73 | 84 | ||
diff --git a/src/audio_core/stream.h b/src/audio_core/stream.h index 5f43b0798..35253920e 100644 --- a/src/audio_core/stream.h +++ b/src/audio_core/stream.h | |||
| @@ -10,14 +10,13 @@ | |||
| 10 | #include <queue> | 10 | #include <queue> |
| 11 | 11 | ||
| 12 | #include "audio_core/buffer.h" | 12 | #include "audio_core/buffer.h" |
| 13 | #include "audio_core/sink_stream.h" | ||
| 13 | #include "common/assert.h" | 14 | #include "common/assert.h" |
| 14 | #include "common/common_types.h" | 15 | #include "common/common_types.h" |
| 15 | #include "core/core_timing.h" | 16 | #include "core/core_timing.h" |
| 16 | 17 | ||
| 17 | namespace AudioCore { | 18 | namespace AudioCore { |
| 18 | 19 | ||
| 19 | using BufferPtr = std::shared_ptr<Buffer>; | ||
| 20 | |||
| 21 | /** | 20 | /** |
| 22 | * Represents an audio stream, which is a sequence of queued buffers, to be outputed by AudioOut | 21 | * Represents an audio stream, which is a sequence of queued buffers, to be outputed by AudioOut |
| 23 | */ | 22 | */ |
| @@ -33,7 +32,8 @@ public: | |||
| 33 | /// Callback function type, used to change guest state on a buffer being released | 32 | /// Callback function type, used to change guest state on a buffer being released |
| 34 | using ReleaseCallback = std::function<void()>; | 33 | using ReleaseCallback = std::function<void()>; |
| 35 | 34 | ||
| 36 | Stream(int sample_rate, Format format, ReleaseCallback&& release_callback); | 35 | Stream(u32 sample_rate, Format format, ReleaseCallback&& release_callback, |
| 36 | SinkStream& sink_stream); | ||
| 37 | 37 | ||
| 38 | /// Plays the audio stream | 38 | /// Plays the audio stream |
| 39 | void Play(); | 39 | void Play(); |
| @@ -60,6 +60,17 @@ public: | |||
| 60 | return queued_buffers.size(); | 60 | return queued_buffers.size(); |
| 61 | } | 61 | } |
| 62 | 62 | ||
| 63 | /// Gets the sample rate | ||
| 64 | u32 GetSampleRate() const { | ||
| 65 | return sample_rate; | ||
| 66 | } | ||
| 67 | |||
| 68 | /// Gets the number of channels | ||
| 69 | u32 GetNumChannels() const; | ||
| 70 | |||
| 71 | /// Gets the sample size in bytes | ||
| 72 | u32 GetSampleSize() const; | ||
| 73 | |||
| 63 | private: | 74 | private: |
| 64 | /// Current state of the stream | 75 | /// Current state of the stream |
| 65 | enum class State { | 76 | enum class State { |
| @@ -76,7 +87,7 @@ private: | |||
| 76 | /// Gets the number of core cycles when the specified buffer will be released | 87 | /// Gets the number of core cycles when the specified buffer will be released |
| 77 | s64 GetBufferReleaseCycles(const Buffer& buffer) const; | 88 | s64 GetBufferReleaseCycles(const Buffer& buffer) const; |
| 78 | 89 | ||
| 79 | int sample_rate; ///< Sample rate of the stream | 90 | u32 sample_rate; ///< Sample rate of the stream |
| 80 | Format format; ///< Format of the stream | 91 | Format format; ///< Format of the stream |
| 81 | ReleaseCallback release_callback; ///< Buffer release callback for the stream | 92 | ReleaseCallback release_callback; ///< Buffer release callback for the stream |
| 82 | State state{State::Stopped}; ///< Playback state of the stream | 93 | State state{State::Stopped}; ///< Playback state of the stream |
| @@ -84,6 +95,9 @@ private: | |||
| 84 | BufferPtr active_buffer; ///< Actively playing buffer in the stream | 95 | BufferPtr active_buffer; ///< Actively playing buffer in the stream |
| 85 | std::queue<BufferPtr> queued_buffers; ///< Buffers queued to be played in the stream | 96 | std::queue<BufferPtr> queued_buffers; ///< Buffers queued to be played in the stream |
| 86 | std::queue<BufferPtr> released_buffers; ///< Buffers recently released from the stream | 97 | std::queue<BufferPtr> released_buffers; ///< Buffers recently released from the stream |
| 98 | SinkStream& sink_stream; ///< Output sink for the stream | ||
| 87 | }; | 99 | }; |
| 88 | 100 | ||
| 101 | using StreamPtr = std::shared_ptr<Stream>; | ||
| 102 | |||
| 89 | } // namespace AudioCore | 103 | } // namespace AudioCore |
diff --git a/src/core/core.cpp b/src/core/core.cpp index 186fa46df..b7f4b4532 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp | |||
| @@ -177,7 +177,6 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) { | |||
| 177 | } | 177 | } |
| 178 | 178 | ||
| 179 | gpu_core = std::make_unique<Tegra::GPU>(); | 179 | gpu_core = std::make_unique<Tegra::GPU>(); |
| 180 | audio_core = std::make_unique<AudioCore::AudioOut>(); | ||
| 181 | telemetry_session = std::make_unique<Core::TelemetrySession>(); | 180 | telemetry_session = std::make_unique<Core::TelemetrySession>(); |
| 182 | service_manager = std::make_shared<Service::SM::ServiceManager>(); | 181 | service_manager = std::make_shared<Service::SM::ServiceManager>(); |
| 183 | 182 | ||
| @@ -229,7 +228,6 @@ void System::Shutdown() { | |||
| 229 | service_manager.reset(); | 228 | service_manager.reset(); |
| 230 | telemetry_session.reset(); | 229 | telemetry_session.reset(); |
| 231 | gpu_core.reset(); | 230 | gpu_core.reset(); |
| 232 | audio_core.reset(); | ||
| 233 | 231 | ||
| 234 | // Close all CPU/threading state | 232 | // Close all CPU/threading state |
| 235 | cpu_barrier->NotifyEnd(); | 233 | cpu_barrier->NotifyEnd(); |
diff --git a/src/core/core.h b/src/core/core.h index 6f4df775f..c123fe401 100644 --- a/src/core/core.h +++ b/src/core/core.h | |||
| @@ -8,7 +8,6 @@ | |||
| 8 | #include <memory> | 8 | #include <memory> |
| 9 | #include <string> | 9 | #include <string> |
| 10 | #include <thread> | 10 | #include <thread> |
| 11 | #include "audio_core/audio_out.h" | ||
| 12 | #include "common/common_types.h" | 11 | #include "common/common_types.h" |
| 13 | #include "core/arm/exclusive_monitor.h" | 12 | #include "core/arm/exclusive_monitor.h" |
| 14 | #include "core/core_cpu.h" | 13 | #include "core/core_cpu.h" |
| @@ -132,11 +131,6 @@ public: | |||
| 132 | return *gpu_core; | 131 | return *gpu_core; |
| 133 | } | 132 | } |
| 134 | 133 | ||
| 135 | /// Gets the AudioCore interface | ||
| 136 | AudioCore::AudioOut& AudioCore() { | ||
| 137 | return *audio_core; | ||
| 138 | } | ||
| 139 | |||
| 140 | /// Gets the scheduler for the CPU core that is currently running | 134 | /// Gets the scheduler for the CPU core that is currently running |
| 141 | Kernel::Scheduler& CurrentScheduler() { | 135 | Kernel::Scheduler& CurrentScheduler() { |
| 142 | return *CurrentCpuCore().Scheduler(); | 136 | return *CurrentCpuCore().Scheduler(); |
| @@ -201,7 +195,6 @@ private: | |||
| 201 | /// AppLoader used to load the current executing application | 195 | /// AppLoader used to load the current executing application |
| 202 | std::unique_ptr<Loader::AppLoader> app_loader; | 196 | std::unique_ptr<Loader::AppLoader> app_loader; |
| 203 | std::unique_ptr<Tegra::GPU> gpu_core; | 197 | std::unique_ptr<Tegra::GPU> gpu_core; |
| 204 | std::unique_ptr<AudioCore::AudioOut> audio_core; | ||
| 205 | std::shared_ptr<Tegra::DebugContext> debug_context; | 198 | std::shared_ptr<Tegra::DebugContext> debug_context; |
| 206 | Kernel::SharedPtr<Kernel::Process> current_process; | 199 | Kernel::SharedPtr<Kernel::Process> current_process; |
| 207 | std::shared_ptr<ExclusiveMonitor> cpu_exclusive_monitor; | 200 | std::shared_ptr<ExclusiveMonitor> cpu_exclusive_monitor; |
diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp index a15d53ff8..ab37c2a69 100644 --- a/src/core/hle/service/audio/audout_u.cpp +++ b/src/core/hle/service/audio/audout_u.cpp | |||
| @@ -25,9 +25,8 @@ constexpr int DefaultSampleRate{48000}; | |||
| 25 | 25 | ||
| 26 | class IAudioOut final : public ServiceFramework<IAudioOut> { | 26 | class IAudioOut final : public ServiceFramework<IAudioOut> { |
| 27 | public: | 27 | public: |
| 28 | IAudioOut(AudoutParams audio_params) | 28 | IAudioOut(AudoutParams audio_params, AudioCore::AudioOut& audio_core) |
| 29 | : ServiceFramework("IAudioOut"), audio_params(audio_params), | 29 | : ServiceFramework("IAudioOut"), audio_params(audio_params), audio_core(audio_core) { |
| 30 | audio_core(Core::System::GetInstance().AudioCore()) { | ||
| 31 | 30 | ||
| 32 | static const FunctionInfo functions[] = { | 31 | static const FunctionInfo functions[] = { |
| 33 | {0, &IAudioOut::GetAudioOutState, "GetAudioOutState"}, | 32 | {0, &IAudioOut::GetAudioOutState, "GetAudioOutState"}, |
| @@ -195,7 +194,7 @@ void AudOutU::OpenAudioOutImpl(Kernel::HLERequestContext& ctx) { | |||
| 195 | // TODO(bunnei): Support more than one IAudioOut interface. When we add this, ListAudioOutsImpl | 194 | // TODO(bunnei): Support more than one IAudioOut interface. When we add this, ListAudioOutsImpl |
| 196 | // will likely need to be updated as well. | 195 | // will likely need to be updated as well. |
| 197 | ASSERT_MSG(!audio_out_interface, "Unimplemented"); | 196 | ASSERT_MSG(!audio_out_interface, "Unimplemented"); |
| 198 | audio_out_interface = std::make_shared<IAudioOut>(std::move(params)); | 197 | audio_out_interface = std::make_shared<IAudioOut>(std::move(params), *audio_core); |
| 199 | 198 | ||
| 200 | IPC::ResponseBuilder rb{ctx, 6, 0, 1}; | 199 | IPC::ResponseBuilder rb{ctx, 6, 0, 1}; |
| 201 | rb.Push(RESULT_SUCCESS); | 200 | rb.Push(RESULT_SUCCESS); |
| @@ -212,6 +211,7 @@ AudOutU::AudOutU() : ServiceFramework("audout:u") { | |||
| 212 | {2, &AudOutU::ListAudioOutsImpl, "ListAudioOutsAuto"}, | 211 | {2, &AudOutU::ListAudioOutsImpl, "ListAudioOutsAuto"}, |
| 213 | {3, &AudOutU::OpenAudioOutImpl, "OpenAudioOutAuto"}}; | 212 | {3, &AudOutU::OpenAudioOutImpl, "OpenAudioOutAuto"}}; |
| 214 | RegisterHandlers(functions); | 213 | RegisterHandlers(functions); |
| 214 | audio_core = std::make_unique<AudioCore::AudioOut>(); | ||
| 215 | } | 215 | } |
| 216 | 216 | ||
| 217 | } // namespace Service::Audio | 217 | } // namespace Service::Audio |
diff --git a/src/core/hle/service/audio/audout_u.h b/src/core/hle/service/audio/audout_u.h index bc43f1f44..e5c2184d5 100644 --- a/src/core/hle/service/audio/audout_u.h +++ b/src/core/hle/service/audio/audout_u.h | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | 4 | ||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include "audio_core/audio_out.h" | ||
| 7 | #include "core/hle/service/service.h" | 8 | #include "core/hle/service/service.h" |
| 8 | 9 | ||
| 9 | namespace Kernel { | 10 | namespace Kernel { |
| @@ -33,6 +34,7 @@ public: | |||
| 33 | 34 | ||
| 34 | private: | 35 | private: |
| 35 | std::shared_ptr<IAudioOut> audio_out_interface; | 36 | std::shared_ptr<IAudioOut> audio_out_interface; |
| 37 | std::unique_ptr<AudioCore::AudioOut> audio_core; | ||
| 36 | 38 | ||
| 37 | void ListAudioOutsImpl(Kernel::HLERequestContext& ctx); | 39 | void ListAudioOutsImpl(Kernel::HLERequestContext& ctx); |
| 38 | void OpenAudioOutImpl(Kernel::HLERequestContext& ctx); | 40 | void OpenAudioOutImpl(Kernel::HLERequestContext& ctx); |