diff options
29 files changed, 610 insertions, 839 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 | ||
| 60 | u32 AudioCore::GetStreamQueue() const { | 60 | void AudioCore::SetNVDECActive(bool active) { |
| 61 | return estimated_queue.load(); | 61 | nvdec_active = active; |
| 62 | } | 62 | } |
| 63 | 63 | ||
| 64 | void AudioCore::SetStreamQueue(u32 size) { | 64 | bool 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 | ||
| 82 | private: | 80 | private: |
| 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 @@ | |||
| 8 | namespace AudioCore { | 8 | namespace AudioCore { |
| 9 | 9 | ||
| 10 | struct AudioBuffer { | 10 | struct 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 | |||
| 283 | private: | 294 | private: |
| 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 | ||
| 12 | namespace AudioCore { | 13 | namespace AudioCore { |
| 13 | 14 | ||
| 14 | DeviceSession::DeviceSession(Core::System& system_) : system{system_} {} | 15 | using namespace std::literals; |
| 16 | constexpr auto INCREMENT_TIME{5ms}; | ||
| 17 | |||
| 18 | DeviceSession::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 | ||
| 16 | DeviceSession::~DeviceSession() { | 25 | DeviceSession::~DeviceSession() { |
| 17 | Finalize(); | 26 | Finalize(); |
| @@ -50,20 +59,21 @@ void DeviceSession::Finalize() { | |||
| 50 | } | 59 | } |
| 51 | 60 | ||
| 52 | void DeviceSession::Start() { | 61 | void 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 | ||
| 57 | void DeviceSession::Stop() { | 69 | void 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 | ||
| 64 | void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const { | 76 | void 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 | ||
| 86 | void DeviceSession::ReleaseBuffer(AudioBuffer& buffer) const { | 96 | void 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 | ||
| 94 | bool DeviceSession::IsBufferConsumed(u64 tag) const { | 103 | bool 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 | ||
| 101 | void DeviceSession::SetVolume(f32 volume) const { | 107 | void DeviceSession::SetVolume(f32 volume) const { |
| @@ -105,10 +111,22 @@ void DeviceSession::SetVolume(f32 volume) const { | |||
| 105 | } | 111 | } |
| 106 | 112 | ||
| 107 | u64 DeviceSession::GetPlayedSampleCount() const { | 113 | u64 DeviceSession::GetPlayedSampleCount() const { |
| 108 | if (stream) { | 114 | return played_sample_count; |
| 109 | return stream->GetPlayedSampleCount(); | 115 | } |
| 116 | |||
| 117 | std::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 | |||
| 128 | void 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 | ||
| 12 | namespace Core { | 15 | namespace Core { |
| 13 | class System; | 16 | class System; |
| 14 | } | 17 | namespace Timing { |
| 18 | struct EventType; | ||
| 19 | } // namespace Timing | ||
| 20 | } // namespace Core | ||
| 15 | 21 | ||
| 16 | namespace AudioCore { | 22 | namespace AudioCore { |
| 23 | |||
| 17 | namespace Sink { | 24 | namespace Sink { |
| 18 | class SinkStream; | 25 | class SinkStream; |
| 19 | struct SinkBuffer; | 26 | struct 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 | |||
| 99 | private: | 116 | private: |
| 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 | ||
| 45 | void BehaviorInfo::CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) { | 45 | void 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 | ||
| 55 | void BehaviorInfo::UpdateFlags(const Flags flags_) { | 57 | void 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 | ||
| 51 | bool DeviceSinkCommand::Verify(const ADSP::CommandListProcessor& processor) { | 55 | bool 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 | ||
| 17 | namespace AudioCore::AudioRenderer { | 17 | namespace AudioCore::AudioRenderer { |
| 18 | constexpr std::chrono::nanoseconds BaseRenderTime{5'000'000UL}; | 18 | constexpr std::chrono::nanoseconds RENDER_TIME{5'000'000UL}; |
| 19 | constexpr std::chrono::nanoseconds RenderTimeOffset{400'000UL}; | ||
| 20 | 19 | ||
| 21 | SystemManager::SystemManager(Core::System& core_) | 20 | SystemManager::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 | ||
| 123 | std::optional<std::chrono::nanoseconds> SystemManager::ThreadFunc2(s64 time) { | 122 | std::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 | ||
| 155 | void SystemManager::PauseCallback(bool paused) { | 128 | void 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 | |||
| 339 | private: | 166 | private: |
| 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 | ||
| 521 | CubebSink::CubebSink(std::string_view target_device_name) { | 216 | CubebSink::CubebSink(std::string_view target_device_name) { |
| @@ -569,15 +264,15 @@ CubebSink::~CubebSink() { | |||
| 569 | #endif | 264 | #endif |
| 570 | } | 265 | } |
| 571 | 266 | ||
| 572 | SinkStream* CubebSink::AcquireSinkStream(Core::System& system, const u32 system_channels, | 267 | SinkStream* 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 | ||
| 580 | void CubebSink::CloseStream(const SinkStream* stream) { | 275 | void 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 | ||
| 614 | void CubebSink::SetDeviceVolume(const f32 volume) { | 309 | void 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 | ||
| 620 | void CubebSink::SetSystemVolume(const f32 volume) { | 315 | void 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 | ||
| 626 | std::vector<std::string> ListCubebSinkDevices(const bool capture) { | 321 | std::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 | ||
| 13 | namespace Core { | ||
| 14 | class System; | ||
| 15 | } // namespace Core | ||
| 16 | |||
| 9 | namespace AudioCore::Sink { | 17 | namespace AudioCore::Sink { |
| 18 | class NullSinkStreamImpl final : public SinkStream { | ||
| 19 | public: | ||
| 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 | ||
| 35 | private: | 55 | private: |
| 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 | |||
| 309 | private: | 136 | private: |
| 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 | ||
| 466 | SDLSink::SDLSink(std::string_view target_device_name) { | 170 | SDLSink::SDLSink(std::string_view target_device_name) { |
| @@ -482,14 +186,14 @@ SDLSink::SDLSink(std::string_view target_device_name) { | |||
| 482 | 186 | ||
| 483 | SDLSink::~SDLSink() = default; | 187 | SDLSink::~SDLSink() = default; |
| 484 | 188 | ||
| 485 | SinkStream* SDLSink::AcquireSinkStream(Core::System& system, const u32 system_channels, | 189 | SinkStream* 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 | ||
| 492 | void SDLSink::CloseStream(const SinkStream* stream) { | 196 | void 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 | ||
| 526 | void SDLSink::SetDeviceVolume(const f32 volume) { | 230 | void 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 | ||
| 532 | void SDLSink::SetSystemVolume(const f32 volume) { | 236 | void 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 | ||
| 538 | std::vector<std::string> ListSDLSinkDevices(const bool capture) { | 242 | std::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 | ||
| 18 | namespace AudioCore::Sink { | 19 | namespace 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..68987eba6 --- /dev/null +++ b/src/audio_core/sink/sink_stream.cpp | |||
| @@ -0,0 +1,263 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <array> | ||
| 5 | #include <atomic> | ||
| 6 | #include <memory> | ||
| 7 | #include <span> | ||
| 8 | #include <vector> | ||
| 9 | |||
| 10 | #include "audio_core/audio_core.h" | ||
| 11 | #include "audio_core/common/common.h" | ||
| 12 | #include "audio_core/sink/sink_stream.h" | ||
| 13 | #include "common/common_types.h" | ||
| 14 | #include "common/fixed_point.h" | ||
| 15 | #include "common/settings.h" | ||
| 16 | #include "core/core.h" | ||
| 17 | |||
| 18 | namespace AudioCore::Sink { | ||
| 19 | |||
| 20 | void SinkStream::AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) { | ||
| 21 | if (type == StreamType::In) { | ||
| 22 | queue.enqueue(buffer); | ||
| 23 | queued_buffers++; | ||
| 24 | return; | ||
| 25 | } | ||
| 26 | |||
| 27 | constexpr s32 min{std::numeric_limits<s16>::min()}; | ||
| 28 | constexpr s32 max{std::numeric_limits<s16>::max()}; | ||
| 29 | |||
| 30 | auto yuzu_volume{Settings::Volume()}; | ||
| 31 | if (yuzu_volume > 1.0f) { | ||
| 32 | yuzu_volume = 0.6f + 20 * std::log10(yuzu_volume); | ||
| 33 | } | ||
| 34 | auto volume{system_volume * device_volume * yuzu_volume}; | ||
| 35 | |||
| 36 | if (system_channels == 6 && device_channels == 2) { | ||
| 37 | // We're given 6 channels, but our device only outputs 2, so downmix. | ||
| 38 | constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f}; | ||
| 39 | |||
| 40 | for (u32 read_index = 0, write_index = 0; read_index < samples.size(); | ||
| 41 | read_index += system_channels, write_index += device_channels) { | ||
| 42 | const auto left_sample{ | ||
| 43 | ((Common::FixedPoint<49, 15>( | ||
| 44 | samples[read_index + static_cast<u32>(Channels::FrontLeft)]) * | ||
| 45 | down_mix_coeff[0] + | ||
| 46 | samples[read_index + static_cast<u32>(Channels::Center)] * down_mix_coeff[1] + | ||
| 47 | samples[read_index + static_cast<u32>(Channels::LFE)] * down_mix_coeff[2] + | ||
| 48 | samples[read_index + static_cast<u32>(Channels::BackLeft)] * down_mix_coeff[3]) * | ||
| 49 | volume) | ||
| 50 | .to_int()}; | ||
| 51 | |||
| 52 | const auto right_sample{ | ||
| 53 | ((Common::FixedPoint<49, 15>( | ||
| 54 | samples[read_index + static_cast<u32>(Channels::FrontRight)]) * | ||
| 55 | down_mix_coeff[0] + | ||
| 56 | samples[read_index + static_cast<u32>(Channels::Center)] * down_mix_coeff[1] + | ||
| 57 | samples[read_index + static_cast<u32>(Channels::LFE)] * down_mix_coeff[2] + | ||
| 58 | samples[read_index + static_cast<u32>(Channels::BackRight)] * down_mix_coeff[3]) * | ||
| 59 | volume) | ||
| 60 | .to_int()}; | ||
| 61 | |||
| 62 | samples[write_index + static_cast<u32>(Channels::FrontLeft)] = | ||
| 63 | static_cast<s16>(std::clamp(left_sample, min, max)); | ||
| 64 | samples[write_index + static_cast<u32>(Channels::FrontRight)] = | ||
| 65 | static_cast<s16>(std::clamp(right_sample, min, max)); | ||
| 66 | } | ||
| 67 | |||
| 68 | samples.resize(samples.size() / system_channels * device_channels); | ||
| 69 | |||
| 70 | } else if (system_channels == 2 && device_channels == 6) { | ||
| 71 | // We need moar samples! Not all games will provide 6 channel audio. | ||
| 72 | // TODO: Implement some upmixing here. Currently just passthrough, with other | ||
| 73 | // channels left as silence. | ||
| 74 | std::vector<s16> new_samples(samples.size() / system_channels * device_channels, 0); | ||
| 75 | |||
| 76 | for (u32 read_index = 0, write_index = 0; read_index < samples.size(); | ||
| 77 | read_index += system_channels, write_index += device_channels) { | ||
| 78 | const auto left_sample{static_cast<s16>(std::clamp( | ||
| 79 | static_cast<s32>( | ||
| 80 | static_cast<f32>(samples[read_index + static_cast<u32>(Channels::FrontLeft)]) * | ||
| 81 | volume), | ||
| 82 | min, max))}; | ||
| 83 | |||
| 84 | new_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample; | ||
| 85 | |||
| 86 | const auto right_sample{static_cast<s16>(std::clamp( | ||
| 87 | static_cast<s32>( | ||
| 88 | static_cast<f32>(samples[read_index + static_cast<u32>(Channels::FrontRight)]) * | ||
| 89 | volume), | ||
| 90 | min, max))}; | ||
| 91 | |||
| 92 | new_samples[write_index + static_cast<u32>(Channels::FrontRight)] = right_sample; | ||
| 93 | } | ||
| 94 | samples = std::move(new_samples); | ||
| 95 | |||
| 96 | } else if (volume != 1.0f) { | ||
| 97 | for (u32 i = 0; i < samples.size(); i++) { | ||
| 98 | samples[i] = static_cast<s16>( | ||
| 99 | std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max)); | ||
| 100 | } | ||
| 101 | } | ||
| 102 | |||
| 103 | samples_buffer.Push(samples); | ||
| 104 | queue.enqueue(buffer); | ||
| 105 | queued_buffers++; | ||
| 106 | } | ||
| 107 | |||
| 108 | std::vector<s16> SinkStream::ReleaseBuffer(u64 num_samples) { | ||
| 109 | constexpr s32 min = std::numeric_limits<s16>::min(); | ||
| 110 | constexpr s32 max = std::numeric_limits<s16>::max(); | ||
| 111 | |||
| 112 | auto samples{samples_buffer.Pop(num_samples)}; | ||
| 113 | |||
| 114 | // TODO: Up-mix to 6 channels if the game expects it. | ||
| 115 | // For audio input this is unlikely to ever be the case though. | ||
| 116 | |||
| 117 | // Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here. | ||
| 118 | // TODO: Play with this and find something that works better. | ||
| 119 | auto volume{system_volume * device_volume * 8}; | ||
| 120 | for (u32 i = 0; i < samples.size(); i++) { | ||
| 121 | samples[i] = static_cast<s16>( | ||
| 122 | std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max)); | ||
| 123 | } | ||
| 124 | |||
| 125 | if (samples.size() < num_samples) { | ||
| 126 | samples.resize(num_samples, 0); | ||
| 127 | } | ||
| 128 | return samples; | ||
| 129 | } | ||
| 130 | |||
| 131 | void SinkStream::ClearQueue() { | ||
| 132 | samples_buffer.Pop(); | ||
| 133 | while (queue.pop()) { | ||
| 134 | } | ||
| 135 | queued_buffers = 0; | ||
| 136 | playing_buffer = {}; | ||
| 137 | playing_buffer.consumed = true; | ||
| 138 | } | ||
| 139 | |||
| 140 | void SinkStream::ProcessAudioIn(std::span<const s16> input_buffer, std::size_t num_frames) { | ||
| 141 | const std::size_t num_channels = GetDeviceChannels(); | ||
| 142 | const std::size_t frame_size = num_channels; | ||
| 143 | const std::size_t frame_size_bytes = frame_size * sizeof(s16); | ||
| 144 | size_t frames_written{0}; | ||
| 145 | |||
| 146 | if (queued_buffers > max_queue_size) { | ||
| 147 | Stall(); | ||
| 148 | } | ||
| 149 | |||
| 150 | while (frames_written < num_frames) { | ||
| 151 | // If the playing buffer has been consumed or has no frames, we need a new one | ||
| 152 | if (playing_buffer.consumed || playing_buffer.frames == 0) { | ||
| 153 | if (!queue.try_dequeue(playing_buffer)) { | ||
| 154 | // If no buffer was available we've underrun, just push the samples and | ||
| 155 | // continue. | ||
| 156 | samples_buffer.Push(&input_buffer[frames_written * frame_size], | ||
| 157 | (num_frames - frames_written) * frame_size); | ||
| 158 | frames_written = num_frames; | ||
| 159 | continue; | ||
| 160 | } | ||
| 161 | // Successfully dequeued a new buffer. | ||
| 162 | queued_buffers--; | ||
| 163 | } | ||
| 164 | |||
| 165 | // Get the minimum frames available between the currently playing buffer, and the | ||
| 166 | // amount we have left to fill | ||
| 167 | size_t frames_available{std::min(playing_buffer.frames - playing_buffer.frames_played, | ||
| 168 | num_frames - frames_written)}; | ||
| 169 | |||
| 170 | samples_buffer.Push(&input_buffer[frames_written * frame_size], | ||
| 171 | frames_available * frame_size); | ||
| 172 | |||
| 173 | frames_written += frames_available; | ||
| 174 | playing_buffer.frames_played += frames_available; | ||
| 175 | |||
| 176 | // If that's all the frames in the current buffer, add its samples and mark it as | ||
| 177 | // consumed | ||
| 178 | if (playing_buffer.frames_played >= playing_buffer.frames) { | ||
| 179 | playing_buffer.consumed = true; | ||
| 180 | } | ||
| 181 | } | ||
| 182 | |||
| 183 | std::memcpy(&last_frame[0], &input_buffer[(frames_written - 1) * frame_size], frame_size_bytes); | ||
| 184 | |||
| 185 | if (queued_buffers <= max_queue_size) { | ||
| 186 | Unstall(); | ||
| 187 | } | ||
| 188 | } | ||
| 189 | |||
| 190 | void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::size_t num_frames) { | ||
| 191 | const std::size_t num_channels = GetDeviceChannels(); | ||
| 192 | const std::size_t frame_size = num_channels; | ||
| 193 | const std::size_t frame_size_bytes = frame_size * sizeof(s16); | ||
| 194 | size_t frames_written{0}; | ||
| 195 | |||
| 196 | // Due to many frames being queued up with nvdec (5 frames or so?), a lot of buffers also get | ||
| 197 | // queued up (30+) but not all at once, which causes constant stalling here, so just let the | ||
| 198 | // video play out without attempting to stall. | ||
| 199 | // Can hopefully remove this later with a more complete NVDEC implementation. | ||
| 200 | const auto nvdec_active{system.AudioCore().IsNVDECActive()}; | ||
| 201 | if (!nvdec_active && queued_buffers > max_queue_size) { | ||
| 202 | Stall(); | ||
| 203 | } | ||
| 204 | |||
| 205 | while (frames_written < num_frames) { | ||
| 206 | // If the playing buffer has been consumed or has no frames, we need a new one | ||
| 207 | if (playing_buffer.consumed || playing_buffer.frames == 0) { | ||
| 208 | if (!queue.try_dequeue(playing_buffer)) { | ||
| 209 | // If no buffer was available we've underrun, fill the remaining buffer with | ||
| 210 | // the last written frame and continue. | ||
| 211 | for (size_t i = frames_written; i < num_frames; i++) { | ||
| 212 | std::memcpy(&output_buffer[i * frame_size], &last_frame[0], frame_size_bytes); | ||
| 213 | } | ||
| 214 | frames_written = num_frames; | ||
| 215 | continue; | ||
| 216 | } | ||
| 217 | // Successfully dequeued a new buffer. | ||
| 218 | queued_buffers--; | ||
| 219 | } | ||
| 220 | |||
| 221 | // Get the minimum frames available between the currently playing buffer, and the | ||
| 222 | // amount we have left to fill | ||
| 223 | size_t frames_available{std::min(playing_buffer.frames - playing_buffer.frames_played, | ||
| 224 | num_frames - frames_written)}; | ||
| 225 | |||
| 226 | samples_buffer.Pop(&output_buffer[frames_written * frame_size], | ||
| 227 | frames_available * frame_size); | ||
| 228 | |||
| 229 | frames_written += frames_available; | ||
| 230 | playing_buffer.frames_played += frames_available; | ||
| 231 | |||
| 232 | // If that's all the frames in the current buffer, add its samples and mark it as | ||
| 233 | // consumed | ||
| 234 | if (playing_buffer.frames_played >= playing_buffer.frames) { | ||
| 235 | playing_buffer.consumed = true; | ||
| 236 | } | ||
| 237 | } | ||
| 238 | |||
| 239 | std::memcpy(&last_frame[0], &output_buffer[(frames_written - 1) * frame_size], | ||
| 240 | frame_size_bytes); | ||
| 241 | |||
| 242 | if (stalled && queued_buffers <= max_queue_size) { | ||
| 243 | Unstall(); | ||
| 244 | } | ||
| 245 | } | ||
| 246 | |||
| 247 | void SinkStream::Stall() { | ||
| 248 | if (stalled) { | ||
| 249 | return; | ||
| 250 | } | ||
| 251 | stalled = true; | ||
| 252 | system.StallProcesses(); | ||
| 253 | } | ||
| 254 | |||
| 255 | void SinkStream::Unstall() { | ||
| 256 | if (!stalled) { | ||
| 257 | return; | ||
| 258 | } | ||
| 259 | system.UnstallProcesses(); | ||
| 260 | stalled = false; | ||
| 261 | } | ||
| 262 | |||
| 263 | } // 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 | |||
| 17 | namespace Core { | ||
| 18 | class System; | ||
| 19 | } // namespace Core | ||
| 12 | 20 | ||
| 13 | namespace AudioCore::Sink { | 21 | namespace 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 | */ |
| 43 | class SinkStream { | 52 | class SinkStream { |
| 44 | public: | 53 | public: |
| 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 | |||
| 203 | protected: | 212 | protected: |
| 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 | |||
| 228 | private: | ||
| 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 | ||
| 222 | using SinkStreamPtr = std::unique_ptr<SinkStream>; | 249 | using SinkStreamPtr = std::unique_ptr<SinkStream>; |
diff --git a/src/common/input.h b/src/common/input.h index 213aa2384..825b0d650 100644 --- a/src/common/input.h +++ b/src/common/input.h | |||
| @@ -102,6 +102,8 @@ struct AnalogProperties { | |||
| 102 | float offset{}; | 102 | float offset{}; |
| 103 | // Invert direction of the sensor data | 103 | // Invert direction of the sensor data |
| 104 | bool inverted{}; | 104 | bool inverted{}; |
| 105 | // Press once to activate, press again to release | ||
| 106 | bool toggle{}; | ||
| 105 | }; | 107 | }; |
| 106 | 108 | ||
| 107 | // Single analog sensor data | 109 | // Single analog sensor data |
| @@ -115,8 +117,11 @@ struct AnalogStatus { | |||
| 115 | struct ButtonStatus { | 117 | struct ButtonStatus { |
| 116 | Common::UUID uuid{}; | 118 | Common::UUID uuid{}; |
| 117 | bool value{}; | 119 | bool value{}; |
| 120 | // Invert value of the button | ||
| 118 | bool inverted{}; | 121 | bool inverted{}; |
| 122 | // Press once to activate, press again to release | ||
| 119 | bool toggle{}; | 123 | bool toggle{}; |
| 124 | // Internal lock for the toggle status | ||
| 120 | bool locked{}; | 125 | bool locked{}; |
| 121 | }; | 126 | }; |
| 122 | 127 | ||
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp index f9f902c2d..01c43be93 100644 --- a/src/core/hid/emulated_controller.cpp +++ b/src/core/hid/emulated_controller.cpp | |||
| @@ -562,6 +562,16 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback | |||
| 562 | return; | 562 | return; |
| 563 | } | 563 | } |
| 564 | 564 | ||
| 565 | // GC controllers have triggers not buttons | ||
| 566 | if (npad_type == NpadStyleIndex::GameCube) { | ||
| 567 | if (index == Settings::NativeButton::ZR) { | ||
| 568 | return; | ||
| 569 | } | ||
| 570 | if (index == Settings::NativeButton::ZL) { | ||
| 571 | return; | ||
| 572 | } | ||
| 573 | } | ||
| 574 | |||
| 565 | switch (index) { | 575 | switch (index) { |
| 566 | case Settings::NativeButton::A: | 576 | case Settings::NativeButton::A: |
| 567 | controller.npad_button_state.a.Assign(current_status.value); | 577 | controller.npad_button_state.a.Assign(current_status.value); |
| @@ -738,6 +748,11 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac | |||
| 738 | return; | 748 | return; |
| 739 | } | 749 | } |
| 740 | 750 | ||
| 751 | // Only GC controllers have analog triggers | ||
| 752 | if (npad_type != NpadStyleIndex::GameCube) { | ||
| 753 | return; | ||
| 754 | } | ||
| 755 | |||
| 741 | const auto& trigger = controller.trigger_values[index]; | 756 | const auto& trigger = controller.trigger_values[index]; |
| 742 | 757 | ||
| 743 | switch (index) { | 758 | switch (index) { |
diff --git a/src/core/hid/input_converter.cpp b/src/core/hid/input_converter.cpp index 68d143a01..52fb69e9c 100644 --- a/src/core/hid/input_converter.cpp +++ b/src/core/hid/input_converter.cpp | |||
| @@ -52,6 +52,9 @@ Common::Input::ButtonStatus TransformToButton(const Common::Input::CallbackStatu | |||
| 52 | Common::Input::ButtonStatus status{}; | 52 | Common::Input::ButtonStatus status{}; |
| 53 | switch (callback.type) { | 53 | switch (callback.type) { |
| 54 | case Common::Input::InputType::Analog: | 54 | case Common::Input::InputType::Analog: |
| 55 | status.value = TransformToTrigger(callback).pressed.value; | ||
| 56 | status.toggle = callback.analog_status.properties.toggle; | ||
| 57 | break; | ||
| 55 | case Common::Input::InputType::Trigger: | 58 | case Common::Input::InputType::Trigger: |
| 56 | status.value = TransformToTrigger(callback).pressed.value; | 59 | status.value = TransformToTrigger(callback).pressed.value; |
| 57 | break; | 60 | break; |
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 | }; |
| 134 | static_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 | ||
| 68 | void nvhost_nvdec::OnOpen(DeviceFD fd) {} | 69 | void nvhost_nvdec::OnOpen(DeviceFD fd) { |
| 70 | LOG_INFO(Service_NVDRV, "NVDEC video stream started"); | ||
| 71 | system.AudioCore().SetNVDECActive(true); | ||
| 72 | } | ||
| 69 | 73 | ||
| 70 | void nvhost_nvdec::OnClose(DeviceFD fd) { | 74 | void 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 |
diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp index 133422d5c..ffb9b945e 100644 --- a/src/input_common/input_poller.cpp +++ b/src/input_common/input_poller.cpp | |||
| @@ -824,6 +824,7 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateAnalogDevice( | |||
| 824 | .threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f), | 824 | .threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f), |
| 825 | .offset = std::clamp(params.Get("offset", 0.0f), -1.0f, 1.0f), | 825 | .offset = std::clamp(params.Get("offset", 0.0f), -1.0f, 1.0f), |
| 826 | .inverted = params.Get("invert", "+") == "-", | 826 | .inverted = params.Get("invert", "+") == "-", |
| 827 | .toggle = static_cast<bool>(params.Get("toggle", false)), | ||
| 827 | }; | 828 | }; |
| 828 | input_engine->PreSetController(identifier); | 829 | input_engine->PreSetController(identifier); |
| 829 | input_engine->PreSetAxis(identifier, axis); | 830 | input_engine->PreSetAxis(identifier, axis); |
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 4646a94f5..9e5a40fe7 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp | |||
| @@ -161,6 +161,7 @@ QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) { | |||
| 161 | 161 | ||
| 162 | const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : ""); | 162 | const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : ""); |
| 163 | const QString inverted = QString::fromStdString(param.Get("inverted", false) ? "!" : ""); | 163 | const QString inverted = QString::fromStdString(param.Get("inverted", false) ? "!" : ""); |
| 164 | const QString invert = QString::fromStdString(param.Get("invert", "+") == "-" ? "-" : ""); | ||
| 164 | const auto common_button_name = input_subsystem->GetButtonName(param); | 165 | const auto common_button_name = input_subsystem->GetButtonName(param); |
| 165 | 166 | ||
| 166 | // Retrieve the names from Qt | 167 | // Retrieve the names from Qt |
| @@ -184,7 +185,7 @@ QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) { | |||
| 184 | } | 185 | } |
| 185 | if (param.Has("axis")) { | 186 | if (param.Has("axis")) { |
| 186 | const QString axis = QString::fromStdString(param.Get("axis", "")); | 187 | const QString axis = QString::fromStdString(param.Get("axis", "")); |
| 187 | return QObject::tr("%1%2Axis %3").arg(toggle, inverted, axis); | 188 | return QObject::tr("%1%2Axis %3").arg(toggle, invert, axis); |
| 188 | } | 189 | } |
| 189 | if (param.Has("axis_x") && param.Has("axis_y") && param.Has("axis_z")) { | 190 | if (param.Has("axis_x") && param.Has("axis_y") && param.Has("axis_z")) { |
| 190 | const QString axis_x = QString::fromStdString(param.Get("axis_x", "")); | 191 | const QString axis_x = QString::fromStdString(param.Get("axis_x", "")); |
| @@ -362,18 +363,18 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i | |||
| 362 | button_map[button_id]->setText(tr("[not set]")); | 363 | button_map[button_id]->setText(tr("[not set]")); |
| 363 | }); | 364 | }); |
| 364 | if (param.Has("code") || param.Has("button") || param.Has("hat")) { | 365 | if (param.Has("code") || param.Has("button") || param.Has("hat")) { |
| 365 | context_menu.addAction(tr("Toggle button"), [&] { | ||
| 366 | const bool toggle_value = !param.Get("toggle", false); | ||
| 367 | param.Set("toggle", toggle_value); | ||
| 368 | button_map[button_id]->setText(ButtonToText(param)); | ||
| 369 | emulated_controller->SetButtonParam(button_id, param); | ||
| 370 | }); | ||
| 371 | context_menu.addAction(tr("Invert button"), [&] { | 366 | context_menu.addAction(tr("Invert button"), [&] { |
| 372 | const bool invert_value = !param.Get("inverted", false); | 367 | const bool invert_value = !param.Get("inverted", false); |
| 373 | param.Set("inverted", invert_value); | 368 | param.Set("inverted", invert_value); |
| 374 | button_map[button_id]->setText(ButtonToText(param)); | 369 | button_map[button_id]->setText(ButtonToText(param)); |
| 375 | emulated_controller->SetButtonParam(button_id, param); | 370 | emulated_controller->SetButtonParam(button_id, param); |
| 376 | }); | 371 | }); |
| 372 | context_menu.addAction(tr("Toggle button"), [&] { | ||
| 373 | const bool toggle_value = !param.Get("toggle", false); | ||
| 374 | param.Set("toggle", toggle_value); | ||
| 375 | button_map[button_id]->setText(ButtonToText(param)); | ||
| 376 | emulated_controller->SetButtonParam(button_id, param); | ||
| 377 | }); | ||
| 377 | } | 378 | } |
| 378 | if (param.Has("axis")) { | 379 | if (param.Has("axis")) { |
| 379 | context_menu.addAction(tr("Invert axis"), [&] { | 380 | context_menu.addAction(tr("Invert axis"), [&] { |
| @@ -398,6 +399,12 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i | |||
| 398 | } | 399 | } |
| 399 | emulated_controller->SetButtonParam(button_id, param); | 400 | emulated_controller->SetButtonParam(button_id, param); |
| 400 | }); | 401 | }); |
| 402 | context_menu.addAction(tr("Toggle axis"), [&] { | ||
| 403 | const bool toggle_value = !param.Get("toggle", false); | ||
| 404 | param.Set("toggle", toggle_value); | ||
| 405 | button_map[button_id]->setText(ButtonToText(param)); | ||
| 406 | emulated_controller->SetButtonParam(button_id, param); | ||
| 407 | }); | ||
| 401 | } | 408 | } |
| 402 | context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); | 409 | context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); |
| 403 | }); | 410 | }); |