From d1e468fefb3285e72e023c9bed951cc3122e2bad Mon Sep 17 00:00:00 2001 From: Vestrel <16190165+Vestrel@users.noreply.github.com> Date: Thu, 5 May 2022 22:47:44 +0900 Subject: [PATCH] sys_rsxaudio: Initial implementation (#11907) --- Utilities/simple_ringbuf.cpp | 229 +- Utilities/simple_ringbuf.h | 62 +- Utilities/transactional_storage.h | 159 ++ rpcs3/Emu/Audio/AudioBackend.cpp | 67 +- rpcs3/Emu/Audio/AudioBackend.h | 129 +- rpcs3/Emu/Audio/AudioDumper.cpp | 66 +- rpcs3/Emu/Audio/AudioDumper.h | 68 +- rpcs3/Emu/Audio/Cubeb/CubebBackend.cpp | 84 +- rpcs3/Emu/Audio/Cubeb/CubebBackend.h | 6 +- rpcs3/Emu/Audio/FAudio/FAudioBackend.cpp | 148 +- rpcs3/Emu/Audio/FAudio/FAudioBackend.h | 11 +- rpcs3/Emu/Audio/Null/NullAudioBackend.h | 10 +- rpcs3/Emu/Audio/XAudio2/XAudio2Backend.cpp | 138 +- rpcs3/Emu/Audio/XAudio2/XAudio2Backend.h | 9 +- rpcs3/Emu/Cell/Modules/cellAudio.cpp | 57 +- rpcs3/Emu/Cell/Modules/cellAudio.h | 76 +- rpcs3/Emu/Cell/lv2/lv2.cpp | 12 +- rpcs3/Emu/Cell/lv2/sys_rsxaudio.cpp | 2309 +++++++++++++++++++- rpcs3/Emu/Cell/lv2/sys_rsxaudio.h | 592 ++++- rpcs3/Emu/Cell/lv2/sys_time.cpp | 30 +- rpcs3/Emu/Cell/lv2/sys_uart.cpp | 323 ++- rpcs3/Emu/Cell/timers.hpp | 1 + rpcs3/Emu/system_config.h | 3 +- rpcs3/Emu/system_config_types.cpp | 18 + rpcs3/Emu/system_config_types.h | 9 + rpcs3/emucore.vcxproj | 4 +- rpcs3/emucore.vcxproj.filters | 8 +- rpcs3/rpcs3qt/emu_settings_type.h | 4 + rpcs3/rpcs3qt/gui_application.cpp | 2 + rpcs3/rpcs3qt/main_window.cpp | 6 +- rpcs3/rpcs3qt/settings_dialog.cpp | 20 + rpcs3/rpcs3qt/settings_dialog.ui | 32 +- rpcs3/rpcs3qt/tooltips.h | 2 + 33 files changed, 4148 insertions(+), 546 deletions(-) create mode 100644 Utilities/transactional_storage.h diff --git a/Utilities/simple_ringbuf.cpp b/Utilities/simple_ringbuf.cpp index bc9950cc48..c31b7af6fa 100644 --- a/Utilities/simple_ringbuf.cpp +++ b/Utilities/simple_ringbuf.cpp @@ -1,129 +1,202 @@ #include "Utilities/simple_ringbuf.h" -simple_ringbuf::simple_ringbuf(u32 size) +simple_ringbuf::simple_ringbuf(u64 size) { set_buf_size(size); } +simple_ringbuf::~simple_ringbuf() +{ + rw_ptr.load(); // Sync +} + +simple_ringbuf::simple_ringbuf(const simple_ringbuf& other) +{ + ctr_state old = other.rw_ptr.load(); + + for (;;) + { + buf = other.buf; + rw_ptr = old; + + const ctr_state current = other.rw_ptr.load(); + if (old == current) + { + break; + } + old = current; + } +} + +simple_ringbuf& simple_ringbuf::operator=(const simple_ringbuf& other) +{ + if (this == &other) return *this; + + ctr_state old = other.rw_ptr.load(); + + for (;;) + { + buf = other.buf; + rw_ptr = old; + + const ctr_state current = other.rw_ptr.load(); + if (old == current) + { + break; + } + old = current; + } + + return *this; +} + simple_ringbuf::simple_ringbuf(simple_ringbuf&& other) { - rw_ptr = other.rw_ptr.load(); - buf_size = other.buf_size; + const ctr_state other_rw_ptr = other.rw_ptr.load(); buf = std::move(other.buf); - initialized = other.initialized.observe(); + rw_ptr = other_rw_ptr; - other.buf_size = 0; - other.rw_ptr = 0; - other.initialized = false; + other.rw_ptr.store({}); } simple_ringbuf& simple_ringbuf::operator=(simple_ringbuf&& other) { if (this == &other) return *this; - rw_ptr = other.rw_ptr.load(); - buf_size = other.buf_size; + const ctr_state other_rw_ptr = other.rw_ptr.load(); buf = std::move(other.buf); - initialized = other.initialized.observe(); + rw_ptr = other_rw_ptr; - other.buf_size = 0; - other.rw_ptr = 0; - other.initialized = false; + other.rw_ptr.store({}); return *this; } -u32 simple_ringbuf::get_free_size() const +u64 simple_ringbuf::get_free_size() const { - const u64 _rw_ptr = rw_ptr; - const u32 rd = static_cast(_rw_ptr); - const u32 wr = static_cast(_rw_ptr >> 32); - - return wr >= rd ? buf_size - 1 - (wr - rd) : rd - wr - 1U; + return get_free_size(rw_ptr); } -u32 simple_ringbuf::get_used_size() const +u64 simple_ringbuf::get_used_size() const { - return buf_size - 1 - get_free_size(); + return get_used_size(rw_ptr); } -u32 simple_ringbuf::get_total_size() const +u64 simple_ringbuf::get_total_size() const { - return buf_size; + rw_ptr.load(); // Sync + return buf.size() - 1; } -void simple_ringbuf::set_buf_size(u32 size) +u64 simple_ringbuf::get_free_size(ctr_state val) const { - ensure(size); + const u64 buf_size = buf.size(); + const u64 rd = val.read_ptr % buf_size; + const u64 wr = val.write_ptr % buf_size; - this->buf_size = size + 1; - buf = std::make_unique(this->buf_size); - flush(); - initialized = true; + return (wr >= rd ? buf_size + rd - wr : rd - wr) - 1; } -void simple_ringbuf::flush() +u64 simple_ringbuf::get_used_size(ctr_state val) const { - rw_ptr.atomic_op([&](u64 &val) + const u64 buf_size = buf.size(); + const u64 rd = val.read_ptr % buf_size; + const u64 wr = val.write_ptr % buf_size; + + return wr >= rd ? wr - rd : buf_size + wr - rd; +} + +void simple_ringbuf::set_buf_size(u64 size) +{ + ensure(size != umax); + + buf.resize(size + 1); + rw_ptr.store({}); +} + +void simple_ringbuf::writer_flush(u64 cnt) +{ + rw_ptr.atomic_op([&](ctr_state& val) { - val = static_cast(val >> 32) | (val & 0xFFFFFFFF'00000000); + const u64 used = get_used_size(val); + if (used == 0) return; + + val.write_ptr += buf.size() - std::min(used, cnt); }); } -u32 simple_ringbuf::push(const void *data, u32 size) +void simple_ringbuf::reader_flush(u64 cnt) { - ensure(data != nullptr && initialized.observe()); - - const u32 old = static_cast(rw_ptr.load() >> 32); - const u32 to_push = std::min(size, get_free_size()); - auto b_data = static_cast(data); - - if (!to_push) return 0; - - if (old + to_push > buf_size) + rw_ptr.atomic_op([&](ctr_state& val) { - const auto first_write_sz = buf_size - old; - memcpy(&buf[old], b_data, first_write_sz); - memcpy(&buf[0], b_data + first_write_sz, to_push - first_write_sz); - } - else - { - memcpy(&buf[old], b_data, to_push); - } - - rw_ptr.atomic_op([&](u64 &val) - { - val = static_cast((old + to_push) % buf_size) << 32 | static_cast(val); + val.read_ptr += std::min(get_used_size(val), cnt); }); - - return to_push; } -u32 simple_ringbuf::pop(void *data, u32 size) +u64 simple_ringbuf::push(const void* data, u64 size, bool force) { - ensure(data != nullptr && initialized.observe()); + ensure(data != nullptr); - const u32 old = static_cast(rw_ptr.load()); - const u32 to_pop = std::min(size, get_used_size()); - u8 *b_data = static_cast(data); - - if (!to_pop) return 0; - - if (old + to_pop > buf_size) + return rw_ptr.atomic_op([&](ctr_state& val) -> u64 { - const auto first_read_sz = buf_size - old; - memcpy(b_data, &buf[old], first_read_sz); - memcpy(b_data + first_read_sz, &buf[0], to_pop - first_read_sz); - } - else - { - memcpy(b_data, &buf[old], to_pop); - } + const u64 buf_size = buf.size(); + const u64 old = val.write_ptr % buf_size; + const u64 free_size = get_free_size(val); + const u64 to_push = std::min(size, free_size); + const auto b_data = static_cast(data); - rw_ptr.atomic_op([&](u64 &val) - { - val = (old + to_pop) % buf_size | (val & 0xFFFFFFFF'00000000); + if (!to_push || (!force && free_size < size)) + { + return 0; + } + + if (old + to_push > buf_size) + { + const auto first_write_sz = buf_size - old; + memcpy(&buf[old], b_data, first_write_sz); + memcpy(&buf[0], b_data + first_write_sz, to_push - first_write_sz); + } + else + { + memcpy(&buf[old], b_data, to_push); + } + + val.write_ptr += to_push; + + return to_push; + }); +} + +u64 simple_ringbuf::pop(void* data, u64 size, bool force) +{ + ensure(data != nullptr); + + return rw_ptr.atomic_op([&](ctr_state& val) -> u64 + { + const u64 buf_size = buf.size(); + const u64 old = val.read_ptr % buf_size; + const u64 used_size = get_used_size(val); + const u64 to_pop = std::min(size, used_size); + const auto b_data = static_cast(data); + + if (!to_pop || (!force && used_size < size)) + { + return 0; + } + + if (old + to_pop > buf_size) + { + const auto first_read_sz = buf_size - old; + memcpy(b_data, &buf[old], first_read_sz); + memcpy(b_data + first_read_sz, &buf[0], to_pop - first_read_sz); + } + else + { + memcpy(b_data, &buf[old], to_pop); + } + + val.read_ptr += to_pop; + + return to_pop; }); - - return to_pop; } diff --git a/Utilities/simple_ringbuf.h b/Utilities/simple_ringbuf.h index 9fb0c6f8c0..92151f7a97 100644 --- a/Utilities/simple_ringbuf.h +++ b/Utilities/simple_ringbuf.h @@ -2,37 +2,53 @@ #include "util/types.hpp" #include "util/atomic.hpp" +#include // Single reader/writer simple ringbuffer. -// Counters are 32-bit. class simple_ringbuf { -private: - - atomic_t rw_ptr = 0; - u32 buf_size = 0; - std::unique_ptr buf{}; - atomic_t initialized = false; - public: - simple_ringbuf() {}; - simple_ringbuf(u32 size); + simple_ringbuf(u64 size = 0); + virtual ~simple_ringbuf(); - simple_ringbuf(const simple_ringbuf&) = delete; - simple_ringbuf& operator=(const simple_ringbuf&) = delete; - - simple_ringbuf(simple_ringbuf&& other); - simple_ringbuf& operator=(simple_ringbuf&& other); - - u32 get_free_size() const; - u32 get_used_size() const; - u32 get_total_size() const; + simple_ringbuf(const simple_ringbuf& other); + simple_ringbuf& operator=(const simple_ringbuf& other); // Thread unsafe functions. - void set_buf_size(u32 size); - void flush(); // Could be safely called from reader. + simple_ringbuf(simple_ringbuf&& other); + simple_ringbuf& operator=(simple_ringbuf&& other); + void set_buf_size(u64 size); - u32 push(const void *data, u32 size); - u32 pop(void *data, u32 size); + // Helper functions + u64 get_free_size() const; + u64 get_used_size() const; + u64 get_total_size() const; + + // Writer functions + u64 push(const void* data, u64 size, bool force = false); + void writer_flush(u64 cnt = umax); + + // Reader functions + u64 pop(void* data, u64 size, bool force = false); + void reader_flush(u64 cnt = umax); + +private: + + struct ctr_state + { + alignas(sizeof(u64) * 2) + u64 read_ptr = 0; + u64 write_ptr = 0; + + auto operator<=>(const ctr_state& other) const = default; + }; + + static_assert(sizeof(ctr_state) == sizeof(u64) * 2); + + atomic_t rw_ptr{}; + std::vector buf{}; + + u64 get_free_size(ctr_state val) const; + u64 get_used_size(ctr_state val) const; }; diff --git a/Utilities/transactional_storage.h b/Utilities/transactional_storage.h new file mode 100644 index 0000000000..dda86efe05 --- /dev/null +++ b/Utilities/transactional_storage.h @@ -0,0 +1,159 @@ +#include "util/types.hpp" +#include +#include + +// Thread-safe object pool with garbage collection +class universal_pool +{ +public: + + universal_pool(u32 gc_interval = 10000) : gc_interval(gc_interval) + { + } + + ~universal_pool() + { + std::lock_guard lock(mutex); + storage.clear(); + } + + universal_pool(const universal_pool&) = delete; + universal_pool& operator=(const universal_pool&) = delete; + + void set_gc_interval(u32 new_val) + { + gc_interval = new_val; + } + + template + requires (std::invocable && std::is_same_v, std::shared_ptr>) + void add_op(F func) + { + std::lock_guard lock(mutex); + if (std::shared_ptr new_val = std::invoke(func); new_val) + { + storage.push_back(new_val); + } + delete_unused(); + } + + void force_gc() + { + std::lock_guard lock(mutex); + delete_unused(); + } + +private: + + void delete_unused() + { + const u32 gc_int = gc_interval.observe(); + + if (u64 crnt_time = get_system_time(); gc_int == 0 || crnt_time > gc_last_time + gc_int) + { + gc_last_time = crnt_time; + storage.erase + ( + std::remove_if(storage.begin(), storage.end(), [](auto& obj) { return obj.use_count() <= 1; }), + storage.end() + ); + } + } + + shared_mutex mutex{}; + std::vector> storage{}; + u64 gc_last_time = get_system_time(); + atomic_t gc_interval = 0; +}; + +template +class transactional_storage +{ +public: + + transactional_storage(std::shared_ptr pool, std::shared_ptr obj = std::make_shared()) + { + ensure(pool && obj); + + this->pool = pool; + add(obj); + } + + transactional_storage(const transactional_storage&) = delete; + transactional_storage& operator=(const transactional_storage&) = delete; + + transactional_storage(transactional_storage&& other) + { + pool = std::move(other.pool); + + std::unique_lock lock_other{other.current_mutex}; + const std::shared_ptr other_current = other.current; + other.current = nullptr; + lock_other.unlock(); + + std::lock_guard lock{current_mutex}; + current = other_current; + } + + transactional_storage& operator=(transactional_storage&& other) + { + if (this == &other) return *this; + + pool = std::move(other.pool); + + std::unique_lock lock_other{other.current_mutex}; + const std::shared_ptr other_current = other.current; + other.current = nullptr; + lock_other.unlock(); + + std::lock_guard lock{current_mutex}; + current = other_current; + + return *this; + } + + std::shared_ptr get_current() + { + reader_lock lock(current_mutex); + return current; + } + + void add(std::shared_ptr obj) + { + if (!obj) + { + return; + } + + pool->add_op([&]() -> std::shared_ptr + { + { + std::lock_guard lock{current_mutex}; + current = obj; + } + return std::move(obj); + }); + } + + template + requires (std::invocable && std::is_same_v, std::shared_ptr>) + void add_op(F func) + { + pool->add_op([&]() -> std::shared_ptr + { + std::shared_ptr obj = std::invoke(func); + if (obj) + { + std::lock_guard lock{current_mutex}; + current = obj; + } + return obj; + }); + } + +private: + + shared_mutex current_mutex{}; + std::shared_ptr current{}; + std::shared_ptr pool{}; +}; diff --git a/rpcs3/Emu/Audio/AudioBackend.cpp b/rpcs3/Emu/Audio/AudioBackend.cpp index 88872440c4..3b971488c1 100644 --- a/rpcs3/Emu/Audio/AudioBackend.cpp +++ b/rpcs3/Emu/Audio/AudioBackend.cpp @@ -4,6 +4,12 @@ AudioBackend::AudioBackend() {} +void AudioBackend::SetErrorCallback(std::function cb) +{ + std::lock_guard lock(m_error_cb_mutex); + m_error_callback = cb; +} + /* * Helper methods */ @@ -29,8 +35,65 @@ bool AudioBackend::get_convert_to_s16() const void AudioBackend::convert_to_s16(u32 cnt, const f32* src, void* dst) { - for (usz i = 0; i < cnt; i++) + for (u32 i = 0; i < cnt; i++) { - static_cast(dst)[i] = static_cast(std::clamp(src[i] * 32768.5f, -32768.0f, 32767.0f)); + static_cast(dst)[i] = static_cast(std::clamp(src[i] * 32768.5f, -32768.0f, 32767.0f)); + } +} + +f32 AudioBackend::apply_volume(const VolumeParam& param, u32 sample_cnt, const f32* src, f32* dst) +{ + ensure(param.ch_cnt > 1 && param.ch_cnt % 2 == 0); // Tends to produce faster code + + const f32 vol_incr = (param.target_volume - param.initial_volume) / (VOLUME_CHANGE_DURATION * param.freq); + f32 crnt_vol = param.current_volume; + u32 sample_idx = 0; + + if (vol_incr >= 0) + { + for (sample_idx = 0; sample_idx < sample_cnt && crnt_vol != param.target_volume; sample_idx += param.ch_cnt) + { + crnt_vol = std::min(param.current_volume + (sample_idx + 1) / param.ch_cnt * vol_incr, param.target_volume); + + for (u32 i = 0; i < param.ch_cnt; i++) + { + dst[sample_idx + i] = src[sample_idx + i] * crnt_vol; + } + } + } + else + { + for (sample_idx = 0; sample_idx < sample_cnt && crnt_vol != param.target_volume; sample_idx += param.ch_cnt) + { + crnt_vol = std::max(param.current_volume + (sample_idx + 1) / param.ch_cnt * vol_incr, param.target_volume); + + for (u32 i = 0; i < param.ch_cnt; i++) + { + dst[sample_idx + i] = src[sample_idx + i] * crnt_vol; + } + } + } + + if (sample_cnt > sample_idx) + { + apply_volume_static(param.target_volume, sample_cnt - sample_idx, &src[sample_idx], &dst[sample_idx]); + } + + return crnt_vol; +} + +void AudioBackend::apply_volume_static(f32 vol, u32 sample_cnt, const f32* src, f32* dst) +{ + for (u32 i = 0; i < sample_cnt; i++) + { + dst[i] = src[i] * vol; + } +} + +void AudioBackend::normalize(u32 sample_cnt, const f32* src, f32* dst) +{ + for (u32 i = 0; i < sample_cnt; i++) + { + dst[i] = std::clamp(src[i], -1.0f, 1.0f); } } diff --git a/rpcs3/Emu/Audio/AudioBackend.h b/rpcs3/Emu/Audio/AudioBackend.h index 43a8adc23f..8fd7052f2c 100644 --- a/rpcs3/Emu/Audio/AudioBackend.h +++ b/rpcs3/Emu/Audio/AudioBackend.h @@ -1,7 +1,9 @@ #pragma once #include "util/types.hpp" +#include "Utilities/mutex.h" #include "Utilities/StrFmt.h" +#include enum : u32 { @@ -38,32 +40,54 @@ enum class AudioChannelCnt : u32 class AudioBackend { public: + + struct VolumeParam + { + f32 initial_volume = 1.0f; + f32 current_volume = 1.0f; + f32 target_volume = 1.0f; + u32 freq = 48000; + u32 ch_cnt = 2; + }; + AudioBackend(); virtual ~AudioBackend() = default; /* - * Pure virtual methods + * Virtual methods */ virtual std::string_view GetName() const = 0; - virtual void Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) = 0; + // (Re)create output stream with new parameters. Blocks until data callback returns. + // Should return 'true' on success. + virtual bool Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) = 0; + + // Reset backend state. Blocks until data callback returns. virtual void Close() = 0; - // Sets write callback. It's called when backend requests new data to be sent - // Callback should return number of submitted bytes. Calling other backend functions from callback is unsafe - virtual void SetWriteCallback(std::function cb) = 0; + // Sets write callback. It's called when backend requests new data to be sent. + // Callback should return number of submitted bytes. Calling other backend functions from callback is unsafe. + virtual void SetWriteCallback(std::function cb) = 0; - // Returns length of one callback frame in seconds. + // Sets error callback. It's called when backend detects uncorrectable error condition in audio chain. + // Calling other backend functions from callback is unsafe. + virtual void SetErrorCallback(std::function cb); + + /* + * All functions below require that Open() was called prior. + */ + + // Returns length of one write callback frame in seconds. Open() must be called prior. virtual f64 GetCallbackFrameLen() = 0; - // Returns true if audio is currently being played, false otherwise - virtual bool IsPlaying() = 0; + // Returns true if audio is currently being played, false otherwise. Reflects end result of Play() and Pause() calls. + virtual bool IsPlaying() { return m_playing; } - // Start playing enqueued data + // Start playing enqueued data. virtual void Play() = 0; - // Pause playing enqueued data + // Pause playing enqueued data. No additional callbacks will be issued. Blocks until data callback returns. virtual void Pause() = 0; /* @@ -93,8 +117,93 @@ public: */ static void convert_to_s16(u32 cnt, const f32* src, void* dst); + /* + * Apply volume parameters to the buffer. Gradually changes volume. src and dst could be the same. + * Number of channels must be >1 and multiple of 2. + * sample_cnt is number of buffer elements. Returns current volume. + */ + static f32 apply_volume(const VolumeParam& param, u32 sample_cnt, const f32* src, f32* dst); + + /* + * Apply volume value to the buffer. src and dst could be the same. sample_cnt is number of buffer elements. + * Returns current volume. + */ + static void apply_volume_static(f32 vol, u32 sample_cnt, const f32* src, f32* dst); + + /* + * Downmix audio stream. + */ + template + static void downmix(u32 sample_cnt, const f32* src, f32* dst) + { + static_assert(from == AudioChannelCnt::SURROUND_5_1 || from == AudioChannelCnt::SURROUND_7_1, "Cannot downmix FROM channel count"); + static_assert(static_cast(from) > static_cast(to), "FROM channel count must be bigger than TO"); + + static constexpr f32 center_coef = std::numbers::sqrt2_v / 2; + static constexpr f32 surround_coef = std::numbers::sqrt2_v / 2; + + for (u32 src_sample = 0, dst_sample = 0; src_sample < sample_cnt; src_sample += static_cast(from), dst_sample += static_cast(to)) + { + const f32 left = src[src_sample + 0]; + const f32 right = src[src_sample + 1]; + const f32 center = src[src_sample + 2]; + const f32 low_freq = src[src_sample + 3]; + + if constexpr (from == AudioChannelCnt::SURROUND_5_1) + { + static_assert(to == AudioChannelCnt::STEREO, "Invalid TO channel count"); + + const f32 side_left = src[src_sample + 4]; + const f32 side_right = src[src_sample + 5]; + + const f32 mid = center * center_coef; + dst[dst_sample + 0] = left + mid + side_left * surround_coef; + dst[dst_sample + 1] = right + mid + side_right * surround_coef; + } + else if constexpr (from == AudioChannelCnt::SURROUND_7_1) + { + static_assert(to == AudioChannelCnt::STEREO || to == AudioChannelCnt::SURROUND_5_1, "Invalid TO channel count"); + + const f32 rear_left = src[src_sample + 4]; + const f32 rear_right = src[src_sample + 5]; + const f32 side_left = src[src_sample + 6]; + const f32 side_right = src[src_sample + 7]; + + if constexpr (to == AudioChannelCnt::SURROUND_5_1) + { + dst[dst_sample + 0] = left; + dst[dst_sample + 1] = right; + dst[dst_sample + 2] = center; + dst[dst_sample + 3] = low_freq; + dst[dst_sample + 4] = side_left + rear_left; + dst[dst_sample + 5] = side_right + rear_right; + } + else + { + const f32 mid = center * center_coef; + dst[dst_sample + 0] = left + mid + (side_left + rear_left) * surround_coef; + dst[dst_sample + 1] = right + mid + (side_right + rear_right) * surround_coef; + } + } + } + } + + /* + * Normalize float samples in range from -1.0 to 1.0. + */ + static void normalize(u32 sample_cnt, const f32* src, f32* dst); + protected: AudioSampleSize m_sample_size = AudioSampleSize::FLOAT; AudioFreq m_sampling_rate = AudioFreq::FREQ_48K; AudioChannelCnt m_channels = AudioChannelCnt::STEREO; + + shared_mutex m_error_cb_mutex{}; + std::function m_error_callback{}; + + bool m_playing = false; + +private: + + static constexpr f32 VOLUME_CHANGE_DURATION = 0.016f; // sec }; diff --git a/rpcs3/Emu/Audio/AudioDumper.cpp b/rpcs3/Emu/Audio/AudioDumper.cpp index 63e4d23a3e..783adf66ed 100644 --- a/rpcs3/Emu/Audio/AudioDumper.cpp +++ b/rpcs3/Emu/Audio/AudioDumper.cpp @@ -4,6 +4,8 @@ #include "Utilities/date_time.h" #include "Emu/System.h" +#include + AudioDumper::AudioDumper() { } @@ -13,30 +15,34 @@ AudioDumper::~AudioDumper() Close(); } -void AudioDumper::Open(u16 ch, u32 sample_rate, u32 sample_size) +void AudioDumper::Open(AudioChannelCnt ch, AudioFreq sample_rate, AudioSampleSize sample_size) { Close(); - if (ch) + m_header = WAVHeader(ch, sample_rate, sample_size); + std::string path = fs::get_cache_dir() + "audio_"; + if (const std::string id = Emu.GetTitleID(); !id.empty()) { - m_header = WAVHeader(ch, sample_rate, sample_size); - std::string path = fs::get_cache_dir() + "audio_"; - if (const std::string id = Emu.GetTitleID(); !id.empty()) - { - path += id + "_"; - } - path += date_time::current_time_narrow<'_'>() + ".wav"; - m_output.open(path, fs::rewrite); - m_output.write(m_header); // write initial file header + path += id + "_"; } + path += date_time::current_time_narrow<'_'>() + ".wav"; + m_output.open(path, fs::rewrite); + m_output.seek(sizeof(m_header)); } void AudioDumper::Close() { if (GetCh()) { + if (m_header.Size & 1) + { + const u8 pad_byte = 0; + m_output.write(pad_byte); + m_header.RIFF.Size += 1; + } + m_output.seek(0); - m_output.write(m_header); // rewrite file header + m_output.write(m_header); // write file header m_output.close(); m_header.FMT.NumChannels = 0; } @@ -44,11 +50,41 @@ void AudioDumper::Close() void AudioDumper::WriteData(const void* buffer, u32 size) { - if (GetCh()) + if (GetCh() && size && buffer) { - ensure(size); - ensure(m_output.write(buffer, size) == size); + const u32 blk_size = GetCh() * GetSampleSize(); + const u32 sample_cnt_per_ch = size / blk_size; + + ensure(size - sample_cnt_per_ch * blk_size == 0); + + if constexpr (std::endian::big == std::endian::native) + { + std::vector tmp_buf(size); + + if (GetSampleSize() == sizeof(f32)) + { + for (u32 sample_idx = 0; sample_idx < sample_cnt_per_ch * GetCh(); sample_idx++) + { + std::bit_cast(tmp_buf.data())[sample_idx] = static_cast*>(buffer)[sample_idx]; + } + } + else + { + for (u32 sample_idx = 0; sample_idx < sample_cnt_per_ch * GetCh(); sample_idx++) + { + std::bit_cast(tmp_buf.data())[sample_idx] = static_cast*>(buffer)[sample_idx]; + } + } + + ensure(m_output.write(tmp_buf.data(), size) == size); + } + else + { + ensure(m_output.write(buffer, size) == size); + } + m_header.Size += size; m_header.RIFF.Size += size; + m_header.FACT.SampleLength += sample_cnt_per_ch; } } diff --git a/rpcs3/Emu/Audio/AudioDumper.h b/rpcs3/Emu/Audio/AudioDumper.h index 79eee14ec7..1fff1352bf 100644 --- a/rpcs3/Emu/Audio/AudioDumper.h +++ b/rpcs3/Emu/Audio/AudioDumper.h @@ -2,60 +2,71 @@ #include "util/types.hpp" #include "Utilities/File.h" +#include "Emu/Audio/AudioBackend.h" struct WAVHeader { struct RIFFHeader { - u32 ID; // "RIFF" - u32 Size; // FileSize - 8 - u32 WAVE; // "WAVE" + u8 ID[4] = { 'R', 'I', 'F', 'F' }; + le_t Size{}; // FileSize - 8 + u8 WAVE[4] = { 'W', 'A', 'V', 'E' }; RIFFHeader() = default; RIFFHeader(u32 size) - : ID("RIFF"_u32) - , Size(size) - , WAVE("WAVE"_u32) + : Size(size) { } } RIFF; struct FMTHeader { - u32 ID; // "fmt " - u32 Size; // 16 - u16 AudioFormat; // 1 for PCM, 3 for IEEE Floating Point - u16 NumChannels; // 1, 2, 6, 8 - u32 SampleRate; // 48000 - u32 ByteRate; // SampleRate * NumChannels * BitsPerSample/8 - u16 BlockAlign; // NumChannels * BitsPerSample/8 - u16 BitsPerSample; // SampleSize * 8 + u8 ID[4] = { 'f', 'm', 't', ' ' }; + le_t Size = 16; + le_t AudioFormat{}; // 1 for PCM, 3 for IEEE Floating Point + le_t NumChannels{}; // 1, 2, 6, 8 + le_t SampleRate{}; // 44100-192000 + le_t ByteRate{}; // SampleRate * NumChannels * BitsPerSample/8 + le_t BlockAlign{}; // NumChannels * BitsPerSample/8 + le_t BitsPerSample{}; // SampleSize * 8 FMTHeader() = default; - FMTHeader(u16 ch, u32 sample_rate, u32 sample_size) - : ID("fmt "_u32) - , Size(16) - , AudioFormat(sample_size == sizeof(float) ? 3 : 1) - , NumChannels(ch) - , SampleRate(sample_rate) - , ByteRate(SampleRate * ch * sample_size) - , BlockAlign(ch * sample_size) - , BitsPerSample(sample_size * 8) + FMTHeader(AudioChannelCnt ch, AudioFreq sample_rate, AudioSampleSize sample_size) + : AudioFormat(sample_size == AudioSampleSize::FLOAT ? 3 : 1) + , NumChannels(static_cast(ch)) + , SampleRate(static_cast(sample_rate)) + , ByteRate(SampleRate * NumChannels * static_cast(sample_size)) + , BlockAlign(NumChannels * static_cast(sample_size)) + , BitsPerSample(static_cast(sample_size) * 8) { } } FMT; - u32 ID; // "data" - u32 Size; // size of data (256 * NumChannels * sizeof(float)) + struct FACTChunk + { + u8 ID[4] = { 'f', 'a', 'c', 't' }; + le_t ChunkLength = 4; + le_t SampleLength = 0; // total samples per channel + + FACTChunk() = default; + + FACTChunk(u32 sample_len) + : SampleLength(sample_len) + { + } + } FACT; + + u8 ID[4] = { 'd', 'a', 't', 'a' }; + le_t Size{}; // size of data (256 * NumChannels * sizeof(f32)) WAVHeader() = default; - WAVHeader(u16 ch, u32 sample_rate, u32 sample_size) + WAVHeader(AudioChannelCnt ch, AudioFreq sample_rate, AudioSampleSize sample_size) : RIFF(sizeof(RIFFHeader) + sizeof(FMTHeader)) , FMT(ch, sample_rate, sample_size) - , ID("data"_u32) + , FACT(0) , Size(0) { } @@ -70,9 +81,10 @@ public: AudioDumper(); ~AudioDumper(); - void Open(u16 ch, u32 sample_rate, u32 sample_size); + void Open(AudioChannelCnt ch, AudioFreq sample_rate, AudioSampleSize sample_size); void Close(); void WriteData(const void* buffer, u32 size); u16 GetCh() const { return m_header.FMT.NumChannels; } + u16 GetSampleSize() const { return m_header.FMT.BitsPerSample / 8; } }; diff --git a/rpcs3/Emu/Audio/Cubeb/CubebBackend.cpp b/rpcs3/Emu/Audio/Cubeb/CubebBackend.cpp index 94a535a141..12fb162fdf 100644 --- a/rpcs3/Emu/Audio/Cubeb/CubebBackend.cpp +++ b/rpcs3/Emu/Audio/Cubeb/CubebBackend.cpp @@ -56,12 +56,17 @@ bool CubebBackend::Initialized() bool CubebBackend::Operational() { - return m_ctx != nullptr && m_stream != nullptr && !m_reset_req.observe(); + std::lock_guard lock(m_error_cb_mutex); + return m_stream != nullptr && !m_reset_req; } -void CubebBackend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) +bool CubebBackend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) { - if (m_ctx == nullptr) return; + if (!Initialized()) + { + Cubeb.error("Open() called uninitialized"); + return false; + } std::lock_guard lock(m_cb_mutex); CloseUnlocked(); @@ -92,46 +97,48 @@ void CubebBackend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChanne if (int err = cubeb_get_min_latency(m_ctx, &stream_param, &min_latency)) { Cubeb.error("cubeb_get_min_latency() failed: 0x%08x", err); + min_latency = 0; } const u32 stream_latency = std::max(static_cast(AUDIO_MIN_LATENCY * get_sampling_rate()), min_latency); if (int err = cubeb_stream_init(m_ctx, &m_stream, "Main stream", nullptr, nullptr, nullptr, &stream_param, stream_latency, data_cb, state_cb, this)) { - m_stream = nullptr; Cubeb.error("cubeb_stream_init() failed: 0x%08x", err); + m_stream = nullptr; + } + else if (int err = cubeb_stream_start(m_stream)) + { + Cubeb.error("cubeb_stream_start() failed: 0x%08x", err); + CloseUnlocked(); + } + else if (int err = cubeb_stream_set_volume(m_stream, 1.0)) + { + Cubeb.error("cubeb_stream_set_volume() failed: 0x%08x", err); } if (m_stream == nullptr) { Cubeb.error("Failed to open audio backend. Make sure that no other application is running that might block audio access (e.g. Netflix)."); - CloseUnlocked(); - return; + return false; } - if (int err = cubeb_stream_set_volume(m_stream, 1.0)) - { - Cubeb.error("cubeb_stream_set_volume() failed: 0x%08x", err); - } - - if (int err = cubeb_stream_start(m_stream)) - { - Cubeb.error("cubeb_stream_start() failed: 0x%08x", err); - } + return true; } void CubebBackend::CloseUnlocked() { - if (m_stream == nullptr) return; - - if (int err = cubeb_stream_stop(m_stream)) + if (m_stream != nullptr) { - Cubeb.error("cubeb_stream_stop() failed: 0x%08x", err); + if (int err = cubeb_stream_stop(m_stream)) + { + Cubeb.error("cubeb_stream_stop() failed: 0x%08x", err); + } + + cubeb_stream_destroy(m_stream); + m_stream = nullptr; } - cubeb_stream_destroy(m_stream); - m_playing = false; - m_stream = nullptr; m_last_sample.fill(0); } @@ -143,6 +150,12 @@ void CubebBackend::Close() void CubebBackend::Play() { + if (m_stream == nullptr) + { + Cubeb.error("Play() called uninitialized"); + return; + } + if (m_playing) return; std::lock_guard lock(m_cb_mutex); @@ -151,16 +164,19 @@ void CubebBackend::Play() void CubebBackend::Pause() { + if (m_stream == nullptr) + { + Cubeb.error("Pause() called uninitialized"); + return; + } + + if (!m_playing) return; + std::lock_guard lock(m_cb_mutex); m_playing = false; m_last_sample.fill(0); } -bool CubebBackend::IsPlaying() -{ - return m_playing; -} - void CubebBackend::SetWriteCallback(std::function cb) { std::lock_guard lock(m_cb_mutex); @@ -169,10 +185,17 @@ void CubebBackend::SetWriteCallback(std::function cb) f64 CubebBackend::GetCallbackFrameLen() { + if (m_stream == nullptr) + { + Cubeb.error("GetCallbackFrameLen() called uninitialized"); + return AUDIO_MIN_LATENCY; + } + u32 stream_latency{}; if (int err = cubeb_stream_get_latency(m_stream, &stream_latency)) { Cubeb.error("cubeb_stream_get_latency() failed: 0x%08x", err); + stream_latency = 0; } return std::max(AUDIO_MIN_LATENCY, static_cast(stream_latency) / get_sampling_rate()); @@ -217,6 +240,13 @@ void CubebBackend::state_cb(cubeb_stream* /* stream */, void* user_ptr, cubeb_st if (state == CUBEB_STATE_ERROR) { Cubeb.error("Stream entered error state"); + + std::lock_guard lock(cubeb->m_error_cb_mutex); cubeb->m_reset_req = true; + + if (cubeb->m_error_callback) + { + cubeb->m_error_callback(); + } } } diff --git a/rpcs3/Emu/Audio/Cubeb/CubebBackend.h b/rpcs3/Emu/Audio/Cubeb/CubebBackend.h index cebe784373..d6e3ddb056 100644 --- a/rpcs3/Emu/Audio/Cubeb/CubebBackend.h +++ b/rpcs3/Emu/Audio/Cubeb/CubebBackend.h @@ -21,7 +21,7 @@ public: bool Initialized() override; bool Operational() override; - void Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) override; + bool Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) override; void Close() override; void SetWriteCallback(std::function cb) override; @@ -29,7 +29,6 @@ public: void Play() override; void Pause() override; - bool IsPlaying() override; private: static constexpr f64 AUDIO_MIN_LATENCY = 512.0 / 48000; // 10ms @@ -45,8 +44,7 @@ private: std::array(AudioChannelCnt::SURROUND_7_1)> m_last_sample{}; atomic_t full_sample_size = 0; - bool m_playing = false; - atomic_t m_reset_req = false; + bool m_reset_req = false; // Cubeb callbacks static long data_cb(cubeb_stream* stream, void* user_ptr, void const* input_buffer, void* output_buffer, long nframes); diff --git a/rpcs3/Emu/Audio/FAudio/FAudioBackend.cpp b/rpcs3/Emu/Audio/FAudio/FAudioBackend.cpp index fd9ddeef7d..b4dfc5f906 100644 --- a/rpcs3/Emu/Audio/FAudio/FAudioBackend.cpp +++ b/rpcs3/Emu/Audio/FAudio/FAudioBackend.cpp @@ -14,27 +14,17 @@ FAudioBackend::FAudioBackend() { FAudio *instance; - u32 res = FAudioCreate(&instance, 0, FAUDIO_DEFAULT_PROCESSOR); - if (res) + if (u32 res = FAudioCreate(&instance, 0, FAUDIO_DEFAULT_PROCESSOR)) { FAudio_.error("FAudioCreate() failed(0x%08x)", res); return; } - res = FAudio_CreateMasteringVoice(instance, &m_master_voice, FAUDIO_DEFAULT_CHANNELS, FAUDIO_DEFAULT_SAMPLERATE, 0, 0, nullptr); - if (res) - { - FAudio_.error("FAudio_CreateMasteringVoice() failed(0x%08x)", res); - FAudio_StopEngine(instance); - return; - } - OnProcessingPassStart = nullptr; OnProcessingPassEnd = nullptr; OnCriticalError = OnCriticalError_func; - res = FAudio_RegisterForCallbacks(instance, this); - if (res) + if (u32 res = FAudio_RegisterForCallbacks(instance, this)) { // Some error recovery functionality will be lost, but otherwise backend is operational FAudio_.error("FAudio_RegisterForCallbacks() failed(0x%08x)", res); @@ -48,11 +38,6 @@ FAudioBackend::~FAudioBackend() { Close(); - if (m_master_voice != nullptr) - { - FAudioVoice_DestroyVoice(m_master_voice); - } - if (m_instance != nullptr) { FAudio_StopEngine(m_instance); @@ -70,56 +55,52 @@ void FAudioBackend::Play() if (m_playing) return; - const u32 res = FAudioSourceVoice_Start(m_source_voice, 0, FAUDIO_COMMIT_NOW); - if (res) - { - FAudio_.error("FAudioSourceVoice_Start() failed(0x%08x)", res); - } - std::lock_guard lock(m_cb_mutex); m_playing = true; } void FAudioBackend::Pause() { - if (m_source_voice) - { - u32 res = FAudioSourceVoice_Stop(m_source_voice, 0, FAUDIO_COMMIT_NOW); - if (res) - { - FAudio_.error("FAudioSourceVoice_Stop() failed(0x%08x)", res); - } - - if ((res = FAudioSourceVoice_FlushSourceBuffers(m_source_voice))) - { - FAudio_.error("FAudioSourceVoice_FlushSourceBuffers() failed(0x%08x)", res); - } - } - else + if (m_source_voice == nullptr) { FAudio_.error("Pause() called uninitialized"); + return; } - std::lock_guard lock(m_cb_mutex); - m_playing = false; - m_last_sample.fill(0); + if (!m_playing) return; + + { + std::lock_guard lock(m_cb_mutex); + m_playing = false; + m_last_sample.fill(0); + } + + if (u32 res = FAudioSourceVoice_FlushSourceBuffers(m_source_voice)) + { + FAudio_.error("FAudioSourceVoice_FlushSourceBuffers() failed(0x%08x)", res); + } } void FAudioBackend::CloseUnlocked() { - if (m_source_voice == nullptr) return; - - const u32 res = FAudioSourceVoice_Stop(m_source_voice, 0, FAUDIO_COMMIT_NOW); - if (res) + if (m_source_voice != nullptr) { - FAudio_.error("FAudioSourceVoice_Stop() failed(0x%08x)", res); + if (u32 res = FAudioSourceVoice_Stop(m_source_voice, 0, FAUDIO_COMMIT_NOW)) + { + FAudio_.error("FAudioSourceVoice_Stop() failed(0x%08x)", res); + } + + FAudioVoice_DestroyVoice(m_source_voice); + m_source_voice = nullptr; + } + + if (m_master_voice) + { + FAudioVoice_DestroyVoice(m_master_voice); + m_master_voice = nullptr; } - FAudioVoice_DestroyVoice(m_source_voice); m_playing = false; - m_source_voice = nullptr; - m_data_buf = nullptr; - m_data_buf_len = 0; m_last_sample.fill(0); } @@ -136,12 +117,17 @@ bool FAudioBackend::Initialized() bool FAudioBackend::Operational() { - return m_instance != nullptr && m_source_voice != nullptr && !m_reset_req.observe(); + std::lock_guard lock(m_error_cb_mutex); + return m_source_voice != nullptr && !m_reset_req; } -void FAudioBackend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) +bool FAudioBackend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) { - if (m_instance == nullptr) return; + if (!Initialized()) + { + FAudio_.error("Open() called uninitialized"); + return false; + } std::lock_guard lock(m_cb_mutex); CloseUnlocked(); @@ -167,27 +153,35 @@ void FAudioBackend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChann OnLoopEnd = nullptr; OnVoiceError = nullptr; - const u32 res = FAudio_CreateSourceVoice(m_instance, &m_source_voice, &waveformatex, 0, FAUDIO_DEFAULT_FREQ_RATIO, this, nullptr, nullptr); - if (res) + if (u32 res = FAudio_CreateMasteringVoice(m_instance, &m_master_voice, FAUDIO_DEFAULT_CHANNELS, FAUDIO_DEFAULT_SAMPLERATE, 0, 0, nullptr)) + { + FAudio_.error("FAudio_CreateMasteringVoice() failed(0x%08x)", res); + m_master_voice = nullptr; + } + else if (u32 res = FAudio_CreateSourceVoice(m_instance, &m_source_voice, &waveformatex, 0, FAUDIO_DEFAULT_FREQ_RATIO, this, nullptr, nullptr)) { FAudio_.error("FAudio_CreateSourceVoice() failed(0x%08x)", res); + CloseUnlocked(); + } + else if (u32 res = FAudioSourceVoice_Start(m_source_voice, 0, FAUDIO_COMMIT_NOW)) + { + FAudio_.error("FAudioSourceVoice_Start() failed(0x%08x)", res); + CloseUnlocked(); + } + else if (u32 res = FAudioVoice_SetVolume(m_source_voice, 1.0f, FAUDIO_COMMIT_NOW)) + { + FAudio_.error("FAudioVoice_SetVolume() failed(0x%08x)", res); } if (m_source_voice == nullptr) { - FAudio_.fatal("Failed to open audio backend. Make sure that no other application is running that might block audio access (e.g. Netflix)."); - return; + FAudio_.error("Failed to open audio backend. Make sure that no other application is running that might block audio access (e.g. Netflix)."); + return false; } - FAudioVoice_SetVolume(m_source_voice, 1.0f, FAUDIO_COMMIT_NOW); + m_data_buf.resize(get_sampling_rate() * get_sample_size() * get_channels() * INTERNAL_BUF_SIZE_MS * static_cast(FAUDIO_DEFAULT_FREQ_RATIO) / 1000); - m_data_buf_len = get_sampling_rate() * get_sample_size() * get_channels() * INTERNAL_BUF_SIZE_MS * static_cast(FAUDIO_DEFAULT_FREQ_RATIO) / 1000; - m_data_buf = std::make_unique(m_data_buf_len); -} - -bool FAudioBackend::IsPlaying() -{ - return m_playing; + return true; } void FAudioBackend::SetWriteCallback(std::function cb) @@ -226,32 +220,27 @@ void FAudioBackend::OnVoiceProcessingPassStart_func(FAudioVoiceCallback *cb_obj, std::unique_lock lock(faudio->m_cb_mutex, std::defer_lock); if (BytesRequired && lock.try_lock() && faudio->m_write_callback && faudio->m_playing) { - ensure(BytesRequired <= faudio->m_data_buf_len, "FAudio internal buffer is too small. Report to developers!"); + ensure(BytesRequired <= faudio->m_data_buf.size(), "FAudio internal buffer is too small. Report to developers!"); const u32 sample_size = faudio->get_sample_size() * faudio->get_channels(); - u32 written = std::min(faudio->m_write_callback(BytesRequired, faudio->m_data_buf.get()), BytesRequired); + u32 written = std::min(faudio->m_write_callback(BytesRequired, faudio->m_data_buf.data()), BytesRequired); written -= written % sample_size; if (written >= sample_size) { - memcpy(faudio->m_last_sample.data(), faudio->m_data_buf.get() + written - sample_size, sample_size); + memcpy(faudio->m_last_sample.data(), faudio->m_data_buf.data() + written - sample_size, sample_size); } for (u32 i = written; i < BytesRequired; i += sample_size) { - memcpy(faudio->m_data_buf.get() + i, faudio->m_last_sample.data(), sample_size); + memcpy(faudio->m_data_buf.data() + i, faudio->m_last_sample.data(), sample_size); } FAudioBuffer buffer{}; buffer.AudioBytes = BytesRequired; - buffer.LoopBegin = FAUDIO_NO_LOOP_REGION; - buffer.pAudioData = static_cast(faudio->m_data_buf.get()); - - const u32 res = FAudioSourceVoice_SubmitSourceBuffer(faudio->m_source_voice, &buffer, nullptr); - if (res) - { - FAudio_.error("FAudioSourceVoice_SubmitSourceBuffer() failed(0x%08x)", res); - } + buffer.pAudioData = static_cast(faudio->m_data_buf.data()); + // Avoid logging in callback and assume that this always succeeds, all errors are caught by error callback anyway + FAudioSourceVoice_SubmitSourceBuffer(faudio->m_source_voice, &buffer, nullptr); } } @@ -260,5 +249,12 @@ void FAudioBackend::OnCriticalError_func(FAudioEngineCallback *cb_obj, u32 Error FAudio_.error("OnCriticalError() failed(0x%08x)", Error); FAudioBackend *faudio = static_cast(cb_obj); + + std::lock_guard lock(faudio->m_error_cb_mutex); faudio->m_reset_req = true; + + if (faudio->m_error_callback) + { + faudio->m_error_callback(); + } } diff --git a/rpcs3/Emu/Audio/FAudio/FAudioBackend.h b/rpcs3/Emu/Audio/FAudio/FAudioBackend.h index 91011d8811..98fd4251e7 100644 --- a/rpcs3/Emu/Audio/FAudio/FAudioBackend.h +++ b/rpcs3/Emu/Audio/FAudio/FAudioBackend.h @@ -10,7 +10,7 @@ #include "FAudio.h" -class FAudioBackend : public AudioBackend, public FAudioVoiceCallback, public FAudioEngineCallback +class FAudioBackend final : public AudioBackend, public FAudioVoiceCallback, public FAudioEngineCallback { public: FAudioBackend(); @@ -24,7 +24,7 @@ public: bool Initialized() override; bool Operational() override; - void Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) override; + bool Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) override; void Close() override; void SetWriteCallback(std::function cb) override; @@ -32,7 +32,6 @@ public: void Play() override; void Pause() override; - bool IsPlaying() override; private: static constexpr u32 INTERNAL_BUF_SIZE_MS = 25; @@ -43,12 +42,10 @@ private: shared_mutex m_cb_mutex{}; std::function m_write_callback{}; - std::unique_ptr m_data_buf{}; - u64 m_data_buf_len = 0; + std::vector m_data_buf{}; std::array(AudioChannelCnt::SURROUND_7_1)> m_last_sample{}; - bool m_playing = false; - atomic_t m_reset_req = false; + bool m_reset_req = false; // FAudio voice callbacks static void OnVoiceProcessingPassStart_func(FAudioVoiceCallback *cb_obj, u32 BytesRequired); diff --git a/rpcs3/Emu/Audio/Null/NullAudioBackend.h b/rpcs3/Emu/Audio/Null/NullAudioBackend.h index 148ee1d8b5..70b02bfde4 100644 --- a/rpcs3/Emu/Audio/Null/NullAudioBackend.h +++ b/rpcs3/Emu/Audio/Null/NullAudioBackend.h @@ -2,7 +2,7 @@ #include "Emu/Audio/AudioBackend.h" -class NullAudioBackend : public AudioBackend +class NullAudioBackend final : public AudioBackend { public: NullAudioBackend() {} @@ -10,12 +10,18 @@ public: std::string_view GetName() const override { return "Null"sv; } - void Open(AudioFreq /* freq */, AudioSampleSize /* sample_size */, AudioChannelCnt /* ch_cnt */) override { m_playing = false; } + bool Open(AudioFreq /* freq */, AudioSampleSize /* sample_size */, AudioChannelCnt /* ch_cnt */) override + { + Close(); + return true; + } void Close() override { m_playing = false; } void SetWriteCallback(std::function /* cb */) override {}; f64 GetCallbackFrameLen() override { return 0.01; }; + void SetErrorCallback(std::function /* cb */) override {}; + void Play() override { m_playing = true; } void Pause() override { m_playing = false; } bool IsPlaying() override { return m_playing; } diff --git a/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.cpp b/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.cpp index 76a5fbf169..c02f253eaa 100644 --- a/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.cpp +++ b/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.cpp @@ -53,10 +53,7 @@ XAudio2Backend::~XAudio2Backend() if (m_xaudio2_instance != nullptr) { m_xaudio2_instance->StopEngine(); - - // TODO: Enabling this might crash afterwards in ComPtr::InternalRelease. - // Maybe it's both trying to do the same thing? - //m_xaudio2_instance->Release(); + m_xaudio2_instance = nullptr; } if (m_com_init_success) @@ -72,12 +69,14 @@ bool XAudio2Backend::Initialized() bool XAudio2Backend::Operational() { + std::lock_guard lock(m_error_cb_mutex); + if (m_dev_listener.output_device_changed()) { m_reset_req = true; } - return m_xaudio2_instance != nullptr && m_source_voice != nullptr && !m_reset_req.observe(); + return m_source_voice != nullptr && !m_reset_req; } void XAudio2Backend::Play() @@ -90,13 +89,6 @@ void XAudio2Backend::Play() if (m_playing) return; - const HRESULT hr = m_source_voice->Start(); - if (FAILED(hr)) - { - XAudio.error("Start() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); - return; - } - std::lock_guard lock(m_cb_mutex); m_playing = true; } @@ -105,8 +97,7 @@ void XAudio2Backend::CloseUnlocked() { if (m_source_voice != nullptr) { - const HRESULT hr = m_source_voice->Stop(); - if (FAILED(hr)) + if (HRESULT hr = m_source_voice->Stop(); FAILED(hr)) { XAudio.error("Stop() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); } @@ -122,8 +113,6 @@ void XAudio2Backend::CloseUnlocked() } m_playing = false; - m_data_buf = nullptr; - m_data_buf_len = 0; m_last_sample.fill(0); } @@ -135,50 +124,37 @@ void XAudio2Backend::Close() void XAudio2Backend::Pause() { - if (m_source_voice) - { - HRESULT hr = m_source_voice->Stop(); - if (FAILED(hr)) - { - XAudio.error("Stop() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); - } - - hr = m_source_voice->FlushSourceBuffers(); - if (FAILED(hr)) - { - XAudio.error("FlushSourceBuffers() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); - } - } - else + if (m_source_voice == nullptr) { XAudio.error("Pause() called uninitialized"); + return; } - std::lock_guard lock(m_cb_mutex); - m_playing = false; - m_last_sample.fill(0); + if (!m_playing) return; + + { + std::lock_guard lock(m_cb_mutex); + m_playing = false; + m_last_sample.fill(0); + } + + if (HRESULT hr = m_source_voice->FlushSourceBuffers(); FAILED(hr)) + { + XAudio.error("FlushSourceBuffers() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); + } } -void XAudio2Backend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) +bool XAudio2Backend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) { + if (!Initialized()) + { + XAudio.error("Open() called uninitialized"); + return false; + } + std::lock_guard lock(m_cb_mutex); CloseUnlocked(); - if (m_xaudio2_instance == nullptr) - { - XAudio.error("Open() called unitiliazed"); - return; - } - - HRESULT hr = m_xaudio2_instance->CreateMasteringVoice(&m_master_voice); - if (FAILED(hr)) - { - XAudio.error("CreateMasteringVoice() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); - XAudio.error("Failed to open audio backend. Make sure that no other application is running that might block audio access (e.g. Netflix)."); - m_reset_req = true; - return; - } - m_sampling_rate = freq; m_sample_size = sample_size; m_channels = ch_cnt; @@ -192,23 +168,35 @@ void XAudio2Backend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChan waveformatex.wBitsPerSample = get_sample_size() * 8; waveformatex.cbSize = 0; - hr = m_xaudio2_instance->CreateSourceVoice(&m_source_voice, &waveformatex, 0, XAUDIO2_DEFAULT_FREQ_RATIO, this); - if (FAILED(hr)) + if (HRESULT hr = m_xaudio2_instance->CreateMasteringVoice(&m_master_voice); FAILED(hr)) + { + XAudio.error("CreateMasteringVoice() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); + m_master_voice = nullptr; + } + else if (HRESULT hr = m_xaudio2_instance->CreateSourceVoice(&m_source_voice, &waveformatex, 0, XAUDIO2_DEFAULT_FREQ_RATIO, this); FAILED(hr)) { XAudio.error("CreateSourceVoice() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); - return; + CloseUnlocked(); + } + else if (HRESULT hr = m_source_voice->Start(); FAILED(hr)) + { + XAudio.error("Start() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); + CloseUnlocked(); + } + else if (HRESULT hr = m_source_voice->SetVolume(1.0f); FAILED(hr)) + { + XAudio.error("SetVolume() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); } - ensure(m_source_voice != nullptr); - m_source_voice->SetVolume(1.0f); + if (m_source_voice == nullptr) + { + XAudio.error("Failed to open audio backend. Make sure that no other application is running that might block audio access (e.g. Netflix)."); + return false; + } - m_data_buf_len = get_sampling_rate() * get_sample_size() * get_channels() * INTERNAL_BUF_SIZE_MS * static_cast(XAUDIO2_DEFAULT_FREQ_RATIO) / 1000; - m_data_buf = std::make_unique(m_data_buf_len); -} + m_data_buf.resize(get_sampling_rate() * get_sample_size() * get_channels() * INTERNAL_BUF_SIZE_MS * static_cast(XAUDIO2_DEFAULT_FREQ_RATIO) / 1000); -bool XAudio2Backend::IsPlaying() -{ - return m_playing; + return true; } void XAudio2Backend::SetWriteCallback(std::function cb) @@ -230,7 +218,7 @@ f64 XAudio2Backend::GetCallbackFrameLen() void *ext; f64 min_latency{}; - HRESULT hr = m_xaudio2_instance->QueryInterface(IID_IXAudio2Extension, &ext); + const HRESULT hr = m_xaudio2_instance->QueryInterface(IID_IXAudio2Extension, &ext); if (FAILED(hr)) { XAudio.error("QueryInterface() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); @@ -254,37 +242,39 @@ void XAudio2Backend::OnVoiceProcessingPassStart(UINT32 BytesRequired) std::unique_lock lock(m_cb_mutex, std::defer_lock); if (BytesRequired && lock.try_lock() && m_write_callback && m_playing) { - ensure(BytesRequired <= m_data_buf_len, "XAudio internal buffer is too small. Report to developers!"); + ensure(BytesRequired <= m_data_buf.size(), "XAudio internal buffer is too small. Report to developers!"); const u32 sample_size = get_sample_size() * get_channels(); - u32 written = std::min(m_write_callback(BytesRequired, m_data_buf.get()), BytesRequired); + u32 written = std::min(m_write_callback(BytesRequired, m_data_buf.data()), BytesRequired); written -= written % sample_size; if (written >= sample_size) { - memcpy(m_last_sample.data(), m_data_buf.get() + written - sample_size, sample_size); + memcpy(m_last_sample.data(), m_data_buf.data() + written - sample_size, sample_size); } for (u32 i = written; i < BytesRequired; i += sample_size) { - memcpy(m_data_buf.get() + i, m_last_sample.data(), sample_size); + memcpy(m_data_buf.data() + i, m_last_sample.data(), sample_size); } XAUDIO2_BUFFER buffer{}; buffer.AudioBytes = BytesRequired; - buffer.LoopBegin = XAUDIO2_NO_LOOP_REGION; - buffer.pAudioData = static_cast(m_data_buf.get()); - - const HRESULT hr = m_source_voice->SubmitSourceBuffer(&buffer); - if (FAILED(hr)) - { - XAudio.error("SubmitSourceBuffer() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); - } + buffer.pAudioData = static_cast(m_data_buf.data()); + // Avoid logging in callback and assume that this always succeeds, all errors are caught by error callback anyway + m_source_voice->SubmitSourceBuffer(&buffer); } } void XAudio2Backend::OnCriticalError(HRESULT Error) { XAudio.error("OnCriticalError() called: %s (0x%08x)", std::system_category().message(Error), static_cast(Error)); + + std::lock_guard lock(m_error_cb_mutex); m_reset_req = true; + + if (m_error_callback) + { + m_error_callback(); + } } diff --git a/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.h b/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.h index 6be3bc70c4..ef9f658afc 100644 --- a/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.h +++ b/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.h @@ -26,7 +26,7 @@ public: bool Initialized() override; bool Operational() override; - void Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) override; + bool Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) override; void Close() override; void SetWriteCallback(std::function cb) override; @@ -34,7 +34,6 @@ public: void Play() override; void Pause() override; - bool IsPlaying() override; private: static constexpr u32 INTERNAL_BUF_SIZE_MS = 25; @@ -46,12 +45,10 @@ private: shared_mutex m_cb_mutex{}; std::function m_write_callback{}; - std::unique_ptr m_data_buf{}; - u64 m_data_buf_len = 0; + std::vector m_data_buf{}; std::array(AudioChannelCnt::SURROUND_7_1)> m_last_sample{}; - bool m_playing = false; - atomic_t m_reset_req = false; + bool m_reset_req = false; audio_device_listener m_dev_listener{}; diff --git a/rpcs3/Emu/Cell/Modules/cellAudio.cpp b/rpcs3/Emu/Cell/Modules/cellAudio.cpp index 6d9b680564..4d1f018b33 100644 --- a/rpcs3/Emu/Cell/Modules/cellAudio.cpp +++ b/rpcs3/Emu/Cell/Modules/cellAudio.cpp @@ -73,16 +73,15 @@ void cell_audio_config::reset(bool backend_changed) } }(); - backend->Open(freq, sample_size, ch_cnt); + const f64 cb_frame_len = backend->Open(freq, sample_size, ch_cnt) ? backend->GetCallbackFrameLen() : 0.0; - audio_channels = backend->get_channels(); - audio_sampling_rate = backend->get_sampling_rate(); + audio_channels = static_cast(ch_cnt); + audio_sampling_rate = static_cast(freq); audio_block_period = AUDIO_BUFFER_SAMPLES * 1'000'000 / audio_sampling_rate; - audio_sample_size = backend->get_sample_size(); - audio_min_buffer_duration = backend->GetCallbackFrameLen() + u32{AUDIO_BUFFER_SAMPLES} * 2.0 / audio_sampling_rate; // Add 2 blocks to allow jitter compensation + audio_sample_size = static_cast(sample_size); + audio_min_buffer_duration = cb_frame_len + u32{AUDIO_BUFFER_SAMPLES} * 2.0 / audio_sampling_rate; // Add 2 blocks to allow jitter compensation audio_buffer_length = AUDIO_BUFFER_SAMPLES * audio_channels; - audio_buffer_size = audio_buffer_length * audio_sample_size; desired_buffer_duration = std::max(static_cast(audio_min_buffer_duration * 1000), raw.desired_buffer_duration) * 1000llu; buffering_enabled = raw.buffering_enabled && raw.renderer != audio_renderer::null; @@ -132,11 +131,11 @@ audio_ringbuffer::audio_ringbuffer(cell_audio_config& _cfg) // Init audio dumper if enabled if (cfg.raw.dump_to_file) { - m_dump.Open(cfg.audio_channels, cfg.audio_sampling_rate, cfg.audio_sample_size); + m_dump.Open(static_cast(cfg.audio_channels), static_cast(cfg.audio_sampling_rate), AudioSampleSize::FLOAT); } // Configure resampler - resampler.set_params(static_cast(cfg.audio_channels), AudioFreq::FREQ_48K); + resampler.set_params(static_cast(cfg.audio_channels), static_cast(cfg.audio_sampling_rate)); resampler.set_tempo(RESAMPLER_MAX_FREQ_VAL); const f64 buffer_dur_mult = [&]() @@ -165,7 +164,7 @@ audio_ringbuffer::~audio_ringbuffer() f32 audio_ringbuffer::set_frequency_ratio(f32 new_ratio) { - frequency_ratio = resampler.set_tempo(new_ratio); + frequency_ratio = static_cast(resampler.set_tempo(new_ratio)); return frequency_ratio; } @@ -181,7 +180,7 @@ u32 audio_ringbuffer::backend_write_callback(u32 size, void *buf) { if (!backend_active.observe()) backend_active = true; - return cb_ringbuf.pop(buf, size); + return static_cast(cb_ringbuf.pop(buf, size, true)); } u64 audio_ringbuffer::get_timestamp() @@ -228,9 +227,6 @@ void audio_ringbuffer::enqueue(bool enqueue_silence, bool force) cur_pos = (cur_pos + 1) % cfg.num_allocated_buffers; } - // Dump audio if enabled - m_dump.WriteData(buf, cfg.audio_buffer_size); - if (!backend_active.observe() && !force) { // backend is not ready yet @@ -261,7 +257,7 @@ void audio_ringbuffer::process_resampled_data() { if (!cfg.time_stretching_enabled) return; - const auto samples = resampler.get_samples(cb_ringbuf.get_free_size() / (cfg.audio_sample_size * cfg.audio_channels)); + const auto samples = resampler.get_samples(static_cast(cb_ringbuf.get_free_size() / (cfg.audio_sample_size * cfg.audio_channels))); commit_data(samples.first, samples.second); } @@ -269,17 +265,15 @@ void audio_ringbuffer::commit_data(f32* buf, u32 sample_cnt) { sample_cnt *= cfg.audio_channels; + // Dump audio if enabled + m_dump.WriteData(buf, sample_cnt * static_cast(AudioSampleSize::FLOAT)); + if (cfg.backend->get_convert_to_s16()) { AudioBackend::convert_to_s16(sample_cnt, buf, buf); } - sample_cnt *= cfg.audio_sample_size; - - if (cb_ringbuf.get_free_size() >= sample_cnt) - { - cb_ringbuf.push(buf, sample_cnt); - } + cb_ringbuf.push(buf, sample_cnt * cfg.audio_sample_size); } void audio_ringbuffer::play() @@ -297,7 +291,7 @@ void audio_ringbuffer::play() void audio_ringbuffer::flush() { backend->Pause(); - cb_ringbuf.flush(); + cb_ringbuf.writer_flush(); resampler.flush(); backend_active = false; playing = false; @@ -566,7 +560,8 @@ namespace audio raw.renderer != new_raw.renderer || raw.dump_to_file != new_raw.dump_to_file) { - g_audio.cfg.raw = new_raw; + std::lock_guard lock{g_audio.emu_cfg_upd_m}; + g_audio.cfg.new_raw = new_raw; g_audio.m_update_configuration = raw.renderer != new_raw.renderer ? audio_backend_update::ALL : audio_backend_update::PARAM; } } @@ -635,18 +630,24 @@ void cell_audio_thread::operator()() if (update_req != audio_backend_update::NONE) { cellAudio.warning("Updating cell_audio_thread configuration"); + { + std::lock_guard lock{emu_cfg_upd_m}; + cfg.raw = cfg.new_raw; + m_update_configuration = audio_backend_update::NONE; + } update_config(update_req == audio_backend_update::ALL); - m_update_configuration = audio_backend_update::NONE; } if (!ringbuffer->get_operational_status()) { - cellAudio.warning("Backend stopped unexpectedly (likely device change). Attempting to recover..."); - if (m_backend_failed) { thread_ctrl::wait_for(500 * 1000); } + else + { + cellAudio.warning("Backend stopped unexpectedly (likely device change). Attempting to recover..."); + } update_config(true); m_backend_failed = true; @@ -743,12 +744,10 @@ void cell_audio_thread::operator()() if (desired_duration_rate < cfg.time_stretching_threshold) { const f32 normalized_desired_duration_rate = desired_duration_rate / cfg.time_stretching_threshold; - const f32 request_ratio = normalized_desired_duration_rate * cfg.time_stretching_scale; - AUDIT(request_ratio <= RESAMPLER_MAX_FREQ_VAL); // change frequency ratio in steps - const f32 req_time_stretching_step = (request_ratio + frequency_ratio) / 2.0f; - if (req_time_stretching_step > cfg.time_stretching_step) + const f32 req_time_stretching_step = (normalized_desired_duration_rate + frequency_ratio) / 2.0f; + if (std::abs(req_time_stretching_step - frequency_ratio) > cfg.time_stretching_step) { ringbuffer->set_frequency_ratio(req_time_stretching_step); } diff --git a/rpcs3/Emu/Cell/Modules/cellAudio.h b/rpcs3/Emu/Cell/Modules/cellAudio.h index 98b8e6e796..c9b1f6709a 100644 --- a/rpcs3/Emu/Cell/Modules/cellAudio.h +++ b/rpcs3/Emu/Cell/Modules/cellAudio.h @@ -84,20 +84,20 @@ enum class audio_backend_update : u32 //libaudio datatypes struct CellAudioPortParam { - be_t nChannel; - be_t nBlock; - be_t attr; - be_t level; + be_t nChannel{}; + be_t nBlock{}; + be_t attr{}; + be_t level{}; }; struct CellAudioPortConfig { - vm::bptr readIndexAddr; - be_t status; - be_t nChannel; - be_t nBlock; - be_t portSize; - be_t portAddr; + vm::bptr readIndexAddr{}; + be_t status{}; + be_t nChannel{}; + be_t nBlock{}; + be_t portSize{}; + be_t portAddr{}; }; enum : u32 @@ -140,27 +140,27 @@ struct audio_port { atomic_t state = audio_port_state::closed; - u32 number; + u32 number = 0; vm::ptr addr{}; vm::ptr index{}; - u32 num_channels; - u32 num_blocks; - u64 attr; - u64 cur_pos; - u64 global_counter; // copy of global counter - u64 active_counter; - u32 size; - u64 timestamp; // copy of global timestamp + u32 num_channels = 0; + u32 num_blocks = 0; + u64 attr = 0; + u64 cur_pos = 0; + u64 global_counter = 0; // copy of global counter + u64 active_counter = 0; + u32 size = 0; + u64 timestamp = 0; // copy of global timestamp struct level_set_t { - float value; - float inc; + float value = 0.0f; + float inc = 0.0f; }; - float level; - atomic_t level_set; + float level = 0.0f; + atomic_t level_set{}; u32 block_size() const { @@ -190,7 +190,7 @@ struct audio_port // Tags - u32 prev_touched_tag_nr; + u32 prev_touched_tag_nr = 0; f32 last_tag_value[PORT_BUFFER_TAG_COUNT] = { 0 }; void tag(s32 offset = 0); @@ -209,7 +209,10 @@ struct cell_audio_config audio_downmix downmix = audio_downmix::downmix_to_stereo; audio_renderer renderer = audio_renderer::null; audio_provider provider = audio_provider::none; - } raw; + }; + + raw_config new_raw{}; + raw_config raw{}; std::shared_ptr backend = nullptr; @@ -220,7 +223,6 @@ struct cell_audio_config f64 audio_min_buffer_duration = 0.0; u32 audio_buffer_length = 0; - u32 audio_buffer_size = 0; /* * Buffering @@ -254,7 +256,6 @@ struct cell_audio_config f32 time_stretching_threshold = 0.0f; // we only apply time stretching below this buffer fill rate (adjusted for average period) static constexpr f32 time_stretching_step = 0.1f; // will only reduce/increase the frequency ratio in steps of at least this value - static constexpr f32 time_stretching_scale = 0.9f; /* * Constructor @@ -278,7 +279,7 @@ private: AudioDumper m_dump{}; - std::unique_ptr buffer[MAX_AUDIO_BUFFERS]; + std::unique_ptr buffer[MAX_AUDIO_BUFFERS]{}; simple_ringbuf cb_ringbuf{}; audio_resampler resampler{}; @@ -347,7 +348,7 @@ public: class cell_audio_thread { private: - std::unique_ptr ringbuffer; + std::unique_ptr ringbuffer{}; void reset_ports(s32 offset = 0); void advance(u64 timestamp); @@ -365,10 +366,11 @@ private: void reset_counters(); public: - cell_audio_config cfg; + shared_mutex emu_cfg_upd_m{}; + cell_audio_config cfg{}; atomic_t m_update_configuration = audio_backend_update::NONE; - shared_mutex mutex; + shared_mutex mutex{}; atomic_t init = 0; u32 key_count = 0; @@ -376,14 +378,14 @@ public: struct key_info { - u8 start_period; // Starting event_period - u32 flags; // iFlags - u64 source; // Event source - std::shared_ptr port; // Underlying event port + u8 start_period = 0; // Starting event_period + u32 flags = 0; // iFlags + u64 source = 0; // Event source + std::shared_ptr port{}; // Underlying event port }; - std::vector keys; - std::array ports; + std::vector keys{}; + std::array ports{}; u64 m_last_period_end = 0; u64 m_counter = 0; diff --git a/rpcs3/Emu/Cell/lv2/lv2.cpp b/rpcs3/Emu/Cell/lv2/lv2.cpp index 3e65b95457..1ec2d43520 100644 --- a/rpcs3/Emu/Cell/lv2/lv2.cpp +++ b/rpcs3/Emu/Cell/lv2/lv2.cpp @@ -664,12 +664,12 @@ const std::array, 1024> g_ppu_sysc BIND_SYSC(sys_rsxaudio_finalize), //651 (0x28B) BIND_SYSC(sys_rsxaudio_import_shared_memory), //652 (0x28C) BIND_SYSC(sys_rsxaudio_unimport_shared_memory), //653 (0x28D) - NULL_FUNC(sys_rsxaudio_create_connection), //654 (0x28E) - NULL_FUNC(sys_rsxaudio_close_connection), //655 (0x28F) - NULL_FUNC(sys_rsxaudio_prepare_process), //656 (0x290) - NULL_FUNC(sys_rsxaudio_start_process), //657 (0x291) - NULL_FUNC(sys_rsxaudio_stop_process), //658 (0x292) - null_func, //BIND_SYSC(sys_rsxaudio_...), //659 (0x293) + BIND_SYSC(sys_rsxaudio_create_connection), //654 (0x28E) + BIND_SYSC(sys_rsxaudio_close_connection), //655 (0x28F) + BIND_SYSC(sys_rsxaudio_prepare_process), //656 (0x290) + BIND_SYSC(sys_rsxaudio_start_process), //657 (0x291) + BIND_SYSC(sys_rsxaudio_stop_process), //658 (0x292) + BIND_SYSC(sys_rsxaudio_get_dma_param), //659 (0x293) uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, //660-665 UNS diff --git a/rpcs3/Emu/Cell/lv2/sys_rsxaudio.cpp b/rpcs3/Emu/Cell/lv2/sys_rsxaudio.cpp index dcb071e908..0191ab5e23 100644 --- a/rpcs3/Emu/Cell/lv2/sys_rsxaudio.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_rsxaudio.cpp @@ -1,39 +1,2330 @@ #include "stdafx.h" #include "Emu/Memory/vm.h" -#include "Emu/Cell/ErrorCodes.h" +#include "Emu/IdManager.h" +#include "Emu/System.h" +#include "sys_process.h" #include "sys_rsxaudio.h" +#include +#include +#include + +#ifdef __linux__ +#include +#include +#include +#include +#elif defined(BSD) || defined(__APPLE__) +#include +#endif + LOG_CHANNEL(sys_rsxaudio); +namespace rsxaudio_ringbuf_reader +{ + static constexpr void clean_buf(rsxaudio_shmem::ringbuf_t& ring_buf) + { + ring_buf.unk2 = 100; + ring_buf.read_idx = 0; + ring_buf.write_idx = 0; + ring_buf.queue_notify_idx = 0; + ring_buf.next_blk_idx = 0; + + for (auto& ring_entry : ring_buf.entries) + { + ring_entry.valid = 0; + ring_entry.audio_blk_idx = 0; + ring_entry.timestamp = 0; + } + } + + static void set_timestamp(rsxaudio_shmem::ringbuf_t& ring_buf, u64 timestamp) + { + const s32 entry_idx_raw = (ring_buf.read_idx + ring_buf.rw_max_idx - (ring_buf.rw_max_idx > 2) - 1) % ring_buf.rw_max_idx; + const s32 entry_idx = std::clamp(entry_idx_raw, 0, SYS_RSXAUDIO_RINGBUF_SZ); + + ring_buf.entries[entry_idx].timestamp = convert_to_timebased_time(timestamp); + } + + static std::tuple update_status(rsxaudio_shmem::ringbuf_t& ring_buf) + { + const s32 read_idx = std::clamp(ring_buf.read_idx, 0, SYS_RSXAUDIO_RINGBUF_SZ); + + if ((ring_buf.entries[read_idx].valid & 1) == 0U) + { + return {}; + } + + const s32 entry_idx_raw = (ring_buf.read_idx + ring_buf.rw_max_idx - (ring_buf.rw_max_idx > 2)) % ring_buf.rw_max_idx; + const s32 entry_idx = std::clamp(entry_idx_raw, 0, SYS_RSXAUDIO_RINGBUF_SZ); + + ring_buf.entries[read_idx].valid = 0; + ring_buf.queue_notify_idx = (ring_buf.queue_notify_idx + 1) % ring_buf.queue_notify_step; + ring_buf.read_idx = (ring_buf.read_idx + 1) % ring_buf.rw_max_idx; + + return std::make_tuple(((ring_buf.rw_max_idx > 2) ^ ring_buf.queue_notify_idx) == 0, ring_buf.entries[entry_idx].audio_blk_idx, ring_buf.entries[entry_idx].timestamp); + } + + static std::pair get_addr(const rsxaudio_shmem::ringbuf_t& ring_buf) + { + const s32 read_idx = std::clamp(ring_buf.read_idx, 0, SYS_RSXAUDIO_RINGBUF_SZ); + + if (ring_buf.entries[read_idx].valid & 1) + { + return std::make_pair(true, ring_buf.entries[read_idx].dma_addr); + } + + return std::make_pair(false, ring_buf.dma_silence_addr); + } + + [[maybe_unused]] + static std::optional get_spdif_channel_data(RsxaudioPort dst, rsxaudio_shmem& shmem) + { + if (dst == RsxaudioPort::SPDIF_0) + { + if (shmem.ctrl.spdif_ch0_channel_data_tx_cycles) + { + shmem.ctrl.spdif_ch0_channel_data_tx_cycles--; + return static_cast(shmem.ctrl.spdif_ch0_channel_data_hi) << 32 | shmem.ctrl.spdif_ch0_channel_data_lo; + } + } + else + { + if (shmem.ctrl.spdif_ch1_channel_data_tx_cycles) + { + shmem.ctrl.spdif_ch1_channel_data_tx_cycles--; + return static_cast(shmem.ctrl.spdif_ch1_channel_data_hi) << 32 | shmem.ctrl.spdif_ch1_channel_data_lo; + } + } + + return std::nullopt; + } +} + error_code sys_rsxaudio_initialize(vm::ptr handle) { - // Creates a lv2 object for rsxaudio, returns handle - sys_rsxaudio.todo("sys_rsxaudio_initialize(handle=*0x%x)", handle); - *handle = 0xcacad0d0; + sys_rsxaudio.trace("sys_rsxaudio_initialize(handle=*0x%x)", handle); + + auto& rsxaudio_thread = g_fxo->get(); + + if (rsxaudio_thread.rsxaudio_ctx_allocated.test_and_set()) + { + return CELL_EINVAL; + } + + if (!vm::check_addr(handle.addr(), vm::page_writable, sizeof(u32))) + { + rsxaudio_thread.rsxaudio_ctx_allocated = false; + return CELL_EFAULT; + } + + const u32 id = idm::make(); + + if (!id) + { + rsxaudio_thread.rsxaudio_ctx_allocated = false; + return CELL_ENOMEM; + } + + const auto rsxaudio_obj = idm::get(id); + std::lock_guard lock(rsxaudio_obj->mutex); + + rsxaudio_obj->shmem = vm::addr_t{vm::alloc(sizeof(rsxaudio_shmem), vm::main)}; + + if (!rsxaudio_obj->shmem) + { + idm::remove(id); + rsxaudio_thread.rsxaudio_ctx_allocated = false; + return CELL_ENOMEM; + } + + rsxaudio_obj->page_lock(); + + rsxaudio_shmem* sh_page = rsxaudio_obj->get_rw_shared_page(); + sh_page->ctrl = {}; + + for (auto& uf : sh_page->ctrl.channel_uf) + { + uf.uf_event_cnt = 0; + uf.unk1 = 0; + } + + sh_page->ctrl.unk4 = 0x8000; + sh_page->ctrl.intr_thread_prio = 0xDEADBEEF; + sh_page->ctrl.unk5 = 0; + + rsxaudio_obj->init = true; + *handle = id; return CELL_OK; } error_code sys_rsxaudio_finalize(u32 handle) { - sys_rsxaudio.todo("sys_rsxaudio_finalize(handle=0x%x)", handle); + sys_rsxaudio.trace("sys_rsxaudio_finalize(handle=0x%x)", handle); + + const auto rsxaudio_obj = idm::get(handle); + + if (!rsxaudio_obj) + { + return CELL_ESRCH; + } + + std::lock_guard lock(rsxaudio_obj->mutex); + + if (!rsxaudio_obj->init) + { + return CELL_ESRCH; + } + + auto& rsxaudio_thread = g_fxo->get(); + + { + std::lock_guard ra_obj_lock{rsxaudio_thread.rsxaudio_obj_upd_m}; + rsxaudio_thread.rsxaudio_obj_ptr = {}; + } + + rsxaudio_obj->init = false; + vm::dealloc(rsxaudio_obj->shmem, vm::main); + for (const auto port : rsxaudio_obj->event_port) + { + idm::remove(port); + } + + idm::remove(handle); + rsxaudio_thread.rsxaudio_ctx_allocated = false; return CELL_OK; } error_code sys_rsxaudio_import_shared_memory(u32 handle, vm::ptr addr) { - sys_rsxaudio.todo("sys_rsxaudio_import_shared_memory(handle=0x%x, addr=*0x%x)", handle, addr); + sys_rsxaudio.trace("sys_rsxaudio_import_shared_memory(handle=0x%x, addr=*0x%x)", handle, addr); - *addr = vm::alloc(0x40000, vm::main); + const auto rsxaudio_obj = idm::get(handle); + + if (!rsxaudio_obj) + { + return CELL_ESRCH; + } + + std::lock_guard lock(rsxaudio_obj->mutex); + + if (!rsxaudio_obj->init) + { + return CELL_ESRCH; + } + + if (!vm::check_addr(addr.addr(), vm::page_writable, sizeof(u64))) + { + return CELL_EFAULT; + } + + *addr = rsxaudio_obj->shmem; + rsxaudio_obj->page_unlock(); return CELL_OK; } -error_code sys_rsxaudio_unimport_shared_memory(u32 handle, vm::ptr addr) +error_code sys_rsxaudio_unimport_shared_memory(u32 handle, vm::ptr addr /* unused */) { - sys_rsxaudio.todo("sys_rsxaudio_unimport_shared_memory(handle=0x%x, addr=*0x%x)", handle, addr); + sys_rsxaudio.trace("sys_rsxaudio_unimport_shared_memory(handle=0x%x, addr=*0x%x)", handle, addr); + + const auto rsxaudio_obj = idm::get(handle); + + if (!rsxaudio_obj) + { + return CELL_ESRCH; + } + + std::lock_guard lock(rsxaudio_obj->mutex); + + if (!rsxaudio_obj->init) + { + return CELL_ESRCH; + } + + rsxaudio_obj->page_lock(); return CELL_OK; } + +error_code sys_rsxaudio_create_connection(u32 handle) +{ + sys_rsxaudio.trace("sys_rsxaudio_create_connection(handle=0x%x)", handle); + + const auto rsxaudio_obj = idm::get(handle); + + if (!rsxaudio_obj) + { + return CELL_ESRCH; + } + + std::lock_guard lock(rsxaudio_obj->mutex); + + if (!rsxaudio_obj->init) + { + return CELL_ESRCH; + } + + rsxaudio_shmem* sh_page = rsxaudio_obj->get_rw_shared_page(); + + const error_code port_create_status = [&]() -> error_code + { + if (auto queue1 = idm::get(sh_page->ctrl.event_queue_1_id)) + { + rsxaudio_obj->event_queue[0] = queue1; + + if (auto queue2 = idm::get(sh_page->ctrl.event_queue_2_id)) + { + rsxaudio_obj->event_queue[1] = queue2; + + if (auto queue3 = idm::get(sh_page->ctrl.event_queue_3_id)) + { + rsxaudio_obj->event_queue[2] = queue3; + + if (auto port1 = idm::make(SYS_EVENT_PORT_LOCAL, 0)) + { + idm::remove(rsxaudio_obj->event_port[0]); + rsxaudio_obj->event_port[0] = port1; + + if (auto port2 = idm::make(SYS_EVENT_PORT_LOCAL, 0)) + { + idm::remove(rsxaudio_obj->event_port[1]); + rsxaudio_obj->event_port[1] = port2; + + if (auto port3 = idm::make(SYS_EVENT_PORT_LOCAL, 0)) + { + idm::remove(rsxaudio_obj->event_port[2]); + rsxaudio_obj->event_port[2] = port3; + + return CELL_OK; + } + + idm::remove(port2); + rsxaudio_obj->event_port[1] = 0; + } + + idm::remove(port1); + rsxaudio_obj->event_port[0] = 0; + } + + return CELL_OK; + } + } + } + + return CELL_ESRCH; + }(); + + if (port_create_status != CELL_OK) + { + return port_create_status; + } + + for (auto& rb : sh_page->ctrl.ringbuf) + { + rb.dma_silence_addr = rsxaudio_obj->dma_io_base + offsetof(rsxaudio_shmem, dma_silence_region); + rb.unk2 = 100; + } + + for (u32 entry_idx = 0; entry_idx < SYS_RSXAUDIO_RINGBUF_SZ; entry_idx++) + { + sh_page->ctrl.ringbuf[static_cast(RsxaudioPort::SERIAL)].entries[entry_idx].dma_addr = rsxaudio_obj->dma_io_base + u32{offsetof(rsxaudio_shmem, dma_serial_region)} + SYS_RSXAUDIO_RINGBUF_BLK_SZ_SERIAL * entry_idx; + sh_page->ctrl.ringbuf[static_cast(RsxaudioPort::SPDIF_0)].entries[entry_idx].dma_addr = rsxaudio_obj->dma_io_base + u32{offsetof(rsxaudio_shmem, dma_spdif_0_region)} + SYS_RSXAUDIO_RINGBUF_BLK_SZ_SPDIF * entry_idx; + sh_page->ctrl.ringbuf[static_cast(RsxaudioPort::SPDIF_1)].entries[entry_idx].dma_addr = rsxaudio_obj->dma_io_base + u32{offsetof(rsxaudio_shmem, dma_spdif_1_region)} + SYS_RSXAUDIO_RINGBUF_BLK_SZ_SPDIF * entry_idx; + } + + return CELL_OK; +} + +error_code sys_rsxaudio_close_connection(u32 handle) +{ + sys_rsxaudio.trace("sys_rsxaudio_close_connection(handle=0x%x)", handle); + + const auto rsxaudio_obj = idm::get(handle); + + if (!rsxaudio_obj) + { + return CELL_ESRCH; + } + + std::lock_guard lock(rsxaudio_obj->mutex); + + if (!rsxaudio_obj->init) + { + return CELL_ESRCH; + } + + { + auto& rsxaudio_thread = g_fxo->get(); + std::lock_guard ra_obj_lock{rsxaudio_thread.rsxaudio_obj_upd_m}; + rsxaudio_thread.rsxaudio_obj_ptr = {}; + } + + for (u32 q_idx = 0; q_idx < SYS_RSXAUDIO_PORT_CNT; q_idx++) + { + idm::remove(rsxaudio_obj->event_port[q_idx]); + rsxaudio_obj->event_port[q_idx] = 0; + rsxaudio_obj->event_queue[q_idx].reset(); + } + + return CELL_OK; +} + +error_code sys_rsxaudio_prepare_process(u32 handle) +{ + sys_rsxaudio.trace("sys_rsxaudio_prepare_process(handle=0x%x)", handle); + + const auto rsxaudio_obj = idm::get(handle); + + if (!rsxaudio_obj) + { + return CELL_ESRCH; + } + + std::lock_guard lock(rsxaudio_obj->mutex); + + if (!rsxaudio_obj->init) + { + return CELL_ESRCH; + } + + auto& rsxaudio_thread = g_fxo->get(); + std::lock_guard ra_obj_lock{rsxaudio_thread.rsxaudio_obj_upd_m}; + + if (rsxaudio_thread.rsxaudio_obj_ptr) + { + return -1; + } + + rsxaudio_thread.rsxaudio_obj_ptr = rsxaudio_obj; + + return CELL_OK; +} + +error_code sys_rsxaudio_start_process(u32 handle) +{ + sys_rsxaudio.trace("sys_rsxaudio_start_process(handle=0x%x)", handle); + + const auto rsxaudio_obj = idm::get(handle); + + if (!rsxaudio_obj) + { + return CELL_ESRCH; + } + + std::lock_guard lock(rsxaudio_obj->mutex); + + if (!rsxaudio_obj->init) + { + return CELL_ESRCH; + } + + rsxaudio_shmem* sh_page = rsxaudio_obj->get_rw_shared_page(); + + for (auto& rb : sh_page->ctrl.ringbuf) + { + if (rb.active) rsxaudio_ringbuf_reader::clean_buf(rb); + } + + for (auto& uf : sh_page->ctrl.channel_uf) + { + uf.uf_event_cnt = 0; + uf.unk1 = 0; + } + + auto& rsxaudio_thread = g_fxo->get(); + rsxaudio_thread.update_hw_param([&](auto& param) + { + if (sh_page->ctrl.ringbuf[static_cast(RsxaudioPort::SERIAL)].active) param.serial.dma_en = true; + if (sh_page->ctrl.ringbuf[static_cast(RsxaudioPort::SPDIF_0)].active) param.spdif[0].dma_en = true; + if (sh_page->ctrl.ringbuf[static_cast(RsxaudioPort::SPDIF_1)].active) param.spdif[1].dma_en = true; + }); + + for (u32 q_idx = 0; q_idx < SYS_RSXAUDIO_PORT_CNT; q_idx++) + { + if (auto queue = rsxaudio_obj->event_queue[q_idx].lock(); rsxaudio_obj->event_port[q_idx] && sh_page->ctrl.ringbuf[q_idx].active) + { + queue->send(s64{process_getpid()} << 32 | u64{rsxaudio_obj->event_port[q_idx]}, q_idx, 0, 0); + } + } + + return CELL_OK; +} + +error_code sys_rsxaudio_stop_process(u32 handle) +{ + sys_rsxaudio.trace("sys_rsxaudio_stop_process(handle=0x%x)", handle); + + const auto rsxaudio_obj = idm::get(handle); + + if (!rsxaudio_obj) + { + return CELL_ESRCH; + } + + std::lock_guard lock(rsxaudio_obj->mutex); + + if (!rsxaudio_obj->init) + { + return CELL_ESRCH; + } + + auto& rsxaudio_thread = g_fxo->get(); + + rsxaudio_thread.update_hw_param([&](auto& param) + { + param.serial.dma_en = false; + param.serial.muted = true; + param.serial.en = false; + + for (auto& spdif : param.spdif) + { + spdif.dma_en = false; + if (!spdif.use_serial_buf) + { + spdif.en = false; + } + } + + param.spdif[1].muted = true; + }); + + rsxaudio_shmem* sh_page = rsxaudio_obj->get_rw_shared_page(); + + for (auto& rb : sh_page->ctrl.ringbuf) + { + if (rb.active) rsxaudio_ringbuf_reader::clean_buf(rb); + } + + return CELL_OK; +} + +error_code sys_rsxaudio_get_dma_param(u32 handle, u32 flag, vm::ptr out) +{ + sys_rsxaudio.trace("sys_rsxaudio_get_dma_param(handle=0x%x, flag=0x%x, out=0x%x)", handle, flag, out); + + const auto rsxaudio_obj = idm::get(handle); + + if (!rsxaudio_obj) + { + return CELL_ESRCH; + } + + std::lock_guard lock(rsxaudio_obj->mutex); + + if (!rsxaudio_obj->init) + { + return CELL_ESRCH; + } + + if (!vm::check_addr(out.addr(), vm::page_writable, sizeof(u64))) + { + return CELL_EFAULT; + } + + if (flag == rsxaudio_dma_flag::IO_ID) + { + *out = rsxaudio_obj->dma_io_id; + } + else if (flag == rsxaudio_dma_flag::IO_BASE) + { + *out = rsxaudio_obj->dma_io_base; + } + + return CELL_OK; +} + +rsxaudio_data_container::rsxaudio_data_container(const rsxaudio_hw_param_t& hw_param, const buf_t& buf, bool serial_rdy, bool spdif_0_rdy, bool spdif_1_rdy) : hwp(hw_param), out_buf(buf) +{ + if (serial_rdy) + { + avport_data_avail[static_cast(RsxaudioAvportIdx::AVMULTI)] = true; + + if (hwp.spdif[0].use_serial_buf) + { + avport_data_avail[static_cast(RsxaudioAvportIdx::SPDIF_0)] = true; + } + + if (hwp.spdif[1].use_serial_buf) + { + avport_data_avail[static_cast(RsxaudioAvportIdx::SPDIF_1)] = true; + } + } + + if (spdif_0_rdy && !hwp.spdif[0].use_serial_buf) + { + avport_data_avail[static_cast(RsxaudioAvportIdx::SPDIF_0)] = true; + } + + if (spdif_1_rdy && !hwp.spdif[1].use_serial_buf) + { + avport_data_avail[static_cast(RsxaudioAvportIdx::SPDIF_1)] = true; + } + + if (hwp.hdmi[0].init) + { + if (hwp.hdmi[0].use_spdif_1) + { + avport_data_avail[static_cast(RsxaudioAvportIdx::HDMI_0)] = avport_data_avail[static_cast(RsxaudioAvportIdx::SPDIF_1)]; + } + else + { + avport_data_avail[static_cast(RsxaudioAvportIdx::HDMI_0)] = serial_rdy; + } + + hdmi_stream_cnt[0] = static_cast(hwp.hdmi[0].ch_cfg.total_ch_cnt) / SYS_RSXAUDIO_CH_PER_STREAM; + } + + if (hwp.hdmi[1].init) + { + if (hwp.hdmi[1].use_spdif_1) + { + avport_data_avail[static_cast(RsxaudioAvportIdx::HDMI_1)] = avport_data_avail[static_cast(RsxaudioAvportIdx::SPDIF_1)]; + } + else + { + avport_data_avail[static_cast(RsxaudioAvportIdx::HDMI_1)] = serial_rdy; + } + + hdmi_stream_cnt[1] = static_cast(hwp.hdmi[1].ch_cfg.total_ch_cnt) / SYS_RSXAUDIO_CH_PER_STREAM; + } +} + +u32 rsxaudio_data_container::get_data_size(RsxaudioAvportIdx avport) +{ + if (!avport_data_avail[static_cast(avport)]) + { + return 0; + } + + switch (avport) + { + case RsxaudioAvportIdx::HDMI_0: + { + const RsxaudioSampleSize depth = hwp.hdmi[0].use_spdif_1 ? hwp.spdif[1].depth : hwp.serial.depth; + + return (depth == RsxaudioSampleSize::_16BIT ? SYS_RSXAUDIO_STREAM_SIZE * 2 : SYS_RSXAUDIO_STREAM_SIZE) * hdmi_stream_cnt[0]; + } + case RsxaudioAvportIdx::HDMI_1: + { + const RsxaudioSampleSize depth = hwp.hdmi[1].use_spdif_1 ? hwp.spdif[1].depth : hwp.serial.depth; + + return (depth == RsxaudioSampleSize::_16BIT ? SYS_RSXAUDIO_STREAM_SIZE * 2 : SYS_RSXAUDIO_STREAM_SIZE) * hdmi_stream_cnt[1]; + } + case RsxaudioAvportIdx::AVMULTI: + { + return hwp.serial.depth == RsxaudioSampleSize::_16BIT ? SYS_RSXAUDIO_STREAM_SIZE * 2 : SYS_RSXAUDIO_STREAM_SIZE; + } + case RsxaudioAvportIdx::SPDIF_0: + { + const RsxaudioSampleSize depth = hwp.spdif[0].use_serial_buf ? hwp.serial.depth : hwp.spdif[0].depth; + + return depth == RsxaudioSampleSize::_16BIT ? SYS_RSXAUDIO_STREAM_SIZE * 2 : SYS_RSXAUDIO_STREAM_SIZE; + } + case RsxaudioAvportIdx::SPDIF_1: + { + const RsxaudioSampleSize depth = hwp.spdif[1].use_serial_buf ? hwp.serial.depth : hwp.spdif[1].depth; + + return depth == RsxaudioSampleSize::_16BIT ? SYS_RSXAUDIO_STREAM_SIZE * 2 : SYS_RSXAUDIO_STREAM_SIZE; + } + default: + { + return 0; + } + } +} + +void rsxaudio_data_container::get_data(RsxaudioAvportIdx avport, data_blk_t& data_out) +{ + if (!avport_data_avail[static_cast(avport)]) + { + return; + } + + data_was_written = true; + + auto spdif_filter_map = [&](u8 hdmi_idx) + { + std::array result; + + for (u64 i = 0; i < SYS_RSXAUDIO_SERIAL_MAX_CH; i++) + { + const u8 old_val = hwp.hdmi[hdmi_idx].ch_cfg.map[i]; + result[i] = old_val >= SYS_RSXAUDIO_SPDIF_MAX_CH ? rsxaudio_hw_param_t::hdmi_param_t::MAP_SILENT_CH : old_val; + } + + return result; + }; + + switch (avport) + { + case RsxaudioAvportIdx::HDMI_0: + case RsxaudioAvportIdx::HDMI_1: + { + const u8 hdmi_idx = avport == RsxaudioAvportIdx::HDMI_1; + + switch (hdmi_stream_cnt[hdmi_idx]) + { + default: + case 0: + { + return; + } + case 1: + { + if (hwp.hdmi[hdmi_idx].use_spdif_1) + { + if (hwp.spdif[1].use_serial_buf) + { + mix<2>(spdif_filter_map(hdmi_idx), hwp.serial.depth, out_buf.serial, data_out); + } + else + { + mix<2>(hwp.hdmi[hdmi_idx].ch_cfg.map, hwp.spdif[1].depth, out_buf.spdif[1], data_out); + } + } + else + { + mix<2>(hwp.hdmi[hdmi_idx].ch_cfg.map, hwp.serial.depth, out_buf.serial, data_out); + } + break; + } + case 3: + { + if (hwp.hdmi[hdmi_idx].use_spdif_1) + { + if (hwp.spdif[1].use_serial_buf) + { + mix<6>(spdif_filter_map(hdmi_idx), hwp.serial.depth, out_buf.serial, data_out); + } + else + { + mix<6>(hwp.hdmi[hdmi_idx].ch_cfg.map, hwp.spdif[1].depth, out_buf.spdif[1], data_out); + } + } + else + { + mix<6>(hwp.hdmi[hdmi_idx].ch_cfg.map, hwp.serial.depth, out_buf.serial, data_out); + } + break; + } + case 4: + { + if (hwp.hdmi[hdmi_idx].use_spdif_1) + { + if (hwp.spdif[1].use_serial_buf) + { + mix<8>(spdif_filter_map(hdmi_idx), hwp.serial.depth, out_buf.serial, data_out); + } + else + { + mix<8>(hwp.hdmi[hdmi_idx].ch_cfg.map, hwp.spdif[1].depth, out_buf.spdif[1], data_out); + } + } + else + { + mix<8>(hwp.hdmi[hdmi_idx].ch_cfg.map, hwp.serial.depth, out_buf.serial, data_out); + } + break; + } + } + + break; + } + case RsxaudioAvportIdx::AVMULTI: + { + mix<2>({2, 3}, hwp.serial.depth, out_buf.serial, data_out); + break; + } + case RsxaudioAvportIdx::SPDIF_0: + case RsxaudioAvportIdx::SPDIF_1: + { + const u8 spdif_idx = avport == RsxaudioAvportIdx::SPDIF_1; + + if (hwp.spdif[spdif_idx].use_serial_buf) + { + mix<2>({0, 1}, hwp.serial.depth, out_buf.serial, data_out); + } + else + { + mix<2>({0, 1}, hwp.spdif[spdif_idx].depth, out_buf.spdif[spdif_idx], data_out); + } + break; + } + default: + { + return; + } + } +} + +bool rsxaudio_data_container::data_was_used() +{ + return data_was_written; +} + +rsxaudio_data_thread::rsxaudio_data_thread() {} + +void rsxaudio_data_thread::operator()() +{ + thread_ctrl::scoped_priority high_prio(+1); + + while (thread_ctrl::state() != thread_state::aborting) + { + static const std::function tmr_callback = [this]() { extract_audio_data(); }; + + switch (timer.wait(tmr_callback)) + { + case rsxaudio_periodic_tmr::wait_result::SUCCESS: + case rsxaudio_periodic_tmr::wait_result::TIMEOUT: + case rsxaudio_periodic_tmr::wait_result::TIMER_CANCELED: + { + continue; + } + case rsxaudio_periodic_tmr::wait_result::INVALID_PARAM: + case rsxaudio_periodic_tmr::wait_result::TIMER_ERROR: + default: + { + fmt::throw_exception("rsxaudio_periodic_tmr::wait() failed"); + } + } + } +} + +rsxaudio_data_thread& rsxaudio_data_thread::operator=(thread_state /* state */) +{ + timer.cancel_wait(); + return *this; +} + +void rsxaudio_data_thread::advance_all_timers() +{ + const u64 crnt_time = get_system_time(); + + timer.vtimer_skip_periods(static_cast(RsxaudioPort::SERIAL), crnt_time); + timer.vtimer_skip_periods(static_cast(RsxaudioPort::SPDIF_0), crnt_time); + timer.vtimer_skip_periods(static_cast(RsxaudioPort::SPDIF_1), crnt_time); +} + +void rsxaudio_data_thread::extract_audio_data() +{ + // Accessing timer state is safe here, since we're in timer::wait() + + const auto rsxaudio_obj = [&]() + { + std::lock_guard ra_obj_lock{rsxaudio_obj_upd_m}; + return rsxaudio_obj_ptr; + }(); + + if (Emu.IsPaused() || !rsxaudio_obj) + { + advance_all_timers(); + return; + } + + std::lock_guard rsxaudio_lock(rsxaudio_obj->mutex); + + if (!rsxaudio_obj->init) + { + advance_all_timers(); + return; + } + + rsxaudio_shmem* sh_page = rsxaudio_obj->get_rw_shared_page(); + const auto hw_cfg = hw_param_ts.get_current(); + const u64 crnt_time = get_system_time(); + + auto process_rb = [&](RsxaudioPort dst, bool dma_en) + { + // SPDIF channel data and underflow events are always disabled by lv1 + + const u32 dst_raw = static_cast(dst); + rsxaudio_ringbuf_reader::set_timestamp(sh_page->ctrl.ringbuf[dst_raw], timer.vtimer_get_sched_time(dst_raw)); + + const auto [data_present, rb_addr] = get_ringbuf_addr(dst, *rsxaudio_obj); + bool reset_periods = !enqueue_data(dst, rb_addr == nullptr, rb_addr, *hw_cfg); + + if (dma_en) + { + if (const auto [notify, blk_idx, timestamp] = rsxaudio_ringbuf_reader::update_status(sh_page->ctrl.ringbuf[dst_raw]); notify) + { + // Too late to recover + reset_periods = true; + + if (auto queue = rsxaudio_obj->event_queue[dst_raw].lock(); rsxaudio_obj->event_port[dst_raw]) + { + queue->send(s64{process_getpid()} << 32 | u64{rsxaudio_obj->event_port[dst_raw]}, dst_raw, blk_idx, timestamp); + } + } + } + + if (reset_periods) + { + timer.vtimer_skip_periods(dst_raw, crnt_time); + } + else + { + timer.vtimer_incr(dst_raw, crnt_time); + } + }; + + if (timer.is_vtimer_behind(static_cast(RsxaudioPort::SERIAL), crnt_time)) + { + process_rb(RsxaudioPort::SERIAL, hw_cfg->serial.dma_en); + } + + if (timer.is_vtimer_behind(static_cast(RsxaudioPort::SPDIF_0), crnt_time)) + { + process_rb(RsxaudioPort::SPDIF_0, hw_cfg->spdif[0].dma_en); + } + + if (timer.is_vtimer_behind(static_cast(RsxaudioPort::SPDIF_1), crnt_time)) + { + process_rb(RsxaudioPort::SPDIF_1, hw_cfg->spdif[1].dma_en); + } +} + +std::pair rsxaudio_data_thread::get_ringbuf_addr(RsxaudioPort dst, const lv2_rsxaudio& rsxaudio_obj) +{ + ensure(dst <= RsxaudioPort::SPDIF_1); + + rsxaudio_shmem* sh_page = rsxaudio_obj.get_rw_shared_page(); + const auto [data_present, addr] = rsxaudio_ringbuf_reader::get_addr(sh_page->ctrl.ringbuf[static_cast(dst)]); + const u32 buf_size = dst == RsxaudioPort::SERIAL ? SYS_RSXAUDIO_RINGBUF_BLK_SZ_SERIAL : SYS_RSXAUDIO_RINGBUF_BLK_SZ_SPDIF; + + if (addr >= rsxaudio_obj.dma_io_base && addr < rsxaudio_obj.dma_io_base + sizeof(rsxaudio_shmem) - buf_size) + { + return std::make_pair(data_present, reinterpret_cast(rsxaudio_obj.get_rw_shared_page()) + addr - rsxaudio_obj.dma_io_base); + } + + // Buffer address is invalid + return std::make_pair(false, nullptr); +} + +void rsxaudio_data_thread::reset_hw() +{ + update_hw_param([&](rsxaudio_hw_param_t& current) + { + const bool serial_dma_en = current.serial.dma_en; + current.serial = {}; + current.serial.dma_en = serial_dma_en; + + for (auto& spdif : current.spdif) + { + const bool spdif_dma_en = spdif.dma_en; + spdif = {}; + spdif.dma_en = spdif_dma_en; + } + + current.serial_freq_base = SYS_RSXAUDIO_FREQ_BASE_384K; + current.spdif_freq_base = SYS_RSXAUDIO_FREQ_BASE_352K; + current.avport_src.fill(RsxaudioPort::INVALID); + }); +} + +void rsxaudio_data_thread::update_hw_param(std::function update_callback) +{ + ensure(update_callback); + + hw_param_ts.add_op([&]() + { + auto new_hw_param = std::make_shared(*hw_param_ts.get_current()); + + update_callback(*new_hw_param); + + const bool serial_active = calc_port_active_state(RsxaudioPort::SERIAL, *new_hw_param); + const bool spdif_active[SYS_RSXAUDIO_SPDIF_CNT] = + { + calc_port_active_state(RsxaudioPort::SPDIF_0, *new_hw_param), + calc_port_active_state(RsxaudioPort::SPDIF_1, *new_hw_param) + }; + + std::array port_cfg{}; + port_cfg[static_cast(RsxaudioAvportIdx::AVMULTI)] = {static_cast(new_hw_param->serial_freq_base / new_hw_param->serial.freq_div), AudioChannelCnt::STEREO}; + + auto gen_spdif_port_cfg = [&](u8 spdif_idx) + { + if (new_hw_param->spdif[spdif_idx].use_serial_buf) + { + return port_cfg[static_cast(RsxaudioAvportIdx::AVMULTI)]; + } + else + { + return rsxaudio_backend_thread::port_config{static_cast(new_hw_param->spdif_freq_base / new_hw_param->spdif[spdif_idx].freq_div), AudioChannelCnt::STEREO}; + } + }; + + port_cfg[static_cast(RsxaudioAvportIdx::SPDIF_0)] = gen_spdif_port_cfg(0); + port_cfg[static_cast(RsxaudioAvportIdx::SPDIF_1)] = gen_spdif_port_cfg(1); + + auto gen_hdmi_port_cfg = [&](u8 hdmi_idx) + { + if (new_hw_param->hdmi[hdmi_idx].use_spdif_1) + { + return rsxaudio_backend_thread::port_config{port_cfg[static_cast(RsxaudioAvportIdx::SPDIF_1)].freq, new_hw_param->hdmi[hdmi_idx].ch_cfg.total_ch_cnt}; + } + else + { + return rsxaudio_backend_thread::port_config{port_cfg[static_cast(RsxaudioAvportIdx::AVMULTI)].freq, new_hw_param->hdmi[hdmi_idx].ch_cfg.total_ch_cnt}; + } + }; + + port_cfg[static_cast(RsxaudioAvportIdx::HDMI_0)] = gen_hdmi_port_cfg(0); + port_cfg[static_cast(RsxaudioAvportIdx::HDMI_1)] = gen_hdmi_port_cfg(1); + // TODO: ideally, old data must be flushed from backend buffers if channel became inactive or its src changed + g_fxo->get().set_new_stream_param(port_cfg, calc_avport_mute_state(*new_hw_param)); + + timer.vtimer_access_sec([&]() + { + const u64 crnt_time = get_system_time(); + + if (serial_active) + { + // 2 channels per stream, streams go in parallel + const u32 new_timer_rate = static_cast(port_cfg[static_cast(RsxaudioAvportIdx::AVMULTI)].freq) * + static_cast(new_hw_param->serial.depth) * + SYS_RSXAUDIO_CH_PER_STREAM; + + timer.enable_vtimer(static_cast(RsxaudioPort::SERIAL), new_timer_rate, crnt_time); + } + else + { + timer.disable_vtimer(static_cast(RsxaudioPort::SERIAL)); + } + + for (u8 spdif_idx = 0; spdif_idx < SYS_RSXAUDIO_SPDIF_CNT; spdif_idx++) + { + const u32 vtimer_id = static_cast(RsxaudioPort::SPDIF_0) + spdif_idx; + + if (spdif_active[spdif_idx] && !new_hw_param->spdif[spdif_idx].use_serial_buf) + { + // 2 channels per stream, single stream + const u32 new_timer_rate = static_cast(port_cfg[static_cast(RsxaudioAvportIdx::SPDIF_0) + spdif_idx].freq) * + static_cast(new_hw_param->spdif[spdif_idx].depth) * + SYS_RSXAUDIO_CH_PER_STREAM; + + timer.enable_vtimer(vtimer_id, new_timer_rate, crnt_time); + } + else + { + timer.disable_vtimer(vtimer_id); + } + } + }); + + return new_hw_param; + }); +} + +void rsxaudio_data_thread::update_mute_state(RsxaudioPort port, bool muted) +{ + hw_param_ts.add_op([&]() + { + auto new_hw_param = std::make_shared(*hw_param_ts.get_current()); + + switch (port) + { + case RsxaudioPort::SERIAL: + { + new_hw_param->serial.muted = muted; + break; + } + case RsxaudioPort::SPDIF_0: + { + new_hw_param->spdif[0].muted = muted; + break; + } + case RsxaudioPort::SPDIF_1: + { + new_hw_param->spdif[1].muted = muted; + break; + } + default: + { + fmt::throw_exception("Invalid RSXAudio port: %u", static_cast(port)); + } + } + + g_fxo->get().set_mute_state(calc_avport_mute_state(*new_hw_param)); + + return new_hw_param; + }); +} + +void rsxaudio_data_thread::update_av_mute_state(RsxaudioAvportIdx avport, bool muted, bool force_mute, bool set) +{ + hw_param_ts.add_op([&]() + { + auto new_hw_param = std::make_shared(*hw_param_ts.get_current()); + + switch (avport) + { + case RsxaudioAvportIdx::HDMI_0: + case RsxaudioAvportIdx::HDMI_1: + { + const u32 hdmi_idx = avport == RsxaudioAvportIdx::HDMI_1; + + if (muted) + { + new_hw_param->hdmi[hdmi_idx].muted = set; + } + + if (force_mute) + { + new_hw_param->hdmi[hdmi_idx].force_mute = set; + } + break; + } + case RsxaudioAvportIdx::AVMULTI: + { + if (muted) + { + new_hw_param->avmulti_av_muted = set; + } + break; + } + default: + { + fmt::throw_exception("Invalid RSXAudio avport: %u", static_cast(avport)); + } + } + + g_fxo->get().set_mute_state(calc_avport_mute_state(*new_hw_param)); + + return new_hw_param; + }); +} + +rsxaudio_backend_thread::avport_bit rsxaudio_data_thread::calc_avport_mute_state(const rsxaudio_hw_param_t& hwp) +{ + const bool serial_active = calc_port_active_state(RsxaudioPort::SERIAL, hwp); + + const bool spdif_active[SYS_RSXAUDIO_SPDIF_CNT] = + { + calc_port_active_state(RsxaudioPort::SPDIF_0, hwp), + calc_port_active_state(RsxaudioPort::SPDIF_1, hwp) + }; + + const bool avmulti = !serial_active || hwp.serial.muted || hwp.avmulti_av_muted; + + auto spdif_muted = [&](u8 spdif_idx) + { + const u8 spdif_port = spdif_idx == 1; + + if (hwp.spdif[spdif_port].use_serial_buf) + { + // TODO: HW test if both serial and spdif mutes are used in serial mode for spdif + return !serial_active || hwp.spdif[spdif_port].freq_div != hwp.serial.freq_div || hwp.serial.muted || hwp.spdif[spdif_port].muted; + } + else + { + return !spdif_active[spdif_port] || hwp.spdif[spdif_port].muted; + } + }; + + auto hdmi_muted = [&](u8 hdmi_idx) + { + const u8 hdmi_port = hdmi_idx == 1; + + if (hwp.hdmi[hdmi_idx].use_spdif_1) + { + return spdif_muted(1) || hwp.hdmi[hdmi_port].muted || hwp.hdmi[hdmi_port].force_mute || !hwp.hdmi[hdmi_port].init; + } + else + { + return !serial_active || hwp.serial.muted || hwp.hdmi[hdmi_port].muted || hwp.hdmi[hdmi_port].force_mute || !hwp.hdmi[hdmi_port].init; + } + }; + + return { hdmi_muted(0), hdmi_muted(1), avmulti, spdif_muted(0), spdif_muted(1) }; +} + +bool rsxaudio_data_thread::calc_port_active_state(RsxaudioPort port, const rsxaudio_hw_param_t& hwp) +{ + auto gen_serial_active = [&]() + { + return hwp.serial.dma_en && hwp.serial.buf_empty_en && hwp.serial.en; + }; + + auto gen_spdif_active = [&](u8 spdif_idx) + { + if (hwp.spdif[spdif_idx].use_serial_buf) + { + return gen_serial_active() && (hwp.spdif[spdif_idx].freq_div == hwp.serial.freq_div); + } + else + { + return hwp.spdif[spdif_idx].dma_en && hwp.spdif[spdif_idx].buf_empty_en && hwp.spdif[spdif_idx].en; + } + }; + + switch (port) + { + case RsxaudioPort::SERIAL: + { + return gen_serial_active(); + } + case RsxaudioPort::SPDIF_0: + { + return gen_spdif_active(0); + } + case RsxaudioPort::SPDIF_1: + { + return gen_spdif_active(1); + } + default: + { + return false; + } + } +} + +f32 rsxaudio_data_thread::pcm_to_float(s32 sample) +{ + return sample * (1.0f / 2147483648.0f); +} + +f32 rsxaudio_data_thread::pcm_to_float(s16 sample) +{ + return sample * (1.0f / 32768.0f); +} + +void rsxaudio_data_thread::pcm_serial_process_channel(RsxaudioSampleSize word_bits, ra_stream_blk_t& buf_out_l, ra_stream_blk_t& buf_out_r, const void* buf_in, u8 src_stream) +{ + const u8 input_word_sz = static_cast(word_bits); + u64 ch_dst = 0; + + for (u64 blk_idx = 0; blk_idx < SYS_RSXAUDIO_STREAM_DATA_BLK_CNT; blk_idx++) + { + for (u64 offset = 0; offset < SYS_RSXAUDIO_DATA_BLK_SIZE / 2; offset += input_word_sz, ch_dst++) + { + const u64 left_ch_src = (blk_idx * SYS_RSXAUDIO_STREAM_SIZE + src_stream * SYS_RSXAUDIO_DATA_BLK_SIZE + offset) / input_word_sz; + const u64 right_ch_src = left_ch_src + (SYS_RSXAUDIO_DATA_BLK_SIZE / 2) / input_word_sz; + + if (word_bits == RsxaudioSampleSize::_16BIT) + { + buf_out_l[ch_dst] = pcm_to_float(static_cast*>(buf_in)[left_ch_src]); + buf_out_r[ch_dst] = pcm_to_float(static_cast*>(buf_in)[right_ch_src]); + } + else + { + // Looks like rsx treats 20bit/24bit samples as 32bit ones + buf_out_l[ch_dst] = pcm_to_float(static_cast*>(buf_in)[left_ch_src]); + buf_out_r[ch_dst] = pcm_to_float(static_cast*>(buf_in)[right_ch_src]); + } + } + } +} + +void rsxaudio_data_thread::pcm_spdif_process_channel(RsxaudioSampleSize word_bits, ra_stream_blk_t& buf_out_l, ra_stream_blk_t& buf_out_r, const void* buf_in) +{ + const u8 input_word_sz = static_cast(word_bits); + + for (u64 offset = 0; offset < SYS_RSXAUDIO_RINGBUF_BLK_SZ_SPDIF / (input_word_sz * SYS_RSXAUDIO_SPDIF_MAX_CH); offset++) + { + const u64 left_ch_src = offset * SYS_RSXAUDIO_SPDIF_MAX_CH; + const u64 right_ch_src = left_ch_src + 1; + + if (word_bits == RsxaudioSampleSize::_16BIT) + { + buf_out_l[offset] = pcm_to_float(static_cast*>(buf_in)[left_ch_src]); + buf_out_r[offset] = pcm_to_float(static_cast*>(buf_in)[right_ch_src]); + } + else + { + // Looks like rsx treats 20bit/24bit samples as 32bit ones + buf_out_l[offset] = pcm_to_float(static_cast*>(buf_in)[left_ch_src]); + buf_out_r[offset] = pcm_to_float(static_cast*>(buf_in)[right_ch_src]); + } + } +} + +bool rsxaudio_data_thread::enqueue_data(RsxaudioPort dst, bool silence, const void* src_addr, const rsxaudio_hw_param_t& hwp) +{ + auto& backend_thread = g_fxo->get(); + + if (dst == RsxaudioPort::SERIAL) + { + if (!silence) + { + for (u8 stream_idx = 0; stream_idx < SYS_RSXAUDIO_SERIAL_STREAM_CNT; stream_idx++) + { + pcm_serial_process_channel(hwp.serial.depth, output_buf.serial[stream_idx * 2], output_buf.serial[stream_idx * 2 + 1], src_addr, stream_idx); + } + } + else + { + output_buf.serial.fill({}); + } + + rsxaudio_data_container cont{hwp, output_buf, true, false, false}; + backend_thread.add_data(cont); + return cont.data_was_used(); + } + else if (dst == RsxaudioPort::SPDIF_0) + { + if (!silence) + { + pcm_spdif_process_channel(hwp.spdif[0].depth, output_buf.spdif[0][0], output_buf.spdif[0][1], src_addr); + } + else + { + output_buf.spdif[0].fill({}); + } + + rsxaudio_data_container cont{hwp, output_buf, false, true, false}; + backend_thread.add_data(cont); + return cont.data_was_used(); + } + else if (dst == RsxaudioPort::SPDIF_1) + { + if (!silence) + { + pcm_spdif_process_channel(hwp.spdif[1].depth, output_buf.spdif[1][0], output_buf.spdif[1][1], src_addr); + } + else + { + output_buf.spdif[1].fill({}); + } + + rsxaudio_data_container cont{hwp, output_buf, false, false, true}; + backend_thread.add_data(cont); + return cont.data_was_used(); + } + + return false; +} + +namespace audio +{ + void configure_rsxaudio() + { + if (g_cfg.audio.provider == audio_provider::rsxaudio) + { + g_fxo->get().update_emu_cfg(); + } + } +} + +rsxaudio_backend_thread::rsxaudio_backend_thread() +{ + const u64 new_vol = g_cfg.audio.volume; + + callback_cfg.atomic_op([&](callback_config& val) + { + val.target_volume = static_cast(new_vol / 100.0 * callback_config::VOL_NOMINAL); + val.initial_volume = val.current_volume; + }); +} + +rsxaudio_backend_thread::~rsxaudio_backend_thread() +{ + if (backend) + { + backend->Close(); + backend->SetWriteCallback(nullptr); + backend->SetErrorCallback(nullptr); + backend = nullptr; + } +} + +void rsxaudio_backend_thread::update_emu_cfg() +{ + std::unique_lock lock(state_update_m); + const emu_audio_cfg _new_emu_cfg = get_emu_cfg(); + const u64 new_vol = g_cfg.audio.volume; + + callback_cfg.atomic_op([&](callback_config& val) + { + val.target_volume = static_cast(new_vol / 100.0 * callback_config::VOL_NOMINAL); + val.initial_volume = val.current_volume; + }); + + if (new_emu_cfg != _new_emu_cfg) + { + new_emu_cfg = _new_emu_cfg; + emu_cfg_changed = true; + lock.unlock(); + state_update_c.notify_all(); + } +} + +rsxaudio_backend_thread::emu_audio_cfg rsxaudio_backend_thread::get_emu_cfg() +{ + const AudioChannelCnt out_ch_cnt = [&]() + { + switch (g_cfg.audio.audio_channel_downmix) + { + case audio_downmix::use_application_settings: + case audio_downmix::downmix_to_stereo: return AudioChannelCnt::STEREO; + case audio_downmix::downmix_to_5_1: return AudioChannelCnt::SURROUND_5_1; + case audio_downmix::no_downmix: return AudioChannelCnt::SURROUND_7_1; + default: + { + fmt::throw_exception("Unsupported downmix level: %u", static_cast(g_cfg.audio.audio_channel_downmix.get())); + } + } + }(); + + emu_audio_cfg cfg = + { + .desired_buffer_duration = g_cfg.audio.desired_buffer_duration, + .time_stretching_threshold = g_cfg.audio.time_stretching_threshold / 100.0, + .buffering_enabled = static_cast(g_cfg.audio.enable_buffering), + .convert_to_s16 = static_cast(g_cfg.audio.convert_to_s16), + .enable_time_stretching = static_cast(g_cfg.audio.enable_time_stretching), + .dump_to_file = static_cast(g_cfg.audio.dump_to_file), + .downmix = out_ch_cnt, + .renderer = g_cfg.audio.renderer, + .provider = g_cfg.audio.provider, + .avport = convert_avport(g_cfg.audio.rsxaudio_port) + }; + + cfg.buffering_enabled = cfg.buffering_enabled && cfg.renderer != audio_renderer::null; + cfg.enable_time_stretching = cfg.buffering_enabled && cfg.enable_time_stretching && cfg.time_stretching_threshold > 0.0; + + return cfg; +} + +void rsxaudio_backend_thread::operator()() +{ + if (g_cfg.audio.provider != audio_provider::rsxaudio) + { + return; + } + + static rsxaudio_state ra_state{}; + static emu_audio_cfg emu_cfg{}; + static bool backend_failed = false; + + for (;;) + { + bool should_update_backend = false; + bool reset_backend = false; + bool should_service_stream = false; + + { + std::unique_lock lock(state_update_m); + for (;;) + { + // Unsafe to access backend under lock (error_callback uses state_update_m -> possible deadlock) + + if (thread_ctrl::state() == thread_state::aborting) + { + lock.unlock(); + backend_stop(); + return; + } + + // Emulated state changed + if (ra_state_changed) + { + const callback_config cb_cfg = callback_cfg.observe(); + should_update_backend |= cb_cfg.cfg_changed; + ra_state_changed = false; + ra_state = new_ra_state; + + if (cb_cfg.cfg_changed) + { + callback_cfg.atomic_op([&](callback_config& val) + { + val.cfg_changed = false; // Acknowledge cfg update + }); + } + } + + // Update emu config + if (emu_cfg_changed) + { + reset_backend |= emu_cfg.renderer != new_emu_cfg.renderer; + emu_cfg_changed = false; + emu_cfg = new_emu_cfg; + should_update_backend = true; + } + + // Handle backend error notification + if (backend_error_occured) + { + reset_backend = true; + should_update_backend = true; + backend_error_occured = false; + } + + if (should_update_backend) + { + backend_current_cfg.cfg = ra_state.port[static_cast(emu_cfg.avport)]; + backend_current_cfg.avport = emu_cfg.avport; + break; + } + + if (backend_failed) + { + state_update_c.wait(state_update_m, ERROR_SERVICE_PERIOD); + break; + } + else if (use_aux_ringbuf) + { + const u64 next_period_time = get_time_until_service(); + should_service_stream = next_period_time <= SERVICE_THRESHOLD; + + if (should_service_stream) + { + break; + } + + state_update_c.wait(state_update_m, next_period_time); + } + else + { + // Nothing to do - wait for events + state_update_c.wait(state_update_m, umax); + } + } + } + + if (should_update_backend) + { + backend_init(ra_state, emu_cfg, reset_backend); + + if (emu_cfg.enable_time_stretching) + { + resampler.set_params(backend_current_cfg.cfg.ch_cnt, backend_current_cfg.cfg.freq); + resampler.set_tempo(RESAMPLER_MAX_FREQ_VAL); + } + + if (emu_cfg.dump_to_file) + { + dumper.Open(backend_current_cfg.cfg.ch_cnt, backend_current_cfg.cfg.freq, AudioSampleSize::FLOAT); + } + else + { + dumper.Close(); + } + } + + if (!backend->Operational()) + { + if (!backend_failed) + { + sys_rsxaudio.warning("Backend stopped unexpectedly (likely device change). Attempting to recover..."); + } + + backend_init(ra_state, emu_cfg); + backend_failed = true; + continue; + } + + if (backend_failed) + { + sys_rsxaudio.warning("Backend recovered"); + backend_failed = false; + } + + if (!Emu.IsPaused() || !use_aux_ringbuf) // Don't pause if thread is in direct mode + { + if (!backend_playing()) + { + backend_start(); + reset_service_time(); + continue; + } + + if (should_service_stream) + { + void* crnt_buf = thread_tmp_buf.data(); + + const u64 bytes_req = ringbuf.get_free_size(); + const u64 bytes_read = aux_ringbuf.pop(crnt_buf, bytes_req, true); + u64 crnt_buf_size = bytes_read; + + if (emu_cfg.enable_time_stretching) + { + const u64 input_ch_cnt = static_cast(ra_state.port[static_cast(emu_cfg.avport)].ch_cnt); + const u64 bytes_per_sample = static_cast(AudioSampleSize::FLOAT) * input_ch_cnt; + const u64 samples_req = bytes_req / bytes_per_sample; + const u64 samples_avail = crnt_buf_size / bytes_per_sample; + const f64 resampler_ratio = resampler.get_resample_ratio(); + f64 fullness_ratio = static_cast(samples_avail + resampler.samples_available()) / samples_req; + + if (fullness_ratio < emu_cfg.time_stretching_threshold) + { + fullness_ratio /= emu_cfg.time_stretching_threshold; + const f64 new_resampler_ratio = (resampler_ratio + fullness_ratio) / 2.0; + if (std::abs(new_resampler_ratio - resampler_ratio) >= TIME_STRETCHING_STEP) + { + resampler.set_tempo(new_resampler_ratio); + } + } + else if (resampler_ratio != RESAMPLER_MAX_FREQ_VAL) + { + resampler.set_tempo(RESAMPLER_MAX_FREQ_VAL); + } + + resampler.put_samples(static_cast(crnt_buf), static_cast(samples_avail)); + const auto [resampled_data, sample_cnt] = resampler.get_samples(static_cast(samples_req)); + crnt_buf = resampled_data; + crnt_buf_size = sample_cnt * bytes_per_sample; + } + + if (emu_cfg.dump_to_file) + { + dumper.WriteData(crnt_buf, static_cast(crnt_buf_size)); + } + + ringbuf.push(crnt_buf, crnt_buf_size); + + update_service_time(); + } + } + else + { + if (backend_playing()) + { + backend_stop(); + } + + if (should_service_stream) + { + update_service_time(); + } + } + } +} + +rsxaudio_backend_thread& rsxaudio_backend_thread::operator=(thread_state /* state */) +{ + { + std::lock_guard lock(state_update_m); + } + state_update_c.notify_all(); + return *this; +} + +void rsxaudio_backend_thread::set_new_stream_param(const std::array &cfg, avport_bit muted_avports) +{ + std::unique_lock lock(state_update_m); + + const auto new_mute_state = gen_mute_state(muted_avports); + const bool should_update = backend_current_cfg.cfg != cfg[static_cast(backend_current_cfg.avport)]; + + callback_cfg.atomic_op([&](callback_config& val) + { + val.mute_state = new_mute_state; + + if (should_update) + { + val.ready = false; // Prevent audio playback until backend is reconfigured + val.cfg_changed = true; + } + }); + + if (new_ra_state.port != cfg) + { + new_ra_state.port = cfg; + ra_state_changed = true; + lock.unlock(); + state_update_c.notify_all(); + } +} + +void rsxaudio_backend_thread::set_mute_state(avport_bit muted_avports) +{ + const auto new_mute_state = gen_mute_state(muted_avports); + + callback_cfg.atomic_op([&](callback_config& val) + { + val.mute_state = new_mute_state; + }); +} + +u8 rsxaudio_backend_thread::gen_mute_state(avport_bit avports) +{ + std::bitset mute_state{0}; + + if (avports.hdmi_0) mute_state[static_cast(RsxaudioAvportIdx::HDMI_0)] = true; + if (avports.hdmi_1) mute_state[static_cast(RsxaudioAvportIdx::HDMI_1)] = true; + if (avports.avmulti) mute_state[static_cast(RsxaudioAvportIdx::AVMULTI)] = true; + if (avports.spdif_0) mute_state[static_cast(RsxaudioAvportIdx::SPDIF_0)] = true; + if (avports.spdif_1) mute_state[static_cast(RsxaudioAvportIdx::SPDIF_1)] = true; + + return static_cast(mute_state.to_ulong()); +} + +void rsxaudio_backend_thread::add_data(rsxaudio_data_container& cont) +{ + std::unique_lock lock(ringbuf_mutex, std::try_to_lock); + if (!lock.owns_lock()) + { + return; + } + + const callback_config cb_cfg = callback_cfg.observe(); + if (!cb_cfg.ready || !cb_cfg.callback_active) + { + return; + } + + static rsxaudio_data_container::data_blk_t in_data_blk{}; + + if (u32 len = cont.get_data_size(cb_cfg.avport_idx)) + { + if (use_aux_ringbuf) + { + if (aux_ringbuf.get_free_size() >= len) + { + cont.get_data(cb_cfg.avport_idx, in_data_blk); + aux_ringbuf.push(in_data_blk.data(), len); + } + } + else + { + if (ringbuf.get_free_size() >= len) + { + cont.get_data(cb_cfg.avport_idx, in_data_blk); + ringbuf.push(in_data_blk.data(), len); + } + } + } +} + +RsxaudioAvportIdx rsxaudio_backend_thread::convert_avport(audio_avport avport) +{ + switch (avport) + { + case audio_avport::hdmi_0: return RsxaudioAvportIdx::HDMI_0; + case audio_avport::hdmi_1: return RsxaudioAvportIdx::HDMI_1; + case audio_avport::avmulti: return RsxaudioAvportIdx::AVMULTI; + case audio_avport::spdif_0: return RsxaudioAvportIdx::SPDIF_0; + case audio_avport::spdif_1: return RsxaudioAvportIdx::SPDIF_1; + default: + { + fmt::throw_exception("Invalid RSXAudio avport: %u", static_cast(avport)); + } + } +} + +void rsxaudio_backend_thread::backend_init(const rsxaudio_state& ra_state, const emu_audio_cfg& emu_cfg, bool reset_backend) +{ + if (reset_backend || !backend) + { + backend = nullptr; + backend = Emu.GetCallbacks().get_audio(); + backend->SetWriteCallback(std::bind(&rsxaudio_backend_thread::write_data_callback, this, std::placeholders::_1, std::placeholders::_2)); + backend->SetErrorCallback(std::bind(&rsxaudio_backend_thread::error_callback, this)); + } + + const port_config& port_cfg = ra_state.port[static_cast(emu_cfg.avport)]; + const AudioSampleSize sample_size = emu_cfg.convert_to_s16 ? AudioSampleSize::S16 : AudioSampleSize::FLOAT; + const AudioChannelCnt ch_cnt = static_cast(std::min(static_cast(port_cfg.ch_cnt), static_cast(emu_cfg.downmix))); + + static constexpr f64 _10ms = 512.0 / 48000.0; + const f64 cb_frame_len = backend->Open(port_cfg.freq, sample_size, ch_cnt) ? backend->GetCallbackFrameLen() : 0.0; + const f64 buffering_len = emu_cfg.buffering_enabled ? (emu_cfg.desired_buffer_duration / 1000.0) : 0.0; + const u64 bytes_per_sec = static_cast(AudioSampleSize::FLOAT) * static_cast(port_cfg.ch_cnt) * static_cast(port_cfg.freq); + + { + std::lock_guard lock(ringbuf_mutex); + use_aux_ringbuf = emu_cfg.enable_time_stretching || emu_cfg.dump_to_file; + + if (use_aux_ringbuf) + { + const f64 frame_len = std::max(buffering_len * 0.5, SERVICE_PERIOD_SEC) + cb_frame_len + _10ms; + const u64 frame_len_bytes = static_cast(std::round(frame_len * bytes_per_sec)); + aux_ringbuf.set_buf_size(frame_len_bytes); + ringbuf.set_buf_size(frame_len_bytes); + thread_tmp_buf.resize(frame_len_bytes); + } + else + { + const f64 frame_len = std::max(buffering_len, cb_frame_len) + _10ms; + ringbuf.set_buf_size(static_cast(std::round(frame_len * bytes_per_sec))); + thread_tmp_buf.resize(0); + } + + callback_tmp_buf.resize(static_cast((cb_frame_len + _10ms) * static_cast(AudioSampleSize::FLOAT) * static_cast(port_cfg.ch_cnt) * static_cast(port_cfg.freq))); + } + + callback_cfg.atomic_op([&](callback_config& val) + { + val.callback_active = false; // Backend may take some time to activate. This prevents overflows on input side. + + if (!val.cfg_changed) + { + val.freq = static_cast(port_cfg.freq); + val.input_ch_cnt = static_cast(port_cfg.ch_cnt); + val.output_ch_cnt = static_cast(ch_cnt); + val.convert_to_s16 = emu_cfg.convert_to_s16; + val.avport_idx = emu_cfg.avport; + val.ready = true; + } + }); +} + +void rsxaudio_backend_thread::backend_start() +{ + ensure(backend != nullptr); + + if (use_aux_ringbuf) + { + resampler.set_tempo(RESAMPLER_MAX_FREQ_VAL); + resampler.flush(); + aux_ringbuf.reader_flush(); + } + + ringbuf.reader_flush(); + backend->Play(); +} + +void rsxaudio_backend_thread::backend_stop() +{ + if (backend == nullptr) + { + return; + } + + backend->Pause(); + callback_cfg.atomic_op([&](callback_config& val) + { + val.callback_active = false; + }); +} + +bool rsxaudio_backend_thread::backend_playing() +{ + if (backend == nullptr) + { + return false; + } + + return backend->IsPlaying(); +} + +u32 rsxaudio_backend_thread::write_data_callback(u32 bytes, void* buf) +{ + const callback_config cb_cfg = callback_cfg.atomic_op([&](callback_config& val) + { + val.callback_active = true; + return val; + }); + + const std::bitset mute_state{cb_cfg.mute_state}; + + if (cb_cfg.ready && !mute_state[static_cast(cb_cfg.avport_idx)] && Emu.IsRunning()) + { + const u32 bytes_ch_adjusted = bytes / cb_cfg.output_ch_cnt * cb_cfg.input_ch_cnt; + const u32 bytes_from_rb = cb_cfg.convert_to_s16 ? bytes_ch_adjusted / static_cast(AudioSampleSize::S16) * static_cast(AudioSampleSize::FLOAT) : bytes_ch_adjusted; + + ensure(callback_tmp_buf.size() * static_cast(AudioSampleSize::FLOAT) >= bytes_from_rb); + + const u32 byte_cnt = static_cast(ringbuf.pop(callback_tmp_buf.data(), bytes_from_rb, true)); + const u32 sample_cnt = byte_cnt / static_cast(AudioSampleSize::FLOAT); + const u32 sample_cnt_out = sample_cnt / cb_cfg.input_ch_cnt * cb_cfg.output_ch_cnt; + + // Buffer is in weird state - drop acquired data + if (sample_cnt == 0 || sample_cnt % cb_cfg.input_ch_cnt != 0) + { + memset(buf, 0, bytes); + return bytes; + } + + if (cb_cfg.input_ch_cnt > cb_cfg.output_ch_cnt) + { + if (cb_cfg.input_ch_cnt == static_cast(AudioChannelCnt::SURROUND_7_1)) + { + if (cb_cfg.output_ch_cnt == static_cast(AudioChannelCnt::SURROUND_5_1)) + { + AudioBackend::downmix(sample_cnt, callback_tmp_buf.data(), callback_tmp_buf.data()); + } + else if (cb_cfg.output_ch_cnt == static_cast(AudioChannelCnt::STEREO)) + { + AudioBackend::downmix(sample_cnt, callback_tmp_buf.data(), callback_tmp_buf.data()); + } + else + { + fmt::throw_exception("Invalid downmix combination: %u -> %u", cb_cfg.input_ch_cnt, cb_cfg.output_ch_cnt); + } + } + else if (cb_cfg.input_ch_cnt == static_cast(AudioChannelCnt::SURROUND_5_1)) + { + if (cb_cfg.output_ch_cnt == static_cast(AudioChannelCnt::STEREO)) + { + AudioBackend::downmix(sample_cnt, callback_tmp_buf.data(), callback_tmp_buf.data()); + } + else + { + fmt::throw_exception("Invalid downmix combination: %u -> %u", cb_cfg.input_ch_cnt, cb_cfg.output_ch_cnt); + } + } + else + { + fmt::throw_exception("Invalid downmix combination: %u -> %u", cb_cfg.input_ch_cnt, cb_cfg.output_ch_cnt); + } + } + + if (cb_cfg.target_volume != cb_cfg.current_volume) + { + const AudioBackend::VolumeParam param = + { + .initial_volume = cb_cfg.initial_volume * callback_config::VOL_NOMINAL_INV, + .current_volume = cb_cfg.current_volume * callback_config::VOL_NOMINAL_INV, + .target_volume = cb_cfg.target_volume * callback_config::VOL_NOMINAL_INV, + .freq = cb_cfg.freq, + .ch_cnt = cb_cfg.input_ch_cnt + }; + + const u16 new_vol = static_cast(std::round(AudioBackend::apply_volume(param, sample_cnt_out, callback_tmp_buf.data(), callback_tmp_buf.data()) * callback_config::VOL_NOMINAL)); + callback_cfg.atomic_op([&](callback_config& val) + { + if (val.target_volume != cb_cfg.target_volume) + { + val.initial_volume = new_vol; + } + + // We don't care about proper volume adjustment if underflow has occured + val.current_volume = bytes_from_rb != byte_cnt ? val.target_volume : new_vol; + }); + } + else if (cb_cfg.current_volume != callback_config::VOL_NOMINAL) + { + AudioBackend::apply_volume_static(cb_cfg.current_volume * callback_config::VOL_NOMINAL_INV, sample_cnt_out, callback_tmp_buf.data(), callback_tmp_buf.data()); + } + + if (cb_cfg.convert_to_s16) + { + AudioBackend::convert_to_s16(sample_cnt_out, callback_tmp_buf.data(), buf); + return sample_cnt_out * static_cast(AudioSampleSize::S16); + } + + AudioBackend::normalize(sample_cnt_out, callback_tmp_buf.data(), static_cast(buf)); + return sample_cnt_out * static_cast(AudioSampleSize::FLOAT); + } + + ringbuf.reader_flush(); + memset(buf, 0, bytes); + return bytes; +} + +void rsxaudio_backend_thread::error_callback() +{ + { + std::lock_guard lock(state_update_m); + backend_error_occured = true; + } + state_update_c.notify_all(); +} + +u64 rsxaudio_backend_thread::get_time_until_service() +{ + const u64 next_service_time = start_time + time_period_idx * SERVICE_PERIOD; + const u64 current_time = get_system_time(); + + return next_service_time >= current_time ? next_service_time - current_time : 0; +} + +void rsxaudio_backend_thread::update_service_time() +{ + if (get_time_until_service() <= SERVICE_THRESHOLD) time_period_idx++; +} + +void rsxaudio_backend_thread::reset_service_time() +{ + start_time = get_system_time(); + time_period_idx = 1; +} + +void rsxaudio_periodic_tmr::sched_timer() +{ + u64 interval = get_rel_next_time(); + + if (interval == 0) + { + zero_period = true; + } + else if (interval == UINT64_MAX) + { + interval = 0; + zero_period = false; + } + else + { + zero_period = false; + } + +#if defined(_WIN32) + if (interval) + { + LARGE_INTEGER due_time{}; + due_time.QuadPart = -static_cast(interval * 10); + ensure(SetWaitableTimerEx(timer_handle, &due_time, 0, nullptr, nullptr, nullptr, 0)); + } + else + { + ensure(CancelWaitableTimer(timer_handle)); + } +#elif defined(__linux__) + const time_t secs = interval / 1'000'000; + const long nsecs = (interval - secs * 1'000'000) * 1000; + const itimerspec tspec = {{}, { secs, nsecs }}; + ensure(timerfd_settime(timer_handle, 0, &tspec, nullptr) == 0); +#elif defined(BSD) || defined(__APPLE__) + handle[TIMER_ID].data = interval * 1000; + if (interval) + { + handle[TIMER_ID].flags = (handle[TIMER_ID].flags & ~EV_DISABLE) | EV_ENABLE; + } + else + { + handle[TIMER_ID].flags = (handle[TIMER_ID].flags & ~EV_ENABLE) | EV_DISABLE; + } + ensure(kevent(kq, &handle[TIMER_ID], 1, nullptr, 0, nullptr) >= 0); +#else +#error "Implement" +#endif +} + +void rsxaudio_periodic_tmr::cancel_timer_unlocked() +{ + zero_period = false; + +#if defined(_WIN32) + ensure(CancelWaitableTimer(timer_handle)); + if (in_wait) + { + ensure(SetEvent(cancel_event)); + } +#elif defined(__linux__) + const itimerspec tspec{}; + ensure(timerfd_settime(timer_handle, 0, &tspec, nullptr) == 0); + if (in_wait) + { + const u64 flag = 1; + const auto wr_res = write(cancel_event, &flag, sizeof(flag)); + ensure(wr_res == sizeof(flag) || wr_res == -EAGAIN); + } +#elif defined(BSD) || defined(__APPLE__) + handle[TIMER_ID].flags = (handle[TIMER_ID].flags & ~EV_ENABLE) | EV_DISABLE; + handle[TIMER_ID].data = 0; + if (in_wait) + { + ensure(kevent(kq, handle, 2, nullptr, 0, nullptr) >= 0); + } + else + { + ensure(kevent(kq, &handle[TIMER_ID], 1, nullptr, 0, nullptr) >= 0); + } +#else +#error "Implement" +#endif +} + +void rsxaudio_periodic_tmr::reset_cancel_flag() +{ +#if defined(_WIN32) + ensure(ResetEvent(cancel_event)); +#elif defined(__linux__) + u64 tmp_buf{}; + [[maybe_unused]] const auto nread = read(cancel_event, &tmp_buf, sizeof(tmp_buf)); +#elif defined(BSD) || defined(__APPLE__) + // Cancel event is reset automatically +#else +#error "Implement" +#endif +} + +rsxaudio_periodic_tmr::rsxaudio_periodic_tmr() +{ +#if defined(_WIN32) + ensure(cancel_event = CreateEvent(nullptr, false, false, nullptr)); + ensure(timer_handle = CreateWaitableTimer(nullptr, false, nullptr)); +#elif defined(__linux__) + timer_handle = timerfd_create(CLOCK_MONOTONIC, 0); + ensure((epoll_fd = epoll_create(2)) >= 0); + epoll_event evnt{ EPOLLIN, {} }; + evnt.data.fd = timer_handle; + ensure(timer_handle >= 0 && epoll_ctl(epoll_fd, EPOLL_CTL_ADD, timer_handle, &evnt) == 0); + cancel_event = eventfd(0, EFD_NONBLOCK); + evnt.data.fd = cancel_event; + ensure(cancel_event >= 0 && epoll_ctl(epoll_fd, EPOLL_CTL_ADD, cancel_event, &evnt) == 0); +#elif defined(BSD) || defined(__APPLE__) + +#if defined(__APPLE__) + static constexpr unsigned int TMR_CFG = NOTE_NSECONDS | NOTE_CRITICAL; +#else + static constexpr unsigned int TMR_CFG = NOTE_NSECONDS; +#endif + + ensure((kq = kqueue()) >= 0); + EV_SET(&handle[TIMER_ID], TIMER_ID, EVFILT_TIMER, EV_ADD | EV_ENABLE | EV_ONESHOT, TMR_CFG, 0, nullptr); + EV_SET(&handle[CANCEL_ID], CANCEL_ID, EVFILT_USER, EV_ADD | EV_ENABLE | EV_CLEAR, NOTE_FFNOP, 0, nullptr); + ensure(kevent(kq, &handle[CANCEL_ID], 1, nullptr, 0, nullptr) >= 0); + handle[CANCEL_ID].fflags |= NOTE_TRIGGER; +#else +#error "Implement" +#endif +} + +rsxaudio_periodic_tmr::~rsxaudio_periodic_tmr() +{ +#if defined(_WIN32) + CloseHandle(timer_handle); + CloseHandle(cancel_event); +#elif defined(__linux__) + close(epoll_fd); + close(timer_handle); + close(cancel_event); +#elif defined(BSD) || defined(__APPLE__) + close(kq); +#else +#error "Implement" +#endif +} + +rsxaudio_periodic_tmr::wait_result rsxaudio_periodic_tmr::wait(const std::function &callback) +{ + std::unique_lock lock(mutex); + + if (in_wait || !callback) + { + return wait_result::INVALID_PARAM; + } + + in_wait = true; + + bool tmr_error = false; + bool timeout = false; + bool wait_canceled = false; + + if (!zero_period) + { + lock.unlock(); + constexpr u8 obj_wait_cnt = 2; + +#if defined(_WIN32) + const HANDLE wait_arr[obj_wait_cnt] = { timer_handle, cancel_event }; + const auto wait_status = WaitForMultipleObjects(obj_wait_cnt, wait_arr, false, INFINITE); + + if (wait_status == WAIT_FAILED || wait_status >= WAIT_ABANDONED_0 && wait_status < WAIT_ABANDONED_0 + obj_wait_cnt) + { + tmr_error = true; + } + else if (wait_status == WAIT_TIMEOUT) + { + timeout = true; + } + else if (wait_status == WAIT_OBJECT_0 + 1) + { + wait_canceled = true; + } +#elif defined(__linux__) + epoll_event event[obj_wait_cnt]{}; + const auto wait_status = epoll_wait(epoll_fd, event, obj_wait_cnt, -1); + + if (wait_status < 0 || wait_status > obj_wait_cnt) + { + tmr_error = true; + } + else if (wait_status == 0) + { + timeout = true; + } + else + { + for (int i = 0; i < wait_status; i++) + { + if (event[i].data.fd == cancel_event) + { + wait_canceled = true; + break; + } + } + } +#elif defined(BSD) || defined(__APPLE__) + struct kevent event[obj_wait_cnt]{}; + const auto wait_status = kevent(kq, nullptr, 0, event, obj_wait_cnt, nullptr); + + if (wait_status < 0 || wait_status > obj_wait_cnt) + { + tmr_error = true; + } + else if (wait_status == 0) + { + timeout = true; + } + else + { + for (int i = 0; i < wait_status; i++) + { + if (event[i].ident == CANCEL_ID) + { + wait_canceled = true; + break; + } + } + } +#else +#error "Implement" +#endif + lock.lock(); + } + else + { + zero_period = false; + } + + in_wait = false; + + if (wait_canceled) + { + reset_cancel_flag(); + } + + if (tmr_error) + { + return wait_result::TIMER_ERROR; + } + else if (timeout) + { + return wait_result::TIMEOUT; + } + else if (wait_canceled) + { + sched_timer(); + return wait_result::TIMER_CANCELED; + } + else + { + callback(); + sched_timer(); + return wait_result::SUCCESS; + } +} + +u64 rsxaudio_periodic_tmr::get_rel_next_time() +{ + const u64 crnt_time = get_system_time(); + u64 next_time = UINT64_MAX; + + for (vtimer& vtimer : vtmr_pool) + { + if (!vtimer.active) continue; + + u64 next_blk_time = static_cast(vtimer.blk_cnt * vtimer.blk_time); + + if (crnt_time >= next_blk_time) + { + const u64 crnt_blk = get_crnt_blk(crnt_time, vtimer.blk_time); + + if (crnt_blk > vtimer.blk_cnt + MAX_BURST_PERIODS) + { + vtimer.blk_cnt = std::max(vtimer.blk_cnt, crnt_blk - MAX_BURST_PERIODS); + next_blk_time = static_cast(vtimer.blk_cnt * vtimer.blk_time); + } + } + + if (crnt_time >= next_blk_time) + { + next_time = 0; + } + else + { + next_time = std::min(next_time, next_blk_time - crnt_time); + } + } + + return next_time; +} + +void rsxaudio_periodic_tmr::cancel_wait() +{ + std::lock_guard lock(mutex); + cancel_timer_unlocked(); +} + +void rsxaudio_periodic_tmr::enable_vtimer(u32 vtimer_id, u32 rate, u64 crnt_time) +{ + ensure(vtimer_id < VTIMER_MAX && rate); + + vtimer& vtimer = vtmr_pool[vtimer_id]; + const f64 new_blk_time = get_blk_time(rate); + + // Avoid timer reset when possible + if (!vtimer.active || new_blk_time != vtimer.blk_time) + { + vtimer.blk_cnt = get_crnt_blk(crnt_time, new_blk_time); + } + + vtimer.blk_time = new_blk_time; + vtimer.active = true; +} + +void rsxaudio_periodic_tmr::disable_vtimer(u32 vtimer_id) +{ + ensure(vtimer_id < VTIMER_MAX); + + vtimer& vtimer = vtmr_pool[vtimer_id]; + vtimer.active = false; +} + +bool rsxaudio_periodic_tmr::is_vtimer_behind(u32 vtimer_id, u64 crnt_time) const +{ + const vtimer& vtimer = vtmr_pool[vtimer_id]; + ensure(vtimer_id < VTIMER_MAX); + + return is_vtimer_behind(vtimer, crnt_time); +} + +void rsxaudio_periodic_tmr::vtimer_skip_periods(u32 vtimer_id, u64 crnt_time) +{ + vtimer& vtimer = vtmr_pool[vtimer_id]; + ensure(vtimer_id < VTIMER_MAX); + + if (is_vtimer_behind(vtimer, crnt_time)) + { + vtimer.blk_cnt = get_crnt_blk(crnt_time, vtimer.blk_time); + } +} + +void rsxaudio_periodic_tmr::vtimer_incr(u32 vtimer_id, u64 crnt_time) +{ + vtimer& vtimer = vtmr_pool[vtimer_id]; + ensure(vtimer_id < VTIMER_MAX); + + if (is_vtimer_behind(vtimer, crnt_time)) + { + vtimer.blk_cnt++; + } +} + +bool rsxaudio_periodic_tmr::is_vtimer_active(u32 vtimer_id) const +{ + const vtimer& vtimer = vtmr_pool[vtimer_id]; + ensure(vtimer_id < VTIMER_MAX); + + return vtimer.active; +} + +u64 rsxaudio_periodic_tmr::vtimer_get_sched_time(u32 vtimer_id) const +{ + const vtimer& vtimer = vtmr_pool[vtimer_id]; + ensure(vtimer_id < VTIMER_MAX); + + return static_cast(vtimer.blk_cnt * vtimer.blk_time); +} + +bool rsxaudio_periodic_tmr::is_vtimer_behind(const vtimer& vtimer, u64 crnt_time) const +{ + return vtimer.active && vtimer.blk_cnt < get_crnt_blk(crnt_time, vtimer.blk_time); +} + +u64 rsxaudio_periodic_tmr::get_crnt_blk(u64 crnt_time, f64 blk_time) const +{ + return static_cast(std::floor(static_cast(crnt_time) / blk_time)) + 1; +} + +f64 rsxaudio_periodic_tmr::get_blk_time(u32 data_rate) const +{ + return static_cast(SYS_RSXAUDIO_STREAM_SIZE * 1'000'000) / data_rate; +} diff --git a/rpcs3/Emu/Cell/lv2/sys_rsxaudio.h b/rpcs3/Emu/Cell/lv2/sys_rsxaudio.h index 9d7b40125d..9f3e48e2f0 100644 --- a/rpcs3/Emu/Cell/lv2/sys_rsxaudio.h +++ b/rpcs3/Emu/Cell/lv2/sys_rsxaudio.h @@ -1,16 +1,41 @@ #pragma once +#include "sys_sync.h" +#include "sys_event.h" +#include "Utilities/Timer.h" +#include "Utilities/simple_ringbuf.h" +#include "Utilities/transactional_storage.h" +#include "Utilities/cond.h" #include "Emu/Memory/vm_ptr.h" #include "Emu/Cell/ErrorCodes.h" +#include "Emu/Audio/AudioDumper.h" +#include "Emu/Audio/AudioBackend.h" +#include "Emu/Audio/audio_resampler.h" + +#if defined(unix) || defined(__unix) || defined(__unix__) +// For BSD detection +#include +#endif + +#ifdef _WIN32 +#include +#elif defined(BSD) || defined(__APPLE__) +#include +#endif enum : u32 { SYS_RSXAUDIO_SERIAL_STREAM_CNT = 4, - SYS_RSXAUDIO_STREAM_SIZE = 1024, + SYS_RSXAUDIO_STREAM_DATA_BLK_CNT = 4, SYS_RSXAUDIO_DATA_BLK_SIZE = 256, + SYS_RSXAUDIO_STREAM_SIZE = SYS_RSXAUDIO_DATA_BLK_SIZE * SYS_RSXAUDIO_STREAM_DATA_BLK_CNT, + SYS_RSXAUDIO_CH_PER_STREAM = 2, + SYS_RSXAUDIO_SERIAL_MAX_CH = 8, + SYS_RSXAUDIO_SPDIF_MAX_CH = 2, + SYS_RSXAUDIO_STREAM_SAMPLE_CNT = SYS_RSXAUDIO_STREAM_SIZE / SYS_RSXAUDIO_CH_PER_STREAM / sizeof(f32), - SYS_RSXAUDIO_RINGBUF_BLK_SZ_SERIAL = 0x1000, - SYS_RSXAUDIO_RINGBUF_BLK_SZ_SPDIF = 0x400, + SYS_RSXAUDIO_RINGBUF_BLK_SZ_SERIAL = SYS_RSXAUDIO_STREAM_SIZE * SYS_RSXAUDIO_SERIAL_STREAM_CNT, + SYS_RSXAUDIO_RINGBUF_BLK_SZ_SPDIF = SYS_RSXAUDIO_STREAM_SIZE, SYS_RSXAUDIO_RINGBUF_SZ = 16, @@ -19,10 +44,6 @@ enum : u32 SYS_RSXAUDIO_FREQ_BASE_384K = 384000, SYS_RSXAUDIO_FREQ_BASE_352K = 352800, - SYS_RSXAUDIO_PORT_SERIAL = 0, - SYS_RSXAUDIO_PORT_SPDIF_0 = 1, - SYS_RSXAUDIO_PORT_SPDIF_1 = 2, - SYS_RSXAUDIO_PORT_INVALID = 0xFF, SYS_RSXAUDIO_PORT_CNT = 3, SYS_RSXAUDIO_SPDIF_CNT = 2, @@ -37,12 +58,563 @@ enum class RsxaudioAvportIdx : u8 SPDIF_1 = 4, }; +enum class RsxaudioPort : u8 +{ + SERIAL = 0, + SPDIF_0 = 1, + SPDIF_1 = 2, + INVALID = 0xFF, +}; + enum class RsxaudioSampleSize : u8 { _16BIT = 2, _32BIT = 4, }; +struct rsxaudio_shmem +{ + struct ringbuf_t + { + struct entry_t + { + be_t valid{}; + be_t unk1{}; + be_t audio_blk_idx{}; + be_t timestamp{}; + be_t buf_addr{}; + be_t dma_addr{}; + }; + + be_t active{}; + be_t unk2{}; + be_t read_idx{}; + be_t write_idx{}; + be_t rw_max_idx{}; + be_t queue_notify_idx{}; + be_t queue_notify_step{}; + be_t unk6{}; + be_t dma_silence_addr{}; + be_t unk7{}; + be_t next_blk_idx{}; + + entry_t entries[16]{}; + }; + + struct uf_event_t + { + be_t unk1{}; + be_t uf_event_cnt{}; + u8 unk2[244]{}; + }; + + struct ctrl_t + { + ringbuf_t ringbuf[SYS_RSXAUDIO_PORT_CNT]{}; + + be_t unk1{}; + be_t event_queue_1_id{}; + u8 unk2[16]{}; + be_t event_queue_2_id{}; + be_t spdif_ch0_channel_data_lo{}; + be_t spdif_ch0_channel_data_hi{}; + be_t spdif_ch0_channel_data_tx_cycles{}; + be_t unk3{}; + be_t event_queue_3_id{}; + be_t spdif_ch1_channel_data_lo{}; + be_t spdif_ch1_channel_data_hi{}; + be_t spdif_ch1_channel_data_tx_cycles{}; + be_t unk4{}; + be_t intr_thread_prio{}; + be_t unk5{}; + u8 unk6[248]{}; + uf_event_t channel_uf[SYS_RSXAUDIO_PORT_CNT]{}; + u8 pad[0x3530]{}; + }; + + u8 dma_serial_region[0x10000]{}; + u8 dma_spdif_0_region[0x4000]{}; + u8 dma_spdif_1_region[0x4000]{}; + u8 dma_silence_region[0x4000]{}; + ctrl_t ctrl{}; +}; + +static_assert(sizeof(rsxaudio_shmem::ringbuf_t) == 0x230U, "rsxAudioRingBufSizeTest"); +static_assert(sizeof(rsxaudio_shmem::uf_event_t) == 0x100U, "rsxAudioUfEventTest"); +static_assert(sizeof(rsxaudio_shmem::ctrl_t) == 0x4000U, "rsxAudioCtrlSizeTest"); +static_assert(sizeof(rsxaudio_shmem) == 0x20000U, "rsxAudioShmemSizeTest"); + +enum rsxaudio_dma_flag : u32 +{ + IO_BASE = 0, + IO_ID = 1 +}; + +struct lv2_rsxaudio final : lv2_obj +{ + static constexpr u32 id_base = 0x60000000; + static constexpr u64 dma_io_id = 1; + static constexpr u32 dma_io_base = 0x30000000; + + shared_mutex mutex{}; + bool init = false; + + vm::addr_t shmem{}; + + std::array, SYS_RSXAUDIO_PORT_CNT> event_queue{}; + std::array event_port{}; + + lv2_rsxaudio() + { + } + + void page_lock() + { + ensure(shmem && vm::page_protect(shmem, sizeof(rsxaudio_shmem), 0, 0, vm::page_readable | vm::page_writable | vm::page_executable)); + } + + void page_unlock() + { + ensure(shmem && vm::page_protect(shmem, sizeof(rsxaudio_shmem), 0, vm::page_readable | vm::page_writable)); + } + + rsxaudio_shmem* get_rw_shared_page() const + { + return reinterpret_cast(vm::g_sudo_addr + u32{shmem}); + } +}; + +class rsxaudio_periodic_tmr +{ +public: + + enum class wait_result + { + SUCCESS, + INVALID_PARAM, + TIMEOUT, + TIMER_ERROR, + TIMER_CANCELED, + }; + + rsxaudio_periodic_tmr(); + ~rsxaudio_periodic_tmr(); + + rsxaudio_periodic_tmr(const rsxaudio_periodic_tmr&) = delete; + rsxaudio_periodic_tmr& operator=(const rsxaudio_periodic_tmr&) = delete; + + // Wait until timer fires and calls callback. + wait_result wait(const std::function &callback); + + // Cancel wait() call + void cancel_wait(); + + // VTimer funtions + + void vtimer_access_sec(std::invocable<> auto func) + { + std::lock_guard lock(mutex); + std::invoke(func); + + // Adjust timer expiration + cancel_timer_unlocked(); + sched_timer(); + } + + void enable_vtimer(u32 vtimer_id, u32 rate, u64 crnt_time); + + void disable_vtimer(u32 vtimer_id); + + bool is_vtimer_behind(u32 vtimer_id, u64 crnt_time) const; + + void vtimer_skip_periods(u32 vtimer_id, u64 crnt_time); + + void vtimer_incr(u32 vtimer_id, u64 crnt_time); + + bool is_vtimer_active(u32 vtimer_id) const; + + u64 vtimer_get_sched_time(u32 vtimer_id) const; + +private: + + static constexpr u64 MAX_BURST_PERIODS = SYS_RSXAUDIO_RINGBUF_SZ; + static constexpr u32 VTIMER_MAX = 4; + + struct vtimer + { + u64 blk_cnt = 0; + f64 blk_time = 0.0; + bool active = false; + }; + + std::array vtmr_pool{}; + + shared_mutex mutex{}; + bool in_wait = false; + bool zero_period = false; + +#if defined(_WIN32) + HANDLE cancel_event{}; + HANDLE timer_handle{}; +#elif defined(__linux__) + int cancel_event{}; + int timer_handle{}; + int epoll_fd{}; +#elif defined(BSD) || defined(__APPLE__) + static constexpr u64 TIMER_ID = 0; + static constexpr u64 CANCEL_ID = 1; + + int kq{}; + struct kevent handle[2]{}; +#else +#error "Implement" +#endif + + void sched_timer(); + void cancel_timer_unlocked(); + void reset_cancel_flag(); + + bool is_vtimer_behind(const vtimer& vtimer, u64 crnt_time) const; + + u64 get_crnt_blk(u64 crnt_time, f64 blk_time) const; + f64 get_blk_time(u32 data_rate) const; + + u64 get_rel_next_time(); +}; + +struct rsxaudio_hw_param_t +{ + struct serial_param_t + { + bool dma_en = false; + bool buf_empty_en = false; + bool muted = true; + bool en = false; + u8 freq_div = 8; + RsxaudioSampleSize depth = RsxaudioSampleSize::_16BIT; + }; + + struct spdif_param_t + { + bool dma_en = false; + bool buf_empty_en = false; + bool muted = true; + bool en = false; + bool use_serial_buf = true; + u8 freq_div = 8; + RsxaudioSampleSize depth = RsxaudioSampleSize::_16BIT; + std::array cs_data = { 0x00, 0x90, 0x00, 0x40, 0x80, 0x00 }; // HW supports only 6 bytes (uart pkt has 8) + }; + + struct hdmi_param_t + { + struct hdmi_ch_cfg_t + { + std::array map{}; + AudioChannelCnt total_ch_cnt = AudioChannelCnt::STEREO; + }; + + static constexpr u8 MAP_SILENT_CH = umax; + + bool init = false; + hdmi_ch_cfg_t ch_cfg{}; + std::array info_frame{}; // TODO: check chstat and info_frame for info on audio layout, add default values + std::array chstat{}; + + bool muted = true; + bool force_mute = true; + bool use_spdif_1 = false; // TODO: unused for now + }; + + u32 serial_freq_base = SYS_RSXAUDIO_FREQ_BASE_384K; + u32 spdif_freq_base = SYS_RSXAUDIO_FREQ_BASE_352K; + + bool avmulti_av_muted = true; + + serial_param_t serial{}; + spdif_param_t spdif[2]{}; + hdmi_param_t hdmi[2]{}; + + std::array avport_src = + { + RsxaudioPort::INVALID, + RsxaudioPort::INVALID, + RsxaudioPort::INVALID, + RsxaudioPort::INVALID, + RsxaudioPort::INVALID + }; +}; + +// 16-bit PCM converted into float, so buffer must be twice as big +using ra_stream_blk_t = std::array; + +class rsxaudio_data_container +{ +public: + + struct buf_t + { + std::array serial{}; + std::array spdif[SYS_RSXAUDIO_SPDIF_CNT]{}; + }; + + using data_blk_t = std::array; + + rsxaudio_data_container(const rsxaudio_hw_param_t& hw_param, const buf_t& buf, bool serial_rdy, bool spdif_0_rdy, bool spdif_1_rdy); + u32 get_data_size(RsxaudioAvportIdx avport); + void get_data(RsxaudioAvportIdx avport, data_blk_t& data_out); + bool data_was_used(); + +private: + + const rsxaudio_hw_param_t& hwp; + const buf_t& out_buf; + + std::array avport_data_avail{}; + u8 hdmi_stream_cnt[2]{}; + bool data_was_written = false; + + rsxaudio_data_container(const rsxaudio_data_container&) = delete; + rsxaudio_data_container& operator=(const rsxaudio_data_container&) = delete; + + rsxaudio_data_container(rsxaudio_data_container&&) = delete; + rsxaudio_data_container& operator=(rsxaudio_data_container&&) = delete; + + // Mix individual channels into final PCM stream. Channels in channel map that are > input_ch_cnt treated as silent. + template + requires (output_ch_cnt > 0 && output_ch_cnt <= 8 && input_ch_cnt > 0) + constexpr void mix(const std::array &ch_map, RsxaudioSampleSize sample_size, const std::array &input_channels, data_blk_t& data_out) + { + const ra_stream_blk_t silent_channel{}; + + // Build final map + std::array real_input_ch = {}; + for (u64 ch_idx = 0; ch_idx < output_ch_cnt; ch_idx++) + { + if (ch_map[ch_idx] >= input_ch_cnt) + { + real_input_ch[ch_idx] = &silent_channel; + } + else + { + real_input_ch[ch_idx] = &input_channels[ch_map[ch_idx]]; + } + } + + const u32 samples_in_buf = sample_size == RsxaudioSampleSize::_16BIT ? SYS_RSXAUDIO_STREAM_SAMPLE_CNT * 2 : SYS_RSXAUDIO_STREAM_SAMPLE_CNT; + + for (u32 sample_idx = 0; sample_idx < samples_in_buf * output_ch_cnt; sample_idx += output_ch_cnt) + { + const u32 src_sample_idx = sample_idx / output_ch_cnt; + + if constexpr (output_ch_cnt >= 1) data_out[sample_idx + 0] = (*real_input_ch[0])[src_sample_idx]; + if constexpr (output_ch_cnt >= 2) data_out[sample_idx + 1] = (*real_input_ch[1])[src_sample_idx]; + if constexpr (output_ch_cnt >= 3) data_out[sample_idx + 2] = (*real_input_ch[2])[src_sample_idx]; + if constexpr (output_ch_cnt >= 4) data_out[sample_idx + 3] = (*real_input_ch[3])[src_sample_idx]; + if constexpr (output_ch_cnt >= 5) data_out[sample_idx + 4] = (*real_input_ch[4])[src_sample_idx]; + if constexpr (output_ch_cnt >= 6) data_out[sample_idx + 5] = (*real_input_ch[5])[src_sample_idx]; + if constexpr (output_ch_cnt >= 7) data_out[sample_idx + 6] = (*real_input_ch[6])[src_sample_idx]; + if constexpr (output_ch_cnt >= 8) data_out[sample_idx + 7] = (*real_input_ch[7])[src_sample_idx]; + } + } +}; + +namespace audio +{ + void configure_rsxaudio(); +} + +class rsxaudio_backend_thread +{ +public: + + struct port_config + { + AudioFreq freq = AudioFreq::FREQ_48K; + AudioChannelCnt ch_cnt = AudioChannelCnt::STEREO; + + auto operator<=>(const port_config&) const = default; + }; + + union avport_bit + { + struct + { + bool hdmi_0 : 1; + bool hdmi_1 : 1; + bool avmulti : 1; + bool spdif_0 : 1; + bool spdif_1 : 1; + }; + u8 raw : 5 = 0; + }; + + rsxaudio_backend_thread(); + ~rsxaudio_backend_thread(); + + void operator()(); + rsxaudio_backend_thread& operator=(thread_state state); + + void set_new_stream_param(const std::array &cfg, avport_bit muted_avports); + void set_mute_state(avport_bit muted_avports); + void add_data(rsxaudio_data_container& cont); + + void update_emu_cfg(); + + static constexpr auto thread_name = "RsxAudio Backend Thread"sv; + +private: + + struct emu_audio_cfg + { + s64 desired_buffer_duration = 0; + f64 time_stretching_threshold = 0; + bool buffering_enabled = false; + bool convert_to_s16 = false; + bool enable_time_stretching = false; + bool dump_to_file = false; + AudioChannelCnt downmix = AudioChannelCnt::STEREO; + audio_renderer renderer = audio_renderer::null; + audio_provider provider = audio_provider::none; + RsxaudioAvportIdx avport = RsxaudioAvportIdx::HDMI_0; + + auto operator<=>(const emu_audio_cfg&) const = default; + }; + + struct rsxaudio_state + { + std::array port{}; + }; + + struct alignas(16) callback_config + { + static constexpr u16 VOL_NOMINAL = 10000; + static constexpr f32 VOL_NOMINAL_INV = 1.0f / VOL_NOMINAL; + + u32 freq : 20 = 48000; + + u16 target_volume = 10000; + u16 initial_volume = 10000; + u16 current_volume = 10000; + + RsxaudioAvportIdx avport_idx = RsxaudioAvportIdx::HDMI_0; + u8 mute_state : SYS_RSXAUDIO_AVPORT_CNT = 0b11111; + + u8 input_ch_cnt : 4 = 2; + u8 output_ch_cnt : 4 = 2; + + bool ready : 1 = false; + bool convert_to_s16 : 1 = false; + bool cfg_changed : 1 = false; + bool callback_active : 1 = false; + }; + + static_assert(sizeof(callback_config) <= 16); + + struct backend_config + { + port_config cfg{}; + RsxaudioAvportIdx avport = RsxaudioAvportIdx::HDMI_0; + }; + + static constexpr u64 ERROR_SERVICE_PERIOD = 500'000; + static constexpr u64 SERVICE_PERIOD = 10'000; + static constexpr f64 SERVICE_PERIOD_SEC = SERVICE_PERIOD / 1'000'000.0; + static constexpr u64 SERVICE_THRESHOLD = 1'500; + + static constexpr f64 TIME_STRETCHING_STEP = 0.1f; + + u64 start_time = get_system_time(); + u64 time_period_idx = 1; + + emu_audio_cfg new_emu_cfg{get_emu_cfg()}; + bool emu_cfg_changed = true; + + rsxaudio_state new_ra_state{}; + bool ra_state_changed = true; + + shared_mutex state_update_m{}; + cond_variable state_update_c{}; + + simple_ringbuf ringbuf{}; + simple_ringbuf aux_ringbuf{}; + std::vector thread_tmp_buf{}; + std::vector callback_tmp_buf{}; + bool use_aux_ringbuf = false; + shared_mutex ringbuf_mutex{}; + + std::shared_ptr backend{}; + backend_config backend_current_cfg{ {}, new_emu_cfg.avport }; + atomic_t callback_cfg{}; + bool backend_error_occured = false; + + AudioDumper dumper{}; + audio_resampler resampler{}; + + // Backend + void backend_init(const rsxaudio_state& ra_state, const emu_audio_cfg& emu_cfg, bool reset_backend = true); + void backend_start(); + void backend_stop(); + bool backend_playing(); + u32 write_data_callback(u32 bytes, void* buf); + void error_callback(); + + // Time management + u64 get_time_until_service(); + void update_service_time(); + void reset_service_time(); + + // Helpers + static emu_audio_cfg get_emu_cfg(); + static u8 gen_mute_state(avport_bit avports); + static RsxaudioAvportIdx convert_avport(audio_avport avport); +}; + +class rsxaudio_data_thread +{ +public: + + // Prevent creation of multiple rsxaudio contexts + atomic_t rsxaudio_ctx_allocated = false; + + shared_mutex rsxaudio_obj_upd_m{}; + std::shared_ptr rsxaudio_obj_ptr{}; + + void operator()(); + rsxaudio_data_thread& operator=(thread_state state); + + rsxaudio_data_thread(); + + void update_hw_param(std::function update_callback); + void update_mute_state(RsxaudioPort port, bool muted); + void update_av_mute_state(RsxaudioAvportIdx avport, bool muted, bool force_mute, bool set = true); + void reset_hw(); + + static constexpr auto thread_name = "RsxAudioData Thread"sv; + +private: + + rsxaudio_data_container::buf_t output_buf{}; + + transactional_storage hw_param_ts{std::make_shared(), std::make_shared()}; + rsxaudio_periodic_tmr timer{}; + + void advance_all_timers(); + void extract_audio_data(); + static std::pair get_ringbuf_addr(RsxaudioPort dst, const lv2_rsxaudio& rsxaudio_obj); + + static f32 pcm_to_float(s32 sample); + static f32 pcm_to_float(s16 sample); + static void pcm_serial_process_channel(RsxaudioSampleSize word_bits, ra_stream_blk_t& buf_out_l, ra_stream_blk_t& buf_out_r, const void* buf_in, u8 src_stream); + static void pcm_spdif_process_channel(RsxaudioSampleSize word_bits, ra_stream_blk_t& buf_out_l, ra_stream_blk_t& buf_out_r, const void* buf_in); + bool enqueue_data(RsxaudioPort dst, bool silence, const void* src_addr, const rsxaudio_hw_param_t& hwp); + + static rsxaudio_backend_thread::avport_bit calc_avport_mute_state(const rsxaudio_hw_param_t& hwp); + static bool calc_port_active_state(RsxaudioPort port, const rsxaudio_hw_param_t& hwp); +}; + +using rsx_audio_backend = named_thread; +using rsx_audio_data = named_thread; // SysCalls @@ -50,3 +622,9 @@ error_code sys_rsxaudio_initialize(vm::ptr handle); error_code sys_rsxaudio_finalize(u32 handle); error_code sys_rsxaudio_import_shared_memory(u32 handle, vm::ptr addr); error_code sys_rsxaudio_unimport_shared_memory(u32 handle, vm::ptr addr); +error_code sys_rsxaudio_create_connection(u32 handle); +error_code sys_rsxaudio_close_connection(u32 handle); +error_code sys_rsxaudio_prepare_process(u32 handle); +error_code sys_rsxaudio_start_process(u32 handle); +error_code sys_rsxaudio_stop_process(u32 handle); +error_code sys_rsxaudio_get_dma_param(u32 handle, u32 flag, vm::ptr out); diff --git a/rpcs3/Emu/Cell/lv2/sys_time.cpp b/rpcs3/Emu/Cell/lv2/sys_time.cpp index f2f4eea393..6b72a9dbee 100644 --- a/rpcs3/Emu/Cell/lv2/sys_time.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_time.cpp @@ -3,6 +3,7 @@ #include "Emu/system_config.h" #include "Emu/Cell/ErrorCodes.h" +#include "Emu/Cell/timers.hpp" #include "util/asm.hpp" @@ -134,23 +135,34 @@ LOG_CHANNEL(sys_time); static constexpr u64 g_timebase_freq = /*79800000*/ 80000000ull; // 80 Mhz -// Auxiliary functions +// Convert time is microseconds to timebased time +u64 convert_to_timebased_time(u64 time) +{ + const u64 result = time * (g_timebase_freq / 1000000ull) * g_cfg.core.clocks_scale / 100u; + ensure(result >= timebase_offset); + return result - timebase_offset; +} + u64 get_timebased_time() { + while (true) + { #ifdef _WIN32 - LARGE_INTEGER count; - ensure(QueryPerformanceCounter(&count)); + LARGE_INTEGER count; + ensure(QueryPerformanceCounter(&count)); - const u64 time = count.QuadPart; - const u64 freq = s_time_aux_info.perf_freq; + const u64 time = count.QuadPart; + const u64 freq = s_time_aux_info.perf_freq; - return ((time / freq * g_timebase_freq + time % freq * g_timebase_freq / freq) * g_cfg.core.clocks_scale / 100u) - timebase_offset; + const u64 result = (time / freq * g_timebase_freq + time % freq * g_timebase_freq / freq) * g_cfg.core.clocks_scale / 100u; #else - struct timespec ts; - ensure(::clock_gettime(CLOCK_MONOTONIC, &ts) == 0); + struct timespec ts; + ensure(::clock_gettime(CLOCK_MONOTONIC, &ts) == 0); - return ((static_cast(ts.tv_sec) * g_timebase_freq + static_cast(ts.tv_nsec) * g_timebase_freq / 1000000000ull) * g_cfg.core.clocks_scale / 100u) - timebase_offset; + const u64 result = (static_cast(ts.tv_sec) * g_timebase_freq + static_cast(ts.tv_nsec) * g_timebase_freq / 1000000000ull) * g_cfg.core.clocks_scale / 100u; #endif + if (result) return result - timebase_offset; + } } // Add an offset to get_timebased_time to avoid leaking PC's uptime into the game diff --git a/rpcs3/Emu/Cell/lv2/sys_uart.cpp b/rpcs3/Emu/Cell/lv2/sys_uart.cpp index ce121d7e1a..ed54e6b3e6 100644 --- a/rpcs3/Emu/Cell/lv2/sys_uart.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_uart.cpp @@ -420,7 +420,7 @@ struct video_disable_signal_cmd : public ps3av_cmd if (pkt->avport <= static_cast(UartAudioAvport::HDMI_1)) { - // TBA + g_fxo->get().update_av_mute_state(vuart.avport_to_idx(static_cast(pkt->avport.get())), false, true); if (pkt->avport == static_cast(UartAudioAvport::HDMI_1) && !g_cfg.core.debug_console_mode) { @@ -499,7 +499,7 @@ struct av_audio_mute_cmd : public ps3av_cmd return; } - // TBA + g_fxo->get().update_av_mute_state(vuart.avport_to_idx(static_cast(pkt->avport.get())), true, false, pkt->mute); vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); } @@ -833,7 +833,7 @@ struct audio_init_cmd : public ps3av_cmd return; } - // TBA + g_fxo->get().reset_hw(); vuart.write_resp(pkt->cid, PS3AV_STATUS_SUCCESS); } @@ -871,7 +871,7 @@ private: bool set_mode(const ps3av_pkt_audio_mode& pkt) { bool spdif_use_serial_buf = false; - u8 avport_src, rsxaudio_port; + RsxaudioPort avport_src, rsxaudio_port; RsxaudioAvportIdx avport_idx; switch (pkt.avport) @@ -881,11 +881,11 @@ private: avport_idx = RsxaudioAvportIdx::HDMI_0; if (pkt.audio_source == UartAudioSource::SPDIF) { - avport_src = rsxaudio_port = SYS_RSXAUDIO_PORT_SPDIF_1; + avport_src = rsxaudio_port = RsxaudioPort::SPDIF_1; } else { - avport_src = rsxaudio_port = SYS_RSXAUDIO_PORT_SERIAL; + avport_src = rsxaudio_port = RsxaudioPort::SERIAL; } break; } @@ -894,32 +894,32 @@ private: avport_idx = RsxaudioAvportIdx::HDMI_1; if (pkt.audio_source == UartAudioSource::SPDIF) { - avport_src = rsxaudio_port = SYS_RSXAUDIO_PORT_SPDIF_1; + avport_src = rsxaudio_port = RsxaudioPort::SPDIF_1; } else { - avport_src = rsxaudio_port = SYS_RSXAUDIO_PORT_SERIAL; + avport_src = rsxaudio_port = RsxaudioPort::SERIAL; } break; } case UartAudioAvport::AVMULTI_0: { avport_idx = RsxaudioAvportIdx::AVMULTI; - avport_src = rsxaudio_port = SYS_RSXAUDIO_PORT_SERIAL; + avport_src = rsxaudio_port = RsxaudioPort::SERIAL; break; } case UartAudioAvport::SPDIF_0: { avport_idx = RsxaudioAvportIdx::SPDIF_0; - rsxaudio_port = SYS_RSXAUDIO_PORT_SPDIF_0; + rsxaudio_port = RsxaudioPort::SPDIF_0; if (pkt.audio_source == UartAudioSource::SERIAL) { spdif_use_serial_buf = true; - avport_src = SYS_RSXAUDIO_PORT_SERIAL; + avport_src = RsxaudioPort::SERIAL; } else { - avport_src = SYS_RSXAUDIO_PORT_SPDIF_0; + avport_src = RsxaudioPort::SPDIF_0; } break; @@ -927,15 +927,15 @@ private: case UartAudioAvport::SPDIF_1: { avport_idx = RsxaudioAvportIdx::SPDIF_1; - rsxaudio_port = SYS_RSXAUDIO_PORT_SPDIF_1; + rsxaudio_port = RsxaudioPort::SPDIF_1; if (pkt.audio_source == UartAudioSource::SERIAL) { spdif_use_serial_buf = true; - avport_src = SYS_RSXAUDIO_PORT_SERIAL; + avport_src = RsxaudioPort::SERIAL; } else { - avport_src = SYS_RSXAUDIO_PORT_SPDIF_1; + avport_src = RsxaudioPort::SPDIF_1; } break; @@ -946,25 +946,11 @@ private: } } - const u32 freq = [&]() - { - switch (pkt.audio_fs) - { - case UartAudioFreq::_44K: return 44100; - case UartAudioFreq::_48K: return 48000; - case UartAudioFreq::_88K: return 88200; - case UartAudioFreq::_96K: return 96000; - case UartAudioFreq::_176K: return 176400; - case UartAudioFreq::_192K: return 192000; - default: return 0; - } - }(); - - if (freq == 0) return false; + if (static_cast(pkt.audio_fs.value()) > static_cast(UartAudioFreq::_192K)) return false; const auto bit_cnt = [&]() { - if ((rsxaudio_port != SYS_RSXAUDIO_PORT_SERIAL && pkt.audio_format != UartAudioFormat::PCM) || + if ((rsxaudio_port != RsxaudioPort::SERIAL && pkt.audio_format != UartAudioFormat::PCM) || pkt.audio_word_bits == UartAudioSampleSize::_16BIT) { return UartAudioSampleSize::_16BIT; @@ -975,29 +961,58 @@ private: } }(); - return commit_param(rsxaudio_port, avport_idx, avport_src, freq, bit_cnt, spdif_use_serial_buf, pkt.audio_cs_info); + return commit_param(rsxaudio_port, avport_idx, avport_src, pkt.audio_fs, bit_cnt, spdif_use_serial_buf, pkt.audio_cs_info); } - bool commit_param(u8 rsxaudio_port, RsxaudioAvportIdx avport, [[maybe_unused]] u8 avport_src, [[maybe_unused]] u32 freq, - UartAudioSampleSize bit_cnt, [[maybe_unused]] bool spdif_use_serial_buf, [[maybe_unused]] const u8 *cs_data) + bool commit_param(RsxaudioPort rsxaudio_port, RsxaudioAvportIdx avport, RsxaudioPort avport_src, UartAudioFreq freq, + UartAudioSampleSize bit_cnt, bool spdif_use_serial_buf, const u8 *cs_data) { - // TBA - [[maybe_unused]] const auto avport_idx = static_cast>(avport); - [[maybe_unused]] const auto rsxaudio_word_depth = bit_cnt == UartAudioSampleSize::_16BIT ? RsxaudioSampleSize::_16BIT : RsxaudioSampleSize::_32BIT; + auto& rsxaudio_thread = g_fxo->get(); + const auto avport_idx = static_cast>(avport); + const auto rsxaudio_word_depth = bit_cnt == UartAudioSampleSize::_16BIT ? RsxaudioSampleSize::_16BIT : RsxaudioSampleSize::_32BIT; + const auto freq_param = [&]() + { + switch (freq) + { + case UartAudioFreq::_44K: return std::make_pair(8, SYS_RSXAUDIO_FREQ_BASE_352K); + default: + case UartAudioFreq::_48K: return std::make_pair(8, SYS_RSXAUDIO_FREQ_BASE_384K); + case UartAudioFreq::_88K: return std::make_pair(4, SYS_RSXAUDIO_FREQ_BASE_352K); + case UartAudioFreq::_96K: return std::make_pair(4, SYS_RSXAUDIO_FREQ_BASE_384K); + case UartAudioFreq::_176K: return std::make_pair(2, SYS_RSXAUDIO_FREQ_BASE_352K); + case UartAudioFreq::_192K: return std::make_pair(2, SYS_RSXAUDIO_FREQ_BASE_384K); + } + }(); switch (rsxaudio_port) { - case SYS_RSXAUDIO_PORT_SERIAL: + case RsxaudioPort::SERIAL: { - // TBA + rsxaudio_thread.update_hw_param([&](auto& obj) + { + obj.serial_freq_base = freq_param.second; + obj.serial.freq_div = freq_param.first; + obj.serial.depth = rsxaudio_word_depth; + obj.serial.buf_empty_en = true; + obj.avport_src[avport_idx] = avport_src; + }); break; } - case SYS_RSXAUDIO_PORT_SPDIF_0: - case SYS_RSXAUDIO_PORT_SPDIF_1: + case RsxaudioPort::SPDIF_0: + case RsxaudioPort::SPDIF_1: { - [[maybe_unused]] const u8 spdif_idx = rsxaudio_port == SYS_RSXAUDIO_PORT_SPDIF_1; + const u8 spdif_idx = rsxaudio_port == RsxaudioPort::SPDIF_1; - // TBA + rsxaudio_thread.update_hw_param([&](auto& obj) + { + obj.spdif_freq_base = freq_param.second; + obj.spdif[spdif_idx].freq_div = freq_param.first; + obj.spdif[spdif_idx].depth = rsxaudio_word_depth; + obj.spdif[spdif_idx].use_serial_buf = spdif_use_serial_buf; + obj.spdif[spdif_idx].buf_empty_en = true; + obj.avport_src[avport_idx] = avport_src; + memcpy(obj.spdif[spdif_idx].cs_data.data(), cs_data, sizeof(obj.spdif[spdif_idx].cs_data)); + }); break; } default: @@ -1034,13 +1049,13 @@ struct audio_mute_cmd : public ps3av_cmd case UartAudioAvport::HDMI_1: case UartAudioAvport::AVMULTI_0: case UartAudioAvport::AVMULTI_1: - // TBA + g_fxo->get().update_mute_state(RsxaudioPort::SERIAL, pkt->mute); break; case UartAudioAvport::SPDIF_0: - // TBA + g_fxo->get().update_mute_state(RsxaudioPort::SPDIF_0, pkt->mute); break; case UartAudioAvport::SPDIF_1: - // TBA + g_fxo->get().update_mute_state(RsxaudioPort::SPDIF_1, pkt->mute); break; default: break; @@ -1067,7 +1082,7 @@ struct audio_set_active_cmd : public ps3av_cmd return; } - [[maybe_unused]] const bool requested_avports[SYS_RSXAUDIO_AVPORT_CNT] = + const bool requested_avports[SYS_RSXAUDIO_AVPORT_CNT] = { (pkt->audio_port & PS3AV_AUDIO_PORT_HDMI_0) != 0U, (pkt->audio_port & PS3AV_AUDIO_PORT_HDMI_1) != 0U, @@ -1076,7 +1091,36 @@ struct audio_set_active_cmd : public ps3av_cmd (pkt->audio_port & PS3AV_AUDIO_PORT_SPDIF_1) != 0U }; - // TBA + g_fxo->get().update_hw_param([&](auto &obj) + { + for (u8 avport_idx = 0; avport_idx < SYS_RSXAUDIO_AVPORT_CNT; avport_idx++) + { + if (requested_avports[avport_idx]) + { + switch (obj.avport_src[avport_idx]) + { + case RsxaudioPort::SERIAL: + obj.serial.en = true; + break; + case RsxaudioPort::SPDIF_0: + case RsxaudioPort::SPDIF_1: + { + const u8 spdif_idx = obj.avport_src[avport_idx] == RsxaudioPort::SPDIF_1; + if (!obj.spdif[spdif_idx].use_serial_buf) + { + obj.spdif[spdif_idx].en = true; + } + break; + } + default: + break; + } + } + } + + obj.serial.muted = false; + obj.spdif[1].muted = false; + }); vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); } @@ -1099,7 +1143,24 @@ struct audio_set_inactive_cmd : public ps3av_cmd return; } - // TBA + g_fxo->get().update_hw_param([&](auto &obj) + { + if ((pkt->audio_port & 0x8000'0000) == 0U) + { + obj.avport_src.fill(RsxaudioPort::INVALID); + } + + obj.serial.en = false; + obj.serial.muted = true; + obj.spdif[1].muted = true; + for (u8 spdif_idx = 0; spdif_idx < SYS_RSXAUDIO_SPDIF_CNT; spdif_idx++) + { + if (!obj.spdif[spdif_idx].use_serial_buf) + { + obj.spdif[spdif_idx].en = false; + } + } + }); vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); } @@ -1122,7 +1183,7 @@ struct audio_spdif_bit_cmd : public ps3av_cmd return; } - [[maybe_unused]] const bool requested_avports[SYS_RSXAUDIO_AVPORT_CNT] = + const bool requested_avports[SYS_RSXAUDIO_AVPORT_CNT] = { (pkt->audio_port & PS3AV_AUDIO_PORT_HDMI_0) != 0U, (pkt->audio_port & PS3AV_AUDIO_PORT_HDMI_1) != 0U, @@ -1131,7 +1192,21 @@ struct audio_spdif_bit_cmd : public ps3av_cmd (pkt->audio_port & PS3AV_AUDIO_PORT_SPDIF_1) != 0U }; - // TBA + g_fxo->get().update_hw_param([&](auto &obj) + { + for (u8 avport_idx = 0; avport_idx < SYS_RSXAUDIO_AVPORT_CNT; avport_idx++) + { + if (requested_avports[avport_idx] && obj.avport_src[avport_idx] == RsxaudioPort::SPDIF_0) + { + auto &b_data = pkt->spdif_bit_data; + + sys_uart.notice("[audio_spdif_bit_cmd] Data 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x", + b_data[0], b_data[1], b_data[2], b_data[3], b_data[4], b_data[5], b_data[6], b_data[7], + b_data[8], b_data[9], b_data[10], b_data[11]); + break; + } + } + }); vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); } @@ -1243,7 +1318,7 @@ struct inc_avset_cmd : public ps3av_cmd return; } - u8 hdcp_done_cnt[2]{}; + bool hdcp_done[2]{}; // AV Video for (u32 video_av_pkt_idx = 0; video_av_pkt_idx < pkt->num_of_av_video_pkt; video_av_pkt_idx++) @@ -1259,29 +1334,24 @@ struct inc_avset_cmd : public ps3av_cmd if (syscon_check_passed) { - if (subcmd_resp.hdcp_done_event[0]) hdcp_done_cnt[0]++; - if (subcmd_resp.hdcp_done_event[1]) hdcp_done_cnt[1]++; + hdcp_done[0] |= subcmd_resp.hdcp_done_event[0]; + hdcp_done[1] |= subcmd_resp.hdcp_done_event[1]; } pkt_data_addr += av_video_pkt->hdr.length + 4ULL; } - while (hdcp_done_cnt[0] || hdcp_done_cnt[1]) - { - vuart.hdmi_res_set[0] = hdcp_done_cnt[0] > 0; - vuart.hdmi_res_set[1] = hdcp_done_cnt[1] > 0; - vuart.add_hdmi_events(UartHdmiEvent::HDCP_DONE, vuart.hdmi_res_set[0], vuart.hdmi_res_set[1]); + vuart.hdmi_res_set[0] = hdcp_done[0]; + vuart.hdmi_res_set[1] = hdcp_done[1]; + vuart.add_hdmi_events(UartHdmiEvent::HDCP_DONE, vuart.hdmi_res_set[0], vuart.hdmi_res_set[1]); - if (hdcp_done_cnt[0]) - { - // TBA - hdcp_done_cnt[0]--; - } - if (hdcp_done_cnt[1]) - { - // TBA - hdcp_done_cnt[1]--; - } + if (vuart.hdmi_res_set[0]) + { + g_fxo->get().update_av_mute_state(RsxaudioAvportIdx::HDMI_0, false, true); + } + if (vuart.hdmi_res_set[1]) + { + g_fxo->get().update_av_mute_state(RsxaudioAvportIdx::HDMI_1, false, true); } bool valid_av_audio_pkt = false; @@ -1301,9 +1371,43 @@ struct inc_avset_cmd : public ps3av_cmd break; } - [[maybe_unused]] const u8 hdmi_idx = av_audio_pkt->avport == static_cast(UartAudioAvport::HDMI_1); + const u8 hdmi_idx = av_audio_pkt->avport == static_cast(UartAudioAvport::HDMI_1); - // TBA + g_fxo->get().update_hw_param([&](auto &obj) + { + auto &hdmi = obj.hdmi[hdmi_idx]; + hdmi.init = true; + + const std::array fifomap = + { + static_cast((av_audio_pkt->fifomap >> 0) & 3U), + static_cast((av_audio_pkt->fifomap >> 2) & 3U), + static_cast((av_audio_pkt->fifomap >> 4) & 3U), + static_cast((av_audio_pkt->fifomap >> 6) & 3U) + }; + + const std::array en_streams = + { + static_cast(av_audio_pkt->enable & 0x10), + static_cast(av_audio_pkt->enable & 0x20), + static_cast(av_audio_pkt->enable & 0x40), + static_cast(av_audio_pkt->enable & 0x80) + }; + + // Might be wrong + const std::array swap_lr = + { + static_cast(av_audio_pkt->swaplr & 0x10), + static_cast(av_audio_pkt->swaplr & 0x20), + static_cast(av_audio_pkt->swaplr & 0x40), + static_cast(av_audio_pkt->swaplr & 0x80) + }; + + memcpy(hdmi.info_frame.data(), av_audio_pkt->info, sizeof(av_audio_pkt->info)); + memcpy(hdmi.chstat.data(), av_audio_pkt->chstat, sizeof(av_audio_pkt->chstat)); + + hdmi.ch_cfg = hdmi_param_conv(fifomap, en_streams, swap_lr); + }); } pkt_data_addr += av_audio_pkt->hdr.length + 4ULL; @@ -1342,7 +1446,7 @@ private: u32 video_pkt_parse(const ps3av_pkt_video_mode &video_head_cfg) { - constexpr video_sce_param sce_param_arr[28] = + static constexpr video_sce_param sce_param_arr[28] = { { 0, 0, 0 }, { 4, 2880, 480 }, @@ -1430,7 +1534,7 @@ private: ((1ULL << video_head_cfg.video_out_format) & 0x1CE07) == 0U || video_head_cfg.unk2 > 3 || video_head_cfg.pitch & 7 || - video_head_cfg.pitch >> 16 || + video_head_cfg.pitch > UINT16_MAX || (video_head_cfg.width != 1280U && ((video_head_cfg.width & 7) != 0U || video_head_cfg.width > UINT16_MAX)) || (sce_param.width != 720 && video_head_cfg.width > sce_param.width / sce_param.width_div) || !((video_head_cfg.height == 1470U && (sce_param.height == 721 || sce_param.height == 481 || sce_param.height == 577)) || (video_head_cfg.height <= sce_param.height && video_head_cfg.height <= UINT16_MAX))) @@ -1486,6 +1590,48 @@ private: return {}; } + + static rsxaudio_hw_param_t::hdmi_param_t::hdmi_ch_cfg_t hdmi_param_conv(const std::array &map, + const std::array &en, + const std::array &swap) + { + std::array result{}; + u8 ch_cnt = 0; + + for (usz stream_idx = 0; stream_idx < SYS_RSXAUDIO_SERIAL_STREAM_CNT; stream_idx++) + { + const u8 stream_pos = map[stream_idx]; + if (en[stream_pos]) + { + result[stream_idx * 2 + 0] = stream_pos * 2 + swap[stream_pos]; + result[stream_idx * 2 + 1] = stream_pos * 2 + !swap[stream_pos]; + ch_cnt = static_cast((stream_idx + 1) * 2); + } + else + { + result[stream_idx * 2 + 0] = rsxaudio_hw_param_t::hdmi_param_t::MAP_SILENT_CH; + result[stream_idx * 2 + 1] = rsxaudio_hw_param_t::hdmi_param_t::MAP_SILENT_CH; + } + } + + const AudioChannelCnt ch_cnt_conv = [&]() + { + switch (ch_cnt) + { + default: + case 0: + case 2: + return AudioChannelCnt::STEREO; + case 4: + case 6: + return AudioChannelCnt::SURROUND_5_1; + case 8: + return AudioChannelCnt::SURROUND_7_1; + } + }(); + + return { result, ch_cnt_conv }; + } }; struct generic_reply_cmd : public ps3av_cmd @@ -1622,7 +1768,7 @@ error_code sys_uart_receive(ppu_thread &ppu, vm::ptr buffer, u64 size, u32 read_size = read_result; } - if (!buffer || !vm::check_addr(buffer.addr(), vm::page_writable, read_size)) + if (!vm::check_addr(buffer.addr(), vm::page_writable, read_size)) { return CELL_EFAULT; } @@ -1781,7 +1927,7 @@ error_code sys_uart_get_params(vm::ptr buffer) return CELL_ESRCH; } - if (!buffer || !vm::check_addr(buffer.addr(), vm::page_writable, sizeof(vuart_params))) + if (!vm::check_addr(buffer.addr(), vm::page_writable, sizeof(vuart_params))) { return CELL_EFAULT; } @@ -1913,6 +2059,9 @@ void vuart_av_thread::parse_tx_buffer(u32 buf_size) vuart_av_thread &vuart_av_thread::operator=(thread_state) { + { + std::lock_guard lock(tx_wake_m); + } tx_wake_c.notify_all(); return *this; } @@ -1920,7 +2069,7 @@ vuart_av_thread &vuart_av_thread::operator=(thread_state) u32 vuart_av_thread::enque_tx_data(const void *data, u32 data_sz) { std::unique_lock lock(tx_wake_m); - if (auto size = tx_buf.push(data, data_sz)) + if (u32 size = static_cast(tx_buf.push(data, data_sz, true))) { lock.unlock(); tx_wake_c.notify_all(); @@ -1932,18 +2081,18 @@ u32 vuart_av_thread::enque_tx_data(const void *data, u32 data_sz) u32 vuart_av_thread::get_tx_bytes() { - return tx_buf.get_used_size(); + return static_cast(tx_buf.get_used_size()); } u32 vuart_av_thread::read_rx_data(void *data, u32 data_sz) { - return rx_buf.pop(data, data_sz); + return static_cast(rx_buf.pop(data, data_sz, true)); } u32 vuart_av_thread::read_tx_data(void *data, u32 data_sz) { std::unique_lock lock(tx_rdy_m); - if (auto size = tx_buf.pop(data, data_sz)) + if (u32 size = static_cast(tx_buf.pop(data, data_sz, true))) { lock.unlock(); tx_rdy_c.notify_all(); @@ -2042,7 +2191,7 @@ void vuart_av_thread::commit_rx_buf(bool syscon_buf) temp_buf &buf = syscon_buf ? temp_rx_sc_buf : temp_rx_buf; std::unique_lock lock(rx_wake_m); - rx_buf.push(buf.buf, buf.crnt_size); + rx_buf.push(buf.buf, buf.crnt_size, true); buf.crnt_size = 0; if (rx_buf.get_used_size()) @@ -2127,14 +2276,14 @@ void vuart_av_thread::add_unplug_event(bool hdmi_0, bool hdmi_1) if (hdmi_0) { - // TBA + g_fxo->get().update_av_mute_state(RsxaudioAvportIdx::HDMI_0, false, true); hdcp_first_auth[0] = true; commit_event_data(&pkt, sizeof(pkt)); } if (hdmi_1) { - // TBA + g_fxo->get().update_av_mute_state(RsxaudioAvportIdx::HDMI_1, false, true); hdcp_first_auth[1] = true; pkt.cid |= 0x10000; commit_event_data(&pkt, sizeof(pkt)); @@ -2152,14 +2301,14 @@ void vuart_av_thread::add_plug_event(bool hdmi_0, bool hdmi_1) if (hdmi_0) { - // TBA + g_fxo->get().update_av_mute_state(RsxaudioAvportIdx::HDMI_0, false, true); av_get_monitor_info_cmd::set_hdmi_display_cfg(*this, pkt.minfo, static_cast(UartAudioAvport::HDMI_0)); commit_event_data(&pkt, sizeof(pkt) - 4); } if (hdmi_1) { - // TBA + g_fxo->get().update_av_mute_state(RsxaudioAvportIdx::HDMI_1, false, true); memset(&pkt.minfo, 0, sizeof(pkt.minfo)); pkt.hdr.cid |= 0x10000; av_get_monitor_info_cmd::set_hdmi_display_cfg(*this, pkt.minfo, static_cast(UartAudioAvport::HDMI_1)); @@ -2184,7 +2333,7 @@ void vuart_av_thread::add_hdcp_done_event(bool hdmi_0, bool hdmi_1) if (hdmi_0) { - // TBA + g_fxo->get().update_av_mute_state(RsxaudioAvportIdx::HDMI_0, false, true, false); if (hdcp_first_auth[0]) { @@ -2204,7 +2353,7 @@ void vuart_av_thread::add_hdcp_done_event(bool hdmi_0, bool hdmi_1) if (hdmi_1) { - // TBA + g_fxo->get().update_av_mute_state(RsxaudioAvportIdx::HDMI_1, false, true, false); if (hdcp_first_auth[1]) { @@ -2226,7 +2375,7 @@ void vuart_av_thread::add_hdcp_done_event(bool hdmi_0, bool hdmi_1) void vuart_av_thread::commit_event_data(const void *data, u16 data_size) { std::unique_lock lock(rx_wake_m); - rx_buf.push(data, data_size); + rx_buf.push(data, data_size, true); if (rx_buf.get_used_size()) { diff --git a/rpcs3/Emu/Cell/timers.hpp b/rpcs3/Emu/Cell/timers.hpp index ec6c2d04eb..7656f9f953 100644 --- a/rpcs3/Emu/Cell/timers.hpp +++ b/rpcs3/Emu/Cell/timers.hpp @@ -2,6 +2,7 @@ #include "util/types.hpp" +u64 convert_to_timebased_time(u64 time); u64 get_timebased_time(); void initalize_timebased_time(); u64 get_system_time(); diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index 6e67ff6060..83953d88a4 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -230,7 +230,8 @@ struct cfg_root : cfg::node node_audio(cfg::node* _this) : cfg::node(_this, "Audio") {} cfg::_enum renderer{ this, "Renderer", audio_renderer::cubeb, true }; - cfg::_enum provider{ this, "Audio provider", audio_provider::cell_audio, false }; + cfg::_enum provider{ this, "Audio Provider", audio_provider::cell_audio, false }; + cfg::_enum rsxaudio_port{ this, "RSXAudio Avport", audio_avport::hdmi_0, true }; cfg::_bool dump_to_file{ this, "Dump to file", false, true }; cfg::_bool convert_to_s16{ this, "Convert to 16 bit", false, true }; cfg::_enum audio_channel_downmix{ this, "Audio Channels", audio_downmix::downmix_to_stereo, true }; diff --git a/rpcs3/Emu/system_config_types.cpp b/rpcs3/Emu/system_config_types.cpp index 7432b6506d..a527486ff5 100644 --- a/rpcs3/Emu/system_config_types.cpp +++ b/rpcs3/Emu/system_config_types.cpp @@ -512,6 +512,24 @@ void fmt_class_string::format(std::string& out, u64 arg) }); } +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](audio_avport value) + { + switch (value) + { + case audio_avport::hdmi_0: return "HDMI 0"; + case audio_avport::hdmi_1: return "HDMI 1"; + case audio_avport::avmulti: return "AV multiout"; + case audio_avport::spdif_0: return "SPDIF 0"; + case audio_avport::spdif_1: return "SPDIF 1"; + } + + return unknown; + }); +} + template <> void fmt_class_string::format(std::string& out, u64 arg) { diff --git a/rpcs3/Emu/system_config_types.h b/rpcs3/Emu/system_config_types.h index 1b8e26a139..8cdf3bf3a7 100644 --- a/rpcs3/Emu/system_config_types.h +++ b/rpcs3/Emu/system_config_types.h @@ -67,6 +67,15 @@ enum class audio_provider rsxaudio }; +enum class audio_avport +{ + hdmi_0, + hdmi_1, + avmulti, + spdif_0, + spdif_1 +}; + enum class audio_downmix { no_downmix, // Surround 7.1 diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index cb5efa5e38..b2b1b45d62 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -446,6 +446,8 @@ + + @@ -813,4 +815,4 @@ - \ No newline at end of file + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index 1aae85bb56..222104efaa 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -2083,6 +2083,12 @@ Emu\Io\Null + + Utilities + + + Utilities + Emu\GPU\RSX\Overlays @@ -2113,4 +2119,4 @@ Emu\GPU\RSX\Common\Interpreter - \ No newline at end of file + diff --git a/rpcs3/rpcs3qt/emu_settings_type.h b/rpcs3/rpcs3qt/emu_settings_type.h index d96cbf9e73..507cf7d470 100644 --- a/rpcs3/rpcs3qt/emu_settings_type.h +++ b/rpcs3/rpcs3qt/emu_settings_type.h @@ -121,6 +121,8 @@ enum class emu_settings_type DumpToFile, ConvertTo16Bit, AudioChannels, + AudioProvider, + AudioAvport, MasterVolume, EnableBuffering, AudioBufferDuration, @@ -288,6 +290,8 @@ inline static const QMap settings_location = { emu_settings_type::DumpToFile, { "Audio", "Dump to file"}}, { emu_settings_type::ConvertTo16Bit, { "Audio", "Convert to 16 bit"}}, { emu_settings_type::AudioChannels, { "Audio", "Audio Channels"}}, + { emu_settings_type::AudioProvider, { "Audio", "Audio Provider"}}, + { emu_settings_type::AudioAvport, { "Audio", "RSXAudio Avport"}}, { emu_settings_type::MasterVolume, { "Audio", "Master Volume"}}, { emu_settings_type::EnableBuffering, { "Audio", "Enable Buffering"}}, { emu_settings_type::AudioBufferDuration, { "Audio", "Desired Audio Buffer Duration"}}, diff --git a/rpcs3/rpcs3qt/gui_application.cpp b/rpcs3/rpcs3qt/gui_application.cpp index 0c210045ff..1a22191ee1 100644 --- a/rpcs3/rpcs3qt/gui_application.cpp +++ b/rpcs3/rpcs3qt/gui_application.cpp @@ -20,6 +20,7 @@ #include "Emu/Io/Null/null_camera_handler.h" #include "Emu/Io/Null/null_music_handler.h" #include "Emu/Cell/Modules/cellAudio.h" +#include "Emu/Cell/lv2/sys_rsxaudio.h" #include "Emu/RSX/Overlays/overlay_perf_metrics.h" #include "Emu/system_utils.hpp" #include "Emu/vfs_config.h" @@ -629,6 +630,7 @@ void gui_application::OnEmuSettingsChange() rpcs3::utils::configure_logs(); audio::configure_audio(); + audio::configure_rsxaudio(); rsx::overlays::reset_performance_overlay(); } diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index bb7cfa36ee..c68dbd1c8a 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -1110,7 +1110,7 @@ void main_window::HandlePupInstallation(const QString& file_path, const QString& { gui_log.error("Error while extracting firmware: Failed to mount '%s'", sstr(dir_path)); critical(tr("Firmware extraction failed: VFS mounting failed.")); - return; + return; } if (!update_files.extract("/pup_extract")) @@ -2789,7 +2789,7 @@ void main_window::CreateFirmwareCache() Emu.GracefulShutdown(false); Emu.SetForceBoot(true); - if (const game_boot_result error = Emu.BootGame(g_cfg_vfs.get_dev_flash() + "sys", "", true); + if (const game_boot_result error = Emu.BootGame(g_cfg_vfs.get_dev_flash(), "", true); error != game_boot_result::no_errors) { gui_log.error("Creating firmware cache failed: reason: %s", error); @@ -3004,7 +3004,7 @@ void main_window::dropEvent(QDropEvent* event) // Refresh game list since we probably unlocked some games now. m_game_list_frame->Refresh(true); } - + break; } case drop_type::drop_psf: // Display PARAM.SFO content diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index c0be6dfd7e..20712e53d0 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -62,6 +62,8 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std if (!m_gui_settings->GetValue(gui::m_showDebugTab).toBool()) { ui->tab_widget_settings->removeTab(9); + ui->audioDump->setVisible(false); + ui->audioDump->setChecked(false); m_gui_settings->SetValue(gui::m_showDebugTab, false); } if (game) @@ -879,6 +881,16 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std enable_buffering_options(enabled && ui->enableBuffering->isChecked()); }; + const auto enable_avport_option = [this](int index) + { + if (index < 0) return; + const QVariantList var_list = ui->audioProviderBox->itemData(index).toList(); + ensure(var_list.size() == 2 && var_list[0].canConvert()); + const QString text = var_list[0].toString(); + const bool enabled = text == "RSXAudio"; + ui->audioAvportBox->setEnabled(enabled); + }; + const QString mic_none = m_emu_settings->m_microphone_creator.get_none(); const auto change_microphone_type = [mic_none, this](int index) @@ -958,6 +970,13 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std // TODO: enable this setting once cellAudioOutConfigure can change downmix on the fly ui->combo_audio_downmix->removeItem(static_cast(audio_downmix::use_application_settings)); + m_emu_settings->EnhanceComboBox(ui->audioProviderBox, emu_settings_type::AudioProvider); + SubscribeTooltip(ui->gb_audio_provider, tooltips.settings.audio_provider); + connect(ui->audioProviderBox, QOverload::of(&QComboBox::currentIndexChanged), enable_avport_option); + + m_emu_settings->EnhanceComboBox(ui->audioAvportBox, emu_settings_type::AudioAvport); + SubscribeTooltip(ui->gb_audio_avport, tooltips.settings.audio_avport); + // Microphone Comboboxes m_mics_combo[0] = ui->microphone1Box; m_mics_combo[1] = ui->microphone2Box; @@ -1014,6 +1033,7 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std connect(ui->enableTimeStretching, &QCheckBox::toggled, enable_time_stretching_options); enable_buffering(ui->audioOutBox->currentIndex()); + enable_avport_option(ui->audioProviderBox->currentIndex()); // Sliders diff --git a/rpcs3/rpcs3qt/settings_dialog.ui b/rpcs3/rpcs3qt/settings_dialog.ui index caa96b6c82..26f3cfdf6a 100644 --- a/rpcs3/rpcs3qt/settings_dialog.ui +++ b/rpcs3/rpcs3qt/settings_dialog.ui @@ -1049,16 +1049,16 @@ - + - Dump to File + Convert to 16-bit - + - Convert to 16-bit + Dump to File @@ -1113,6 +1113,30 @@ + + + + Audio Provider + + + + + + + + + + + + RSXAudio Avport + + + + + + + + diff --git a/rpcs3/rpcs3qt/tooltips.h b/rpcs3/rpcs3qt/tooltips.h index 7024653f61..c6bd7d08c4 100644 --- a/rpcs3/rpcs3qt/tooltips.h +++ b/rpcs3/rpcs3qt/tooltips.h @@ -46,6 +46,8 @@ public: const QString audio_out = tr("Cubeb uses a cross-platform approach and supports audio buffering, so it is the recommended option.\nXAudio2 uses native Windows sounds system, is the next best alternative."); const QString audio_out_linux = tr("Cubeb uses a cross-platform approach and supports audio buffering, so it is the recommended option.\nIf it's not available, FAudio could be used instead."); + const QString audio_provider = tr("Controls which PS3 audio API is used.\nGames use CellAudio, while VSH requires RSXAudio."); + const QString audio_avport = tr("Controls which avport is used to sample audio data from."); const QString audio_dump = tr("Saves all audio as a raw wave file. If unsure, leave this unchecked."); const QString convert = tr("Uses 16-bit audio samples instead of default 32-bit floating point.\nUse with buggy audio drivers if you have no sound or completely broken sound."); const QString downmix = tr("Uses chosen audio output instead of default 7.1 surround sound.\nUse downmix to stereo with stereo audio devices. Use 5.1 or higher only if you are using a surround sound audio system.");