1
0
mirror of https://github.com/RPCS3/rpcs3.git synced 2024-11-21 18:22:33 +01:00

sys_rsxaudio: Initial implementation (#11907)

This commit is contained in:
Vestrel 2022-05-05 22:47:44 +09:00 committed by GitHub
parent 0ac90ac395
commit d1e468fefb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 4148 additions and 546 deletions

View File

@ -1,129 +1,202 @@
#include "Utilities/simple_ringbuf.h" #include "Utilities/simple_ringbuf.h"
simple_ringbuf::simple_ringbuf(u32 size) simple_ringbuf::simple_ringbuf(u64 size)
{ {
set_buf_size(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) simple_ringbuf::simple_ringbuf(simple_ringbuf&& other)
{ {
rw_ptr = other.rw_ptr.load(); const ctr_state other_rw_ptr = other.rw_ptr.load();
buf_size = other.buf_size;
buf = std::move(other.buf); buf = std::move(other.buf);
initialized = other.initialized.observe(); rw_ptr = other_rw_ptr;
other.buf_size = 0; other.rw_ptr.store({});
other.rw_ptr = 0;
other.initialized = false;
} }
simple_ringbuf& simple_ringbuf::operator=(simple_ringbuf&& other) simple_ringbuf& simple_ringbuf::operator=(simple_ringbuf&& other)
{ {
if (this == &other) return *this; if (this == &other) return *this;
rw_ptr = other.rw_ptr.load(); const ctr_state other_rw_ptr = other.rw_ptr.load();
buf_size = other.buf_size;
buf = std::move(other.buf); buf = std::move(other.buf);
initialized = other.initialized.observe(); rw_ptr = other_rw_ptr;
other.buf_size = 0; other.rw_ptr.store({});
other.rw_ptr = 0;
other.initialized = false;
return *this; return *this;
} }
u32 simple_ringbuf::get_free_size() const u64 simple_ringbuf::get_free_size() const
{ {
const u64 _rw_ptr = rw_ptr; return get_free_size(rw_ptr);
const u32 rd = static_cast<u32>(_rw_ptr);
const u32 wr = static_cast<u32>(_rw_ptr >> 32);
return wr >= rd ? buf_size - 1 - (wr - rd) : rd - wr - 1U;
} }
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; return (wr >= rd ? buf_size + rd - wr : rd - wr) - 1;
buf = std::make_unique<u8[]>(this->buf_size);
flush();
initialized = true;
} }
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<u32>(val >> 32) | (val & 0xFFFFFFFF'00000000); const u64 used = get_used_size(val);
if (used == 0) return;
val.write_ptr += buf.size() - std::min<u64>(used, cnt);
}); });
} }
u32 simple_ringbuf::push(const void *data, u32 size) void simple_ringbuf::reader_flush(u64 cnt)
{ {
ensure(data != nullptr && initialized.observe()); rw_ptr.atomic_op([&](ctr_state& val)
const u32 old = static_cast<u32>(rw_ptr.load() >> 32);
const u32 to_push = std::min(size, get_free_size());
auto b_data = static_cast<const u8*>(data);
if (!to_push) return 0;
if (old + to_push > buf_size)
{ {
const auto first_write_sz = buf_size - old; val.read_ptr += std::min(get_used_size(val), cnt);
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<u64>((old + to_push) % buf_size) << 32 | static_cast<u32>(val);
}); });
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<u32>(rw_ptr.load()); return rw_ptr.atomic_op([&](ctr_state& val) -> u64
const u32 to_pop = std::min(size, get_used_size());
u8 *b_data = static_cast<u8*>(data);
if (!to_pop) return 0;
if (old + to_pop > buf_size)
{ {
const auto first_read_sz = buf_size - old; const u64 buf_size = buf.size();
memcpy(b_data, &buf[old], first_read_sz); const u64 old = val.write_ptr % buf_size;
memcpy(b_data + first_read_sz, &buf[0], to_pop - first_read_sz); const u64 free_size = get_free_size(val);
} const u64 to_push = std::min(size, free_size);
else const auto b_data = static_cast<const u8*>(data);
{
memcpy(b_data, &buf[old], to_pop);
}
rw_ptr.atomic_op([&](u64 &val) if (!to_push || (!force && free_size < size))
{ {
val = (old + to_pop) % buf_size | (val & 0xFFFFFFFF'00000000); 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<u8*>(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;
} }

View File

@ -2,37 +2,53 @@
#include "util/types.hpp" #include "util/types.hpp"
#include "util/atomic.hpp" #include "util/atomic.hpp"
#include <vector>
// Single reader/writer simple ringbuffer. // Single reader/writer simple ringbuffer.
// Counters are 32-bit.
class simple_ringbuf class simple_ringbuf
{ {
private:
atomic_t<u64> rw_ptr = 0;
u32 buf_size = 0;
std::unique_ptr<u8[]> buf{};
atomic_t<bool> initialized = false;
public: public:
simple_ringbuf() {}; simple_ringbuf(u64 size = 0);
simple_ringbuf(u32 size); virtual ~simple_ringbuf();
simple_ringbuf(const simple_ringbuf&) = delete; simple_ringbuf(const simple_ringbuf& other);
simple_ringbuf& operator=(const simple_ringbuf&) = delete; simple_ringbuf& operator=(const simple_ringbuf& other);
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;
// Thread unsafe functions. // Thread unsafe functions.
void set_buf_size(u32 size); simple_ringbuf(simple_ringbuf&& other);
void flush(); // Could be safely called from reader. simple_ringbuf& operator=(simple_ringbuf&& other);
void set_buf_size(u64 size);
u32 push(const void *data, u32 size); // Helper functions
u32 pop(void *data, u32 size); 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<ctr_state> rw_ptr{};
std::vector<u8> buf{};
u64 get_free_size(ctr_state val) const;
u64 get_used_size(ctr_state val) const;
}; };

View File

@ -0,0 +1,159 @@
#include "util/types.hpp"
#include <vector>
#include <mutex>
// 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 <typename F>
requires (std::invocable<F&> && std::is_same_v<std::invoke_result_t<F&>, std::shared_ptr<void>>)
void add_op(F func)
{
std::lock_guard lock(mutex);
if (std::shared_ptr<void> 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<std::shared_ptr<void>> storage{};
u64 gc_last_time = get_system_time();
atomic_t<u32> gc_interval = 0;
};
template<typename T>
class transactional_storage
{
public:
transactional_storage(std::shared_ptr<universal_pool> pool, std::shared_ptr<T> obj = std::make_shared<T>())
{
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<T> 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<T> other_current = other.current;
other.current = nullptr;
lock_other.unlock();
std::lock_guard lock{current_mutex};
current = other_current;
return *this;
}
std::shared_ptr<const T> get_current()
{
reader_lock lock(current_mutex);
return current;
}
void add(std::shared_ptr<T> obj)
{
if (!obj)
{
return;
}
pool->add_op([&]() -> std::shared_ptr<void>
{
{
std::lock_guard lock{current_mutex};
current = obj;
}
return std::move(obj);
});
}
template <typename F>
requires (std::invocable<F&> && std::is_same_v<std::invoke_result_t<F&>, std::shared_ptr<T>>)
void add_op(F func)
{
pool->add_op([&]() -> std::shared_ptr<void>
{
std::shared_ptr<T> obj = std::invoke(func);
if (obj)
{
std::lock_guard lock{current_mutex};
current = obj;
}
return obj;
});
}
private:
shared_mutex current_mutex{};
std::shared_ptr<T> current{};
std::shared_ptr<universal_pool> pool{};
};

View File

@ -4,6 +4,12 @@
AudioBackend::AudioBackend() {} AudioBackend::AudioBackend() {}
void AudioBackend::SetErrorCallback(std::function<void()> cb)
{
std::lock_guard lock(m_error_cb_mutex);
m_error_callback = cb;
}
/* /*
* Helper methods * 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) 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<s16 *>(dst)[i] = static_cast<s16>(std::clamp(src[i] * 32768.5f, -32768.0f, 32767.0f)); static_cast<s16*>(dst)[i] = static_cast<s16>(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<f32>(src[i], -1.0f, 1.0f);
} }
} }

View File

@ -1,7 +1,9 @@
#pragma once #pragma once
#include "util/types.hpp" #include "util/types.hpp"
#include "Utilities/mutex.h"
#include "Utilities/StrFmt.h" #include "Utilities/StrFmt.h"
#include <numbers>
enum : u32 enum : u32
{ {
@ -38,32 +40,54 @@ enum class AudioChannelCnt : u32
class AudioBackend class AudioBackend
{ {
public: 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(); AudioBackend();
virtual ~AudioBackend() = default; virtual ~AudioBackend() = default;
/* /*
* Pure virtual methods * Virtual methods
*/ */
virtual std::string_view GetName() const = 0; 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; virtual void Close() = 0;
// Sets write callback. It's called when backend requests new data to be sent // 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 // Callback should return number of submitted bytes. Calling other backend functions from callback is unsafe.
virtual void SetWriteCallback(std::function<u32(u32 /* byte_cnt */, void * /* buffer */)> cb) = 0; virtual void SetWriteCallback(std::function<u32(u32 /* byte_cnt */, void* /* buffer */)> 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<void()> 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; virtual f64 GetCallbackFrameLen() = 0;
// Returns true if audio is currently being played, false otherwise // Returns true if audio is currently being played, false otherwise. Reflects end result of Play() and Pause() calls.
virtual bool IsPlaying() = 0; virtual bool IsPlaying() { return m_playing; }
// Start playing enqueued data // Start playing enqueued data.
virtual void Play() = 0; 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; virtual void Pause() = 0;
/* /*
@ -93,8 +117,93 @@ public:
*/ */
static void convert_to_s16(u32 cnt, const f32* src, void* dst); 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<AudioChannelCnt from, AudioChannelCnt to>
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<u32>(from) > static_cast<u32>(to), "FROM channel count must be bigger than TO");
static constexpr f32 center_coef = std::numbers::sqrt2_v<f32> / 2;
static constexpr f32 surround_coef = std::numbers::sqrt2_v<f32> / 2;
for (u32 src_sample = 0, dst_sample = 0; src_sample < sample_cnt; src_sample += static_cast<u32>(from), dst_sample += static_cast<u32>(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: protected:
AudioSampleSize m_sample_size = AudioSampleSize::FLOAT; AudioSampleSize m_sample_size = AudioSampleSize::FLOAT;
AudioFreq m_sampling_rate = AudioFreq::FREQ_48K; AudioFreq m_sampling_rate = AudioFreq::FREQ_48K;
AudioChannelCnt m_channels = AudioChannelCnt::STEREO; AudioChannelCnt m_channels = AudioChannelCnt::STEREO;
shared_mutex m_error_cb_mutex{};
std::function<void()> m_error_callback{};
bool m_playing = false;
private:
static constexpr f32 VOLUME_CHANGE_DURATION = 0.016f; // sec
}; };

View File

@ -4,6 +4,8 @@
#include "Utilities/date_time.h" #include "Utilities/date_time.h"
#include "Emu/System.h" #include "Emu/System.h"
#include <bit>
AudioDumper::AudioDumper() AudioDumper::AudioDumper()
{ {
} }
@ -13,30 +15,34 @@ AudioDumper::~AudioDumper()
Close(); Close();
} }
void AudioDumper::Open(u16 ch, u32 sample_rate, u32 sample_size) void AudioDumper::Open(AudioChannelCnt ch, AudioFreq sample_rate, AudioSampleSize sample_size)
{ {
Close(); 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); path += id + "_";
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 += date_time::current_time_narrow<'_'>() + ".wav";
m_output.open(path, fs::rewrite);
m_output.seek(sizeof(m_header));
} }
void AudioDumper::Close() void AudioDumper::Close()
{ {
if (GetCh()) 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.seek(0);
m_output.write(m_header); // rewrite file header m_output.write(m_header); // write file header
m_output.close(); m_output.close();
m_header.FMT.NumChannels = 0; m_header.FMT.NumChannels = 0;
} }
@ -44,11 +50,41 @@ void AudioDumper::Close()
void AudioDumper::WriteData(const void* buffer, u32 size) void AudioDumper::WriteData(const void* buffer, u32 size)
{ {
if (GetCh()) if (GetCh() && size && buffer)
{ {
ensure(size); const u32 blk_size = GetCh() * GetSampleSize();
ensure(m_output.write(buffer, size) == size); 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<u8> tmp_buf(size);
if (GetSampleSize() == sizeof(f32))
{
for (u32 sample_idx = 0; sample_idx < sample_cnt_per_ch * GetCh(); sample_idx++)
{
std::bit_cast<f32*>(tmp_buf.data())[sample_idx] = static_cast<const be_t<f32>*>(buffer)[sample_idx];
}
}
else
{
for (u32 sample_idx = 0; sample_idx < sample_cnt_per_ch * GetCh(); sample_idx++)
{
std::bit_cast<s16*>(tmp_buf.data())[sample_idx] = static_cast<const be_t<s16>*>(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.Size += size;
m_header.RIFF.Size += size; m_header.RIFF.Size += size;
m_header.FACT.SampleLength += sample_cnt_per_ch;
} }
} }

View File

@ -2,60 +2,71 @@
#include "util/types.hpp" #include "util/types.hpp"
#include "Utilities/File.h" #include "Utilities/File.h"
#include "Emu/Audio/AudioBackend.h"
struct WAVHeader struct WAVHeader
{ {
struct RIFFHeader struct RIFFHeader
{ {
u32 ID; // "RIFF" u8 ID[4] = { 'R', 'I', 'F', 'F' };
u32 Size; // FileSize - 8 le_t<u32> Size{}; // FileSize - 8
u32 WAVE; // "WAVE" u8 WAVE[4] = { 'W', 'A', 'V', 'E' };
RIFFHeader() = default; RIFFHeader() = default;
RIFFHeader(u32 size) RIFFHeader(u32 size)
: ID("RIFF"_u32) : Size(size)
, Size(size)
, WAVE("WAVE"_u32)
{ {
} }
} RIFF; } RIFF;
struct FMTHeader struct FMTHeader
{ {
u32 ID; // "fmt " u8 ID[4] = { 'f', 'm', 't', ' ' };
u32 Size; // 16 le_t<u32> Size = 16;
u16 AudioFormat; // 1 for PCM, 3 for IEEE Floating Point le_t<u16> AudioFormat{}; // 1 for PCM, 3 for IEEE Floating Point
u16 NumChannels; // 1, 2, 6, 8 le_t<u16> NumChannels{}; // 1, 2, 6, 8
u32 SampleRate; // 48000 le_t<u32> SampleRate{}; // 44100-192000
u32 ByteRate; // SampleRate * NumChannels * BitsPerSample/8 le_t<u32> ByteRate{}; // SampleRate * NumChannels * BitsPerSample/8
u16 BlockAlign; // NumChannels * BitsPerSample/8 le_t<u16> BlockAlign{}; // NumChannels * BitsPerSample/8
u16 BitsPerSample; // SampleSize * 8 le_t<u16> BitsPerSample{}; // SampleSize * 8
FMTHeader() = default; FMTHeader() = default;
FMTHeader(u16 ch, u32 sample_rate, u32 sample_size) FMTHeader(AudioChannelCnt ch, AudioFreq sample_rate, AudioSampleSize sample_size)
: ID("fmt "_u32) : AudioFormat(sample_size == AudioSampleSize::FLOAT ? 3 : 1)
, Size(16) , NumChannels(static_cast<u16>(ch))
, AudioFormat(sample_size == sizeof(float) ? 3 : 1) , SampleRate(static_cast<u32>(sample_rate))
, NumChannels(ch) , ByteRate(SampleRate * NumChannels * static_cast<u32>(sample_size))
, SampleRate(sample_rate) , BlockAlign(NumChannels * static_cast<u32>(sample_size))
, ByteRate(SampleRate * ch * sample_size) , BitsPerSample(static_cast<u32>(sample_size) * 8)
, BlockAlign(ch * sample_size)
, BitsPerSample(sample_size * 8)
{ {
} }
} FMT; } FMT;
u32 ID; // "data" struct FACTChunk
u32 Size; // size of data (256 * NumChannels * sizeof(float)) {
u8 ID[4] = { 'f', 'a', 'c', 't' };
le_t<u32> ChunkLength = 4;
le_t<u32> 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<u32> Size{}; // size of data (256 * NumChannels * sizeof(f32))
WAVHeader() = default; 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)) : RIFF(sizeof(RIFFHeader) + sizeof(FMTHeader))
, FMT(ch, sample_rate, sample_size) , FMT(ch, sample_rate, sample_size)
, ID("data"_u32) , FACT(0)
, Size(0) , Size(0)
{ {
} }
@ -70,9 +81,10 @@ public:
AudioDumper(); AudioDumper();
~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 Close();
void WriteData(const void* buffer, u32 size); void WriteData(const void* buffer, u32 size);
u16 GetCh() const { return m_header.FMT.NumChannels; } u16 GetCh() const { return m_header.FMT.NumChannels; }
u16 GetSampleSize() const { return m_header.FMT.BitsPerSample / 8; }
}; };

View File

@ -56,12 +56,17 @@ bool CubebBackend::Initialized()
bool CubebBackend::Operational() 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); std::lock_guard lock(m_cb_mutex);
CloseUnlocked(); 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)) if (int err = cubeb_get_min_latency(m_ctx, &stream_param, &min_latency))
{ {
Cubeb.error("cubeb_get_min_latency() failed: 0x%08x", err); Cubeb.error("cubeb_get_min_latency() failed: 0x%08x", err);
min_latency = 0;
} }
const u32 stream_latency = std::max(static_cast<u32>(AUDIO_MIN_LATENCY * get_sampling_rate()), min_latency); const u32 stream_latency = std::max(static_cast<u32>(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)) 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); 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) 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)."); 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 false;
return;
} }
if (int err = cubeb_stream_set_volume(m_stream, 1.0)) return true;
{
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);
}
} }
void CubebBackend::CloseUnlocked() void CubebBackend::CloseUnlocked()
{ {
if (m_stream == nullptr) return; if (m_stream != nullptr)
if (int err = cubeb_stream_stop(m_stream))
{ {
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_playing = false;
m_stream = nullptr;
m_last_sample.fill(0); m_last_sample.fill(0);
} }
@ -143,6 +150,12 @@ void CubebBackend::Close()
void CubebBackend::Play() void CubebBackend::Play()
{ {
if (m_stream == nullptr)
{
Cubeb.error("Play() called uninitialized");
return;
}
if (m_playing) return; if (m_playing) return;
std::lock_guard lock(m_cb_mutex); std::lock_guard lock(m_cb_mutex);
@ -151,16 +164,19 @@ void CubebBackend::Play()
void CubebBackend::Pause() void CubebBackend::Pause()
{ {
if (m_stream == nullptr)
{
Cubeb.error("Pause() called uninitialized");
return;
}
if (!m_playing) return;
std::lock_guard lock(m_cb_mutex); std::lock_guard lock(m_cb_mutex);
m_playing = false; m_playing = false;
m_last_sample.fill(0); m_last_sample.fill(0);
} }
bool CubebBackend::IsPlaying()
{
return m_playing;
}
void CubebBackend::SetWriteCallback(std::function<u32(u32, void *)> cb) void CubebBackend::SetWriteCallback(std::function<u32(u32, void *)> cb)
{ {
std::lock_guard lock(m_cb_mutex); std::lock_guard lock(m_cb_mutex);
@ -169,10 +185,17 @@ void CubebBackend::SetWriteCallback(std::function<u32(u32, void *)> cb)
f64 CubebBackend::GetCallbackFrameLen() f64 CubebBackend::GetCallbackFrameLen()
{ {
if (m_stream == nullptr)
{
Cubeb.error("GetCallbackFrameLen() called uninitialized");
return AUDIO_MIN_LATENCY;
}
u32 stream_latency{}; u32 stream_latency{};
if (int err = cubeb_stream_get_latency(m_stream, &stream_latency)) if (int err = cubeb_stream_get_latency(m_stream, &stream_latency))
{ {
Cubeb.error("cubeb_stream_get_latency() failed: 0x%08x", err); Cubeb.error("cubeb_stream_get_latency() failed: 0x%08x", err);
stream_latency = 0;
} }
return std::max<f64>(AUDIO_MIN_LATENCY, static_cast<f64>(stream_latency) / get_sampling_rate()); return std::max<f64>(AUDIO_MIN_LATENCY, static_cast<f64>(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) if (state == CUBEB_STATE_ERROR)
{ {
Cubeb.error("Stream entered error state"); Cubeb.error("Stream entered error state");
std::lock_guard lock(cubeb->m_error_cb_mutex);
cubeb->m_reset_req = true; cubeb->m_reset_req = true;
if (cubeb->m_error_callback)
{
cubeb->m_error_callback();
}
} }
} }

View File

@ -21,7 +21,7 @@ public:
bool Initialized() override; bool Initialized() override;
bool Operational() 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 Close() override;
void SetWriteCallback(std::function<u32(u32, void *)> cb) override; void SetWriteCallback(std::function<u32(u32, void *)> cb) override;
@ -29,7 +29,6 @@ public:
void Play() override; void Play() override;
void Pause() override; void Pause() override;
bool IsPlaying() override;
private: private:
static constexpr f64 AUDIO_MIN_LATENCY = 512.0 / 48000; // 10ms static constexpr f64 AUDIO_MIN_LATENCY = 512.0 / 48000; // 10ms
@ -45,8 +44,7 @@ private:
std::array<u8, sizeof(float) * static_cast<u32>(AudioChannelCnt::SURROUND_7_1)> m_last_sample{}; std::array<u8, sizeof(float) * static_cast<u32>(AudioChannelCnt::SURROUND_7_1)> m_last_sample{};
atomic_t<u8> full_sample_size = 0; atomic_t<u8> full_sample_size = 0;
bool m_playing = false; bool m_reset_req = false;
atomic_t<bool> m_reset_req = false;
// Cubeb callbacks // Cubeb callbacks
static long data_cb(cubeb_stream* stream, void* user_ptr, void const* input_buffer, void* output_buffer, long nframes); static long data_cb(cubeb_stream* stream, void* user_ptr, void const* input_buffer, void* output_buffer, long nframes);

View File

@ -14,27 +14,17 @@ FAudioBackend::FAudioBackend()
{ {
FAudio *instance; FAudio *instance;
u32 res = FAudioCreate(&instance, 0, FAUDIO_DEFAULT_PROCESSOR); if (u32 res = FAudioCreate(&instance, 0, FAUDIO_DEFAULT_PROCESSOR))
if (res)
{ {
FAudio_.error("FAudioCreate() failed(0x%08x)", res); FAudio_.error("FAudioCreate() failed(0x%08x)", res);
return; 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; OnProcessingPassStart = nullptr;
OnProcessingPassEnd = nullptr; OnProcessingPassEnd = nullptr;
OnCriticalError = OnCriticalError_func; OnCriticalError = OnCriticalError_func;
res = FAudio_RegisterForCallbacks(instance, this); if (u32 res = FAudio_RegisterForCallbacks(instance, this))
if (res)
{ {
// Some error recovery functionality will be lost, but otherwise backend is operational // Some error recovery functionality will be lost, but otherwise backend is operational
FAudio_.error("FAudio_RegisterForCallbacks() failed(0x%08x)", res); FAudio_.error("FAudio_RegisterForCallbacks() failed(0x%08x)", res);
@ -48,11 +38,6 @@ FAudioBackend::~FAudioBackend()
{ {
Close(); Close();
if (m_master_voice != nullptr)
{
FAudioVoice_DestroyVoice(m_master_voice);
}
if (m_instance != nullptr) if (m_instance != nullptr)
{ {
FAudio_StopEngine(m_instance); FAudio_StopEngine(m_instance);
@ -70,56 +55,52 @@ void FAudioBackend::Play()
if (m_playing) return; 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); std::lock_guard lock(m_cb_mutex);
m_playing = true; m_playing = true;
} }
void FAudioBackend::Pause() void FAudioBackend::Pause()
{ {
if (m_source_voice) if (m_source_voice == nullptr)
{
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
{ {
FAudio_.error("Pause() called uninitialized"); FAudio_.error("Pause() called uninitialized");
return;
} }
std::lock_guard lock(m_cb_mutex); if (!m_playing) return;
m_playing = false;
m_last_sample.fill(0); {
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() void FAudioBackend::CloseUnlocked()
{ {
if (m_source_voice == nullptr) return; if (m_source_voice != nullptr)
const u32 res = FAudioSourceVoice_Stop(m_source_voice, 0, FAUDIO_COMMIT_NOW);
if (res)
{ {
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_playing = false;
m_source_voice = nullptr;
m_data_buf = nullptr;
m_data_buf_len = 0;
m_last_sample.fill(0); m_last_sample.fill(0);
} }
@ -136,12 +117,17 @@ bool FAudioBackend::Initialized()
bool FAudioBackend::Operational() 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); std::lock_guard lock(m_cb_mutex);
CloseUnlocked(); CloseUnlocked();
@ -167,27 +153,35 @@ void FAudioBackend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChann
OnLoopEnd = nullptr; OnLoopEnd = nullptr;
OnVoiceError = nullptr; OnVoiceError = nullptr;
const u32 res = FAudio_CreateSourceVoice(m_instance, &m_source_voice, &waveformatex, 0, FAUDIO_DEFAULT_FREQ_RATIO, this, nullptr, nullptr); if (u32 res = FAudio_CreateMasteringVoice(m_instance, &m_master_voice, FAUDIO_DEFAULT_CHANNELS, FAUDIO_DEFAULT_SAMPLERATE, 0, 0, nullptr))
if (res) {
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); 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) 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)."); FAudio_.error("Failed to open audio backend. Make sure that no other application is running that might block audio access (e.g. Netflix).");
return; 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<u32>(FAUDIO_DEFAULT_FREQ_RATIO) / 1000);
m_data_buf_len = get_sampling_rate() * get_sample_size() * get_channels() * INTERNAL_BUF_SIZE_MS * static_cast<u32>(FAUDIO_DEFAULT_FREQ_RATIO) / 1000; return true;
m_data_buf = std::make_unique<u8[]>(m_data_buf_len);
}
bool FAudioBackend::IsPlaying()
{
return m_playing;
} }
void FAudioBackend::SetWriteCallback(std::function<u32(u32, void *)> cb) void FAudioBackend::SetWriteCallback(std::function<u32(u32, void *)> cb)
@ -226,32 +220,27 @@ void FAudioBackend::OnVoiceProcessingPassStart_func(FAudioVoiceCallback *cb_obj,
std::unique_lock lock(faudio->m_cb_mutex, std::defer_lock); std::unique_lock lock(faudio->m_cb_mutex, std::defer_lock);
if (BytesRequired && lock.try_lock() && faudio->m_write_callback && faudio->m_playing) 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(); 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; written -= written % sample_size;
if (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) 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{}; FAudioBuffer buffer{};
buffer.AudioBytes = BytesRequired; buffer.AudioBytes = BytesRequired;
buffer.LoopBegin = FAUDIO_NO_LOOP_REGION; buffer.pAudioData = static_cast<const u8*>(faudio->m_data_buf.data());
buffer.pAudioData = static_cast<const u8*>(faudio->m_data_buf.get()); // 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);
const u32 res = FAudioSourceVoice_SubmitSourceBuffer(faudio->m_source_voice, &buffer, nullptr);
if (res)
{
FAudio_.error("FAudioSourceVoice_SubmitSourceBuffer() failed(0x%08x)", res);
}
} }
} }
@ -260,5 +249,12 @@ void FAudioBackend::OnCriticalError_func(FAudioEngineCallback *cb_obj, u32 Error
FAudio_.error("OnCriticalError() failed(0x%08x)", Error); FAudio_.error("OnCriticalError() failed(0x%08x)", Error);
FAudioBackend *faudio = static_cast<FAudioBackend *>(cb_obj); FAudioBackend *faudio = static_cast<FAudioBackend *>(cb_obj);
std::lock_guard lock(faudio->m_error_cb_mutex);
faudio->m_reset_req = true; faudio->m_reset_req = true;
if (faudio->m_error_callback)
{
faudio->m_error_callback();
}
} }

View File

@ -10,7 +10,7 @@
#include "FAudio.h" #include "FAudio.h"
class FAudioBackend : public AudioBackend, public FAudioVoiceCallback, public FAudioEngineCallback class FAudioBackend final : public AudioBackend, public FAudioVoiceCallback, public FAudioEngineCallback
{ {
public: public:
FAudioBackend(); FAudioBackend();
@ -24,7 +24,7 @@ public:
bool Initialized() override; bool Initialized() override;
bool Operational() 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 Close() override;
void SetWriteCallback(std::function<u32(u32, void *)> cb) override; void SetWriteCallback(std::function<u32(u32, void *)> cb) override;
@ -32,7 +32,6 @@ public:
void Play() override; void Play() override;
void Pause() override; void Pause() override;
bool IsPlaying() override;
private: private:
static constexpr u32 INTERNAL_BUF_SIZE_MS = 25; static constexpr u32 INTERNAL_BUF_SIZE_MS = 25;
@ -43,12 +42,10 @@ private:
shared_mutex m_cb_mutex{}; shared_mutex m_cb_mutex{};
std::function<u32(u32, void *)> m_write_callback{}; std::function<u32(u32, void *)> m_write_callback{};
std::unique_ptr<u8[]> m_data_buf{}; std::vector<u8> m_data_buf{};
u64 m_data_buf_len = 0;
std::array<u8, sizeof(float) * static_cast<u32>(AudioChannelCnt::SURROUND_7_1)> m_last_sample{}; std::array<u8, sizeof(float) * static_cast<u32>(AudioChannelCnt::SURROUND_7_1)> m_last_sample{};
bool m_playing = false; bool m_reset_req = false;
atomic_t<bool> m_reset_req = false;
// FAudio voice callbacks // FAudio voice callbacks
static void OnVoiceProcessingPassStart_func(FAudioVoiceCallback *cb_obj, u32 BytesRequired); static void OnVoiceProcessingPassStart_func(FAudioVoiceCallback *cb_obj, u32 BytesRequired);

View File

@ -2,7 +2,7 @@
#include "Emu/Audio/AudioBackend.h" #include "Emu/Audio/AudioBackend.h"
class NullAudioBackend : public AudioBackend class NullAudioBackend final : public AudioBackend
{ {
public: public:
NullAudioBackend() {} NullAudioBackend() {}
@ -10,12 +10,18 @@ public:
std::string_view GetName() const override { return "Null"sv; } 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 Close() override { m_playing = false; }
void SetWriteCallback(std::function<u32(u32, void *)> /* cb */) override {}; void SetWriteCallback(std::function<u32(u32, void *)> /* cb */) override {};
f64 GetCallbackFrameLen() override { return 0.01; }; f64 GetCallbackFrameLen() override { return 0.01; };
void SetErrorCallback(std::function<void()> /* cb */) override {};
void Play() override { m_playing = true; } void Play() override { m_playing = true; }
void Pause() override { m_playing = false; } void Pause() override { m_playing = false; }
bool IsPlaying() override { return m_playing; } bool IsPlaying() override { return m_playing; }

View File

@ -53,10 +53,7 @@ XAudio2Backend::~XAudio2Backend()
if (m_xaudio2_instance != nullptr) if (m_xaudio2_instance != nullptr)
{ {
m_xaudio2_instance->StopEngine(); m_xaudio2_instance->StopEngine();
m_xaudio2_instance = nullptr;
// TODO: Enabling this might crash afterwards in ComPtr::InternalRelease.
// Maybe it's both trying to do the same thing?
//m_xaudio2_instance->Release();
} }
if (m_com_init_success) if (m_com_init_success)
@ -72,12 +69,14 @@ bool XAudio2Backend::Initialized()
bool XAudio2Backend::Operational() bool XAudio2Backend::Operational()
{ {
std::lock_guard lock(m_error_cb_mutex);
if (m_dev_listener.output_device_changed()) if (m_dev_listener.output_device_changed())
{ {
m_reset_req = true; 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() void XAudio2Backend::Play()
@ -90,13 +89,6 @@ void XAudio2Backend::Play()
if (m_playing) return; 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<u32>(hr));
return;
}
std::lock_guard lock(m_cb_mutex); std::lock_guard lock(m_cb_mutex);
m_playing = true; m_playing = true;
} }
@ -105,8 +97,7 @@ void XAudio2Backend::CloseUnlocked()
{ {
if (m_source_voice != nullptr) if (m_source_voice != nullptr)
{ {
const HRESULT hr = m_source_voice->Stop(); if (HRESULT hr = m_source_voice->Stop(); FAILED(hr))
if (FAILED(hr))
{ {
XAudio.error("Stop() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr)); XAudio.error("Stop() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
} }
@ -122,8 +113,6 @@ void XAudio2Backend::CloseUnlocked()
} }
m_playing = false; m_playing = false;
m_data_buf = nullptr;
m_data_buf_len = 0;
m_last_sample.fill(0); m_last_sample.fill(0);
} }
@ -135,50 +124,37 @@ void XAudio2Backend::Close()
void XAudio2Backend::Pause() void XAudio2Backend::Pause()
{ {
if (m_source_voice) if (m_source_voice == nullptr)
{
HRESULT hr = m_source_voice->Stop();
if (FAILED(hr))
{
XAudio.error("Stop() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
}
hr = m_source_voice->FlushSourceBuffers();
if (FAILED(hr))
{
XAudio.error("FlushSourceBuffers() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
}
}
else
{ {
XAudio.error("Pause() called uninitialized"); XAudio.error("Pause() called uninitialized");
return;
} }
std::lock_guard lock(m_cb_mutex); if (!m_playing) return;
m_playing = false;
m_last_sample.fill(0); {
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<u32>(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); std::lock_guard lock(m_cb_mutex);
CloseUnlocked(); 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<u32>(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_sampling_rate = freq;
m_sample_size = sample_size; m_sample_size = sample_size;
m_channels = ch_cnt; m_channels = ch_cnt;
@ -192,23 +168,35 @@ void XAudio2Backend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChan
waveformatex.wBitsPerSample = get_sample_size() * 8; waveformatex.wBitsPerSample = get_sample_size() * 8;
waveformatex.cbSize = 0; waveformatex.cbSize = 0;
hr = m_xaudio2_instance->CreateSourceVoice(&m_source_voice, &waveformatex, 0, XAUDIO2_DEFAULT_FREQ_RATIO, this); if (HRESULT hr = m_xaudio2_instance->CreateMasteringVoice(&m_master_voice); FAILED(hr))
if (FAILED(hr)) {
XAudio.error("CreateMasteringVoice() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(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<u32>(hr)); XAudio.error("CreateSourceVoice() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(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<u32>(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<u32>(hr));
} }
ensure(m_source_voice != nullptr); if (m_source_voice == nullptr)
m_source_voice->SetVolume(1.0f); {
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<u32>(XAUDIO2_DEFAULT_FREQ_RATIO) / 1000; m_data_buf.resize(get_sampling_rate() * get_sample_size() * get_channels() * INTERNAL_BUF_SIZE_MS * static_cast<u32>(XAUDIO2_DEFAULT_FREQ_RATIO) / 1000);
m_data_buf = std::make_unique<u8[]>(m_data_buf_len);
}
bool XAudio2Backend::IsPlaying() return true;
{
return m_playing;
} }
void XAudio2Backend::SetWriteCallback(std::function<u32(u32, void *)> cb) void XAudio2Backend::SetWriteCallback(std::function<u32(u32, void *)> cb)
@ -230,7 +218,7 @@ f64 XAudio2Backend::GetCallbackFrameLen()
void *ext; void *ext;
f64 min_latency{}; 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)) if (FAILED(hr))
{ {
XAudio.error("QueryInterface() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr)); XAudio.error("QueryInterface() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
@ -254,37 +242,39 @@ void XAudio2Backend::OnVoiceProcessingPassStart(UINT32 BytesRequired)
std::unique_lock lock(m_cb_mutex, std::defer_lock); std::unique_lock lock(m_cb_mutex, std::defer_lock);
if (BytesRequired && lock.try_lock() && m_write_callback && m_playing) 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(); 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; written -= written % sample_size;
if (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) 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{}; XAUDIO2_BUFFER buffer{};
buffer.AudioBytes = BytesRequired; buffer.AudioBytes = BytesRequired;
buffer.LoopBegin = XAUDIO2_NO_LOOP_REGION; buffer.pAudioData = static_cast<const BYTE*>(m_data_buf.data());
buffer.pAudioData = static_cast<const BYTE*>(m_data_buf.get()); // Avoid logging in callback and assume that this always succeeds, all errors are caught by error callback anyway
m_source_voice->SubmitSourceBuffer(&buffer);
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<u32>(hr));
}
} }
} }
void XAudio2Backend::OnCriticalError(HRESULT Error) void XAudio2Backend::OnCriticalError(HRESULT Error)
{ {
XAudio.error("OnCriticalError() called: %s (0x%08x)", std::system_category().message(Error), static_cast<u32>(Error)); XAudio.error("OnCriticalError() called: %s (0x%08x)", std::system_category().message(Error), static_cast<u32>(Error));
std::lock_guard lock(m_error_cb_mutex);
m_reset_req = true; m_reset_req = true;
if (m_error_callback)
{
m_error_callback();
}
} }

View File

@ -26,7 +26,7 @@ public:
bool Initialized() override; bool Initialized() override;
bool Operational() 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 Close() override;
void SetWriteCallback(std::function<u32(u32, void *)> cb) override; void SetWriteCallback(std::function<u32(u32, void *)> cb) override;
@ -34,7 +34,6 @@ public:
void Play() override; void Play() override;
void Pause() override; void Pause() override;
bool IsPlaying() override;
private: private:
static constexpr u32 INTERNAL_BUF_SIZE_MS = 25; static constexpr u32 INTERNAL_BUF_SIZE_MS = 25;
@ -46,12 +45,10 @@ private:
shared_mutex m_cb_mutex{}; shared_mutex m_cb_mutex{};
std::function<u32(u32, void *)> m_write_callback{}; std::function<u32(u32, void *)> m_write_callback{};
std::unique_ptr<u8[]> m_data_buf{}; std::vector<u8> m_data_buf{};
u64 m_data_buf_len = 0;
std::array<u8, sizeof(float) * static_cast<u32>(AudioChannelCnt::SURROUND_7_1)> m_last_sample{}; std::array<u8, sizeof(float) * static_cast<u32>(AudioChannelCnt::SURROUND_7_1)> m_last_sample{};
bool m_playing = false; bool m_reset_req = false;
atomic_t<bool> m_reset_req = false;
audio_device_listener m_dev_listener{}; audio_device_listener m_dev_listener{};

View File

@ -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_channels = static_cast<u32>(ch_cnt);
audio_sampling_rate = backend->get_sampling_rate(); audio_sampling_rate = static_cast<u32>(freq);
audio_block_period = AUDIO_BUFFER_SAMPLES * 1'000'000 / audio_sampling_rate; audio_block_period = AUDIO_BUFFER_SAMPLES * 1'000'000 / audio_sampling_rate;
audio_sample_size = backend->get_sample_size(); audio_sample_size = static_cast<u32>(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_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_length = AUDIO_BUFFER_SAMPLES * audio_channels;
audio_buffer_size = audio_buffer_length * audio_sample_size;
desired_buffer_duration = std::max(static_cast<s64>(audio_min_buffer_duration * 1000), raw.desired_buffer_duration) * 1000llu; desired_buffer_duration = std::max(static_cast<s64>(audio_min_buffer_duration * 1000), raw.desired_buffer_duration) * 1000llu;
buffering_enabled = raw.buffering_enabled && raw.renderer != audio_renderer::null; 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 // Init audio dumper if enabled
if (cfg.raw.dump_to_file) 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<AudioChannelCnt>(cfg.audio_channels), static_cast<AudioFreq>(cfg.audio_sampling_rate), AudioSampleSize::FLOAT);
} }
// Configure resampler // Configure resampler
resampler.set_params(static_cast<AudioChannelCnt>(cfg.audio_channels), AudioFreq::FREQ_48K); resampler.set_params(static_cast<AudioChannelCnt>(cfg.audio_channels), static_cast<AudioFreq>(cfg.audio_sampling_rate));
resampler.set_tempo(RESAMPLER_MAX_FREQ_VAL); resampler.set_tempo(RESAMPLER_MAX_FREQ_VAL);
const f64 buffer_dur_mult = [&]() const f64 buffer_dur_mult = [&]()
@ -165,7 +164,7 @@ audio_ringbuffer::~audio_ringbuffer()
f32 audio_ringbuffer::set_frequency_ratio(f32 new_ratio) f32 audio_ringbuffer::set_frequency_ratio(f32 new_ratio)
{ {
frequency_ratio = resampler.set_tempo(new_ratio); frequency_ratio = static_cast<f32>(resampler.set_tempo(new_ratio));
return frequency_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; if (!backend_active.observe()) backend_active = true;
return cb_ringbuf.pop(buf, size); return static_cast<u32>(cb_ringbuf.pop(buf, size, true));
} }
u64 audio_ringbuffer::get_timestamp() 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; 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) if (!backend_active.observe() && !force)
{ {
// backend is not ready yet // backend is not ready yet
@ -261,7 +257,7 @@ void audio_ringbuffer::process_resampled_data()
{ {
if (!cfg.time_stretching_enabled) return; 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<u32>(cb_ringbuf.get_free_size() / (cfg.audio_sample_size * cfg.audio_channels)));
commit_data(samples.first, samples.second); 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; sample_cnt *= cfg.audio_channels;
// Dump audio if enabled
m_dump.WriteData(buf, sample_cnt * static_cast<u32>(AudioSampleSize::FLOAT));
if (cfg.backend->get_convert_to_s16()) if (cfg.backend->get_convert_to_s16())
{ {
AudioBackend::convert_to_s16(sample_cnt, buf, buf); AudioBackend::convert_to_s16(sample_cnt, buf, buf);
} }
sample_cnt *= cfg.audio_sample_size; cb_ringbuf.push(buf, sample_cnt * cfg.audio_sample_size);
if (cb_ringbuf.get_free_size() >= sample_cnt)
{
cb_ringbuf.push(buf, sample_cnt);
}
} }
void audio_ringbuffer::play() void audio_ringbuffer::play()
@ -297,7 +291,7 @@ void audio_ringbuffer::play()
void audio_ringbuffer::flush() void audio_ringbuffer::flush()
{ {
backend->Pause(); backend->Pause();
cb_ringbuf.flush(); cb_ringbuf.writer_flush();
resampler.flush(); resampler.flush();
backend_active = false; backend_active = false;
playing = false; playing = false;
@ -566,7 +560,8 @@ namespace audio
raw.renderer != new_raw.renderer || raw.renderer != new_raw.renderer ||
raw.dump_to_file != new_raw.dump_to_file) 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; 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) if (update_req != audio_backend_update::NONE)
{ {
cellAudio.warning("Updating cell_audio_thread configuration"); 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); update_config(update_req == audio_backend_update::ALL);
m_update_configuration = audio_backend_update::NONE;
} }
if (!ringbuffer->get_operational_status()) if (!ringbuffer->get_operational_status())
{ {
cellAudio.warning("Backend stopped unexpectedly (likely device change). Attempting to recover...");
if (m_backend_failed) if (m_backend_failed)
{ {
thread_ctrl::wait_for(500 * 1000); thread_ctrl::wait_for(500 * 1000);
} }
else
{
cellAudio.warning("Backend stopped unexpectedly (likely device change). Attempting to recover...");
}
update_config(true); update_config(true);
m_backend_failed = true; m_backend_failed = true;
@ -743,12 +744,10 @@ void cell_audio_thread::operator()()
if (desired_duration_rate < cfg.time_stretching_threshold) if (desired_duration_rate < cfg.time_stretching_threshold)
{ {
const f32 normalized_desired_duration_rate = 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 // change frequency ratio in steps
const f32 req_time_stretching_step = (request_ratio + frequency_ratio) / 2.0f; const f32 req_time_stretching_step = (normalized_desired_duration_rate + frequency_ratio) / 2.0f;
if (req_time_stretching_step > cfg.time_stretching_step) if (std::abs(req_time_stretching_step - frequency_ratio) > cfg.time_stretching_step)
{ {
ringbuffer->set_frequency_ratio(req_time_stretching_step); ringbuffer->set_frequency_ratio(req_time_stretching_step);
} }

View File

@ -84,20 +84,20 @@ enum class audio_backend_update : u32
//libaudio datatypes //libaudio datatypes
struct CellAudioPortParam struct CellAudioPortParam
{ {
be_t<u64> nChannel; be_t<u64> nChannel{};
be_t<u64> nBlock; be_t<u64> nBlock{};
be_t<u64> attr; be_t<u64> attr{};
be_t<float> level; be_t<float> level{};
}; };
struct CellAudioPortConfig struct CellAudioPortConfig
{ {
vm::bptr<u64> readIndexAddr; vm::bptr<u64> readIndexAddr{};
be_t<u32> status; be_t<u32> status{};
be_t<u64> nChannel; be_t<u64> nChannel{};
be_t<u64> nBlock; be_t<u64> nBlock{};
be_t<u32> portSize; be_t<u32> portSize{};
be_t<u32> portAddr; be_t<u32> portAddr{};
}; };
enum : u32 enum : u32
@ -140,27 +140,27 @@ struct audio_port
{ {
atomic_t<audio_port_state> state = audio_port_state::closed; atomic_t<audio_port_state> state = audio_port_state::closed;
u32 number; u32 number = 0;
vm::ptr<char> addr{}; vm::ptr<char> addr{};
vm::ptr<u64> index{}; vm::ptr<u64> index{};
u32 num_channels; u32 num_channels = 0;
u32 num_blocks; u32 num_blocks = 0;
u64 attr; u64 attr = 0;
u64 cur_pos; u64 cur_pos = 0;
u64 global_counter; // copy of global counter u64 global_counter = 0; // copy of global counter
u64 active_counter; u64 active_counter = 0;
u32 size; u32 size = 0;
u64 timestamp; // copy of global timestamp u64 timestamp = 0; // copy of global timestamp
struct level_set_t struct level_set_t
{ {
float value; float value = 0.0f;
float inc; float inc = 0.0f;
}; };
float level; float level = 0.0f;
atomic_t<level_set_t> level_set; atomic_t<level_set_t> level_set{};
u32 block_size() const u32 block_size() const
{ {
@ -190,7 +190,7 @@ struct audio_port
// Tags // Tags
u32 prev_touched_tag_nr; u32 prev_touched_tag_nr = 0;
f32 last_tag_value[PORT_BUFFER_TAG_COUNT] = { 0 }; f32 last_tag_value[PORT_BUFFER_TAG_COUNT] = { 0 };
void tag(s32 offset = 0); void tag(s32 offset = 0);
@ -209,7 +209,10 @@ struct cell_audio_config
audio_downmix downmix = audio_downmix::downmix_to_stereo; audio_downmix downmix = audio_downmix::downmix_to_stereo;
audio_renderer renderer = audio_renderer::null; audio_renderer renderer = audio_renderer::null;
audio_provider provider = audio_provider::none; audio_provider provider = audio_provider::none;
} raw; };
raw_config new_raw{};
raw_config raw{};
std::shared_ptr<AudioBackend> backend = nullptr; std::shared_ptr<AudioBackend> backend = nullptr;
@ -220,7 +223,6 @@ struct cell_audio_config
f64 audio_min_buffer_duration = 0.0; f64 audio_min_buffer_duration = 0.0;
u32 audio_buffer_length = 0; u32 audio_buffer_length = 0;
u32 audio_buffer_size = 0;
/* /*
* Buffering * 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) 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_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 * Constructor
@ -278,7 +279,7 @@ private:
AudioDumper m_dump{}; AudioDumper m_dump{};
std::unique_ptr<float[]> buffer[MAX_AUDIO_BUFFERS]; std::unique_ptr<float[]> buffer[MAX_AUDIO_BUFFERS]{};
simple_ringbuf cb_ringbuf{}; simple_ringbuf cb_ringbuf{};
audio_resampler resampler{}; audio_resampler resampler{};
@ -347,7 +348,7 @@ public:
class cell_audio_thread class cell_audio_thread
{ {
private: private:
std::unique_ptr<audio_ringbuffer> ringbuffer; std::unique_ptr<audio_ringbuffer> ringbuffer{};
void reset_ports(s32 offset = 0); void reset_ports(s32 offset = 0);
void advance(u64 timestamp); void advance(u64 timestamp);
@ -365,10 +366,11 @@ private:
void reset_counters(); void reset_counters();
public: public:
cell_audio_config cfg; shared_mutex emu_cfg_upd_m{};
cell_audio_config cfg{};
atomic_t<audio_backend_update> m_update_configuration = audio_backend_update::NONE; atomic_t<audio_backend_update> m_update_configuration = audio_backend_update::NONE;
shared_mutex mutex; shared_mutex mutex{};
atomic_t<u32> init = 0; atomic_t<u32> init = 0;
u32 key_count = 0; u32 key_count = 0;
@ -376,14 +378,14 @@ public:
struct key_info struct key_info
{ {
u8 start_period; // Starting event_period u8 start_period = 0; // Starting event_period
u32 flags; // iFlags u32 flags = 0; // iFlags
u64 source; // Event source u64 source = 0; // Event source
std::shared_ptr<lv2_event_queue> port; // Underlying event port std::shared_ptr<lv2_event_queue> port{}; // Underlying event port
}; };
std::vector<key_info> keys; std::vector<key_info> keys{};
std::array<audio_port, AUDIO_PORT_COUNT> ports; std::array<audio_port, AUDIO_PORT_COUNT> ports{};
u64 m_last_period_end = 0; u64 m_last_period_end = 0;
u64 m_counter = 0; u64 m_counter = 0;

View File

@ -664,12 +664,12 @@ const std::array<std::pair<ppu_intrp_func_t, std::string_view>, 1024> g_ppu_sysc
BIND_SYSC(sys_rsxaudio_finalize), //651 (0x28B) BIND_SYSC(sys_rsxaudio_finalize), //651 (0x28B)
BIND_SYSC(sys_rsxaudio_import_shared_memory), //652 (0x28C) BIND_SYSC(sys_rsxaudio_import_shared_memory), //652 (0x28C)
BIND_SYSC(sys_rsxaudio_unimport_shared_memory), //653 (0x28D) BIND_SYSC(sys_rsxaudio_unimport_shared_memory), //653 (0x28D)
NULL_FUNC(sys_rsxaudio_create_connection), //654 (0x28E) BIND_SYSC(sys_rsxaudio_create_connection), //654 (0x28E)
NULL_FUNC(sys_rsxaudio_close_connection), //655 (0x28F) BIND_SYSC(sys_rsxaudio_close_connection), //655 (0x28F)
NULL_FUNC(sys_rsxaudio_prepare_process), //656 (0x290) BIND_SYSC(sys_rsxaudio_prepare_process), //656 (0x290)
NULL_FUNC(sys_rsxaudio_start_process), //657 (0x291) BIND_SYSC(sys_rsxaudio_start_process), //657 (0x291)
NULL_FUNC(sys_rsxaudio_stop_process), //658 (0x292) BIND_SYSC(sys_rsxaudio_stop_process), //658 (0x292)
null_func, //BIND_SYSC(sys_rsxaudio_...), //659 (0x293) BIND_SYSC(sys_rsxaudio_get_dma_param), //659 (0x293)
uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, //660-665 UNS uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, //660-665 UNS

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,41 @@
#pragma once #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/Memory/vm_ptr.h"
#include "Emu/Cell/ErrorCodes.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 <sys/param.h>
#endif
#ifdef _WIN32
#include <windows.h>
#elif defined(BSD) || defined(__APPLE__)
#include <sys/event.h>
#endif
enum : u32 enum : u32
{ {
SYS_RSXAUDIO_SERIAL_STREAM_CNT = 4, 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_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_SERIAL = SYS_RSXAUDIO_STREAM_SIZE * SYS_RSXAUDIO_SERIAL_STREAM_CNT,
SYS_RSXAUDIO_RINGBUF_BLK_SZ_SPDIF = 0x400, SYS_RSXAUDIO_RINGBUF_BLK_SZ_SPDIF = SYS_RSXAUDIO_STREAM_SIZE,
SYS_RSXAUDIO_RINGBUF_SZ = 16, SYS_RSXAUDIO_RINGBUF_SZ = 16,
@ -19,10 +44,6 @@ enum : u32
SYS_RSXAUDIO_FREQ_BASE_384K = 384000, SYS_RSXAUDIO_FREQ_BASE_384K = 384000,
SYS_RSXAUDIO_FREQ_BASE_352K = 352800, 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_PORT_CNT = 3,
SYS_RSXAUDIO_SPDIF_CNT = 2, SYS_RSXAUDIO_SPDIF_CNT = 2,
@ -37,12 +58,563 @@ enum class RsxaudioAvportIdx : u8
SPDIF_1 = 4, SPDIF_1 = 4,
}; };
enum class RsxaudioPort : u8
{
SERIAL = 0,
SPDIF_0 = 1,
SPDIF_1 = 2,
INVALID = 0xFF,
};
enum class RsxaudioSampleSize : u8 enum class RsxaudioSampleSize : u8
{ {
_16BIT = 2, _16BIT = 2,
_32BIT = 4, _32BIT = 4,
}; };
struct rsxaudio_shmem
{
struct ringbuf_t
{
struct entry_t
{
be_t<u32> valid{};
be_t<u32> unk1{};
be_t<u64> audio_blk_idx{};
be_t<u64> timestamp{};
be_t<u32> buf_addr{};
be_t<u32> dma_addr{};
};
be_t<u32> active{};
be_t<u32> unk2{};
be_t<s32> read_idx{};
be_t<u32> write_idx{};
be_t<s32> rw_max_idx{};
be_t<s32> queue_notify_idx{};
be_t<s32> queue_notify_step{};
be_t<u32> unk6{};
be_t<u32> dma_silence_addr{};
be_t<u32> unk7{};
be_t<u64> next_blk_idx{};
entry_t entries[16]{};
};
struct uf_event_t
{
be_t<u64> unk1{};
be_t<u32> uf_event_cnt{};
u8 unk2[244]{};
};
struct ctrl_t
{
ringbuf_t ringbuf[SYS_RSXAUDIO_PORT_CNT]{};
be_t<u32> unk1{};
be_t<u32> event_queue_1_id{};
u8 unk2[16]{};
be_t<u32> event_queue_2_id{};
be_t<u32> spdif_ch0_channel_data_lo{};
be_t<u32> spdif_ch0_channel_data_hi{};
be_t<u32> spdif_ch0_channel_data_tx_cycles{};
be_t<u32> unk3{};
be_t<u32> event_queue_3_id{};
be_t<u32> spdif_ch1_channel_data_lo{};
be_t<u32> spdif_ch1_channel_data_hi{};
be_t<u32> spdif_ch1_channel_data_tx_cycles{};
be_t<u32> unk4{};
be_t<u32> intr_thread_prio{};
be_t<u32> 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<std::weak_ptr<lv2_event_queue>, SYS_RSXAUDIO_PORT_CNT> event_queue{};
std::array<u32, SYS_RSXAUDIO_PORT_CNT> 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<rsxaudio_shmem*>(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<void()> &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<vtimer, VTIMER_MAX> 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<u8, 6> 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<u8, SYS_RSXAUDIO_SERIAL_MAX_CH> 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<u8, 5> info_frame{}; // TODO: check chstat and info_frame for info on audio layout, add default values
std::array<u8, 5> 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<RsxaudioPort, SYS_RSXAUDIO_AVPORT_CNT> 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<f32, SYS_RSXAUDIO_STREAM_SAMPLE_CNT * 2>;
class rsxaudio_data_container
{
public:
struct buf_t
{
std::array<ra_stream_blk_t, SYS_RSXAUDIO_SERIAL_MAX_CH> serial{};
std::array<ra_stream_blk_t, SYS_RSXAUDIO_SPDIF_MAX_CH> spdif[SYS_RSXAUDIO_SPDIF_CNT]{};
};
using data_blk_t = std::array<f32, SYS_RSXAUDIO_STREAM_SAMPLE_CNT * SYS_RSXAUDIO_SERIAL_MAX_CH * 2>;
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<bool, 5> 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<usz output_ch_cnt, usz input_ch_cnt>
requires (output_ch_cnt > 0 && output_ch_cnt <= 8 && input_ch_cnt > 0)
constexpr void mix(const std::array<u8, 8> &ch_map, RsxaudioSampleSize sample_size, const std::array<ra_stream_blk_t, input_ch_cnt> &input_channels, data_blk_t& data_out)
{
const ra_stream_blk_t silent_channel{};
// Build final map
std::array<const ra_stream_blk_t*, output_ch_cnt> 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<port_config, SYS_RSXAUDIO_AVPORT_CNT> &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_config, SYS_RSXAUDIO_AVPORT_CNT> 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<u8> thread_tmp_buf{};
std::vector<f32> callback_tmp_buf{};
bool use_aux_ringbuf = false;
shared_mutex ringbuf_mutex{};
std::shared_ptr<AudioBackend> backend{};
backend_config backend_current_cfg{ {}, new_emu_cfg.avport };
atomic_t<callback_config> 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<bool> rsxaudio_ctx_allocated = false;
shared_mutex rsxaudio_obj_upd_m{};
std::shared_ptr<lv2_rsxaudio> rsxaudio_obj_ptr{};
void operator()();
rsxaudio_data_thread& operator=(thread_state state);
rsxaudio_data_thread();
void update_hw_param(std::function<void(rsxaudio_hw_param_t&)> 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<rsxaudio_hw_param_t> hw_param_ts{std::make_shared<universal_pool>(), std::make_shared<rsxaudio_hw_param_t>()};
rsxaudio_periodic_tmr timer{};
void advance_all_timers();
void extract_audio_data();
static std::pair<bool /*data_present*/, void* /*addr*/> 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<rsxaudio_backend_thread>;
using rsx_audio_data = named_thread<rsxaudio_data_thread>;
// SysCalls // SysCalls
@ -50,3 +622,9 @@ error_code sys_rsxaudio_initialize(vm::ptr<u32> handle);
error_code sys_rsxaudio_finalize(u32 handle); error_code sys_rsxaudio_finalize(u32 handle);
error_code sys_rsxaudio_import_shared_memory(u32 handle, vm::ptr<u64> addr); error_code sys_rsxaudio_import_shared_memory(u32 handle, vm::ptr<u64> addr);
error_code sys_rsxaudio_unimport_shared_memory(u32 handle, vm::ptr<u64> addr); error_code sys_rsxaudio_unimport_shared_memory(u32 handle, vm::ptr<u64> 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<u64> out);

View File

@ -3,6 +3,7 @@
#include "Emu/system_config.h" #include "Emu/system_config.h"
#include "Emu/Cell/ErrorCodes.h" #include "Emu/Cell/ErrorCodes.h"
#include "Emu/Cell/timers.hpp"
#include "util/asm.hpp" #include "util/asm.hpp"
@ -134,23 +135,34 @@ LOG_CHANNEL(sys_time);
static constexpr u64 g_timebase_freq = /*79800000*/ 80000000ull; // 80 Mhz 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() u64 get_timebased_time()
{ {
while (true)
{
#ifdef _WIN32 #ifdef _WIN32
LARGE_INTEGER count; LARGE_INTEGER count;
ensure(QueryPerformanceCounter(&count)); ensure(QueryPerformanceCounter(&count));
const u64 time = count.QuadPart; const u64 time = count.QuadPart;
const u64 freq = s_time_aux_info.perf_freq; 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 #else
struct timespec ts; struct timespec ts;
ensure(::clock_gettime(CLOCK_MONOTONIC, &ts) == 0); ensure(::clock_gettime(CLOCK_MONOTONIC, &ts) == 0);
return ((static_cast<u64>(ts.tv_sec) * g_timebase_freq + static_cast<u64>(ts.tv_nsec) * g_timebase_freq / 1000000000ull) * g_cfg.core.clocks_scale / 100u) - timebase_offset; const u64 result = (static_cast<u64>(ts.tv_sec) * g_timebase_freq + static_cast<u64>(ts.tv_nsec) * g_timebase_freq / 1000000000ull) * g_cfg.core.clocks_scale / 100u;
#endif #endif
if (result) return result - timebase_offset;
}
} }
// Add an offset to get_timebased_time to avoid leaking PC's uptime into the game // Add an offset to get_timebased_time to avoid leaking PC's uptime into the game

View File

@ -420,7 +420,7 @@ struct video_disable_signal_cmd : public ps3av_cmd
if (pkt->avport <= static_cast<u16>(UartAudioAvport::HDMI_1)) if (pkt->avport <= static_cast<u16>(UartAudioAvport::HDMI_1))
{ {
// TBA g_fxo->get<rsx_audio_data>().update_av_mute_state(vuart.avport_to_idx(static_cast<UartAudioAvport>(pkt->avport.get())), false, true);
if (pkt->avport == static_cast<u16>(UartAudioAvport::HDMI_1) && !g_cfg.core.debug_console_mode) if (pkt->avport == static_cast<u16>(UartAudioAvport::HDMI_1) && !g_cfg.core.debug_console_mode)
{ {
@ -499,7 +499,7 @@ struct av_audio_mute_cmd : public ps3av_cmd
return; return;
} }
// TBA g_fxo->get<rsx_audio_data>().update_av_mute_state(vuart.avport_to_idx(static_cast<UartAudioAvport>(pkt->avport.get())), true, false, pkt->mute);
vuart.write_resp<true>(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); vuart.write_resp<true>(pkt->hdr.cid, PS3AV_STATUS_SUCCESS);
} }
@ -833,7 +833,7 @@ struct audio_init_cmd : public ps3av_cmd
return; return;
} }
// TBA g_fxo->get<rsx_audio_data>().reset_hw();
vuart.write_resp(pkt->cid, PS3AV_STATUS_SUCCESS); vuart.write_resp(pkt->cid, PS3AV_STATUS_SUCCESS);
} }
@ -871,7 +871,7 @@ private:
bool set_mode(const ps3av_pkt_audio_mode& pkt) bool set_mode(const ps3av_pkt_audio_mode& pkt)
{ {
bool spdif_use_serial_buf = false; bool spdif_use_serial_buf = false;
u8 avport_src, rsxaudio_port; RsxaudioPort avport_src, rsxaudio_port;
RsxaudioAvportIdx avport_idx; RsxaudioAvportIdx avport_idx;
switch (pkt.avport) switch (pkt.avport)
@ -881,11 +881,11 @@ private:
avport_idx = RsxaudioAvportIdx::HDMI_0; avport_idx = RsxaudioAvportIdx::HDMI_0;
if (pkt.audio_source == UartAudioSource::SPDIF) if (pkt.audio_source == UartAudioSource::SPDIF)
{ {
avport_src = rsxaudio_port = SYS_RSXAUDIO_PORT_SPDIF_1; avport_src = rsxaudio_port = RsxaudioPort::SPDIF_1;
} }
else else
{ {
avport_src = rsxaudio_port = SYS_RSXAUDIO_PORT_SERIAL; avport_src = rsxaudio_port = RsxaudioPort::SERIAL;
} }
break; break;
} }
@ -894,32 +894,32 @@ private:
avport_idx = RsxaudioAvportIdx::HDMI_1; avport_idx = RsxaudioAvportIdx::HDMI_1;
if (pkt.audio_source == UartAudioSource::SPDIF) if (pkt.audio_source == UartAudioSource::SPDIF)
{ {
avport_src = rsxaudio_port = SYS_RSXAUDIO_PORT_SPDIF_1; avport_src = rsxaudio_port = RsxaudioPort::SPDIF_1;
} }
else else
{ {
avport_src = rsxaudio_port = SYS_RSXAUDIO_PORT_SERIAL; avport_src = rsxaudio_port = RsxaudioPort::SERIAL;
} }
break; break;
} }
case UartAudioAvport::AVMULTI_0: case UartAudioAvport::AVMULTI_0:
{ {
avport_idx = RsxaudioAvportIdx::AVMULTI; avport_idx = RsxaudioAvportIdx::AVMULTI;
avport_src = rsxaudio_port = SYS_RSXAUDIO_PORT_SERIAL; avport_src = rsxaudio_port = RsxaudioPort::SERIAL;
break; break;
} }
case UartAudioAvport::SPDIF_0: case UartAudioAvport::SPDIF_0:
{ {
avport_idx = RsxaudioAvportIdx::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) if (pkt.audio_source == UartAudioSource::SERIAL)
{ {
spdif_use_serial_buf = true; spdif_use_serial_buf = true;
avport_src = SYS_RSXAUDIO_PORT_SERIAL; avport_src = RsxaudioPort::SERIAL;
} }
else else
{ {
avport_src = SYS_RSXAUDIO_PORT_SPDIF_0; avport_src = RsxaudioPort::SPDIF_0;
} }
break; break;
@ -927,15 +927,15 @@ private:
case UartAudioAvport::SPDIF_1: case UartAudioAvport::SPDIF_1:
{ {
avport_idx = RsxaudioAvportIdx::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) if (pkt.audio_source == UartAudioSource::SERIAL)
{ {
spdif_use_serial_buf = true; spdif_use_serial_buf = true;
avport_src = SYS_RSXAUDIO_PORT_SERIAL; avport_src = RsxaudioPort::SERIAL;
} }
else else
{ {
avport_src = SYS_RSXAUDIO_PORT_SPDIF_1; avport_src = RsxaudioPort::SPDIF_1;
} }
break; break;
@ -946,25 +946,11 @@ private:
} }
} }
const u32 freq = [&]() if (static_cast<u32>(pkt.audio_fs.value()) > static_cast<u32>(UartAudioFreq::_192K)) return false;
{
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;
const auto bit_cnt = [&]() 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) pkt.audio_word_bits == UartAudioSampleSize::_16BIT)
{ {
return 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, bool commit_param(RsxaudioPort rsxaudio_port, RsxaudioAvportIdx avport, RsxaudioPort avport_src, UartAudioFreq freq,
UartAudioSampleSize bit_cnt, [[maybe_unused]] bool spdif_use_serial_buf, [[maybe_unused]] const u8 *cs_data) UartAudioSampleSize bit_cnt, bool spdif_use_serial_buf, const u8 *cs_data)
{ {
// TBA auto& rsxaudio_thread = g_fxo->get<rsx_audio_data>();
[[maybe_unused]] const auto avport_idx = static_cast<std::underlying_type_t<decltype(avport)>>(avport); const auto avport_idx = static_cast<std::underlying_type_t<decltype(avport)>>(avport);
[[maybe_unused]] const auto rsxaudio_word_depth = bit_cnt == UartAudioSampleSize::_16BIT ? RsxaudioSampleSize::_16BIT : RsxaudioSampleSize::_32BIT; 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) 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; break;
} }
case SYS_RSXAUDIO_PORT_SPDIF_0: case RsxaudioPort::SPDIF_0:
case SYS_RSXAUDIO_PORT_SPDIF_1: 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; break;
} }
default: default:
@ -1034,13 +1049,13 @@ struct audio_mute_cmd : public ps3av_cmd
case UartAudioAvport::HDMI_1: case UartAudioAvport::HDMI_1:
case UartAudioAvport::AVMULTI_0: case UartAudioAvport::AVMULTI_0:
case UartAudioAvport::AVMULTI_1: case UartAudioAvport::AVMULTI_1:
// TBA g_fxo->get<rsx_audio_data>().update_mute_state(RsxaudioPort::SERIAL, pkt->mute);
break; break;
case UartAudioAvport::SPDIF_0: case UartAudioAvport::SPDIF_0:
// TBA g_fxo->get<rsx_audio_data>().update_mute_state(RsxaudioPort::SPDIF_0, pkt->mute);
break; break;
case UartAudioAvport::SPDIF_1: case UartAudioAvport::SPDIF_1:
// TBA g_fxo->get<rsx_audio_data>().update_mute_state(RsxaudioPort::SPDIF_1, pkt->mute);
break; break;
default: default:
break; break;
@ -1067,7 +1082,7 @@ struct audio_set_active_cmd : public ps3av_cmd
return; 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_0) != 0U,
(pkt->audio_port & PS3AV_AUDIO_PORT_HDMI_1) != 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 (pkt->audio_port & PS3AV_AUDIO_PORT_SPDIF_1) != 0U
}; };
// TBA g_fxo->get<rsx_audio_data>().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); vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS);
} }
@ -1099,7 +1143,24 @@ struct audio_set_inactive_cmd : public ps3av_cmd
return; return;
} }
// TBA g_fxo->get<rsx_audio_data>().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); vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS);
} }
@ -1122,7 +1183,7 @@ struct audio_spdif_bit_cmd : public ps3av_cmd
return; 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_0) != 0U,
(pkt->audio_port & PS3AV_AUDIO_PORT_HDMI_1) != 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 (pkt->audio_port & PS3AV_AUDIO_PORT_SPDIF_1) != 0U
}; };
// TBA g_fxo->get<rsx_audio_data>().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); vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS);
} }
@ -1243,7 +1318,7 @@ struct inc_avset_cmd : public ps3av_cmd
return; return;
} }
u8 hdcp_done_cnt[2]{}; bool hdcp_done[2]{};
// AV Video // AV Video
for (u32 video_av_pkt_idx = 0; video_av_pkt_idx < pkt->num_of_av_video_pkt; video_av_pkt_idx++) 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 (syscon_check_passed)
{ {
if (subcmd_resp.hdcp_done_event[0]) hdcp_done_cnt[0]++; hdcp_done[0] |= subcmd_resp.hdcp_done_event[0];
if (subcmd_resp.hdcp_done_event[1]) hdcp_done_cnt[1]++; hdcp_done[1] |= subcmd_resp.hdcp_done_event[1];
} }
pkt_data_addr += av_video_pkt->hdr.length + 4ULL; 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[0];
{ vuart.hdmi_res_set[1] = hdcp_done[1];
vuart.hdmi_res_set[0] = hdcp_done_cnt[0] > 0; vuart.add_hdmi_events(UartHdmiEvent::HDCP_DONE, vuart.hdmi_res_set[0], vuart.hdmi_res_set[1]);
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]);
if (hdcp_done_cnt[0]) if (vuart.hdmi_res_set[0])
{ {
// TBA g_fxo->get<rsx_audio_data>().update_av_mute_state(RsxaudioAvportIdx::HDMI_0, false, true);
hdcp_done_cnt[0]--; }
} if (vuart.hdmi_res_set[1])
if (hdcp_done_cnt[1]) {
{ g_fxo->get<rsx_audio_data>().update_av_mute_state(RsxaudioAvportIdx::HDMI_1, false, true);
// TBA
hdcp_done_cnt[1]--;
}
} }
bool valid_av_audio_pkt = false; bool valid_av_audio_pkt = false;
@ -1301,9 +1371,43 @@ struct inc_avset_cmd : public ps3av_cmd
break; break;
} }
[[maybe_unused]] const u8 hdmi_idx = av_audio_pkt->avport == static_cast<u16>(UartAudioAvport::HDMI_1); const u8 hdmi_idx = av_audio_pkt->avport == static_cast<u16>(UartAudioAvport::HDMI_1);
// TBA g_fxo->get<rsx_audio_data>().update_hw_param([&](auto &obj)
{
auto &hdmi = obj.hdmi[hdmi_idx];
hdmi.init = true;
const std::array<u8, SYS_RSXAUDIO_SERIAL_STREAM_CNT> fifomap =
{
static_cast<u8>((av_audio_pkt->fifomap >> 0) & 3U),
static_cast<u8>((av_audio_pkt->fifomap >> 2) & 3U),
static_cast<u8>((av_audio_pkt->fifomap >> 4) & 3U),
static_cast<u8>((av_audio_pkt->fifomap >> 6) & 3U)
};
const std::array<bool, SYS_RSXAUDIO_SERIAL_STREAM_CNT> en_streams =
{
static_cast<bool>(av_audio_pkt->enable & 0x10),
static_cast<bool>(av_audio_pkt->enable & 0x20),
static_cast<bool>(av_audio_pkt->enable & 0x40),
static_cast<bool>(av_audio_pkt->enable & 0x80)
};
// Might be wrong
const std::array<bool, SYS_RSXAUDIO_SERIAL_STREAM_CNT> swap_lr =
{
static_cast<bool>(av_audio_pkt->swaplr & 0x10),
static_cast<bool>(av_audio_pkt->swaplr & 0x20),
static_cast<bool>(av_audio_pkt->swaplr & 0x40),
static_cast<bool>(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; 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) 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 }, { 0, 0, 0 },
{ 4, 2880, 480 }, { 4, 2880, 480 },
@ -1430,7 +1534,7 @@ private:
((1ULL << video_head_cfg.video_out_format) & 0x1CE07) == 0U || ((1ULL << video_head_cfg.video_out_format) & 0x1CE07) == 0U ||
video_head_cfg.unk2 > 3 || video_head_cfg.unk2 > 3 ||
video_head_cfg.pitch & 7 || 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)) || (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) || (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))) !((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 {}; return {};
} }
static rsxaudio_hw_param_t::hdmi_param_t::hdmi_ch_cfg_t hdmi_param_conv(const std::array<u8, SYS_RSXAUDIO_SERIAL_STREAM_CNT> &map,
const std::array<bool, SYS_RSXAUDIO_SERIAL_STREAM_CNT> &en,
const std::array<bool, SYS_RSXAUDIO_SERIAL_STREAM_CNT> &swap)
{
std::array<u8, SYS_RSXAUDIO_SERIAL_MAX_CH> 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<u8>((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 struct generic_reply_cmd : public ps3av_cmd
@ -1622,7 +1768,7 @@ error_code sys_uart_receive(ppu_thread &ppu, vm::ptr<void> buffer, u64 size, u32
read_size = read_result; 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; return CELL_EFAULT;
} }
@ -1781,7 +1927,7 @@ error_code sys_uart_get_params(vm::ptr<vuart_params> buffer)
return CELL_ESRCH; 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; 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) vuart_av_thread &vuart_av_thread::operator=(thread_state)
{ {
{
std::lock_guard lock(tx_wake_m);
}
tx_wake_c.notify_all(); tx_wake_c.notify_all();
return *this; 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) u32 vuart_av_thread::enque_tx_data(const void *data, u32 data_sz)
{ {
std::unique_lock<shared_mutex> lock(tx_wake_m); std::unique_lock<shared_mutex> lock(tx_wake_m);
if (auto size = tx_buf.push(data, data_sz)) if (u32 size = static_cast<u32>(tx_buf.push(data, data_sz, true)))
{ {
lock.unlock(); lock.unlock();
tx_wake_c.notify_all(); 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() u32 vuart_av_thread::get_tx_bytes()
{ {
return tx_buf.get_used_size(); return static_cast<u32>(tx_buf.get_used_size());
} }
u32 vuart_av_thread::read_rx_data(void *data, u32 data_sz) u32 vuart_av_thread::read_rx_data(void *data, u32 data_sz)
{ {
return rx_buf.pop(data, data_sz); return static_cast<u32>(rx_buf.pop(data, data_sz, true));
} }
u32 vuart_av_thread::read_tx_data(void *data, u32 data_sz) u32 vuart_av_thread::read_tx_data(void *data, u32 data_sz)
{ {
std::unique_lock<shared_mutex> lock(tx_rdy_m); std::unique_lock<shared_mutex> lock(tx_rdy_m);
if (auto size = tx_buf.pop(data, data_sz)) if (u32 size = static_cast<u32>(tx_buf.pop(data, data_sz, true)))
{ {
lock.unlock(); lock.unlock();
tx_rdy_c.notify_all(); 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; temp_buf &buf = syscon_buf ? temp_rx_sc_buf : temp_rx_buf;
std::unique_lock<shared_mutex> lock(rx_wake_m); std::unique_lock<shared_mutex> 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; buf.crnt_size = 0;
if (rx_buf.get_used_size()) 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) if (hdmi_0)
{ {
// TBA g_fxo->get<rsx_audio_data>().update_av_mute_state(RsxaudioAvportIdx::HDMI_0, false, true);
hdcp_first_auth[0] = true; hdcp_first_auth[0] = true;
commit_event_data(&pkt, sizeof(pkt)); commit_event_data(&pkt, sizeof(pkt));
} }
if (hdmi_1) if (hdmi_1)
{ {
// TBA g_fxo->get<rsx_audio_data>().update_av_mute_state(RsxaudioAvportIdx::HDMI_1, false, true);
hdcp_first_auth[1] = true; hdcp_first_auth[1] = true;
pkt.cid |= 0x10000; pkt.cid |= 0x10000;
commit_event_data(&pkt, sizeof(pkt)); 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) if (hdmi_0)
{ {
// TBA g_fxo->get<rsx_audio_data>().update_av_mute_state(RsxaudioAvportIdx::HDMI_0, false, true);
av_get_monitor_info_cmd::set_hdmi_display_cfg(*this, pkt.minfo, static_cast<u8>(UartAudioAvport::HDMI_0)); av_get_monitor_info_cmd::set_hdmi_display_cfg(*this, pkt.minfo, static_cast<u8>(UartAudioAvport::HDMI_0));
commit_event_data(&pkt, sizeof(pkt) - 4); commit_event_data(&pkt, sizeof(pkt) - 4);
} }
if (hdmi_1) if (hdmi_1)
{ {
// TBA g_fxo->get<rsx_audio_data>().update_av_mute_state(RsxaudioAvportIdx::HDMI_1, false, true);
memset(&pkt.minfo, 0, sizeof(pkt.minfo)); memset(&pkt.minfo, 0, sizeof(pkt.minfo));
pkt.hdr.cid |= 0x10000; pkt.hdr.cid |= 0x10000;
av_get_monitor_info_cmd::set_hdmi_display_cfg(*this, pkt.minfo, static_cast<u8>(UartAudioAvport::HDMI_1)); av_get_monitor_info_cmd::set_hdmi_display_cfg(*this, pkt.minfo, static_cast<u8>(UartAudioAvport::HDMI_1));
@ -2184,7 +2333,7 @@ void vuart_av_thread::add_hdcp_done_event(bool hdmi_0, bool hdmi_1)
if (hdmi_0) if (hdmi_0)
{ {
// TBA g_fxo->get<rsx_audio_data>().update_av_mute_state(RsxaudioAvportIdx::HDMI_0, false, true, false);
if (hdcp_first_auth[0]) 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) if (hdmi_1)
{ {
// TBA g_fxo->get<rsx_audio_data>().update_av_mute_state(RsxaudioAvportIdx::HDMI_1, false, true, false);
if (hdcp_first_auth[1]) 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) void vuart_av_thread::commit_event_data(const void *data, u16 data_size)
{ {
std::unique_lock<shared_mutex> lock(rx_wake_m); std::unique_lock<shared_mutex> lock(rx_wake_m);
rx_buf.push(data, data_size); rx_buf.push(data, data_size, true);
if (rx_buf.get_used_size()) if (rx_buf.get_used_size())
{ {

View File

@ -2,6 +2,7 @@
#include "util/types.hpp" #include "util/types.hpp"
u64 convert_to_timebased_time(u64 time);
u64 get_timebased_time(); u64 get_timebased_time();
void initalize_timebased_time(); void initalize_timebased_time();
u64 get_system_time(); u64 get_system_time();

View File

@ -230,7 +230,8 @@ struct cfg_root : cfg::node
node_audio(cfg::node* _this) : cfg::node(_this, "Audio") {} node_audio(cfg::node* _this) : cfg::node(_this, "Audio") {}
cfg::_enum<audio_renderer> renderer{ this, "Renderer", audio_renderer::cubeb, true }; cfg::_enum<audio_renderer> renderer{ this, "Renderer", audio_renderer::cubeb, true };
cfg::_enum<audio_provider> provider{ this, "Audio provider", audio_provider::cell_audio, false }; cfg::_enum<audio_provider> provider{ this, "Audio Provider", audio_provider::cell_audio, false };
cfg::_enum<audio_avport> rsxaudio_port{ this, "RSXAudio Avport", audio_avport::hdmi_0, true };
cfg::_bool dump_to_file{ this, "Dump to file", false, 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::_bool convert_to_s16{ this, "Convert to 16 bit", false, true };
cfg::_enum<audio_downmix> audio_channel_downmix{ this, "Audio Channels", audio_downmix::downmix_to_stereo, true }; cfg::_enum<audio_downmix> audio_channel_downmix{ this, "Audio Channels", audio_downmix::downmix_to_stereo, true };

View File

@ -512,6 +512,24 @@ void fmt_class_string<audio_provider>::format(std::string& out, u64 arg)
}); });
} }
template <>
void fmt_class_string<audio_avport>::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 <> template <>
void fmt_class_string<audio_downmix>::format(std::string& out, u64 arg) void fmt_class_string<audio_downmix>::format(std::string& out, u64 arg)
{ {

View File

@ -67,6 +67,15 @@ enum class audio_provider
rsxaudio rsxaudio
}; };
enum class audio_avport
{
hdmi_0,
hdmi_1,
avmulti,
spdif_0,
spdif_1
};
enum class audio_downmix enum class audio_downmix
{ {
no_downmix, // Surround 7.1 no_downmix, // Surround 7.1

View File

@ -446,6 +446,8 @@
<ClInclude Include="..\3rdparty\stblib\include\stb_image.h" /> <ClInclude Include="..\3rdparty\stblib\include\stb_image.h" />
<ClInclude Include="..\Utilities\address_range.h" /> <ClInclude Include="..\Utilities\address_range.h" />
<ClInclude Include="..\Utilities\cheat_info.h" /> <ClInclude Include="..\Utilities\cheat_info.h" />
<ClInclude Include="..\Utilities\simple_ringbuf.h" />
<ClInclude Include="..\Utilities\transactional_storage.h" />
<ClInclude Include="Emu\Audio\audio_device_listener.h" /> <ClInclude Include="Emu\Audio\audio_device_listener.h" />
<ClInclude Include="Emu\Audio\audio_resampler.h" /> <ClInclude Include="Emu\Audio\audio_resampler.h" />
<ClInclude Include="Emu\Audio\FAudio\FAudioBackend.h"> <ClInclude Include="Emu\Audio\FAudio\FAudioBackend.h">
@ -813,4 +815,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets"> <ImportGroup Label="ExtensionTargets">
</ImportGroup> </ImportGroup>
</Project> </Project>

View File

@ -2083,6 +2083,12 @@
<ClInclude Include="Emu\Io\Null\null_music_handler.h"> <ClInclude Include="Emu\Io\Null\null_music_handler.h">
<Filter>Emu\Io\Null</Filter> <Filter>Emu\Io\Null</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\Utilities\simple_ringbuf.h">
<Filter>Utilities</Filter>
</ClInclude>
<ClInclude Include="..\Utilities\transactional_storage.h">
<Filter>Utilities</Filter>
</ClInclude>
<ClInclude Include="Emu\RSX\Overlays\overlay_media_list_dialog.h"> <ClInclude Include="Emu\RSX\Overlays\overlay_media_list_dialog.h">
<Filter>Emu\GPU\RSX\Overlays</Filter> <Filter>Emu\GPU\RSX\Overlays</Filter>
</ClInclude> </ClInclude>
@ -2113,4 +2119,4 @@
<Filter>Emu\GPU\RSX\Common\Interpreter</Filter> <Filter>Emu\GPU\RSX\Common\Interpreter</Filter>
</None> </None>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -121,6 +121,8 @@ enum class emu_settings_type
DumpToFile, DumpToFile,
ConvertTo16Bit, ConvertTo16Bit,
AudioChannels, AudioChannels,
AudioProvider,
AudioAvport,
MasterVolume, MasterVolume,
EnableBuffering, EnableBuffering,
AudioBufferDuration, AudioBufferDuration,
@ -288,6 +290,8 @@ inline static const QMap<emu_settings_type, cfg_location> settings_location =
{ emu_settings_type::DumpToFile, { "Audio", "Dump to file"}}, { emu_settings_type::DumpToFile, { "Audio", "Dump to file"}},
{ emu_settings_type::ConvertTo16Bit, { "Audio", "Convert to 16 bit"}}, { emu_settings_type::ConvertTo16Bit, { "Audio", "Convert to 16 bit"}},
{ emu_settings_type::AudioChannels, { "Audio", "Audio Channels"}}, { 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::MasterVolume, { "Audio", "Master Volume"}},
{ emu_settings_type::EnableBuffering, { "Audio", "Enable Buffering"}}, { emu_settings_type::EnableBuffering, { "Audio", "Enable Buffering"}},
{ emu_settings_type::AudioBufferDuration, { "Audio", "Desired Audio Buffer Duration"}}, { emu_settings_type::AudioBufferDuration, { "Audio", "Desired Audio Buffer Duration"}},

View File

@ -20,6 +20,7 @@
#include "Emu/Io/Null/null_camera_handler.h" #include "Emu/Io/Null/null_camera_handler.h"
#include "Emu/Io/Null/null_music_handler.h" #include "Emu/Io/Null/null_music_handler.h"
#include "Emu/Cell/Modules/cellAudio.h" #include "Emu/Cell/Modules/cellAudio.h"
#include "Emu/Cell/lv2/sys_rsxaudio.h"
#include "Emu/RSX/Overlays/overlay_perf_metrics.h" #include "Emu/RSX/Overlays/overlay_perf_metrics.h"
#include "Emu/system_utils.hpp" #include "Emu/system_utils.hpp"
#include "Emu/vfs_config.h" #include "Emu/vfs_config.h"
@ -629,6 +630,7 @@ void gui_application::OnEmuSettingsChange()
rpcs3::utils::configure_logs(); rpcs3::utils::configure_logs();
audio::configure_audio(); audio::configure_audio();
audio::configure_rsxaudio();
rsx::overlays::reset_performance_overlay(); rsx::overlays::reset_performance_overlay();
} }

View File

@ -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)); gui_log.error("Error while extracting firmware: Failed to mount '%s'", sstr(dir_path));
critical(tr("Firmware extraction failed: VFS mounting failed.")); critical(tr("Firmware extraction failed: VFS mounting failed."));
return; return;
} }
if (!update_files.extract("/pup_extract")) if (!update_files.extract("/pup_extract"))
@ -2789,7 +2789,7 @@ void main_window::CreateFirmwareCache()
Emu.GracefulShutdown(false); Emu.GracefulShutdown(false);
Emu.SetForceBoot(true); 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) error != game_boot_result::no_errors)
{ {
gui_log.error("Creating firmware cache failed: reason: %s", error); 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. // Refresh game list since we probably unlocked some games now.
m_game_list_frame->Refresh(true); m_game_list_frame->Refresh(true);
} }
break; break;
} }
case drop_type::drop_psf: // Display PARAM.SFO content case drop_type::drop_psf: // Display PARAM.SFO content

View File

@ -62,6 +62,8 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> gui_settings, std
if (!m_gui_settings->GetValue(gui::m_showDebugTab).toBool()) if (!m_gui_settings->GetValue(gui::m_showDebugTab).toBool())
{ {
ui->tab_widget_settings->removeTab(9); ui->tab_widget_settings->removeTab(9);
ui->audioDump->setVisible(false);
ui->audioDump->setChecked(false);
m_gui_settings->SetValue(gui::m_showDebugTab, false); m_gui_settings->SetValue(gui::m_showDebugTab, false);
} }
if (game) if (game)
@ -879,6 +881,16 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> gui_settings, std
enable_buffering_options(enabled && ui->enableBuffering->isChecked()); 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<QString>());
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 QString mic_none = m_emu_settings->m_microphone_creator.get_none();
const auto change_microphone_type = [mic_none, this](int index) const auto change_microphone_type = [mic_none, this](int index)
@ -958,6 +970,13 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> gui_settings, std
// TODO: enable this setting once cellAudioOutConfigure can change downmix on the fly // TODO: enable this setting once cellAudioOutConfigure can change downmix on the fly
ui->combo_audio_downmix->removeItem(static_cast<int>(audio_downmix::use_application_settings)); ui->combo_audio_downmix->removeItem(static_cast<int>(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<int>::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 // Microphone Comboboxes
m_mics_combo[0] = ui->microphone1Box; m_mics_combo[0] = ui->microphone1Box;
m_mics_combo[1] = ui->microphone2Box; m_mics_combo[1] = ui->microphone2Box;
@ -1014,6 +1033,7 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> gui_settings, std
connect(ui->enableTimeStretching, &QCheckBox::toggled, enable_time_stretching_options); connect(ui->enableTimeStretching, &QCheckBox::toggled, enable_time_stretching_options);
enable_buffering(ui->audioOutBox->currentIndex()); enable_buffering(ui->audioOutBox->currentIndex());
enable_avport_option(ui->audioProviderBox->currentIndex());
// Sliders // Sliders

View File

@ -1049,16 +1049,16 @@
</property> </property>
<layout class="QVBoxLayout" name="gb_audio_settings_layout"> <layout class="QVBoxLayout" name="gb_audio_settings_layout">
<item> <item>
<widget class="QCheckBox" name="audioDump"> <widget class="QCheckBox" name="convert">
<property name="text"> <property name="text">
<string>Dump to File</string> <string>Convert to 16-bit</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="convert"> <widget class="QCheckBox" name="audioDump">
<property name="text"> <property name="text">
<string>Convert to 16-bit</string> <string>Dump to File</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -1113,6 +1113,30 @@
</item> </item>
<item> <item>
<layout class="QVBoxLayout" name="audioTabLayoutTopMiddle"> <layout class="QVBoxLayout" name="audioTabLayoutTopMiddle">
<item>
<widget class="QGroupBox" name="gb_audio_provider">
<property name="title">
<string>Audio Provider</string>
</property>
<layout class="QVBoxLayout" name="gb_audio_provider_layout">
<item>
<widget class="QComboBox" name="audioProviderBox"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="gb_audio_avport">
<property name="title">
<string>RSXAudio Avport</string>
</property>
<layout class="QVBoxLayout" name="gb_audio_avport_layout">
<item>
<widget class="QComboBox" name="audioAvportBox"/>
</item>
</layout>
</widget>
</item>
<item> <item>
<widget class="QGroupBox" name="gb_audio_volume"> <widget class="QGroupBox" name="gb_audio_volume">
<property name="title"> <property name="title">

View File

@ -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 = 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_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 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 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."); 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.");