Rework audio output, connecting AudioOut into coretiming to fix desync during heavy loads.
This commit is contained in:
parent
a83a5d2e4c
commit
ea9ff71725
23 changed files with 550 additions and 841 deletions
|
@ -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.
|
||||
|
|
|
@ -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{};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue