Project Andio

This commit is contained in:
Kelebek1 2022-07-16 23:48:45 +01:00
parent 6e36f4d230
commit 458da8a948
270 changed files with 33712 additions and 8445 deletions

View file

@ -0,0 +1,21 @@
// 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 completed playing.
s64 played_timestamp;
/// Game memory address for these samples.
VAddr samples;
/// Unqiue identifier for this buffer.
u64 tag;
/// Size of the samples buffer.
u64 size;
};
} // namespace AudioCore

View file

@ -0,0 +1,304 @@
// 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 "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(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(std::vector<AudioBuffer>& 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 timestamp - The released timestamp for this buffer.
* @return Is the buffer was released.
*/
bool ReleaseBuffers(Core::Timing::CoreTiming& core_timing, DeviceSession& session) {
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 (!session.IsBufferConsumed(buffers[index].tag)) {
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;
}
tags[released++] = tag;
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(std::vector<AudioBuffer>& 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};
std::vector<AudioBuffer> 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;
}
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,114 @@
// 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/memory.h"
namespace AudioCore {
DeviceSession::DeviceSession(Core::System& system_) : system{system_} {}
DeviceSession::~DeviceSession() {
Finalize();
}
Result DeviceSession::Initialize(std::string_view name_, SampleFormat sample_format_,
u16 channel_count_, size_t session_id_, u32 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_;
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;
}
}
void DeviceSession::Start() {
stream->SetPlayedSampleCount(played_sample_count);
stream->Start();
}
void DeviceSession::Stop() {
if (stream) {
played_sample_count = stream->GetPlayedSampleCount();
stream->Stop();
}
}
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)),
.frames_played = 0,
.tag = buffers[i].tag,
.consumed = false,
};
if (type == Sink::StreamType::In) {
std::vector<s16> samples{};
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);
stream->AppendBuffer(new_buffer, samples);
}
}
}
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);
}
}
bool DeviceSession::IsBufferConsumed(u64 tag) const {
if (stream) {
return stream->IsBufferConsumed(tag);
}
return true;
}
void DeviceSession::SetVolume(f32 volume) const {
if (stream) {
stream->SetSystemVolume(volume);
}
}
u64 DeviceSession::GetPlayedSampleCount() const {
if (stream) {
return stream->GetPlayedSampleCount();
}
return 0;
}
} // namespace AudioCore

View file

@ -0,0 +1,126 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <span>
#include "audio_core/common/common.h"
#include "audio_core/sink/sink.h"
#include "core/hle/service/audio/errors.h"
namespace Core {
class System;
}
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 - Handle for this device session (unused).
* @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, u32 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<AudioBuffer> buffers) const;
/**
* (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(AudioBuffer& buffer) const;
/**
* Check if the buffer for the given tag has been consumed by the backend.
*
* @param tag - Unqiue tag of the buffer to check.
* @return true if the buffer has been consumed, otherwise false.
*/
bool IsBufferConsumed(u64 tag) const;
/**
* Start this device session, starting the backend stream.
*/
void Start();
/**
* Stop this device session, stopping the backend stream.
*/
void Stop();
/**
* 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;
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{};
/// Handle of this device session
u32 handle{};
/// 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{};
/// Is this session initialised?
bool initialized{};
};
} // namespace AudioCore