Project Andio
This commit is contained in:
parent
6e36f4d230
commit
458da8a948
270 changed files with 33712 additions and 8445 deletions
21
src/audio_core/device/audio_buffer.h
Normal file
21
src/audio_core/device/audio_buffer.h
Normal 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
|
304
src/audio_core/device/audio_buffers.h
Normal file
304
src/audio_core/device/audio_buffers.h
Normal 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
|
114
src/audio_core/device/device_session.cpp
Normal file
114
src/audio_core/device/device_session.cpp
Normal 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
|
126
src/audio_core/device/device_session.h
Normal file
126
src/audio_core/device/device_session.h
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue