Rework audio output, connecting AudioOut into coretiming to fix desync during heavy loads.

This commit is contained in:
Kelebek1 2022-08-01 02:58:13 +01:00
parent a83a5d2e4c
commit ea9ff71725
23 changed files with 550 additions and 841 deletions

View file

@ -8,6 +8,10 @@
namespace AudioCore {
struct AudioBuffer {
/// Timestamp this buffer started playing.
u64 start_timestamp;
/// Timestamp this buffer should finish playing.
u64 end_timestamp;
/// Timestamp this buffer completed playing.
s64 played_timestamp;
/// Game memory address for these samples.

View file

@ -58,6 +58,7 @@ public:
if (index < 0) {
index += N;
}
out_buffers.push_back(buffers[index]);
registered_count++;
registered_index = (registered_index + 1) % append_limit;
@ -100,7 +101,7 @@ public:
}
// Check with the backend if this buffer can be released yet.
if (!session.IsBufferConsumed(buffers[index].tag)) {
if (!session.IsBufferConsumed(buffers[index])) {
break;
}
@ -280,6 +281,16 @@ public:
return true;
}
u64 GetNextTimestamp() const {
// Iterate backwards through the buffer queue, and take the most recent buffer's end
std::scoped_lock l{lock};
auto index{appended_index - 1};
if (index < 0) {
index += append_limit;
}
return buffers[index].end_timestamp;
}
private:
/// Buffer lock
mutable std::recursive_mutex lock{};

View file

@ -7,11 +7,20 @@
#include "audio_core/device/device_session.h"
#include "audio_core/sink/sink_stream.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/memory.h"
namespace AudioCore {
DeviceSession::DeviceSession(Core::System& system_) : system{system_} {}
using namespace std::literals;
constexpr auto INCREMENT_TIME{5ms};
DeviceSession::DeviceSession(Core::System& system_)
: system{system_}, thread_event{Core::Timing::CreateEvent(
"AudioOutSampleTick",
[this](std::uintptr_t, s64 time, std::chrono::nanoseconds) {
return ThreadFunc();
})} {}
DeviceSession::~DeviceSession() {
Finalize();
@ -50,20 +59,21 @@ void DeviceSession::Finalize() {
}
void DeviceSession::Start() {
stream->SetPlayedSampleCount(played_sample_count);
stream->Start();
if (stream) {
stream->Start();
system.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds::zero(), INCREMENT_TIME,
thread_event);
}
}
void DeviceSession::Stop() {
if (stream) {
played_sample_count = stream->GetPlayedSampleCount();
stream->Stop();
system.CoreTiming().UnscheduleEvent(thread_event, {});
}
}
void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const {
auto& memory{system.Memory()};
for (size_t i = 0; i < buffers.size(); i++) {
Sink::SinkBuffer new_buffer{
.frames = buffers[i].size / (channel_count * sizeof(s16)),
@ -77,7 +87,7 @@ void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const {
stream->AppendBuffer(new_buffer, samples);
} else {
std::vector<s16> samples(buffers[i].size / sizeof(s16));
memory.ReadBlockUnsafe(buffers[i].samples, samples.data(), buffers[i].size);
system.Memory().ReadBlockUnsafe(buffers[i].samples, samples.data(), buffers[i].size);
stream->AppendBuffer(new_buffer, samples);
}
}
@ -85,17 +95,13 @@ void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const {
void DeviceSession::ReleaseBuffer(AudioBuffer& buffer) const {
if (type == Sink::StreamType::In) {
auto& memory{system.Memory()};
auto samples{stream->ReleaseBuffer(buffer.size / sizeof(s16))};
memory.WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size);
system.Memory().WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size);
}
}
bool DeviceSession::IsBufferConsumed(u64 tag) const {
if (stream) {
return stream->IsBufferConsumed(tag);
}
return true;
bool DeviceSession::IsBufferConsumed(AudioBuffer& buffer) const {
return played_sample_count >= buffer.end_timestamp;
}
void DeviceSession::SetVolume(f32 volume) const {
@ -105,10 +111,22 @@ void DeviceSession::SetVolume(f32 volume) const {
}
u64 DeviceSession::GetPlayedSampleCount() const {
if (stream) {
return stream->GetPlayedSampleCount();
return played_sample_count;
}
std::optional<std::chrono::nanoseconds> DeviceSession::ThreadFunc() {
// Add 5ms of samples at a 48K sample rate.
played_sample_count += 48'000 * INCREMENT_TIME / 1s;
if (type == Sink::StreamType::Out) {
system.AudioCore().GetAudioManager().SetEvent(Event::Type::AudioOutManager, true);
} else {
system.AudioCore().GetAudioManager().SetEvent(Event::Type::AudioInManager, true);
}
return 0;
return std::nullopt;
}
void DeviceSession::SetRingSize(u32 ring_size) {
stream->SetRingSize(ring_size);
}
} // namespace AudioCore

View file

@ -3,6 +3,9 @@
#pragma once
#include <chrono>
#include <memory>
#include <optional>
#include <span>
#include "audio_core/common/common.h"
@ -11,9 +14,13 @@
namespace Core {
class System;
}
namespace Timing {
struct EventType;
} // namespace Timing
} // namespace Core
namespace AudioCore {
namespace Sink {
class SinkStream;
struct SinkBuffer;
@ -70,7 +77,7 @@ public:
* @param tag - Unqiue tag of the buffer to check.
* @return true if the buffer has been consumed, otherwise false.
*/
bool IsBufferConsumed(u64 tag) const;
bool IsBufferConsumed(AudioBuffer& buffer) const;
/**
* Start this device session, starting the backend stream.
@ -96,6 +103,16 @@ public:
*/
u64 GetPlayedSampleCount() const;
/*
* CoreTiming callback to increment played_sample_count over time.
*/
std::optional<std::chrono::nanoseconds> ThreadFunc();
/*
* Set the size of the ring buffer.
*/
void SetRingSize(u32 ring_size);
private:
/// System
Core::System& system;
@ -118,9 +135,13 @@ private:
/// Applet resource user id of this device session
u64 applet_resource_user_id{};
/// Total number of samples played by this device session
u64 played_sample_count{};
std::atomic<u64> played_sample_count{};
/// Event increasing the played sample count every 5ms
std::shared_ptr<Core::Timing::EventType> thread_event;
/// Is this session initialised?
bool initialized{};
/// Buffer queue
std::vector<AudioBuffer> buffer_queue{};
};
} // namespace AudioCore