Ver Fonte

audio: Interpolate system manager sample count using host sink sample info

This avoids the need to stall if the host sink sporadically misses the deadline, in such a case the previous implementation would report them samples as being played on-time, causing the guest to send more samples and leading to a gradual buildup.
Billy Laws há 3 anos atrás
pai
commit
d8fc3f403b

+ 1 - 2
src/audio_core/device/device_session.cpp

@@ -121,8 +121,7 @@ u64 DeviceSession::GetPlayedSampleCount() const {
 }
 }
 
 
 std::optional<std::chrono::nanoseconds> DeviceSession::ThreadFunc() {
 std::optional<std::chrono::nanoseconds> DeviceSession::ThreadFunc() {
-    // Add 5ms of samples at a 48K sample rate.
-    played_sample_count += 48'000 * INCREMENT_TIME / 1s;
+    played_sample_count = stream->GetExpectedPlayedSampleCount();
     if (type == Sink::StreamType::Out) {
     if (type == Sink::StreamType::Out) {
         system.AudioCore().GetAudioManager().SetEvent(Event::Type::AudioOutManager, true);
         system.AudioCore().GetAudioManager().SetEvent(Event::Type::AudioOutManager, true);
     } else {
     } else {

+ 0 - 1
src/audio_core/renderer/system_manager.cpp

@@ -15,7 +15,6 @@ MICROPROFILE_DEFINE(Audio_RenderSystemManager, "Audio", "Render System Manager",
                     MP_RGB(60, 19, 97));
                     MP_RGB(60, 19, 97));
 
 
 namespace AudioCore::AudioRenderer {
 namespace AudioCore::AudioRenderer {
-constexpr std::chrono::nanoseconds RENDER_TIME{5'000'000UL};
 
 
 SystemManager::SystemManager(Core::System& core_)
 SystemManager::SystemManager(Core::System& core_)
     : core{core_}, adsp{core.AudioCore().GetADSP()}, mailbox{adsp.GetRenderMailbox()},
     : core{core_}, adsp{core.AudioCore().GetADSP()}, mailbox{adsp.GetRenderMailbox()},

+ 21 - 0
src/audio_core/sink/sink_stream.cpp

@@ -14,6 +14,8 @@
 #include "common/fixed_point.h"
 #include "common/fixed_point.h"
 #include "common/settings.h"
 #include "common/settings.h"
 #include "core/core.h"
 #include "core/core.h"
+#include "core/core_timing.h"
+#include "core/core_timing_util.h"
 
 
 namespace AudioCore::Sink {
 namespace AudioCore::Sink {
 
 
@@ -198,6 +200,7 @@ void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::siz
     const std::size_t frame_size = num_channels;
     const std::size_t frame_size = num_channels;
     const std::size_t frame_size_bytes = frame_size * sizeof(s16);
     const std::size_t frame_size_bytes = frame_size * sizeof(s16);
     size_t frames_written{0};
     size_t frames_written{0};
+    size_t actual_frames_written{0};
 
 
     // If we're paused or going to shut down, we don't want to consume buffers as coretiming is
     // If we're paused or going to shut down, we don't want to consume buffers as coretiming is
     // paused and we'll desync, so just play silence.
     // paused and we'll desync, so just play silence.
@@ -248,6 +251,7 @@ void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::siz
                            frames_available * frame_size);
                            frames_available * frame_size);
 
 
         frames_written += frames_available;
         frames_written += frames_available;
+        actual_frames_written += frames_available;
         playing_buffer.frames_played += frames_available;
         playing_buffer.frames_played += frames_available;
 
 
         // If that's all the frames in the current buffer, add its samples and mark it as
         // If that's all the frames in the current buffer, add its samples and mark it as
@@ -260,6 +264,13 @@ void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::siz
     std::memcpy(&last_frame[0], &output_buffer[(frames_written - 1) * frame_size],
     std::memcpy(&last_frame[0], &output_buffer[(frames_written - 1) * frame_size],
                 frame_size_bytes);
                 frame_size_bytes);
 
 
+    {
+        std::scoped_lock lk{sample_count_lock};
+        last_sample_count_update_time = Core::Timing::CyclesToUs(system.CoreTiming().GetClockTicks());
+        min_played_sample_count = max_played_sample_count;
+        max_played_sample_count += actual_frames_written;
+    }
+
     if (system.IsMulticore() && queued_buffers <= max_queue_size) {
     if (system.IsMulticore() && queued_buffers <= max_queue_size) {
         Unstall();
         Unstall();
     }
     }
@@ -282,4 +293,14 @@ void SinkStream::Unstall() {
     stalled_lock.unlock();
     stalled_lock.unlock();
 }
 }
 
 
+u64 SinkStream::GetExpectedPlayedSampleCount() {
+    std::scoped_lock lk{sample_count_lock};
+    auto cur_time{Core::Timing::CyclesToUs(system.CoreTiming().GetClockTicks())};
+    auto time_delta{cur_time - last_sample_count_update_time};
+    auto exp_played_sample_count{min_played_sample_count +
+                                 (TargetSampleRate * time_delta) / std::chrono::seconds{1}};
+
+    return std::min<u64>(exp_played_sample_count, max_played_sample_count);
+}
+
 } // namespace AudioCore::Sink
 } // namespace AudioCore::Sink

+ 17 - 0
src/audio_core/sink/sink_stream.h

@@ -5,6 +5,7 @@
 
 
 #include <array>
 #include <array>
 #include <atomic>
 #include <atomic>
+#include <chrono>
 #include <memory>
 #include <memory>
 #include <mutex>
 #include <mutex>
 #include <span>
 #include <span>
@@ -14,6 +15,7 @@
 #include "common/common_types.h"
 #include "common/common_types.h"
 #include "common/reader_writer_queue.h"
 #include "common/reader_writer_queue.h"
 #include "common/ring_buffer.h"
 #include "common/ring_buffer.h"
+#include "common/thread.h"
 
 
 namespace Core {
 namespace Core {
 class System;
 class System;
@@ -210,6 +212,13 @@ public:
      */
      */
     void Unstall();
     void Unstall();
 
 
+    /**
+     * Get the total number of samples expected to have been played by this stream.
+     *
+     * @return The number of samples.
+     */
+    u64 GetExpectedPlayedSampleCount();
+
 protected:
 protected:
     /// Core system
     /// Core system
     Core::System& system;
     Core::System& system;
@@ -237,6 +246,14 @@ private:
     std::atomic<u32> queued_buffers{};
     std::atomic<u32> queued_buffers{};
     /// The ring size for audio out buffers (usually 4, rarely 2 or 8)
     /// The ring size for audio out buffers (usually 4, rarely 2 or 8)
     u32 max_queue_size{};
     u32 max_queue_size{};
+    /// Locks access to sample count tracking info
+    std::mutex sample_count_lock;
+    /// Minimum number of total samples that have been played since the last callback
+    u64 min_played_sample_count{};
+    /// Maximum number of total samples that can be played since the last callback
+    u64 max_played_sample_count{};
+    /// The time the two above tracking variables were last written to
+    std::chrono::microseconds last_sample_count_update_time{};
     /// Set by the audio render/in/out system which uses this stream
     /// Set by the audio render/in/out system which uses this stream
     f32 system_volume{1.0f};
     f32 system_volume{1.0f};
     /// Set via IAudioDevice service calls
     /// Set via IAudioDevice service calls