From 921250bcd2f950158eab834814e813bd45ece333 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Fri, 23 Feb 2024 23:49:10 +0200 Subject: [PATCH] Optimize audio output a bit --- include/audio/teakra_core.hpp | 11 +- include/ring_buffer.hpp | 201 ++++++++++++++-------------- src/core/audio/miniaudio_device.cpp | 9 -- src/core/audio/teakra_core.cpp | 16 ++- 4 files changed, 113 insertions(+), 124 deletions(-) diff --git a/include/audio/teakra_core.hpp b/include/audio/teakra_core.hpp index 95831ff7..db44f5ed 100644 --- a/include/audio/teakra_core.hpp +++ b/include/audio/teakra_core.hpp @@ -1,4 +1,6 @@ #pragma once +#include + #include "audio/dsp_core.hpp" #include "memory.hpp" #include "swap.hpp" @@ -10,6 +12,11 @@ namespace Audio { u32 pipeBaseAddr; bool running; // Is the DSP running? bool loaded; // Have we finished loading a binary with LoadComponent? + bool signalledData; + bool signalledSemaphore; + + uint audioFrameIndex = 0; // Index in our audio frame + std::array audioFrame; // Get a pointer to a data memory address u8* getDataPointer(u32 address) { return getDspMemory() + Memory::DSP_DATA_MEMORY_OFFSET + address; } @@ -62,10 +69,6 @@ namespace Audio { std::memcpy(statusAddress + 6, &status.writePointer, sizeof(u16)); } } - - bool signalledData; - bool signalledSemaphore; - // Run 1 slice of DSP instructions void runSlice() { if (running) { diff --git a/include/ring_buffer.hpp b/include/ring_buffer.hpp index 918f23de..00b1ab41 100644 --- a/include/ring_buffer.hpp +++ b/include/ring_buffer.hpp @@ -1,117 +1,110 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +/* + +MIT License + +Copyright (c) 2021 PCSX-Redux authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ #pragma once +#include + #include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include namespace Common { -/// SPSC ring buffer -/// @tparam T Element type -/// @tparam capacity Number of slots in ring buffer -template -class RingBuffer { - /// A "slot" is made of a single `T`. - static constexpr std::size_t slot_size = sizeof(T); - // T must be safely memcpy-able and have a trivial default constructor. - static_assert(std::is_trivial_v); - // Ensure capacity is sensible. - static_assert(capacity < std::numeric_limits::max() / 2); - static_assert((capacity & (capacity - 1)) == 0, "capacity must be a power of two"); - // Ensure lock-free. - static_assert(std::atomic_size_t::is_always_lock_free); + template + class RingBuffer { -public: - /// Pushes slots into the ring buffer - /// @param new_slots Pointer to the slots to push - /// @param slot_count Number of slots to push - /// @returns The number of slots actually pushed - std::size_t push(const void* new_slots, std::size_t slot_count) { - const std::size_t write_index = m_write_index.load(); - const std::size_t slots_free = capacity + m_read_index.load() - write_index; - const std::size_t push_count = std::min(slot_count, slots_free); + public: + static constexpr size_t BUFFER_SIZE = BS; + size_t available() { + std::unique_lock l(m_mu); + return availableLocked(); + } + size_t buffered() { + std::unique_lock l(m_mu); + return bufferedLocked(); + } - const std::size_t pos = write_index % capacity; - const std::size_t first_copy = std::min(capacity - pos, push_count); - const std::size_t second_copy = push_count - first_copy; + bool push(const T* data, size_t N) { + if (N > BUFFER_SIZE) { + throw std::runtime_error("Trying to enqueue too much data"); + } + std::unique_lock l(m_mu); + using namespace std::chrono_literals; + bool safe = m_cv.wait_for(l, 20ms, [this, N]() -> bool { return N < availableLocked(); }); + if (safe) enqueueSafe(data, N); + return safe; + } + size_t pop(T* data, size_t N) { + std::unique_lock l(m_mu); + N = std::min(N, bufferedLocked()); + dequeueSafe(data, N); - const char* in = static_cast(new_slots); - std::memcpy(m_data.data() + pos, in, first_copy * slot_size); - in += first_copy * slot_size; - std::memcpy(m_data.data(), in, second_copy * slot_size); + return N; + } - m_write_index.store(write_index + push_count); + private: + size_t availableLocked() const { return BUFFER_SIZE - m_size; } + size_t bufferedLocked() const { return m_size; } + void enqueueSafe(const T* data, size_t N) { + size_t end = m_end; + const size_t subLen = BUFFER_SIZE - end; + if (N > subLen) { + enqueueSafe(data, subLen); + enqueueSafe(data + subLen, N - subLen); + } else { + memcpy(m_buffer + end, data, N * sizeof(T)); + end += N; + if (end == BUFFER_SIZE) end = 0; + m_end = end; + m_size += N; + } + } + void dequeueSafe(T* data, size_t N) { + size_t begin = m_begin; + const size_t subLen = BUFFER_SIZE - begin; + if (N > subLen) { + dequeueSafe(data, subLen); + dequeueSafe(data + subLen, N - subLen); + } else { + memcpy(data, m_buffer + begin, N * sizeof(T)); + begin += N; + if (begin == BUFFER_SIZE) begin = 0; + m_begin = begin; + m_size -= N; + m_cv.notify_one(); + } + } - return push_count; - } + size_t m_begin = 0, m_end = 0, m_size = 0; + T m_buffer[BUFFER_SIZE]; - std::size_t push(std::span input) { - return push(input.data(), input.size()); - } - - /// Pops slots from the ring buffer - /// @param output Where to store the popped slots - /// @param max_slots Maximum number of slots to pop - /// @returns The number of slots actually popped - std::size_t pop(void* output, std::size_t max_slots = ~std::size_t(0)) { - const std::size_t read_index = m_read_index.load(); - const std::size_t slots_filled = m_write_index.load() - read_index; - const std::size_t pop_count = std::min(slots_filled, max_slots); - - const std::size_t pos = read_index % capacity; - const std::size_t first_copy = std::min(capacity - pos, pop_count); - const std::size_t second_copy = pop_count - first_copy; - - char* out = static_cast(output); - std::memcpy(out, m_data.data() + pos, first_copy * slot_size); - out += first_copy * slot_size; - std::memcpy(out, m_data.data(), second_copy * slot_size); - - m_read_index.store(read_index + pop_count); - - return pop_count; - } - - std::vector pop(std::size_t max_slots = ~std::size_t(0)) { - std::vector out(std::min(max_slots, capacity)); - const std::size_t count = Pop(out.data(), out.size()); - out.resize(count); - return out; - } - - /// @returns Number of slots used - [[nodiscard]] std::size_t size() const { - return m_write_index.load() - m_read_index.load(); - } - - /// @returns Maximum size of ring buffer - [[nodiscard]] constexpr std::size_t Capacity() const { - return capacity; - } - -private: - // It is important to align the below variables for performance reasons: - // Having them on the same cache-line would result in false-sharing between them. - // TODO: Remove this ifdef whenever clang and GCC support - // std::hardware_destructive_interference_size. -#ifdef __cpp_lib_hardware_interference_size - alignas(std::hardware_destructive_interference_size) std::atomic_size_t m_read_index{0}; - alignas(std::hardware_destructive_interference_size) std::atomic_size_t m_write_index{0}; -#else - alignas(128) std::atomic_size_t m_read_index{0}; - alignas(128) std::atomic_size_t m_write_index{0}; -#endif - - std::array m_data; -}; - -} // namespace Common \ No newline at end of file + std::mutex m_mu; + std::condition_variable m_cv; + }; +} // namespace Common \ No newline at end of file diff --git a/src/core/audio/miniaudio_device.cpp b/src/core/audio/miniaudio_device.cpp index ae45e9ab..bfc1df6f 100644 --- a/src/core/audio/miniaudio_device.cpp +++ b/src/core/audio/miniaudio_device.cpp @@ -92,15 +92,6 @@ void MiniAudioDevice::init(Samples& samples, bool safe) { auto self = reinterpret_cast(device->pUserData); s16* output = reinterpret_cast(out); - // Wait until there's enough samples to pop - while (self->samples->size() < frameCount * channelCount) { - printf("Waiting\n"); - // If audio output is disabled from the emulator thread, make sure that this callback will return and not hang - if (!self->running) { - return; - } - } - self->samples->pop(output, frameCount * channelCount); }; diff --git a/src/core/audio/teakra_core.cpp b/src/core/audio/teakra_core.cpp index 282ca528..344ce61c 100644 --- a/src/core/audio/teakra_core.cpp +++ b/src/core/audio/teakra_core.cpp @@ -6,10 +6,6 @@ #include "services/dsp.hpp" using namespace Audio; -static constexpr u32 sampleRate = 32768; -static constexpr u32 duration = 30; -static s16 samples[sampleRate * duration * 2]; -static uint sampleIndex = 0; struct Dsp1 { // All sizes are in bytes unless otherwise specified @@ -115,6 +111,8 @@ void TeakraDSP::reset() { running = false; loaded = false; signalledData = signalledSemaphore = false; + + audioFrameIndex = 0; } void TeakraDSP::setAudioEnabled(bool enable) { @@ -124,10 +122,14 @@ void TeakraDSP::setAudioEnabled(bool enable) { // Set the appropriate audio callback for Teakra if (audioEnabled) { teakra.SetAudioCallback([=](std::array sample) { - // Wait until we can push our samples - while (sampleBuffer.size() + 2 > sampleBuffer.Capacity()) { + audioFrame[audioFrameIndex++] = sample[0]; + audioFrame[audioFrameIndex++] = sample[1]; + + // Push our samples at the end of an audio frame + if (audioFrameIndex >= audioFrame.size()) { + audioFrameIndex -= audioFrame.size(); + sampleBuffer.push(audioFrame.data(), audioFrame.size()); } - sampleBuffer.push(sample.data(), 2); }); } else { teakra.SetAudioCallback([=](std::array sample) { /* Do nothing */ });