summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/audio_core/CMakeLists.txt1
-rw-r--r--src/audio_core/audio_core.cpp8
-rw-r--r--src/audio_core/audio_core.h16
-rw-r--r--src/audio_core/device/audio_buffer.h4
-rw-r--r--src/audio_core/device/audio_buffers.h13
-rw-r--r--src/audio_core/device/device_session.cpp52
-rw-r--r--src/audio_core/device/device_session.h27
-rw-r--r--src/audio_core/in/audio_in_system.cpp10
-rw-r--r--src/audio_core/out/audio_out_system.cpp10
-rw-r--r--src/audio_core/renderer/adsp/audio_renderer.cpp9
-rw-r--r--src/audio_core/renderer/behavior/behavior_info.cpp14
-rw-r--r--src/audio_core/renderer/command/sink/device.cpp4
-rw-r--r--src/audio_core/renderer/system_manager.cpp35
-rw-r--r--src/audio_core/sink/cubeb_sink.cpp349
-rw-r--r--src/audio_core/sink/cubeb_sink.h2
-rw-r--r--src/audio_core/sink/null_sink.h47
-rw-r--r--src/audio_core/sink/sdl2_sink.cpp350
-rw-r--r--src/audio_core/sink/sdl2_sink.h2
-rw-r--r--src/audio_core/sink/sink.h2
-rw-r--r--src/audio_core/sink/sink_details.cpp6
-rw-r--r--src/audio_core/sink/sink_stream.cpp265
-rw-r--r--src/audio_core/sink/sink_stream.h171
-rw-r--r--src/core/hle/result.h2
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp7
24 files changed, 574 insertions, 832 deletions
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt
index 5fe1d5fa5..144f1bab2 100644
--- a/src/audio_core/CMakeLists.txt
+++ b/src/audio_core/CMakeLists.txt
@@ -194,6 +194,7 @@ add_library(audio_core STATIC
194 sink/sink.h 194 sink/sink.h
195 sink/sink_details.cpp 195 sink/sink_details.cpp
196 sink/sink_details.h 196 sink/sink_details.h
197 sink/sink_stream.cpp
197 sink/sink_stream.h 198 sink/sink_stream.h
198) 199)
199 200
diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp
index 78e615a10..9feec1829 100644
--- a/src/audio_core/audio_core.cpp
+++ b/src/audio_core/audio_core.cpp
@@ -57,12 +57,12 @@ void AudioCore::PauseSinks(const bool pausing) const {
57 } 57 }
58} 58}
59 59
60u32 AudioCore::GetStreamQueue() const { 60void AudioCore::SetNVDECActive(bool active) {
61 return estimated_queue.load(); 61 nvdec_active = active;
62} 62}
63 63
64void AudioCore::SetStreamQueue(u32 size) { 64bool AudioCore::IsNVDECActive() const {
65 estimated_queue.store(size); 65 return nvdec_active;
66} 66}
67 67
68} // namespace AudioCore 68} // namespace AudioCore
diff --git a/src/audio_core/audio_core.h b/src/audio_core/audio_core.h
index 0f7d61ee4..ac9afefaa 100644
--- a/src/audio_core/audio_core.h
+++ b/src/audio_core/audio_core.h
@@ -66,18 +66,16 @@ public:
66 void PauseSinks(bool pausing) const; 66 void PauseSinks(bool pausing) const;
67 67
68 /** 68 /**
69 * Get the size of the current stream queue. 69 * Toggle NVDEC state, used to avoid stall in playback.
70 * 70 *
71 * @return Current stream queue size. 71 * @param active - Set true if nvdec is active, otherwise false.
72 */ 72 */
73 u32 GetStreamQueue() const; 73 void SetNVDECActive(bool active);
74 74
75 /** 75 /**
76 * Get the size of the current stream queue. 76 * Get NVDEC state.
77 *
78 * @param size - New stream size.
79 */ 77 */
80 void SetStreamQueue(u32 size); 78 bool IsNVDECActive() const;
81 79
82private: 80private:
83 /** 81 /**
@@ -93,8 +91,8 @@ private:
93 std::unique_ptr<Sink::Sink> input_sink; 91 std::unique_ptr<Sink::Sink> input_sink;
94 /// The ADSP in the sysmodule 92 /// The ADSP in the sysmodule
95 std::unique_ptr<AudioRenderer::ADSP::ADSP> adsp; 93 std::unique_ptr<AudioRenderer::ADSP::ADSP> adsp;
96 /// Current size of the stream queue 94 /// Is NVDec currently active?
97 std::atomic<u32> estimated_queue{0}; 95 bool nvdec_active{false};
98}; 96};
99 97
100} // namespace AudioCore 98} // namespace AudioCore
diff --git a/src/audio_core/device/audio_buffer.h b/src/audio_core/device/audio_buffer.h
index cae7fa970..7128ef72a 100644
--- a/src/audio_core/device/audio_buffer.h
+++ b/src/audio_core/device/audio_buffer.h
@@ -8,6 +8,10 @@
8namespace AudioCore { 8namespace AudioCore {
9 9
10struct AudioBuffer { 10struct AudioBuffer {
11 /// Timestamp this buffer started playing.
12 u64 start_timestamp;
13 /// Timestamp this buffer should finish playing.
14 u64 end_timestamp;
11 /// Timestamp this buffer completed playing. 15 /// Timestamp this buffer completed playing.
12 s64 played_timestamp; 16 s64 played_timestamp;
13 /// Game memory address for these samples. 17 /// Game memory address for these samples.
diff --git a/src/audio_core/device/audio_buffers.h b/src/audio_core/device/audio_buffers.h
index 5d1979ea0..57c78d439 100644
--- a/src/audio_core/device/audio_buffers.h
+++ b/src/audio_core/device/audio_buffers.h
@@ -58,6 +58,7 @@ public:
58 if (index < 0) { 58 if (index < 0) {
59 index += N; 59 index += N;
60 } 60 }
61
61 out_buffers.push_back(buffers[index]); 62 out_buffers.push_back(buffers[index]);
62 registered_count++; 63 registered_count++;
63 registered_index = (registered_index + 1) % append_limit; 64 registered_index = (registered_index + 1) % append_limit;
@@ -100,7 +101,7 @@ public:
100 } 101 }
101 102
102 // Check with the backend if this buffer can be released yet. 103 // Check with the backend if this buffer can be released yet.
103 if (!session.IsBufferConsumed(buffers[index].tag)) { 104 if (!session.IsBufferConsumed(buffers[index])) {
104 break; 105 break;
105 } 106 }
106 107
@@ -280,6 +281,16 @@ public:
280 return true; 281 return true;
281 } 282 }
282 283
284 u64 GetNextTimestamp() const {
285 // Iterate backwards through the buffer queue, and take the most recent buffer's end
286 std::scoped_lock l{lock};
287 auto index{appended_index - 1};
288 if (index < 0) {
289 index += append_limit;
290 }
291 return buffers[index].end_timestamp;
292 }
293
283private: 294private:
284 /// Buffer lock 295 /// Buffer lock
285 mutable std::recursive_mutex lock{}; 296 mutable std::recursive_mutex lock{};
diff --git a/src/audio_core/device/device_session.cpp b/src/audio_core/device/device_session.cpp
index 095fc96ce..c71c3a376 100644
--- a/src/audio_core/device/device_session.cpp
+++ b/src/audio_core/device/device_session.cpp
@@ -7,11 +7,20 @@
7#include "audio_core/device/device_session.h" 7#include "audio_core/device/device_session.h"
8#include "audio_core/sink/sink_stream.h" 8#include "audio_core/sink/sink_stream.h"
9#include "core/core.h" 9#include "core/core.h"
10#include "core/core_timing.h"
10#include "core/memory.h" 11#include "core/memory.h"
11 12
12namespace AudioCore { 13namespace AudioCore {
13 14
14DeviceSession::DeviceSession(Core::System& system_) : system{system_} {} 15using namespace std::literals;
16constexpr auto INCREMENT_TIME{5ms};
17
18DeviceSession::DeviceSession(Core::System& system_)
19 : system{system_}, thread_event{Core::Timing::CreateEvent(
20 "AudioOutSampleTick",
21 [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) {
22 return ThreadFunc();
23 })} {}
15 24
16DeviceSession::~DeviceSession() { 25DeviceSession::~DeviceSession() {
17 Finalize(); 26 Finalize();
@@ -50,20 +59,21 @@ void DeviceSession::Finalize() {
50} 59}
51 60
52void DeviceSession::Start() { 61void DeviceSession::Start() {
53 stream->SetPlayedSampleCount(played_sample_count); 62 if (stream) {
54 stream->Start(); 63 stream->Start();
64 system.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds::zero(), INCREMENT_TIME,
65 thread_event);
66 }
55} 67}
56 68
57void DeviceSession::Stop() { 69void DeviceSession::Stop() {
58 if (stream) { 70 if (stream) {
59 played_sample_count = stream->GetPlayedSampleCount();
60 stream->Stop(); 71 stream->Stop();
72 system.CoreTiming().UnscheduleEvent(thread_event, {});
61 } 73 }
62} 74}
63 75
64void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const { 76void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const {
65 auto& memory{system.Memory()};
66
67 for (size_t i = 0; i < buffers.size(); i++) { 77 for (size_t i = 0; i < buffers.size(); i++) {
68 Sink::SinkBuffer new_buffer{ 78 Sink::SinkBuffer new_buffer{
69 .frames = buffers[i].size / (channel_count * sizeof(s16)), 79 .frames = buffers[i].size / (channel_count * sizeof(s16)),
@@ -77,7 +87,7 @@ void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const {
77 stream->AppendBuffer(new_buffer, samples); 87 stream->AppendBuffer(new_buffer, samples);
78 } else { 88 } else {
79 std::vector<s16> samples(buffers[i].size / sizeof(s16)); 89 std::vector<s16> samples(buffers[i].size / sizeof(s16));
80 memory.ReadBlockUnsafe(buffers[i].samples, samples.data(), buffers[i].size); 90 system.Memory().ReadBlockUnsafe(buffers[i].samples, samples.data(), buffers[i].size);
81 stream->AppendBuffer(new_buffer, samples); 91 stream->AppendBuffer(new_buffer, samples);
82 } 92 }
83 } 93 }
@@ -85,17 +95,13 @@ void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const {
85 95
86void DeviceSession::ReleaseBuffer(AudioBuffer& buffer) const { 96void DeviceSession::ReleaseBuffer(AudioBuffer& buffer) const {
87 if (type == Sink::StreamType::In) { 97 if (type == Sink::StreamType::In) {
88 auto& memory{system.Memory()};
89 auto samples{stream->ReleaseBuffer(buffer.size / sizeof(s16))}; 98 auto samples{stream->ReleaseBuffer(buffer.size / sizeof(s16))};
90 memory.WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size); 99 system.Memory().WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size);
91 } 100 }
92} 101}
93 102
94bool DeviceSession::IsBufferConsumed(u64 tag) const { 103bool DeviceSession::IsBufferConsumed(AudioBuffer& buffer) const {
95 if (stream) { 104 return played_sample_count >= buffer.end_timestamp;
96 return stream->IsBufferConsumed(tag);
97 }
98 return true;
99} 105}
100 106
101void DeviceSession::SetVolume(f32 volume) const { 107void DeviceSession::SetVolume(f32 volume) const {
@@ -105,10 +111,22 @@ void DeviceSession::SetVolume(f32 volume) const {
105} 111}
106 112
107u64 DeviceSession::GetPlayedSampleCount() const { 113u64 DeviceSession::GetPlayedSampleCount() const {
108 if (stream) { 114 return played_sample_count;
109 return stream->GetPlayedSampleCount(); 115}
116
117std::optional<std::chrono::nanoseconds> DeviceSession::ThreadFunc() {
118 // Add 5ms of samples at a 48K sample rate.
119 played_sample_count += 48'000 * INCREMENT_TIME / 1s;
120 if (type == Sink::StreamType::Out) {
121 system.AudioCore().GetAudioManager().SetEvent(Event::Type::AudioOutManager, true);
122 } else {
123 system.AudioCore().GetAudioManager().SetEvent(Event::Type::AudioInManager, true);
110 } 124 }
111 return 0; 125 return std::nullopt;
126}
127
128void DeviceSession::SetRingSize(u32 ring_size) {
129 stream->SetRingSize(ring_size);
112} 130}
113 131
114} // namespace AudioCore 132} // namespace AudioCore
diff --git a/src/audio_core/device/device_session.h b/src/audio_core/device/device_session.h
index 4a031b765..3414e2c06 100644
--- a/src/audio_core/device/device_session.h
+++ b/src/audio_core/device/device_session.h
@@ -3,6 +3,9 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <chrono>
7#include <memory>
8#include <optional>
6#include <span> 9#include <span>
7 10
8#include "audio_core/common/common.h" 11#include "audio_core/common/common.h"
@@ -11,9 +14,13 @@
11 14
12namespace Core { 15namespace Core {
13class System; 16class System;
14} 17namespace Timing {
18struct EventType;
19} // namespace Timing
20} // namespace Core
15 21
16namespace AudioCore { 22namespace AudioCore {
23
17namespace Sink { 24namespace Sink {
18class SinkStream; 25class SinkStream;
19struct SinkBuffer; 26struct SinkBuffer;
@@ -70,7 +77,7 @@ public:
70 * @param tag - Unqiue tag of the buffer to check. 77 * @param tag - Unqiue tag of the buffer to check.
71 * @return true if the buffer has been consumed, otherwise false. 78 * @return true if the buffer has been consumed, otherwise false.
72 */ 79 */
73 bool IsBufferConsumed(u64 tag) const; 80 bool IsBufferConsumed(AudioBuffer& buffer) const;
74 81
75 /** 82 /**
76 * Start this device session, starting the backend stream. 83 * Start this device session, starting the backend stream.
@@ -96,6 +103,16 @@ public:
96 */ 103 */
97 u64 GetPlayedSampleCount() const; 104 u64 GetPlayedSampleCount() const;
98 105
106 /*
107 * CoreTiming callback to increment played_sample_count over time.
108 */
109 std::optional<std::chrono::nanoseconds> ThreadFunc();
110
111 /*
112 * Set the size of the ring buffer.
113 */
114 void SetRingSize(u32 ring_size);
115
99private: 116private:
100 /// System 117 /// System
101 Core::System& system; 118 Core::System& system;
@@ -118,9 +135,13 @@ private:
118 /// Applet resource user id of this device session 135 /// Applet resource user id of this device session
119 u64 applet_resource_user_id{}; 136 u64 applet_resource_user_id{};
120 /// Total number of samples played by this device session 137 /// Total number of samples played by this device session
121 u64 played_sample_count{}; 138 std::atomic<u64> played_sample_count{};
139 /// Event increasing the played sample count every 5ms
140 std::shared_ptr<Core::Timing::EventType> thread_event;
122 /// Is this session initialised? 141 /// Is this session initialised?
123 bool initialized{}; 142 bool initialized{};
143 /// Buffer queue
144 std::vector<AudioBuffer> buffer_queue{};
124}; 145};
125 146
126} // namespace AudioCore 147} // namespace AudioCore
diff --git a/src/audio_core/in/audio_in_system.cpp b/src/audio_core/in/audio_in_system.cpp
index ec5d37ed4..7e80ba03c 100644
--- a/src/audio_core/in/audio_in_system.cpp
+++ b/src/audio_core/in/audio_in_system.cpp
@@ -93,6 +93,7 @@ Result System::Start() {
93 std::vector<AudioBuffer> buffers_to_flush{}; 93 std::vector<AudioBuffer> buffers_to_flush{};
94 buffers.RegisterBuffers(buffers_to_flush); 94 buffers.RegisterBuffers(buffers_to_flush);
95 session->AppendBuffers(buffers_to_flush); 95 session->AppendBuffers(buffers_to_flush);
96 session->SetRingSize(static_cast<u32>(buffers_to_flush.size()));
96 97
97 return ResultSuccess; 98 return ResultSuccess;
98} 99}
@@ -112,8 +113,13 @@ bool System::AppendBuffer(const AudioInBuffer& buffer, const u64 tag) {
112 return false; 113 return false;
113 } 114 }
114 115
115 AudioBuffer new_buffer{ 116 const auto timestamp{buffers.GetNextTimestamp()};
116 .played_timestamp = 0, .samples = buffer.samples, .tag = tag, .size = buffer.size}; 117 AudioBuffer new_buffer{.start_timestamp = timestamp,
118 .end_timestamp = timestamp + buffer.size / (channel_count * sizeof(s16)),
119 .played_timestamp = 0,
120 .samples = buffer.samples,
121 .tag = tag,
122 .size = buffer.size};
117 123
118 buffers.AppendBuffer(new_buffer); 124 buffers.AppendBuffer(new_buffer);
119 RegisterBuffers(); 125 RegisterBuffers();
diff --git a/src/audio_core/out/audio_out_system.cpp b/src/audio_core/out/audio_out_system.cpp
index 35afddf06..8941b09a0 100644
--- a/src/audio_core/out/audio_out_system.cpp
+++ b/src/audio_core/out/audio_out_system.cpp
@@ -92,6 +92,7 @@ Result System::Start() {
92 std::vector<AudioBuffer> buffers_to_flush{}; 92 std::vector<AudioBuffer> buffers_to_flush{};
93 buffers.RegisterBuffers(buffers_to_flush); 93 buffers.RegisterBuffers(buffers_to_flush);
94 session->AppendBuffers(buffers_to_flush); 94 session->AppendBuffers(buffers_to_flush);
95 session->SetRingSize(static_cast<u32>(buffers_to_flush.size()));
95 96
96 return ResultSuccess; 97 return ResultSuccess;
97} 98}
@@ -111,8 +112,13 @@ bool System::AppendBuffer(const AudioOutBuffer& buffer, u64 tag) {
111 return false; 112 return false;
112 } 113 }
113 114
114 AudioBuffer new_buffer{ 115 const auto timestamp{buffers.GetNextTimestamp()};
115 .played_timestamp = 0, .samples = buffer.samples, .tag = tag, .size = buffer.size}; 116 AudioBuffer new_buffer{.start_timestamp = timestamp,
117 .end_timestamp = timestamp + buffer.size / (channel_count * sizeof(s16)),
118 .played_timestamp = 0,
119 .samples = buffer.samples,
120 .tag = tag,
121 .size = buffer.size};
116 122
117 buffers.AppendBuffer(new_buffer); 123 buffers.AppendBuffer(new_buffer);
118 RegisterBuffers(); 124 RegisterBuffers();
diff --git a/src/audio_core/renderer/adsp/audio_renderer.cpp b/src/audio_core/renderer/adsp/audio_renderer.cpp
index 3967ccfe6..bcd889ecb 100644
--- a/src/audio_core/renderer/adsp/audio_renderer.cpp
+++ b/src/audio_core/renderer/adsp/audio_renderer.cpp
@@ -106,9 +106,6 @@ void AudioRenderer::Start(AudioRenderer_Mailbox* mailbox_) {
106 106
107 mailbox = mailbox_; 107 mailbox = mailbox_;
108 thread = std::thread(&AudioRenderer::ThreadFunc, this); 108 thread = std::thread(&AudioRenderer::ThreadFunc, this);
109 for (auto& stream : streams) {
110 stream->Start();
111 }
112 running = true; 109 running = true;
113} 110}
114 111
@@ -130,6 +127,7 @@ void AudioRenderer::CreateSinkStreams() {
130 std::string name{fmt::format("ADSP_RenderStream-{}", i)}; 127 std::string name{fmt::format("ADSP_RenderStream-{}", i)};
131 streams[i] = 128 streams[i] =
132 sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render); 129 sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render);
130 streams[i]->SetRingSize(4);
133 } 131 }
134} 132}
135 133
@@ -198,11 +196,6 @@ void AudioRenderer::ThreadFunc() {
198 command_list_processor.Process(index) - start_time; 196 command_list_processor.Process(index) - start_time;
199 } 197 }
200 198
201 if (index == 0) {
202 auto stream{command_list_processor.GetOutputSinkStream()};
203 system.AudioCore().SetStreamQueue(stream->GetQueueSize());
204 }
205
206 const auto end_time{system.CoreTiming().GetClockTicks()}; 199 const auto end_time{system.CoreTiming().GetClockTicks()};
207 200
208 command_buffer.remaining_command_count = 201 command_buffer.remaining_command_count =
diff --git a/src/audio_core/renderer/behavior/behavior_info.cpp b/src/audio_core/renderer/behavior/behavior_info.cpp
index c5d4d66d8..92140aaea 100644
--- a/src/audio_core/renderer/behavior/behavior_info.cpp
+++ b/src/audio_core/renderer/behavior/behavior_info.cpp
@@ -43,13 +43,15 @@ void BehaviorInfo::AppendError(ErrorInfo& error) {
43} 43}
44 44
45void BehaviorInfo::CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) { 45void BehaviorInfo::CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) {
46 auto error_count_{std::min(error_count, MaxErrors)}; 46 out_count = std::min(error_count, MaxErrors);
47 std::memset(out_errors.data(), 0, MaxErrors * sizeof(ErrorInfo)); 47
48 48 for (size_t i = 0; i < MaxErrors; i++) {
49 for (size_t i = 0; i < error_count_; i++) { 49 if (i < out_count) {
50 out_errors[i] = errors[i]; 50 out_errors[i] = errors[i];
51 } else {
52 out_errors[i] = {};
53 }
51 } 54 }
52 out_count = error_count_;
53} 55}
54 56
55void BehaviorInfo::UpdateFlags(const Flags flags_) { 57void BehaviorInfo::UpdateFlags(const Flags flags_) {
diff --git a/src/audio_core/renderer/command/sink/device.cpp b/src/audio_core/renderer/command/sink/device.cpp
index 47e0c6722..e88372a75 100644
--- a/src/audio_core/renderer/command/sink/device.cpp
+++ b/src/audio_core/renderer/command/sink/device.cpp
@@ -46,6 +46,10 @@ void DeviceSinkCommand::Process(const ADSP::CommandListProcessor& processor) {
46 46
47 out_buffer.tag = reinterpret_cast<u64>(samples.data()); 47 out_buffer.tag = reinterpret_cast<u64>(samples.data());
48 stream->AppendBuffer(out_buffer, samples); 48 stream->AppendBuffer(out_buffer, samples);
49
50 if (stream->IsPaused()) {
51 stream->Start();
52 }
49} 53}
50 54
51bool DeviceSinkCommand::Verify(const ADSP::CommandListProcessor& processor) { 55bool DeviceSinkCommand::Verify(const ADSP::CommandListProcessor& processor) {
diff --git a/src/audio_core/renderer/system_manager.cpp b/src/audio_core/renderer/system_manager.cpp
index b326819ed..bc2dd9e6e 100644
--- a/src/audio_core/renderer/system_manager.cpp
+++ b/src/audio_core/renderer/system_manager.cpp
@@ -15,8 +15,7 @@ MICROPROFILE_DEFINE(Audio_RenderSystemManager, "Audio", "Render System Manager",
15 MP_RGB(60, 19, 97)); 15 MP_RGB(60, 19, 97));
16 16
17namespace AudioCore::AudioRenderer { 17namespace AudioCore::AudioRenderer {
18constexpr std::chrono::nanoseconds BaseRenderTime{5'000'000UL}; 18constexpr std::chrono::nanoseconds RENDER_TIME{5'000'000UL};
19constexpr std::chrono::nanoseconds RenderTimeOffset{400'000UL};
20 19
21SystemManager::SystemManager(Core::System& core_) 20SystemManager::SystemManager(Core::System& core_)
22 : core{core_}, adsp{core.AudioCore().GetADSP()}, mailbox{adsp.GetRenderMailbox()}, 21 : core{core_}, adsp{core.AudioCore().GetADSP()}, mailbox{adsp.GetRenderMailbox()},
@@ -36,8 +35,8 @@ bool SystemManager::InitializeUnsafe() {
36 if (adsp.Start()) { 35 if (adsp.Start()) {
37 active = true; 36 active = true;
38 thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(); }); 37 thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(); });
39 core.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds(0), 38 core.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds(0), RENDER_TIME,
40 BaseRenderTime - RenderTimeOffset, thread_event); 39 thread_event);
41 } 40 }
42 } 41 }
43 42
@@ -121,35 +120,9 @@ void SystemManager::ThreadFunc() {
121} 120}
122 121
123std::optional<std::chrono::nanoseconds> SystemManager::ThreadFunc2(s64 time) { 122std::optional<std::chrono::nanoseconds> SystemManager::ThreadFunc2(s64 time) {
124 std::optional<std::chrono::nanoseconds> new_schedule_time{std::nullopt};
125 const auto queue_size{core.AudioCore().GetStreamQueue()};
126 switch (state) {
127 case StreamState::Filling:
128 if (queue_size >= 5) {
129 new_schedule_time = BaseRenderTime;
130 state = StreamState::Steady;
131 }
132 break;
133 case StreamState::Steady:
134 if (queue_size <= 2) {
135 new_schedule_time = BaseRenderTime - RenderTimeOffset;
136 state = StreamState::Filling;
137 } else if (queue_size > 5) {
138 new_schedule_time = BaseRenderTime + RenderTimeOffset;
139 state = StreamState::Draining;
140 }
141 break;
142 case StreamState::Draining:
143 if (queue_size <= 5) {
144 new_schedule_time = BaseRenderTime;
145 state = StreamState::Steady;
146 }
147 break;
148 }
149
150 update.store(true); 123 update.store(true);
151 update.notify_all(); 124 update.notify_all();
152 return new_schedule_time; 125 return std::nullopt;
153} 126}
154 127
155void SystemManager::PauseCallback(bool paused) { 128void SystemManager::PauseCallback(bool paused) {
diff --git a/src/audio_core/sink/cubeb_sink.cpp b/src/audio_core/sink/cubeb_sink.cpp
index 90d049e8e..9ae043611 100644
--- a/src/audio_core/sink/cubeb_sink.cpp
+++ b/src/audio_core/sink/cubeb_sink.cpp
@@ -1,21 +1,13 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include <algorithm>
5#include <atomic>
6#include <span> 4#include <span>
5#include <vector>
7 6
8#include "audio_core/audio_core.h" 7#include "audio_core/common/common.h"
9#include "audio_core/audio_event.h"
10#include "audio_core/audio_manager.h"
11#include "audio_core/sink/cubeb_sink.h" 8#include "audio_core/sink/cubeb_sink.h"
12#include "audio_core/sink/sink_stream.h" 9#include "audio_core/sink/sink_stream.h"
13#include "common/assert.h"
14#include "common/fixed_point.h"
15#include "common/logging/log.h" 10#include "common/logging/log.h"
16#include "common/reader_writer_queue.h"
17#include "common/ring_buffer.h"
18#include "common/settings.h"
19#include "core/core.h" 11#include "core/core.h"
20 12
21#ifdef _WIN32 13#ifdef _WIN32
@@ -42,10 +34,10 @@ public:
42 * @param system_ - Core system. 34 * @param system_ - Core system.
43 * @param event - Event used only for audio renderer, signalled on buffer consume. 35 * @param event - Event used only for audio renderer, signalled on buffer consume.
44 */ 36 */
45 CubebSinkStream(cubeb* ctx_, const u32 device_channels_, const u32 system_channels_, 37 CubebSinkStream(cubeb* ctx_, u32 device_channels_, u32 system_channels_,
46 cubeb_devid output_device, cubeb_devid input_device, const std::string& name_, 38 cubeb_devid output_device, cubeb_devid input_device, const std::string& name_,
47 const StreamType type_, Core::System& system_) 39 StreamType type_, Core::System& system_)
48 : ctx{ctx_}, type{type_}, system{system_} { 40 : SinkStream(system_, type_), ctx{ctx_} {
49#ifdef _WIN32 41#ifdef _WIN32
50 CoInitializeEx(nullptr, COINIT_MULTITHREADED); 42 CoInitializeEx(nullptr, COINIT_MULTITHREADED);
51#endif 43#endif
@@ -79,12 +71,10 @@ public:
79 71
80 minimum_latency = std::max(minimum_latency, 256u); 72 minimum_latency = std::max(minimum_latency, 256u);
81 73
82 playing_buffer.consumed = true; 74 LOG_INFO(Service_Audio,
83 75 "Opening cubeb stream {} type {} with: rate {} channels {} (system channels {}) "
84 LOG_DEBUG(Service_Audio, 76 "latency {}",
85 "Opening cubeb stream {} type {} with: rate {} channels {} (system channels {}) " 77 name, type, params.rate, params.channels, system_channels, minimum_latency);
86 "latency {}",
87 name, type, params.rate, params.channels, system_channels, minimum_latency);
88 78
89 auto init_error{0}; 79 auto init_error{0};
90 if (type == StreamType::In) { 80 if (type == StreamType::In) {
@@ -111,6 +101,8 @@ public:
111 ~CubebSinkStream() override { 101 ~CubebSinkStream() override {
112 LOG_DEBUG(Service_Audio, "Destructing cubeb stream {}", name); 102 LOG_DEBUG(Service_Audio, "Destructing cubeb stream {}", name);
113 103
104 Unstall();
105
114 if (!ctx) { 106 if (!ctx) {
115 return; 107 return;
116 } 108 }
@@ -136,7 +128,7 @@ public:
136 * @param resume - Set to true if this is resuming the stream a previously-active stream. 128 * @param resume - Set to true if this is resuming the stream a previously-active stream.
137 * Default false. 129 * Default false.
138 */ 130 */
139 void Start(const bool resume = false) override { 131 void Start(bool resume = false) override {
140 if (!ctx) { 132 if (!ctx) {
141 return; 133 return;
142 } 134 }
@@ -158,6 +150,7 @@ public:
158 * Stop the sink stream. 150 * Stop the sink stream.
159 */ 151 */
160 void Stop() override { 152 void Stop() override {
153 Unstall();
161 if (!ctx) { 154 if (!ctx) {
162 return; 155 return;
163 } 156 }
@@ -170,195 +163,8 @@ public:
170 paused = true; 163 paused = true;
171 } 164 }
172 165
173 /**
174 * Append a new buffer and its samples to a waiting queue to play.
175 *
176 * @param buffer - Audio buffer information to be queued.
177 * @param samples - The s16 samples to be queue for playback.
178 */
179 void AppendBuffer(::AudioCore::Sink::SinkBuffer& buffer, std::vector<s16>& samples) override {
180 if (type == StreamType::In) {
181 queue.enqueue(buffer);
182 queued_buffers++;
183 } else {
184 constexpr s32 min{std::numeric_limits<s16>::min()};
185 constexpr s32 max{std::numeric_limits<s16>::max()};
186
187 auto yuzu_volume{Settings::Volume()};
188 if (yuzu_volume > 1.0f) {
189 yuzu_volume = 0.6f + 20 * std::log10(yuzu_volume);
190 }
191 auto volume{system_volume * device_volume * yuzu_volume};
192
193 if (system_channels == 6 && device_channels == 2) {
194 // We're given 6 channels, but our device only outputs 2, so downmix.
195 constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f};
196
197 for (u32 read_index = 0, write_index = 0; read_index < samples.size();
198 read_index += system_channels, write_index += device_channels) {
199 const auto left_sample{
200 ((Common::FixedPoint<49, 15>(
201 samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
202 down_mix_coeff[0] +
203 samples[read_index + static_cast<u32>(Channels::Center)] *
204 down_mix_coeff[1] +
205 samples[read_index + static_cast<u32>(Channels::LFE)] *
206 down_mix_coeff[2] +
207 samples[read_index + static_cast<u32>(Channels::BackLeft)] *
208 down_mix_coeff[3]) *
209 volume)
210 .to_int()};
211
212 const auto right_sample{
213 ((Common::FixedPoint<49, 15>(
214 samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
215 down_mix_coeff[0] +
216 samples[read_index + static_cast<u32>(Channels::Center)] *
217 down_mix_coeff[1] +
218 samples[read_index + static_cast<u32>(Channels::LFE)] *
219 down_mix_coeff[2] +
220 samples[read_index + static_cast<u32>(Channels::BackRight)] *
221 down_mix_coeff[3]) *
222 volume)
223 .to_int()};
224
225 samples[write_index + static_cast<u32>(Channels::FrontLeft)] =
226 static_cast<s16>(std::clamp(left_sample, min, max));
227 samples[write_index + static_cast<u32>(Channels::FrontRight)] =
228 static_cast<s16>(std::clamp(right_sample, min, max));
229 }
230
231 samples.resize(samples.size() / system_channels * device_channels);
232
233 } else if (system_channels == 2 && device_channels == 6) {
234 // We need moar samples! Not all games will provide 6 channel audio.
235 // TODO: Implement some upmixing here. Currently just passthrough, with other
236 // channels left as silence.
237 std::vector<s16> new_samples(samples.size() / system_channels * device_channels, 0);
238
239 for (u32 read_index = 0, write_index = 0; read_index < samples.size();
240 read_index += system_channels, write_index += device_channels) {
241 const auto left_sample{static_cast<s16>(std::clamp(
242 static_cast<s32>(
243 static_cast<f32>(
244 samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
245 volume),
246 min, max))};
247
248 new_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample;
249
250 const auto right_sample{static_cast<s16>(std::clamp(
251 static_cast<s32>(
252 static_cast<f32>(
253 samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
254 volume),
255 min, max))};
256
257 new_samples[write_index + static_cast<u32>(Channels::FrontRight)] =
258 right_sample;
259 }
260 samples = std::move(new_samples);
261
262 } else if (volume != 1.0f) {
263 for (u32 i = 0; i < samples.size(); i++) {
264 samples[i] = static_cast<s16>(std::clamp(
265 static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
266 }
267 }
268
269 samples_buffer.Push(samples);
270 queue.enqueue(buffer);
271 queued_buffers++;
272 }
273 }
274
275 /**
276 * Release a buffer. Audio In only, will fill a buffer with recorded samples.
277 *
278 * @param num_samples - Maximum number of samples to receive.
279 * @return Vector of recorded samples. May have fewer than num_samples.
280 */
281 std::vector<s16> ReleaseBuffer(const u64 num_samples) override {
282 static constexpr s32 min = std::numeric_limits<s16>::min();
283 static constexpr s32 max = std::numeric_limits<s16>::max();
284
285 auto samples{samples_buffer.Pop(num_samples)};
286
287 // TODO: Up-mix to 6 channels if the game expects it.
288 // For audio input this is unlikely to ever be the case though.
289
290 // Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here.
291 // TODO: Play with this and find something that works better.
292 auto volume{system_volume * device_volume * 8};
293 for (u32 i = 0; i < samples.size(); i++) {
294 samples[i] = static_cast<s16>(
295 std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
296 }
297
298 if (samples.size() < num_samples) {
299 samples.resize(num_samples, 0);
300 }
301 return samples;
302 }
303
304 /**
305 * Check if a certain buffer has been consumed (fully played).
306 *
307 * @param tag - Unique tag of a buffer to check for.
308 * @return True if the buffer has been played, otherwise false.
309 */
310 bool IsBufferConsumed(const u64 tag) override {
311 if (released_buffer.tag == 0) {
312 if (!released_buffers.try_dequeue(released_buffer)) {
313 return false;
314 }
315 }
316
317 if (released_buffer.tag == tag) {
318 released_buffer.tag = 0;
319 return true;
320 }
321 return false;
322 }
323
324 /**
325 * Empty out the buffer queue.
326 */
327 void ClearQueue() override {
328 samples_buffer.Pop();
329 while (queue.pop()) {
330 }
331 while (released_buffers.pop()) {
332 }
333 queued_buffers = 0;
334 released_buffer = {};
335 playing_buffer = {};
336 playing_buffer.consumed = true;
337 }
338
339private: 166private:
340 /** 167 /**
341 * Signal events back to the audio system that a buffer was played/can be filled.
342 *
343 * @param buffer - Consumed audio buffer to be released.
344 */
345 void SignalEvent(const ::AudioCore::Sink::SinkBuffer& buffer) {
346 auto& manager{system.AudioCore().GetAudioManager()};
347 switch (type) {
348 case StreamType::Out:
349 released_buffers.enqueue(buffer);
350 manager.SetEvent(Event::Type::AudioOutManager, true);
351 break;
352 case StreamType::In:
353 released_buffers.enqueue(buffer);
354 manager.SetEvent(Event::Type::AudioInManager, true);
355 break;
356 case StreamType::Render:
357 break;
358 }
359 }
360
361 /**
362 * Main callback from Cubeb. Either expects samples from us (audio render/audio out), or will 168 * Main callback from Cubeb. Either expects samples from us (audio render/audio out), or will
363 * provide samples to be copied (audio in). 169 * provide samples to be copied (audio in).
364 * 170 *
@@ -378,106 +184,15 @@ private:
378 184
379 const std::size_t num_channels = impl->GetDeviceChannels(); 185 const std::size_t num_channels = impl->GetDeviceChannels();
380 const std::size_t frame_size = num_channels; 186 const std::size_t frame_size = num_channels;
381 const std::size_t frame_size_bytes = frame_size * sizeof(s16);
382 const std::size_t num_frames{static_cast<size_t>(num_frames_)}; 187 const std::size_t num_frames{static_cast<size_t>(num_frames_)};
383 size_t frames_written{0};
384 [[maybe_unused]] bool underrun{false};
385 188
386 if (impl->type == StreamType::In) { 189 if (impl->type == StreamType::In) {
387 // INPUT
388 std::span<const s16> input_buffer{reinterpret_cast<const s16*>(in_buff), 190 std::span<const s16> input_buffer{reinterpret_cast<const s16*>(in_buff),
389 num_frames * frame_size}; 191 num_frames * frame_size};
390 192 impl->ProcessAudioIn(input_buffer, num_frames);
391 while (frames_written < num_frames) {
392 auto& playing_buffer{impl->playing_buffer};
393
394 // If the playing buffer has been consumed or has no frames, we need a new one
395 if (playing_buffer.consumed || playing_buffer.frames == 0) {
396 if (!impl->queue.try_dequeue(impl->playing_buffer)) {
397 // If no buffer was available we've underrun, just push the samples and
398 // continue.
399 underrun = true;
400 impl->samples_buffer.Push(&input_buffer[frames_written * frame_size],
401 (num_frames - frames_written) * frame_size);
402 frames_written = num_frames;
403 continue;
404 } else {
405 // Successfully got a new buffer, mark the old one as consumed and signal.
406 impl->queued_buffers--;
407 impl->SignalEvent(impl->playing_buffer);
408 }
409 }
410
411 // Get the minimum frames available between the currently playing buffer, and the
412 // amount we have left to fill
413 size_t frames_available{
414 std::min(playing_buffer.frames - playing_buffer.frames_played,
415 num_frames - frames_written)};
416
417 impl->samples_buffer.Push(&input_buffer[frames_written * frame_size],
418 frames_available * frame_size);
419
420 frames_written += frames_available;
421 playing_buffer.frames_played += frames_available;
422
423 // If that's all the frames in the current buffer, add its samples and mark it as
424 // consumed
425 if (playing_buffer.frames_played >= playing_buffer.frames) {
426 impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels);
427 impl->playing_buffer.consumed = true;
428 }
429 }
430
431 std::memcpy(&impl->last_frame[0], &input_buffer[(frames_written - 1) * frame_size],
432 frame_size_bytes);
433 } else { 193 } else {
434 // OUTPUT
435 std::span<s16> output_buffer{reinterpret_cast<s16*>(out_buff), num_frames * frame_size}; 194 std::span<s16> output_buffer{reinterpret_cast<s16*>(out_buff), num_frames * frame_size};
436 195 impl->ProcessAudioOutAndRender(output_buffer, num_frames);
437 while (frames_written < num_frames) {
438 auto& playing_buffer{impl->playing_buffer};
439
440 // If the playing buffer has been consumed or has no frames, we need a new one
441 if (playing_buffer.consumed || playing_buffer.frames == 0) {
442 if (!impl->queue.try_dequeue(impl->playing_buffer)) {
443 // If no buffer was available we've underrun, fill the remaining buffer with
444 // the last written frame and continue.
445 underrun = true;
446 for (size_t i = frames_written; i < num_frames; i++) {
447 std::memcpy(&output_buffer[i * frame_size], &impl->last_frame[0],
448 frame_size_bytes);
449 }
450 frames_written = num_frames;
451 continue;
452 } else {
453 // Successfully got a new buffer, mark the old one as consumed and signal.
454 impl->queued_buffers--;
455 impl->SignalEvent(impl->playing_buffer);
456 }
457 }
458
459 // Get the minimum frames available between the currently playing buffer, and the
460 // amount we have left to fill
461 size_t frames_available{
462 std::min(playing_buffer.frames - playing_buffer.frames_played,
463 num_frames - frames_written)};
464
465 impl->samples_buffer.Pop(&output_buffer[frames_written * frame_size],
466 frames_available * frame_size);
467
468 frames_written += frames_available;
469 playing_buffer.frames_played += frames_available;
470
471 // If that's all the frames in the current buffer, add its samples and mark it as
472 // consumed
473 if (playing_buffer.frames_played >= playing_buffer.frames) {
474 impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels);
475 impl->playing_buffer.consumed = true;
476 }
477 }
478
479 std::memcpy(&impl->last_frame[0], &output_buffer[(frames_written - 1) * frame_size],
480 frame_size_bytes);
481 } 196 }
482 197
483 return num_frames_; 198 return num_frames_;
@@ -490,32 +205,12 @@ private:
490 * @param user_data - Custom data pointer passed along, points to a CubebSinkStream. 205 * @param user_data - Custom data pointer passed along, points to a CubebSinkStream.
491 * @param state - New state of the device. 206 * @param state - New state of the device.
492 */ 207 */
493 static void StateCallback([[maybe_unused]] cubeb_stream* stream, 208 static void StateCallback(cubeb_stream*, void*, cubeb_state) {}
494 [[maybe_unused]] void* user_data,
495 [[maybe_unused]] cubeb_state state) {}
496 209
497 /// Main Cubeb context 210 /// Main Cubeb context
498 cubeb* ctx{}; 211 cubeb* ctx{};
499 /// Cubeb stream backend 212 /// Cubeb stream backend
500 cubeb_stream* stream_backend{}; 213 cubeb_stream* stream_backend{};
501 /// Name of this stream
502 std::string name{};
503 /// Type of this stream
504 StreamType type;
505 /// Core system
506 Core::System& system;
507 /// Ring buffer of the samples waiting to be played or consumed
508 Common::RingBuffer<s16, 0x10000> samples_buffer;
509 /// Audio buffers queued and waiting to play
510 Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> queue;
511 /// The currently-playing audio buffer
512 ::AudioCore::Sink::SinkBuffer playing_buffer{};
513 /// Audio buffers which have been played and are in queue to be released by the audio system
514 Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> released_buffers{};
515 /// Currently released buffer waiting to be taken by the audio system
516 ::AudioCore::Sink::SinkBuffer released_buffer{};
517 /// The last played (or received) frame of audio, used when the callback underruns
518 std::array<s16, MaxChannels> last_frame{};
519}; 214};
520 215
521CubebSink::CubebSink(std::string_view target_device_name) { 216CubebSink::CubebSink(std::string_view target_device_name) {
@@ -569,15 +264,15 @@ CubebSink::~CubebSink() {
569#endif 264#endif
570} 265}
571 266
572SinkStream* CubebSink::AcquireSinkStream(Core::System& system, const u32 system_channels, 267SinkStream* CubebSink::AcquireSinkStream(Core::System& system, u32 system_channels,
573 const std::string& name, const StreamType type) { 268 const std::string& name, StreamType type) {
574 SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<CubebSinkStream>( 269 SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<CubebSinkStream>(
575 ctx, device_channels, system_channels, output_device, input_device, name, type, system)); 270 ctx, device_channels, system_channels, output_device, input_device, name, type, system));
576 271
577 return stream.get(); 272 return stream.get();
578} 273}
579 274
580void CubebSink::CloseStream(const SinkStream* stream) { 275void CubebSink::CloseStream(SinkStream* stream) {
581 for (size_t i = 0; i < sink_streams.size(); i++) { 276 for (size_t i = 0; i < sink_streams.size(); i++) {
582 if (sink_streams[i].get() == stream) { 277 if (sink_streams[i].get() == stream) {
583 sink_streams[i].reset(); 278 sink_streams[i].reset();
@@ -611,19 +306,19 @@ f32 CubebSink::GetDeviceVolume() const {
611 return sink_streams[0]->GetDeviceVolume(); 306 return sink_streams[0]->GetDeviceVolume();
612} 307}
613 308
614void CubebSink::SetDeviceVolume(const f32 volume) { 309void CubebSink::SetDeviceVolume(f32 volume) {
615 for (auto& stream : sink_streams) { 310 for (auto& stream : sink_streams) {
616 stream->SetDeviceVolume(volume); 311 stream->SetDeviceVolume(volume);
617 } 312 }
618} 313}
619 314
620void CubebSink::SetSystemVolume(const f32 volume) { 315void CubebSink::SetSystemVolume(f32 volume) {
621 for (auto& stream : sink_streams) { 316 for (auto& stream : sink_streams) {
622 stream->SetSystemVolume(volume); 317 stream->SetSystemVolume(volume);
623 } 318 }
624} 319}
625 320
626std::vector<std::string> ListCubebSinkDevices(const bool capture) { 321std::vector<std::string> ListCubebSinkDevices(bool capture) {
627 std::vector<std::string> device_list; 322 std::vector<std::string> device_list;
628 cubeb* ctx; 323 cubeb* ctx;
629 324
diff --git a/src/audio_core/sink/cubeb_sink.h b/src/audio_core/sink/cubeb_sink.h
index f0f43dfa1..91a6480fa 100644
--- a/src/audio_core/sink/cubeb_sink.h
+++ b/src/audio_core/sink/cubeb_sink.h
@@ -46,7 +46,7 @@ public:
46 * 46 *
47 * @param stream - The stream to close. 47 * @param stream - The stream to close.
48 */ 48 */
49 void CloseStream(const SinkStream* stream) override; 49 void CloseStream(SinkStream* stream) override;
50 50
51 /** 51 /**
52 * Close all streams. 52 * Close all streams.
diff --git a/src/audio_core/sink/null_sink.h b/src/audio_core/sink/null_sink.h
index 47a342171..eab9c3a0c 100644
--- a/src/audio_core/sink/null_sink.h
+++ b/src/audio_core/sink/null_sink.h
@@ -3,10 +3,29 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <string>
7#include <string_view>
8#include <vector>
9
6#include "audio_core/sink/sink.h" 10#include "audio_core/sink/sink.h"
7#include "audio_core/sink/sink_stream.h" 11#include "audio_core/sink/sink_stream.h"
8 12
13namespace Core {
14class System;
15} // namespace Core
16
9namespace AudioCore::Sink { 17namespace AudioCore::Sink {
18class NullSinkStreamImpl final : public SinkStream {
19public:
20 explicit NullSinkStreamImpl(Core::System& system_, StreamType type_)
21 : SinkStream{system_, type_} {}
22 ~NullSinkStreamImpl() override {}
23 void AppendBuffer(SinkBuffer&, std::vector<s16>&) override {}
24 std::vector<s16> ReleaseBuffer(u64) override {
25 return {};
26 }
27};
28
10/** 29/**
11 * A no-op sink for when no audio out is wanted. 30 * A no-op sink for when no audio out is wanted.
12 */ 31 */
@@ -15,14 +34,15 @@ public:
15 explicit NullSink(std::string_view) {} 34 explicit NullSink(std::string_view) {}
16 ~NullSink() override = default; 35 ~NullSink() override = default;
17 36
18 SinkStream* AcquireSinkStream([[maybe_unused]] Core::System& system, 37 SinkStream* AcquireSinkStream(Core::System& system, u32, const std::string&,
19 [[maybe_unused]] u32 system_channels, 38 StreamType type) override {
20 [[maybe_unused]] const std::string& name, 39 if (null_sink == nullptr) {
21 [[maybe_unused]] StreamType type) override { 40 null_sink = std::make_unique<NullSinkStreamImpl>(system, type);
22 return &null_sink_stream; 41 }
42 return null_sink.get();
23 } 43 }
24 44
25 void CloseStream([[maybe_unused]] const SinkStream* stream) override {} 45 void CloseStream(SinkStream*) override {}
26 void CloseStreams() override {} 46 void CloseStreams() override {}
27 void PauseStreams() override {} 47 void PauseStreams() override {}
28 void UnpauseStreams() override {} 48 void UnpauseStreams() override {}
@@ -33,20 +53,7 @@ public:
33 void SetSystemVolume(f32 volume) override {} 53 void SetSystemVolume(f32 volume) override {}
34 54
35private: 55private:
36 struct NullSinkStreamImpl final : SinkStream { 56 SinkStreamPtr null_sink{};
37 void Finalize() override {}
38 void Start(bool resume = false) override {}
39 void Stop() override {}
40 void AppendBuffer([[maybe_unused]] ::AudioCore::Sink::SinkBuffer& buffer,
41 [[maybe_unused]] std::vector<s16>& samples) override {}
42 std::vector<s16> ReleaseBuffer([[maybe_unused]] u64 num_samples) override {
43 return {};
44 }
45 bool IsBufferConsumed([[maybe_unused]] const u64 tag) {
46 return true;
47 }
48 void ClearQueue() override {}
49 } null_sink_stream;
50}; 57};
51 58
52} // namespace AudioCore::Sink 59} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/sdl2_sink.cpp b/src/audio_core/sink/sdl2_sink.cpp
index d6c9ec90d..7ee1dd7cd 100644
--- a/src/audio_core/sink/sdl2_sink.cpp
+++ b/src/audio_core/sink/sdl2_sink.cpp
@@ -1,20 +1,13 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include <algorithm> 4#include <span>
5#include <atomic> 5#include <vector>
6 6
7#include "audio_core/audio_core.h" 7#include "audio_core/common/common.h"
8#include "audio_core/audio_event.h"
9#include "audio_core/audio_manager.h"
10#include "audio_core/sink/sdl2_sink.h" 8#include "audio_core/sink/sdl2_sink.h"
11#include "audio_core/sink/sink_stream.h" 9#include "audio_core/sink/sink_stream.h"
12#include "common/assert.h"
13#include "common/fixed_point.h"
14#include "common/logging/log.h" 10#include "common/logging/log.h"
15#include "common/reader_writer_queue.h"
16#include "common/ring_buffer.h"
17#include "common/settings.h"
18#include "core/core.h" 11#include "core/core.h"
19 12
20// Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307 13// Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307
@@ -44,10 +37,9 @@ public:
44 * @param system_ - Core system. 37 * @param system_ - Core system.
45 * @param event - Event used only for audio renderer, signalled on buffer consume. 38 * @param event - Event used only for audio renderer, signalled on buffer consume.
46 */ 39 */
47 SDLSinkStream(u32 device_channels_, const u32 system_channels_, 40 SDLSinkStream(u32 device_channels_, u32 system_channels_, const std::string& output_device,
48 const std::string& output_device, const std::string& input_device, 41 const std::string& input_device, StreamType type_, Core::System& system_)
49 const StreamType type_, Core::System& system_) 42 : SinkStream{system_, type_} {
50 : type{type_}, system{system_} {
51 system_channels = system_channels_; 43 system_channels = system_channels_;
52 device_channels = device_channels_; 44 device_channels = device_channels_;
53 45
@@ -63,8 +55,6 @@ public:
63 spec.callback = &SDLSinkStream::DataCallback; 55 spec.callback = &SDLSinkStream::DataCallback;
64 spec.userdata = this; 56 spec.userdata = this;
65 57
66 playing_buffer.consumed = true;
67
68 std::string device_name{output_device}; 58 std::string device_name{output_device};
69 bool capture{false}; 59 bool capture{false};
70 if (type == StreamType::In) { 60 if (type == StreamType::In) {
@@ -84,31 +74,30 @@ public:
84 return; 74 return;
85 } 75 }
86 76
87 LOG_DEBUG(Service_Audio, 77 LOG_INFO(Service_Audio,
88 "Opening sdl stream {} with: rate {} channels {} (system channels {}) " 78 "Opening SDL stream {} with: rate {} channels {} (system channels {}) "
89 " samples {}", 79 " samples {}",
90 device, obtained.freq, obtained.channels, system_channels, obtained.samples); 80 device, obtained.freq, obtained.channels, system_channels, obtained.samples);
91 } 81 }
92 82
93 /** 83 /**
94 * Destroy the sink stream. 84 * Destroy the sink stream.
95 */ 85 */
96 ~SDLSinkStream() override { 86 ~SDLSinkStream() override {
97 if (device == 0) { 87 LOG_DEBUG(Service_Audio, "Destructing SDL stream {}", name);
98 return; 88 Finalize();
99 }
100
101 SDL_CloseAudioDevice(device);
102 } 89 }
103 90
104 /** 91 /**
105 * Finalize the sink stream. 92 * Finalize the sink stream.
106 */ 93 */
107 void Finalize() override { 94 void Finalize() override {
95 Unstall();
108 if (device == 0) { 96 if (device == 0) {
109 return; 97 return;
110 } 98 }
111 99
100 Stop();
112 SDL_CloseAudioDevice(device); 101 SDL_CloseAudioDevice(device);
113 } 102 }
114 103
@@ -118,7 +107,7 @@ public:
118 * @param resume - Set to true if this is resuming the stream a previously-active stream. 107 * @param resume - Set to true if this is resuming the stream a previously-active stream.
119 * Default false. 108 * Default false.
120 */ 109 */
121 void Start(const bool resume = false) override { 110 void Start(bool resume = false) override {
122 if (device == 0) { 111 if (device == 0) {
123 return; 112 return;
124 } 113 }
@@ -135,7 +124,8 @@ public:
135 /** 124 /**
136 * Stop the sink stream. 125 * Stop the sink stream.
137 */ 126 */
138 void Stop() { 127 void Stop() override {
128 Unstall();
139 if (device == 0) { 129 if (device == 0) {
140 return; 130 return;
141 } 131 }
@@ -143,192 +133,8 @@ public:
143 paused = true; 133 paused = true;
144 } 134 }
145 135
146 /**
147 * Append a new buffer and its samples to a waiting queue to play.
148 *
149 * @param buffer - Audio buffer information to be queued.
150 * @param samples - The s16 samples to be queue for playback.
151 */
152 void AppendBuffer(::AudioCore::Sink::SinkBuffer& buffer, std::vector<s16>& samples) override {
153 if (type == StreamType::In) {
154 queue.enqueue(buffer);
155 queued_buffers++;
156 } else {
157 constexpr s32 min = std::numeric_limits<s16>::min();
158 constexpr s32 max = std::numeric_limits<s16>::max();
159
160 auto yuzu_volume{Settings::Volume()};
161 auto volume{system_volume * device_volume * yuzu_volume};
162
163 if (system_channels == 6 && device_channels == 2) {
164 // We're given 6 channels, but our device only outputs 2, so downmix.
165 constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f};
166
167 for (u32 read_index = 0, write_index = 0; read_index < samples.size();
168 read_index += system_channels, write_index += device_channels) {
169 const auto left_sample{
170 ((Common::FixedPoint<49, 15>(
171 samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
172 down_mix_coeff[0] +
173 samples[read_index + static_cast<u32>(Channels::Center)] *
174 down_mix_coeff[1] +
175 samples[read_index + static_cast<u32>(Channels::LFE)] *
176 down_mix_coeff[2] +
177 samples[read_index + static_cast<u32>(Channels::BackLeft)] *
178 down_mix_coeff[3]) *
179 volume)
180 .to_int()};
181
182 const auto right_sample{
183 ((Common::FixedPoint<49, 15>(
184 samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
185 down_mix_coeff[0] +
186 samples[read_index + static_cast<u32>(Channels::Center)] *
187 down_mix_coeff[1] +
188 samples[read_index + static_cast<u32>(Channels::LFE)] *
189 down_mix_coeff[2] +
190 samples[read_index + static_cast<u32>(Channels::BackRight)] *
191 down_mix_coeff[3]) *
192 volume)
193 .to_int()};
194
195 samples[write_index + static_cast<u32>(Channels::FrontLeft)] =
196 static_cast<s16>(std::clamp(left_sample, min, max));
197 samples[write_index + static_cast<u32>(Channels::FrontRight)] =
198 static_cast<s16>(std::clamp(right_sample, min, max));
199 }
200
201 samples.resize(samples.size() / system_channels * device_channels);
202
203 } else if (system_channels == 2 && device_channels == 6) {
204 // We need moar samples! Not all games will provide 6 channel audio.
205 // TODO: Implement some upmixing here. Currently just passthrough, with other
206 // channels left as silence.
207 std::vector<s16> new_samples(samples.size() / system_channels * device_channels, 0);
208
209 for (u32 read_index = 0, write_index = 0; read_index < samples.size();
210 read_index += system_channels, write_index += device_channels) {
211 const auto left_sample{static_cast<s16>(std::clamp(
212 static_cast<s32>(
213 static_cast<f32>(
214 samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
215 volume),
216 min, max))};
217
218 new_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample;
219
220 const auto right_sample{static_cast<s16>(std::clamp(
221 static_cast<s32>(
222 static_cast<f32>(
223 samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
224 volume),
225 min, max))};
226
227 new_samples[write_index + static_cast<u32>(Channels::FrontRight)] =
228 right_sample;
229 }
230 samples = std::move(new_samples);
231
232 } else if (volume != 1.0f) {
233 for (u32 i = 0; i < samples.size(); i++) {
234 samples[i] = static_cast<s16>(std::clamp(
235 static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
236 }
237 }
238
239 samples_buffer.Push(samples);
240 queue.enqueue(buffer);
241 queued_buffers++;
242 }
243 }
244
245 /**
246 * Release a buffer. Audio In only, will fill a buffer with recorded samples.
247 *
248 * @param num_samples - Maximum number of samples to receive.
249 * @return Vector of recorded samples. May have fewer than num_samples.
250 */
251 std::vector<s16> ReleaseBuffer(const u64 num_samples) override {
252 static constexpr s32 min = std::numeric_limits<s16>::min();
253 static constexpr s32 max = std::numeric_limits<s16>::max();
254
255 auto samples{samples_buffer.Pop(num_samples)};
256
257 // TODO: Up-mix to 6 channels if the game expects it.
258 // For audio input this is unlikely to ever be the case though.
259
260 // Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here.
261 // TODO: Play with this and find something that works better.
262 auto volume{system_volume * device_volume * 8};
263 for (u32 i = 0; i < samples.size(); i++) {
264 samples[i] = static_cast<s16>(
265 std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
266 }
267
268 if (samples.size() < num_samples) {
269 samples.resize(num_samples, 0);
270 }
271 return samples;
272 }
273
274 /**
275 * Check if a certain buffer has been consumed (fully played).
276 *
277 * @param tag - Unique tag of a buffer to check for.
278 * @return True if the buffer has been played, otherwise false.
279 */
280 bool IsBufferConsumed(const u64 tag) override {
281 if (released_buffer.tag == 0) {
282 if (!released_buffers.try_dequeue(released_buffer)) {
283 return false;
284 }
285 }
286
287 if (released_buffer.tag == tag) {
288 released_buffer.tag = 0;
289 return true;
290 }
291 return false;
292 }
293
294 /**
295 * Empty out the buffer queue.
296 */
297 void ClearQueue() override {
298 samples_buffer.Pop();
299 while (queue.pop()) {
300 }
301 while (released_buffers.pop()) {
302 }
303 released_buffer = {};
304 playing_buffer = {};
305 playing_buffer.consumed = true;
306 queued_buffers = 0;
307 }
308
309private: 136private:
310 /** 137 /**
311 * Signal events back to the audio system that a buffer was played/can be filled.
312 *
313 * @param buffer - Consumed audio buffer to be released.
314 */
315 void SignalEvent(const ::AudioCore::Sink::SinkBuffer& buffer) {
316 auto& manager{system.AudioCore().GetAudioManager()};
317 switch (type) {
318 case StreamType::Out:
319 released_buffers.enqueue(buffer);
320 manager.SetEvent(Event::Type::AudioOutManager, true);
321 break;
322 case StreamType::In:
323 released_buffers.enqueue(buffer);
324 manager.SetEvent(Event::Type::AudioInManager, true);
325 break;
326 case StreamType::Render:
327 break;
328 }
329 }
330
331 /**
332 * Main callback from SDL. Either expects samples from us (audio render/audio out), or will 138 * Main callback from SDL. Either expects samples from us (audio render/audio out), or will
333 * provide samples to be copied (audio in). 139 * provide samples to be copied (audio in).
334 * 140 *
@@ -345,122 +151,20 @@ private:
345 151
346 const std::size_t num_channels = impl->GetDeviceChannels(); 152 const std::size_t num_channels = impl->GetDeviceChannels();
347 const std::size_t frame_size = num_channels; 153 const std::size_t frame_size = num_channels;
348 const std::size_t frame_size_bytes = frame_size * sizeof(s16);
349 const std::size_t num_frames{len / num_channels / sizeof(s16)}; 154 const std::size_t num_frames{len / num_channels / sizeof(s16)};
350 size_t frames_written{0};
351 [[maybe_unused]] bool underrun{false};
352 155
353 if (impl->type == StreamType::In) { 156 if (impl->type == StreamType::In) {
354 std::span<s16> input_buffer{reinterpret_cast<s16*>(stream), num_frames * frame_size}; 157 std::span<const s16> input_buffer{reinterpret_cast<const s16*>(stream),
355 158 num_frames * frame_size};
356 while (frames_written < num_frames) { 159 impl->ProcessAudioIn(input_buffer, num_frames);
357 auto& playing_buffer{impl->playing_buffer};
358
359 // If the playing buffer has been consumed or has no frames, we need a new one
360 if (playing_buffer.consumed || playing_buffer.frames == 0) {
361 if (!impl->queue.try_dequeue(impl->playing_buffer)) {
362 // If no buffer was available we've underrun, just push the samples and
363 // continue.
364 underrun = true;
365 impl->samples_buffer.Push(&input_buffer[frames_written * frame_size],
366 (num_frames - frames_written) * frame_size);
367 frames_written = num_frames;
368 continue;
369 } else {
370 impl->queued_buffers--;
371 impl->SignalEvent(impl->playing_buffer);
372 }
373 }
374
375 // Get the minimum frames available between the currently playing buffer, and the
376 // amount we have left to fill
377 size_t frames_available{
378 std::min(playing_buffer.frames - playing_buffer.frames_played,
379 num_frames - frames_written)};
380
381 impl->samples_buffer.Push(&input_buffer[frames_written * frame_size],
382 frames_available * frame_size);
383
384 frames_written += frames_available;
385 playing_buffer.frames_played += frames_available;
386
387 // If that's all the frames in the current buffer, add its samples and mark it as
388 // consumed
389 if (playing_buffer.frames_played >= playing_buffer.frames) {
390 impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels);
391 impl->playing_buffer.consumed = true;
392 }
393 }
394
395 std::memcpy(&impl->last_frame[0], &input_buffer[(frames_written - 1) * frame_size],
396 frame_size_bytes);
397 } else { 160 } else {
398 std::span<s16> output_buffer{reinterpret_cast<s16*>(stream), num_frames * frame_size}; 161 std::span<s16> output_buffer{reinterpret_cast<s16*>(stream), num_frames * frame_size};
399 162 impl->ProcessAudioOutAndRender(output_buffer, num_frames);
400 while (frames_written < num_frames) {
401 auto& playing_buffer{impl->playing_buffer};
402
403 // If the playing buffer has been consumed or has no frames, we need a new one
404 if (playing_buffer.consumed || playing_buffer.frames == 0) {
405 if (!impl->queue.try_dequeue(impl->playing_buffer)) {
406 // If no buffer was available we've underrun, fill the remaining buffer with
407 // the last written frame and continue.
408 underrun = true;
409 for (size_t i = frames_written; i < num_frames; i++) {
410 std::memcpy(&output_buffer[i * frame_size], &impl->last_frame[0],
411 frame_size_bytes);
412 }
413 frames_written = num_frames;
414 continue;
415 } else {
416 impl->queued_buffers--;
417 impl->SignalEvent(impl->playing_buffer);
418 }
419 }
420
421 // Get the minimum frames available between the currently playing buffer, and the
422 // amount we have left to fill
423 size_t frames_available{
424 std::min(playing_buffer.frames - playing_buffer.frames_played,
425 num_frames - frames_written)};
426
427 impl->samples_buffer.Pop(&output_buffer[frames_written * frame_size],
428 frames_available * frame_size);
429
430 frames_written += frames_available;
431 playing_buffer.frames_played += frames_available;
432
433 // If that's all the frames in the current buffer, add its samples and mark it as
434 // consumed
435 if (playing_buffer.frames_played >= playing_buffer.frames) {
436 impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels);
437 impl->playing_buffer.consumed = true;
438 }
439 }
440
441 std::memcpy(&impl->last_frame[0], &output_buffer[(frames_written - 1) * frame_size],
442 frame_size_bytes);
443 } 163 }
444 } 164 }
445 165
446 /// SDL device id of the opened input/output device 166 /// SDL device id of the opened input/output device
447 SDL_AudioDeviceID device{}; 167 SDL_AudioDeviceID device{};
448 /// Type of this stream
449 StreamType type;
450 /// Core system
451 Core::System& system;
452 /// Ring buffer of the samples waiting to be played or consumed
453 Common::RingBuffer<s16, 0x10000> samples_buffer;
454 /// Audio buffers queued and waiting to play
455 Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> queue;
456 /// The currently-playing audio buffer
457 ::AudioCore::Sink::SinkBuffer playing_buffer{};
458 /// Audio buffers which have been played and are in queue to be released by the audio system
459 Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> released_buffers{};
460 /// Currently released buffer waiting to be taken by the audio system
461 ::AudioCore::Sink::SinkBuffer released_buffer{};
462 /// The last played (or received) frame of audio, used when the callback underruns
463 std::array<s16, MaxChannels> last_frame{};
464}; 168};
465 169
466SDLSink::SDLSink(std::string_view target_device_name) { 170SDLSink::SDLSink(std::string_view target_device_name) {
@@ -482,14 +186,14 @@ SDLSink::SDLSink(std::string_view target_device_name) {
482 186
483SDLSink::~SDLSink() = default; 187SDLSink::~SDLSink() = default;
484 188
485SinkStream* SDLSink::AcquireSinkStream(Core::System& system, const u32 system_channels, 189SinkStream* SDLSink::AcquireSinkStream(Core::System& system, u32 system_channels,
486 const std::string&, const StreamType type) { 190 const std::string&, StreamType type) {
487 SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>( 191 SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>(
488 device_channels, system_channels, output_device, input_device, type, system)); 192 device_channels, system_channels, output_device, input_device, type, system));
489 return stream.get(); 193 return stream.get();
490} 194}
491 195
492void SDLSink::CloseStream(const SinkStream* stream) { 196void SDLSink::CloseStream(SinkStream* stream) {
493 for (size_t i = 0; i < sink_streams.size(); i++) { 197 for (size_t i = 0; i < sink_streams.size(); i++) {
494 if (sink_streams[i].get() == stream) { 198 if (sink_streams[i].get() == stream) {
495 sink_streams[i].reset(); 199 sink_streams[i].reset();
@@ -523,19 +227,19 @@ f32 SDLSink::GetDeviceVolume() const {
523 return sink_streams[0]->GetDeviceVolume(); 227 return sink_streams[0]->GetDeviceVolume();
524} 228}
525 229
526void SDLSink::SetDeviceVolume(const f32 volume) { 230void SDLSink::SetDeviceVolume(f32 volume) {
527 for (auto& stream : sink_streams) { 231 for (auto& stream : sink_streams) {
528 stream->SetDeviceVolume(volume); 232 stream->SetDeviceVolume(volume);
529 } 233 }
530} 234}
531 235
532void SDLSink::SetSystemVolume(const f32 volume) { 236void SDLSink::SetSystemVolume(f32 volume) {
533 for (auto& stream : sink_streams) { 237 for (auto& stream : sink_streams) {
534 stream->SetSystemVolume(volume); 238 stream->SetSystemVolume(volume);
535 } 239 }
536} 240}
537 241
538std::vector<std::string> ListSDLSinkDevices(const bool capture) { 242std::vector<std::string> ListSDLSinkDevices(bool capture) {
539 std::vector<std::string> device_list; 243 std::vector<std::string> device_list;
540 244
541 if (!SDL_WasInit(SDL_INIT_AUDIO)) { 245 if (!SDL_WasInit(SDL_INIT_AUDIO)) {
diff --git a/src/audio_core/sink/sdl2_sink.h b/src/audio_core/sink/sdl2_sink.h
index 186bc2fa3..57de9b6c2 100644
--- a/src/audio_core/sink/sdl2_sink.h
+++ b/src/audio_core/sink/sdl2_sink.h
@@ -44,7 +44,7 @@ public:
44 * 44 *
45 * @param stream - The stream to close. 45 * @param stream - The stream to close.
46 */ 46 */
47 void CloseStream(const SinkStream* stream) override; 47 void CloseStream(SinkStream* stream) override;
48 48
49 /** 49 /**
50 * Close all streams. 50 * Close all streams.
diff --git a/src/audio_core/sink/sink.h b/src/audio_core/sink/sink.h
index 91fe455e4..43d99b62e 100644
--- a/src/audio_core/sink/sink.h
+++ b/src/audio_core/sink/sink.h
@@ -32,7 +32,7 @@ public:
32 * 32 *
33 * @param stream - The stream to close. 33 * @param stream - The stream to close.
34 */ 34 */
35 virtual void CloseStream(const SinkStream* stream) = 0; 35 virtual void CloseStream(SinkStream* stream) = 0;
36 36
37 /** 37 /**
38 * Close all streams. 38 * Close all streams.
diff --git a/src/audio_core/sink/sink_details.cpp b/src/audio_core/sink/sink_details.cpp
index 253c0fd1e..67bdab779 100644
--- a/src/audio_core/sink/sink_details.cpp
+++ b/src/audio_core/sink/sink_details.cpp
@@ -5,7 +5,7 @@
5#include <memory> 5#include <memory>
6#include <string> 6#include <string>
7#include <vector> 7#include <vector>
8#include "audio_core/sink/null_sink.h" 8
9#include "audio_core/sink/sink_details.h" 9#include "audio_core/sink/sink_details.h"
10#ifdef HAVE_CUBEB 10#ifdef HAVE_CUBEB
11#include "audio_core/sink/cubeb_sink.h" 11#include "audio_core/sink/cubeb_sink.h"
@@ -13,6 +13,7 @@
13#ifdef HAVE_SDL2 13#ifdef HAVE_SDL2
14#include "audio_core/sink/sdl2_sink.h" 14#include "audio_core/sink/sdl2_sink.h"
15#endif 15#endif
16#include "audio_core/sink/null_sink.h"
16#include "common/logging/log.h" 17#include "common/logging/log.h"
17 18
18namespace AudioCore::Sink { 19namespace AudioCore::Sink {
@@ -59,8 +60,7 @@ const SinkDetails& GetOutputSinkDetails(std::string_view sink_id) {
59 60
60 if (sink_id == "auto" || iter == std::end(sink_details)) { 61 if (sink_id == "auto" || iter == std::end(sink_details)) {
61 if (sink_id != "auto") { 62 if (sink_id != "auto") {
62 LOG_ERROR(Audio, "AudioCore::Sink::GetOutputSinkDetails given invalid sink_id {}", 63 LOG_ERROR(Audio, "Invalid sink_id {}", sink_id);
63 sink_id);
64 } 64 }
65 // Auto-select. 65 // Auto-select.
66 // sink_details is ordered in terms of desirability, with the best choice at the front. 66 // sink_details is ordered in terms of desirability, with the best choice at the front.
diff --git a/src/audio_core/sink/sink_stream.cpp b/src/audio_core/sink/sink_stream.cpp
new file mode 100644
index 000000000..24636e512
--- /dev/null
+++ b/src/audio_core/sink/sink_stream.cpp
@@ -0,0 +1,265 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <atomic>
8#include <memory>
9#include <span>
10#include <vector>
11
12#include "audio_core/audio_core.h"
13#include "audio_core/common/common.h"
14#include "audio_core/sink/sink_stream.h"
15#include "common/common_types.h"
16#include "common/fixed_point.h"
17#include "common/settings.h"
18#include "core/core.h"
19
20namespace AudioCore::Sink {
21
22void SinkStream::AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) {
23 if (type == StreamType::In) {
24 queue.enqueue(buffer);
25 queued_buffers++;
26 return;
27 }
28
29 constexpr s32 min{std::numeric_limits<s16>::min()};
30 constexpr s32 max{std::numeric_limits<s16>::max()};
31
32 auto yuzu_volume{Settings::Volume()};
33 if (yuzu_volume > 1.0f) {
34 yuzu_volume = 0.6f + 20 * std::log10(yuzu_volume);
35 }
36 auto volume{system_volume * device_volume * yuzu_volume};
37
38 if (system_channels == 6 && device_channels == 2) {
39 // We're given 6 channels, but our device only outputs 2, so downmix.
40 constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f};
41
42 for (u32 read_index = 0, write_index = 0; read_index < samples.size();
43 read_index += system_channels, write_index += device_channels) {
44 const auto left_sample{
45 ((Common::FixedPoint<49, 15>(
46 samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
47 down_mix_coeff[0] +
48 samples[read_index + static_cast<u32>(Channels::Center)] * down_mix_coeff[1] +
49 samples[read_index + static_cast<u32>(Channels::LFE)] * down_mix_coeff[2] +
50 samples[read_index + static_cast<u32>(Channels::BackLeft)] * down_mix_coeff[3]) *
51 volume)
52 .to_int()};
53
54 const auto right_sample{
55 ((Common::FixedPoint<49, 15>(
56 samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
57 down_mix_coeff[0] +
58 samples[read_index + static_cast<u32>(Channels::Center)] * down_mix_coeff[1] +
59 samples[read_index + static_cast<u32>(Channels::LFE)] * down_mix_coeff[2] +
60 samples[read_index + static_cast<u32>(Channels::BackRight)] * down_mix_coeff[3]) *
61 volume)
62 .to_int()};
63
64 samples[write_index + static_cast<u32>(Channels::FrontLeft)] =
65 static_cast<s16>(std::clamp(left_sample, min, max));
66 samples[write_index + static_cast<u32>(Channels::FrontRight)] =
67 static_cast<s16>(std::clamp(right_sample, min, max));
68 }
69
70 samples.resize(samples.size() / system_channels * device_channels);
71
72 } else if (system_channels == 2 && device_channels == 6) {
73 // We need moar samples! Not all games will provide 6 channel audio.
74 // TODO: Implement some upmixing here. Currently just passthrough, with other
75 // channels left as silence.
76 std::vector<s16> new_samples(samples.size() / system_channels * device_channels, 0);
77
78 for (u32 read_index = 0, write_index = 0; read_index < samples.size();
79 read_index += system_channels, write_index += device_channels) {
80 const auto left_sample{static_cast<s16>(std::clamp(
81 static_cast<s32>(
82 static_cast<f32>(samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
83 volume),
84 min, max))};
85
86 new_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample;
87
88 const auto right_sample{static_cast<s16>(std::clamp(
89 static_cast<s32>(
90 static_cast<f32>(samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
91 volume),
92 min, max))};
93
94 new_samples[write_index + static_cast<u32>(Channels::FrontRight)] = right_sample;
95 }
96 samples = std::move(new_samples);
97
98 } else if (volume != 1.0f) {
99 for (u32 i = 0; i < samples.size(); i++) {
100 samples[i] = static_cast<s16>(
101 std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
102 }
103 }
104
105 samples_buffer.Push(samples);
106 queue.enqueue(buffer);
107 queued_buffers++;
108}
109
110std::vector<s16> SinkStream::ReleaseBuffer(u64 num_samples) {
111 constexpr s32 min = std::numeric_limits<s16>::min();
112 constexpr s32 max = std::numeric_limits<s16>::max();
113
114 auto samples{samples_buffer.Pop(num_samples)};
115
116 // TODO: Up-mix to 6 channels if the game expects it.
117 // For audio input this is unlikely to ever be the case though.
118
119 // Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here.
120 // TODO: Play with this and find something that works better.
121 auto volume{system_volume * device_volume * 8};
122 for (u32 i = 0; i < samples.size(); i++) {
123 samples[i] = static_cast<s16>(
124 std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
125 }
126
127 if (samples.size() < num_samples) {
128 samples.resize(num_samples, 0);
129 }
130 return samples;
131}
132
133void SinkStream::ClearQueue() {
134 samples_buffer.Pop();
135 while (queue.pop()) {
136 }
137 queued_buffers = 0;
138 playing_buffer = {};
139 playing_buffer.consumed = true;
140}
141
142void SinkStream::ProcessAudioIn(std::span<const s16> input_buffer, std::size_t num_frames) {
143 const std::size_t num_channels = GetDeviceChannels();
144 const std::size_t frame_size = num_channels;
145 const std::size_t frame_size_bytes = frame_size * sizeof(s16);
146 size_t frames_written{0};
147
148 if (queued_buffers > max_queue_size) {
149 Stall();
150 }
151
152 while (frames_written < num_frames) {
153 // If the playing buffer has been consumed or has no frames, we need a new one
154 if (playing_buffer.consumed || playing_buffer.frames == 0) {
155 if (!queue.try_dequeue(playing_buffer)) {
156 // If no buffer was available we've underrun, just push the samples and
157 // continue.
158 samples_buffer.Push(&input_buffer[frames_written * frame_size],
159 (num_frames - frames_written) * frame_size);
160 frames_written = num_frames;
161 continue;
162 }
163 // Successfully dequeued a new buffer.
164 queued_buffers--;
165 }
166
167 // Get the minimum frames available between the currently playing buffer, and the
168 // amount we have left to fill
169 size_t frames_available{std::min(playing_buffer.frames - playing_buffer.frames_played,
170 num_frames - frames_written)};
171
172 samples_buffer.Push(&input_buffer[frames_written * frame_size],
173 frames_available * frame_size);
174
175 frames_written += frames_available;
176 playing_buffer.frames_played += frames_available;
177
178 // If that's all the frames in the current buffer, add its samples and mark it as
179 // consumed
180 if (playing_buffer.frames_played >= playing_buffer.frames) {
181 playing_buffer.consumed = true;
182 }
183 }
184
185 std::memcpy(&last_frame[0], &input_buffer[(frames_written - 1) * frame_size], frame_size_bytes);
186
187 if (queued_buffers <= max_queue_size) {
188 Unstall();
189 }
190}
191
192void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::size_t num_frames) {
193 const std::size_t num_channels = GetDeviceChannels();
194 const std::size_t frame_size = num_channels;
195 const std::size_t frame_size_bytes = frame_size * sizeof(s16);
196 size_t frames_written{0};
197
198 // Due to many frames being queued up with nvdec (5 frames or so?), a lot of buffers also get
199 // queued up (30+) but not all at once, which causes constant stalling here, so just let the
200 // video play out without attempting to stall.
201 // Can hopefully remove this later with a more complete NVDEC implementation.
202 const auto nvdec_active{system.AudioCore().IsNVDECActive()};
203 if (!nvdec_active && queued_buffers > max_queue_size) {
204 Stall();
205 }
206
207 while (frames_written < num_frames) {
208 // If the playing buffer has been consumed or has no frames, we need a new one
209 if (playing_buffer.consumed || playing_buffer.frames == 0) {
210 if (!queue.try_dequeue(playing_buffer)) {
211 // If no buffer was available we've underrun, fill the remaining buffer with
212 // the last written frame and continue.
213 for (size_t i = frames_written; i < num_frames; i++) {
214 std::memcpy(&output_buffer[i * frame_size], &last_frame[0], frame_size_bytes);
215 }
216 frames_written = num_frames;
217 continue;
218 }
219 // Successfully dequeued a new buffer.
220 queued_buffers--;
221 }
222
223 // Get the minimum frames available between the currently playing buffer, and the
224 // amount we have left to fill
225 size_t frames_available{std::min(playing_buffer.frames - playing_buffer.frames_played,
226 num_frames - frames_written)};
227
228 samples_buffer.Pop(&output_buffer[frames_written * frame_size],
229 frames_available * frame_size);
230
231 frames_written += frames_available;
232 playing_buffer.frames_played += frames_available;
233
234 // If that's all the frames in the current buffer, add its samples and mark it as
235 // consumed
236 if (playing_buffer.frames_played >= playing_buffer.frames) {
237 playing_buffer.consumed = true;
238 }
239 }
240
241 std::memcpy(&last_frame[0], &output_buffer[(frames_written - 1) * frame_size],
242 frame_size_bytes);
243
244 if (stalled && queued_buffers <= max_queue_size) {
245 Unstall();
246 }
247}
248
249void SinkStream::Stall() {
250 if (stalled) {
251 return;
252 }
253 stalled = true;
254 system.StallProcesses();
255}
256
257void SinkStream::Unstall() {
258 if (!stalled) {
259 return;
260 }
261 system.UnstallProcesses();
262 stalled = false;
263}
264
265} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/sink_stream.h b/src/audio_core/sink/sink_stream.h
index 17ed6593f..db7cff45e 100644
--- a/src/audio_core/sink/sink_stream.h
+++ b/src/audio_core/sink/sink_stream.h
@@ -3,12 +3,20 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <array>
6#include <atomic> 7#include <atomic>
7#include <memory> 8#include <memory>
9#include <span>
8#include <vector> 10#include <vector>
9 11
10#include "audio_core/common/common.h" 12#include "audio_core/common/common.h"
11#include "common/common_types.h" 13#include "common/common_types.h"
14#include "common/reader_writer_queue.h"
15#include "common/ring_buffer.h"
16
17namespace Core {
18class System;
19} // namespace Core
12 20
13namespace AudioCore::Sink { 21namespace AudioCore::Sink {
14 22
@@ -34,20 +42,24 @@ struct SinkBuffer {
34 * You should regularly call IsBufferConsumed with the unique SinkBuffer tag to check if the buffer 42 * You should regularly call IsBufferConsumed with the unique SinkBuffer tag to check if the buffer
35 * has been consumed. 43 * has been consumed.
36 * 44 *
37 * Since these are a FIFO queue, always check IsBufferConsumed in the same order you appended the 45 * Since these are a FIFO queue, IsBufferConsumed must be checked in the same order buffers were
38 * buffers, skipping a buffer will result in all following buffers to never release. 46 * appended, skipping a buffer will result in the queue getting stuck, and all following buffers to
47 * never release.
39 * 48 *
40 * If the buffers appear to be stuck, you can stop and re-open an IAudioIn/IAudioOut service (this 49 * If the buffers appear to be stuck, you can stop and re-open an IAudioIn/IAudioOut service (this
41 * is what games do), or call ClearQueue to flush all of the buffers without a full restart. 50 * is what games do), or call ClearQueue to flush all of the buffers without a full restart.
42 */ 51 */
43class SinkStream { 52class SinkStream {
44public: 53public:
45 virtual ~SinkStream() = default; 54 explicit SinkStream(Core::System& system_, StreamType type_) : system{system_}, type{type_} {}
55 virtual ~SinkStream() {
56 Unstall();
57 }
46 58
47 /** 59 /**
48 * Finalize the sink stream. 60 * Finalize the sink stream.
49 */ 61 */
50 virtual void Finalize() = 0; 62 virtual void Finalize() {}
51 63
52 /** 64 /**
53 * Start the sink stream. 65 * Start the sink stream.
@@ -55,48 +67,19 @@ public:
55 * @param resume - Set to true if this is resuming the stream a previously-active stream. 67 * @param resume - Set to true if this is resuming the stream a previously-active stream.
56 * Default false. 68 * Default false.
57 */ 69 */
58 virtual void Start(bool resume = false) = 0; 70 virtual void Start(bool resume = false) {}
59 71
60 /** 72 /**
61 * Stop the sink stream. 73 * Stop the sink stream.
62 */ 74 */
63 virtual void Stop() = 0; 75 virtual void Stop() {}
64
65 /**
66 * Append a new buffer and its samples to a waiting queue to play.
67 *
68 * @param buffer - Audio buffer information to be queued.
69 * @param samples - The s16 samples to be queue for playback.
70 */
71 virtual void AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) = 0;
72
73 /**
74 * Release a buffer. Audio In only, will fill a buffer with recorded samples.
75 *
76 * @param num_samples - Maximum number of samples to receive.
77 * @return Vector of recorded samples. May have fewer than num_samples.
78 */
79 virtual std::vector<s16> ReleaseBuffer(u64 num_samples) = 0;
80
81 /**
82 * Check if a certain buffer has been consumed (fully played).
83 *
84 * @param tag - Unique tag of a buffer to check for.
85 * @return True if the buffer has been played, otherwise false.
86 */
87 virtual bool IsBufferConsumed(u64 tag) = 0;
88
89 /**
90 * Empty out the buffer queue.
91 */
92 virtual void ClearQueue() = 0;
93 76
94 /** 77 /**
95 * Check if the stream is paused. 78 * Check if the stream is paused.
96 * 79 *
97 * @return True if paused, otherwise false. 80 * @return True if paused, otherwise false.
98 */ 81 */
99 bool IsPaused() { 82 bool IsPaused() const {
100 return paused; 83 return paused;
101 } 84 }
102 85
@@ -128,34 +111,6 @@ public:
128 } 111 }
129 112
130 /** 113 /**
131 * Get the total number of samples played by this stream.
132 *
133 * @return Number of samples played.
134 */
135 u64 GetPlayedSampleCount() const {
136 return played_sample_count;
137 }
138
139 /**
140 * Set the number of samples played.
141 * This is started and stopped on system start/stop.
142 *
143 * @param played_sample_count_ - Number of samples to set.
144 */
145 void SetPlayedSampleCount(u64 played_sample_count_) {
146 played_sample_count = played_sample_count_;
147 }
148
149 /**
150 * Add to the played sample count.
151 *
152 * @param num_samples - Number of samples to add.
153 */
154 void AddPlayedSampleCount(u64 num_samples) {
155 played_sample_count += num_samples;
156 }
157
158 /**
159 * Get the system volume. 114 * Get the system volume.
160 * 115 *
161 * @return The current system volume. 116 * @return The current system volume.
@@ -200,15 +155,65 @@ public:
200 return queued_buffers.load(); 155 return queued_buffers.load();
201 } 156 }
202 157
158 /**
159 * Set the maximum buffer queue size.
160 */
161 void SetRingSize(u32 ring_size) {
162 max_queue_size = ring_size;
163 }
164
165 /**
166 * Append a new buffer and its samples to a waiting queue to play.
167 *
168 * @param buffer - Audio buffer information to be queued.
169 * @param samples - The s16 samples to be queue for playback.
170 */
171 virtual void AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples);
172
173 /**
174 * Release a buffer. Audio In only, will fill a buffer with recorded samples.
175 *
176 * @param num_samples - Maximum number of samples to receive.
177 * @return Vector of recorded samples. May have fewer than num_samples.
178 */
179 virtual std::vector<s16> ReleaseBuffer(u64 num_samples);
180
181 /**
182 * Empty out the buffer queue.
183 */
184 void ClearQueue();
185
186 /**
187 * Callback for AudioIn.
188 *
189 * @param input_buffer - Input buffer to be filled with samples.
190 * @param num_frames - Number of frames to be filled.
191 */
192 void ProcessAudioIn(std::span<const s16> input_buffer, std::size_t num_frames);
193
194 /**
195 * Callback for AudioOut and AudioRenderer.
196 *
197 * @param output_buffer - Output buffer to be filled with samples.
198 * @param num_frames - Number of frames to be filled.
199 */
200 void ProcessAudioOutAndRender(std::span<s16> output_buffer, std::size_t num_frames);
201
202 /**
203 * Stall core processes if the audio thread falls too far behind.
204 */
205 void Stall();
206
207 /**
208 * Unstall core processes.
209 */
210 void Unstall();
211
203protected: 212protected:
204 /// Number of buffers waiting to be played 213 /// Core system
205 std::atomic<u32> queued_buffers{}; 214 Core::System& system;
206 /// Total samples played by this stream 215 /// Type of this stream
207 std::atomic<u64> played_sample_count{}; 216 StreamType type;
208 /// Set by the audio render/in/out system which uses this stream
209 f32 system_volume{1.0f};
210 /// Set via IAudioDevice service calls
211 f32 device_volume{1.0f};
212 /// Set by the audio render/in/out systen which uses this stream 217 /// Set by the audio render/in/out systen which uses this stream
213 u32 system_channels{2}; 218 u32 system_channels{2};
214 /// Channels supported by hardware 219 /// Channels supported by hardware
@@ -217,6 +222,28 @@ protected:
217 std::atomic<bool> paused{true}; 222 std::atomic<bool> paused{true};
218 /// Was this stream previously playing? 223 /// Was this stream previously playing?
219 std::atomic<bool> was_playing{false}; 224 std::atomic<bool> was_playing{false};
225 /// Name of this stream
226 std::string name{};
227
228private:
229 /// Ring buffer of the samples waiting to be played or consumed
230 Common::RingBuffer<s16, 0x10000> samples_buffer;
231 /// Audio buffers queued and waiting to play
232 Common::ReaderWriterQueue<SinkBuffer> queue;
233 /// The currently-playing audio buffer
234 SinkBuffer playing_buffer{};
235 /// The last played (or received) frame of audio, used when the callback underruns
236 std::array<s16, MaxChannels> last_frame{};
237 /// Number of buffers waiting to be played
238 std::atomic<u32> queued_buffers{};
239 /// The ring size for audio out buffers (usually 4, rarely 2 or 8)
240 u32 max_queue_size{};
241 /// Set by the audio render/in/out system which uses this stream
242 f32 system_volume{1.0f};
243 /// Set via IAudioDevice service calls
244 f32 device_volume{1.0f};
245 /// True if coretiming has been stalled
246 bool stalled{false};
220}; 247};
221 248
222using SinkStreamPtr = std::unique_ptr<SinkStream>; 249using SinkStreamPtr = std::unique_ptr<SinkStream>;
diff --git a/src/core/hle/result.h b/src/core/hle/result.h
index 4de44cd06..47a1b829b 100644
--- a/src/core/hle/result.h
+++ b/src/core/hle/result.h
@@ -117,6 +117,7 @@ union Result {
117 BitField<0, 9, ErrorModule> module; 117 BitField<0, 9, ErrorModule> module;
118 BitField<9, 13, u32> description; 118 BitField<9, 13, u32> description;
119 119
120 Result() = default;
120 constexpr explicit Result(u32 raw_) : raw(raw_) {} 121 constexpr explicit Result(u32 raw_) : raw(raw_) {}
121 122
122 constexpr Result(ErrorModule module_, u32 description_) 123 constexpr Result(ErrorModule module_, u32 description_)
@@ -130,6 +131,7 @@ union Result {
130 return !IsSuccess(); 131 return !IsSuccess();
131 } 132 }
132}; 133};
134static_assert(std::is_trivial_v<Result>);
133 135
134[[nodiscard]] constexpr bool operator==(const Result& a, const Result& b) { 136[[nodiscard]] constexpr bool operator==(const Result& a, const Result& b) {
135 return a.raw == b.raw; 137 return a.raw == b.raw;
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
index 2a5128c60..a7385fce8 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
@@ -1,6 +1,7 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "audio_core/audio_core.h"
4#include "common/assert.h" 5#include "common/assert.h"
5#include "common/logging/log.h" 6#include "common/logging/log.h"
6#include "core/core.h" 7#include "core/core.h"
@@ -65,7 +66,10 @@ NvResult nvhost_nvdec::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>&
65 return NvResult::NotImplemented; 66 return NvResult::NotImplemented;
66} 67}
67 68
68void nvhost_nvdec::OnOpen(DeviceFD fd) {} 69void nvhost_nvdec::OnOpen(DeviceFD fd) {
70 LOG_INFO(Service_NVDRV, "NVDEC video stream started");
71 system.AudioCore().SetNVDECActive(true);
72}
69 73
70void nvhost_nvdec::OnClose(DeviceFD fd) { 74void nvhost_nvdec::OnClose(DeviceFD fd) {
71 LOG_INFO(Service_NVDRV, "NVDEC video stream ended"); 75 LOG_INFO(Service_NVDRV, "NVDEC video stream ended");
@@ -73,6 +77,7 @@ void nvhost_nvdec::OnClose(DeviceFD fd) {
73 if (iter != fd_to_id.end()) { 77 if (iter != fd_to_id.end()) {
74 system.GPU().ClearCdmaInstance(iter->second); 78 system.GPU().ClearCdmaInstance(iter->second);
75 } 79 }
80 system.AudioCore().SetNVDECActive(false);
76} 81}
77 82
78} // namespace Service::Nvidia::Devices 83} // namespace Service::Nvidia::Devices