summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/audio_core/CMakeLists.txt7
-rw-r--r--src/audio_core/audio_out.cpp13
-rw-r--r--src/audio_core/audio_out.h2
-rw-r--r--src/audio_core/cubeb_sink.cpp190
-rw-r--r--src/audio_core/cubeb_sink.h31
-rw-r--r--src/audio_core/sink_details.cpp6
-rw-r--r--src/audio_core/stream.cpp11
-rw-r--r--src/audio_core/stream.h7
8 files changed, 261 insertions, 6 deletions
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt
index 6bc8f586a..81121167d 100644
--- a/src/audio_core/CMakeLists.txt
+++ b/src/audio_core/CMakeLists.txt
@@ -2,6 +2,8 @@ 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
5 null_sink.h 7 null_sink.h
6 stream.cpp 8 stream.cpp
7 stream.h 9 stream.h
@@ -14,3 +16,8 @@ add_library(audio_core STATIC
14create_target_directory_groups(audio_core) 16create_target_directory_groups(audio_core)
15 17
16target_link_libraries(audio_core PUBLIC common core) 18target_link_libraries(audio_core PUBLIC common core)
19
20if(ENABLE_CUBEB)
21 target_link_libraries(audio_core PRIVATE cubeb)
22 target_compile_definitions(audio_core PRIVATE -DHAVE_CUBEB=1)
23endif()
diff --git a/src/audio_core/audio_out.cpp b/src/audio_core/audio_out.cpp
index 43414f197..77cedb6ba 100644
--- a/src/audio_core/audio_out.cpp
+++ b/src/audio_core/audio_out.cpp
@@ -3,6 +3,8 @@
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
@@ -26,9 +28,14 @@ static Stream::Format ChannelsToStreamFormat(u32 num_channels) {
26 28
27StreamPtr AudioOut::OpenStream(u32 sample_rate, u32 num_channels, 29StreamPtr 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
34std::vector<u64> AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream, size_t max_count) { 41std::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 962360d09..8d9b695d4 100644
--- a/src/audio_core/audio_out.h
+++ b/src/audio_core/audio_out.h
@@ -8,6 +8,7 @@
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
@@ -36,7 +37,6 @@ public:
36 37
37private: 38private:
38 SinkPtr sink; 39 SinkPtr sink;
39 std::vector<StreamPtr> streams;
40}; 40};
41 41
42} // namespace AudioCore 42} // 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
12namespace AudioCore {
13
14class SinkStreamImpl final : public SinkStream {
15public:
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, &params, &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, &params, 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
83private:
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
96CubebSink::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
120CubebSink::~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
132SinkStream& 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
137long 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
162void SinkStreamImpl::StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state) {}
163
164std::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
14namespace AudioCore {
15
16class CubebSink final : public Sink {
17public:
18 explicit CubebSink(std::string device_id);
19 ~CubebSink() override;
20
21 SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels) override;
22
23private:
24 cubeb* ctx{};
25 cubeb_devid output_device{};
26 std::vector<SinkStreamPtr> sink_streams;
27};
28
29std::vector<std::string> ListCubebSinkDevices();
30
31} // namespace AudioCore
diff --git a/src/audio_core/sink_details.cpp b/src/audio_core/sink_details.cpp
index 6396d8065..955ba20fb 100644
--- a/src/audio_core/sink_details.cpp
+++ b/src/audio_core/sink_details.cpp
@@ -8,12 +8,18 @@
8#include <vector> 8#include <vector>
9#include "audio_core/null_sink.h" 9#include "audio_core/null_sink.h"
10#include "audio_core/sink_details.h" 10#include "audio_core/sink_details.h"
11#ifdef HAVE_CUBEB
12#include "audio_core/cubeb_sink.h"
13#endif
11#include "common/logging/log.h" 14#include "common/logging/log.h"
12 15
13namespace AudioCore { 16namespace AudioCore {
14 17
15// g_sink_details is ordered in terms of desirability, with the best choice at the top. 18// g_sink_details is ordered in terms of desirability, with the best choice at the top.
16const std::vector<SinkDetails> g_sink_details = { 19const std::vector<SinkDetails> g_sink_details = {
20#ifdef HAVE_CUBEB
21 SinkDetails{"cubeb", &std::make_unique<CubebSink, std::string>, &ListCubebSinkDevices},
22#endif
17 SinkDetails{"null", &std::make_unique<NullSink, std::string>, 23 SinkDetails{"null", &std::make_unique<NullSink, std::string>,
18 [] { return std::vector<std::string>{"null"}; }}, 24 [] { return std::vector<std::string>{"null"}; }},
19}; 25};
diff --git a/src/audio_core/stream.cpp b/src/audio_core/stream.cpp
index 63edc6c8d..689f51a1d 100644
--- a/src/audio_core/stream.cpp
+++ b/src/audio_core/stream.cpp
@@ -7,6 +7,8 @@
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
12namespace AudioCore { 14namespace AudioCore {
@@ -31,6 +33,11 @@ u32 Stream::GetSampleSize() const {
31 return GetNumChannels() * 2; 33 return GetNumChannels() * 2;
32} 34}
33 35
36Stream::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}
@@ -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 5c1005899..35253920e 100644
--- a/src/audio_core/stream.h
+++ b/src/audio_core/stream.h
@@ -10,6 +10,7 @@
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"
@@ -31,7 +32,8 @@ public:
31 /// 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
32 using ReleaseCallback = std::function<void()>; 33 using ReleaseCallback = std::function<void()>;
33 34
34 Stream(int sample_rate, Format format, ReleaseCallback&& release_callback); 35 Stream(u32 sample_rate, Format format, ReleaseCallback&& release_callback,
36 SinkStream& sink_stream);
35 37
36 /// Plays the audio stream 38 /// Plays the audio stream
37 void Play(); 39 void Play();
@@ -85,7 +87,7 @@ private:
85 /// 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
86 s64 GetBufferReleaseCycles(const Buffer& buffer) const; 88 s64 GetBufferReleaseCycles(const Buffer& buffer) const;
87 89
88 int sample_rate; ///< Sample rate of the stream 90 u32 sample_rate; ///< Sample rate of the stream
89 Format format; ///< Format of the stream 91 Format format; ///< Format of the stream
90 ReleaseCallback release_callback; ///< Buffer release callback for the stream 92 ReleaseCallback release_callback; ///< Buffer release callback for the stream
91 State state{State::Stopped}; ///< Playback state of the stream 93 State state{State::Stopped}; ///< Playback state of the stream
@@ -93,6 +95,7 @@ private:
93 BufferPtr active_buffer; ///< Actively playing buffer in the stream 95 BufferPtr active_buffer; ///< Actively playing buffer in the stream
94 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
95 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
96}; 99};
97 100
98using StreamPtr = std::shared_ptr<Stream>; 101using StreamPtr = std::shared_ptr<Stream>;