1
0
mirror of https://git.suyu.dev/suyu/suyu synced 2025-09-07 06:46:31 -05:00

Initial commit

This commit is contained in:
Crimson-Hawk
2024-03-05 16:42:40 +08:00
commit f1e4595ebf
39576 changed files with 7006612 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/common_types.h"
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.
VAddr samples;
/// Unique identifier for this buffer.
u64 tag;
/// Size of the samples buffer.
u64 size;
};
} // namespace AudioCore

View File

@@ -0,0 +1,325 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <mutex>
#include <span>
#include <vector>
#include <boost/container/static_vector.hpp>
#include "audio_buffer.h"
#include "audio_core/device/device_session.h"
#include "core/core_timing.h"
namespace AudioCore {
constexpr s32 BufferAppendLimit = 4;
/**
* A ringbuffer of N audio buffers.
* The buffer contains 3 sections:
* Appended - Buffers added to the ring, but have yet to be sent to the audio backend.
* Registered - Buffers sent to the backend and queued for playback.
* Released - Buffers which have been played, and can now be recycled.
* Any others are free/untracked.
*
* @tparam N - Maximum number of buffers in the ring.
*/
template <size_t N>
class AudioBuffers {
public:
explicit AudioBuffers(size_t limit) : append_limit{static_cast<u32>(limit)} {}
/**
* Append a new audio buffer to the ring.
*
* @param buffer - The new buffer.
*/
void AppendBuffer(const AudioBuffer& buffer) {
std::scoped_lock l{lock};
buffers[appended_index] = buffer;
appended_count++;
appended_index = (appended_index + 1) % append_limit;
}
/**
* Register waiting buffers, up to a maximum of BufferAppendLimit.
*
* @param out_buffers - The buffers which were registered.
*/
void RegisterBuffers(boost::container::static_vector<AudioBuffer, N>& out_buffers) {
std::scoped_lock l{lock};
const s32 to_register{std::min(std::min(appended_count, BufferAppendLimit),
BufferAppendLimit - registered_count)};
for (s32 i = 0; i < to_register; i++) {
s32 index{appended_index - appended_count};
if (index < 0) {
index += N;
}
out_buffers.push_back(buffers[index]);
registered_count++;
registered_index = (registered_index + 1) % append_limit;
appended_count--;
if (appended_count == 0) {
break;
}
}
}
/**
* Release a single buffer. Must be already registered.
*
* @param index - The buffer index to release.
* @param timestamp - The released timestamp for this buffer.
*/
void ReleaseBuffer(s32 index, s64 timestamp) {
std::scoped_lock l{lock};
buffers[index].played_timestamp = timestamp;
registered_count--;
released_count++;
released_index = (released_index + 1) % append_limit;
}
/**
* Release all registered buffers.
*
* @param core_timing - The CoreTiming instance
* @param session - The device session
*
* @return If any buffer was released.
*/
bool ReleaseBuffers(const Core::Timing::CoreTiming& core_timing, const DeviceSession& session,
bool force) {
std::scoped_lock l{lock};
bool buffer_released{false};
while (registered_count > 0) {
auto index{registered_index - registered_count};
if (index < 0) {
index += N;
}
// Check with the backend if this buffer can be released yet.
// If we're shutting down, we don't care if it's been played or not.
if (!force && !session.IsBufferConsumed(buffers[index])) {
break;
}
ReleaseBuffer(index, core_timing.GetGlobalTimeNs().count());
buffer_released = true;
}
return buffer_released || registered_count == 0;
}
/**
* Get all released buffers.
*
* @param tags - Container to be filled with the released buffers' tags.
* @return The number of buffers released.
*/
u32 GetReleasedBuffers(std::span<u64> tags) {
std::scoped_lock l{lock};
u32 released{0};
while (released_count > 0) {
auto index{released_index - released_count};
if (index < 0) {
index += N;
}
auto& buffer{buffers[index]};
released_count--;
auto tag{buffer.tag};
buffer.played_timestamp = 0;
buffer.samples = 0;
buffer.tag = 0;
buffer.size = 0;
if (tag == 0) {
break;
}
if (released < tags.size()) {
tags[released] = tag;
}
released++;
if (released >= tags.size()) {
break;
}
}
return released;
}
/**
* Get all appended and registered buffers.
*
* @param buffers_flushed - Output vector for the buffers which are released.
* @param max_buffers - Maximum number of buffers to released.
* @return The number of buffers released.
*/
u32 GetRegisteredAppendedBuffers(
boost::container::static_vector<AudioBuffer, N>& buffers_flushed, u32 max_buffers) {
std::scoped_lock l{lock};
if (registered_count + appended_count == 0) {
return 0;
}
size_t buffers_to_flush{
std::min(static_cast<u32>(registered_count + appended_count), max_buffers)};
if (buffers_to_flush == 0) {
return 0;
}
while (registered_count > 0) {
auto index{registered_index - registered_count};
if (index < 0) {
index += N;
}
buffers_flushed.push_back(buffers[index]);
registered_count--;
released_count++;
released_index = (released_index + 1) % append_limit;
if (buffers_flushed.size() >= buffers_to_flush) {
break;
}
}
while (appended_count > 0) {
auto index{appended_index - appended_count};
if (index < 0) {
index += N;
}
buffers_flushed.push_back(buffers[index]);
appended_count--;
released_count++;
released_index = (released_index + 1) % append_limit;
if (buffers_flushed.size() >= buffers_to_flush) {
break;
}
}
return static_cast<u32>(buffers_flushed.size());
}
/**
* Check if the given tag is in the buffers.
*
* @param tag - Unique tag of the buffer to search for.
* @return True if the buffer is still in the ring, otherwise false.
*/
bool ContainsBuffer(const u64 tag) const {
std::scoped_lock l{lock};
const auto registered_buffers{appended_count + registered_count + released_count};
if (registered_buffers == 0) {
return false;
}
auto index{released_index - released_count};
if (index < 0) {
index += append_limit;
}
for (s32 i = 0; i < registered_buffers; i++) {
if (buffers[index].tag == tag) {
return true;
}
index = (index + 1) % append_limit;
}
return false;
}
/**
* Get the number of active buffers in the ring.
* That is, appended, registered and released buffers.
*
* @return Number of active buffers.
*/
u32 GetAppendedRegisteredCount() const {
std::scoped_lock l{lock};
return appended_count + registered_count;
}
/**
* Get the total number of active buffers in the ring.
* That is, appended, registered and released buffers.
*
* @return Number of active buffers.
*/
u32 GetTotalBufferCount() const {
std::scoped_lock l{lock};
return static_cast<u32>(appended_count + registered_count + released_count);
}
/**
* Flush all of the currently appended and registered buffers
*
* @param buffers_released - Output count for the number of buffers released.
* @return True if buffers were successfully flushed, otherwise false.
*/
bool FlushBuffers(u32& buffers_released) {
std::scoped_lock l{lock};
boost::container::static_vector<AudioBuffer, N> buffers_flushed{};
buffers_released = GetRegisteredAppendedBuffers(buffers_flushed, append_limit);
if (registered_count > 0) {
return false;
}
if (static_cast<u32>(released_count + appended_count) > append_limit) {
return false;
}
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{};
/// The audio buffers
std::array<AudioBuffer, N> buffers{};
/// Current released index
s32 released_index{};
/// Number of released buffers
s32 released_count{};
/// Current registered index
s32 registered_index{};
/// Number of registered buffers
s32 registered_count{};
/// Current appended index
s32 appended_index{};
/// Number of appended buffers
s32 appended_count{};
/// Maximum number of buffers (default 32)
u32 append_limit{};
};
} // namespace AudioCore

View File

@@ -0,0 +1,144 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/audio_core.h"
#include "audio_core/audio_manager.h"
#include "audio_core/device/audio_buffer.h"
#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/guest_memory.h"
#include "core/memory.h"
#include "core/hle/kernel/k_process.h"
namespace AudioCore {
using namespace std::literals;
constexpr auto INCREMENT_TIME{5ms};
DeviceSession::DeviceSession(Core::System& system_)
: system{system_}, thread_event{Core::Timing::CreateEvent(
"AudioOutSampleTick",
[this](s64 time, std::chrono::nanoseconds) { return ThreadFunc(); })} {}
DeviceSession::~DeviceSession() {
Finalize();
}
Result DeviceSession::Initialize(std::string_view name_, SampleFormat sample_format_,
u16 channel_count_, size_t session_id_, Kernel::KProcess* handle_,
u64 applet_resource_user_id_, Sink::StreamType type_) {
if (stream) {
Finalize();
}
name = fmt::format("{}-{}", name_, session_id_);
type = type_;
sample_format = sample_format_;
channel_count = channel_count_;
session_id = session_id_;
handle = handle_;
handle->Open();
applet_resource_user_id = applet_resource_user_id_;
if (type == Sink::StreamType::In) {
sink = &system.AudioCore().GetInputSink();
} else {
sink = &system.AudioCore().GetOutputSink();
}
stream = sink->AcquireSinkStream(system, channel_count, name, type);
initialized = true;
return ResultSuccess;
}
void DeviceSession::Finalize() {
if (initialized) {
Stop();
sink->CloseStream(stream);
stream = nullptr;
}
if (handle) {
handle->Close();
handle = nullptr;
}
}
void DeviceSession::Start() {
if (stream) {
stream->Start();
system.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds::zero(), INCREMENT_TIME,
thread_event);
}
}
void DeviceSession::Stop() {
if (stream) {
stream->Stop();
system.CoreTiming().UnscheduleEvent(thread_event, {});
}
}
void DeviceSession::ClearBuffers() {
if (stream) {
stream->ClearQueue();
}
}
void DeviceSession::AppendBuffers(std::span<const AudioBuffer> buffers) {
for (const auto& buffer : buffers) {
Sink::SinkBuffer new_buffer{
.frames = buffer.size / (channel_count * sizeof(s16)),
.frames_played = 0,
.tag = buffer.tag,
.consumed = false,
};
tmp_samples.resize_destructive(buffer.size / sizeof(s16));
if (type == Sink::StreamType::In) {
stream->AppendBuffer(new_buffer, tmp_samples);
} else {
Core::Memory::CpuGuestMemory<s16, Core::Memory::GuestMemoryFlags::UnsafeRead> samples(
handle->GetMemory(), buffer.samples, buffer.size / sizeof(s16));
stream->AppendBuffer(new_buffer, samples);
}
}
}
void DeviceSession::ReleaseBuffer(const AudioBuffer& buffer) const {
if (type == Sink::StreamType::In) {
auto samples{stream->ReleaseBuffer(buffer.size / sizeof(s16))};
handle->GetMemory().WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size);
}
}
bool DeviceSession::IsBufferConsumed(const AudioBuffer& buffer) const {
return played_sample_count >= buffer.end_timestamp;
}
void DeviceSession::SetVolume(f32 volume) const {
if (stream) {
stream->SetSystemVolume(volume);
}
}
u64 DeviceSession::GetPlayedSampleCount() const {
return played_sample_count;
}
std::optional<std::chrono::nanoseconds> DeviceSession::ThreadFunc() {
played_sample_count = stream->GetExpectedPlayedSampleCount();
if (type == Sink::StreamType::Out) {
system.AudioCore().GetAudioManager().SetEvent(Event::Type::AudioOutManager, true);
} else {
system.AudioCore().GetAudioManager().SetEvent(Event::Type::AudioInManager, true);
}
return std::nullopt;
}
void DeviceSession::SetRingSize(u32 ring_size) {
stream->SetRingSize(ring_size);
}
} // namespace AudioCore

View File

@@ -0,0 +1,158 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <chrono>
#include <memory>
#include <optional>
#include <span>
#include "audio_core/common/common.h"
#include "audio_core/sink/sink.h"
#include "common/scratch_buffer.h"
#include "core/hle/service/audio/errors.h"
namespace Core {
class System;
namespace Timing {
struct EventType;
} // namespace Timing
} // namespace Core
namespace Kernel {
class KProcess;
} // namespace Kernel
namespace AudioCore {
namespace Sink {
class SinkStream;
struct SinkBuffer;
} // namespace Sink
struct AudioBuffer;
/**
* Represents an input or output device stream for audio in and audio out (not used for render).
**/
class DeviceSession {
public:
explicit DeviceSession(Core::System& system);
~DeviceSession();
/**
* Initialize this device session.
*
* @param name - Name of this device.
* @param sample_format - Sample format for this device's output.
* @param channel_count - Number of channels for this device (2 or 6).
* @param session_id - This session's id.
* @param handle - Process handle for this device session.
* @param applet_resource_user_id - Applet resource user id for this device session (unused).
* @param type - Type of this stream (Render, In, Out).
* @return Result code for this call.
*/
Result Initialize(std::string_view name, SampleFormat sample_format, u16 channel_count,
size_t session_id, Kernel::KProcess* handle, u64 applet_resource_user_id,
Sink::StreamType type);
/**
* Finalize this device session.
*/
void Finalize();
/**
* Append audio buffers to this device session to be played back.
*
* @param buffers - The buffers to play.
*/
void AppendBuffers(std::span<const AudioBuffer> buffers);
/**
* (Audio In only) Pop samples from the backend, and write them back to this buffer's address.
*
* @param buffer - The buffer to write to.
*/
void ReleaseBuffer(const AudioBuffer& buffer) const;
/**
* Check if the buffer for the given tag has been consumed by the backend.
*
* @param buffer - the buffer to check.
*
* @return true if the buffer has been consumed, otherwise false.
*/
bool IsBufferConsumed(const AudioBuffer& buffer) const;
/**
* Start this device session, starting the backend stream.
*/
void Start();
/**
* Stop this device session, stopping the backend stream.
*/
void Stop();
/**
* Clear out the underlying audio buffers in the backend stream.
*/
void ClearBuffers();
/**
* Set this device session's volume.
*
* @param volume - New volume for this session.
*/
void SetVolume(f32 volume) const;
/**
* Get this device session's total played sample count.
*
* @return Samples played by this session.
*/
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;
/// Output sink this device will use
Sink::Sink* sink{};
/// The backend stream for this device session to send samples to
Sink::SinkStream* stream{};
/// Name of this device session
std::string name{};
/// Type of this device session (render/in/out)
Sink::StreamType type{};
/// Sample format for this device.
SampleFormat sample_format{SampleFormat::PcmInt16};
/// Channel count for this device session
u16 channel_count{};
/// Session id of this device session
size_t session_id{};
/// Process handle of device memory owner
Kernel::KProcess* handle{};
/// Applet resource user id of this device session
u64 applet_resource_user_id{};
/// Total number of samples played by this device session
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{};
/// Temporary sample buffer
Common::ScratchBuffer<s16> tmp_samples{};
};
} // namespace AudioCore