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:
parent
0ac90ac395
commit
d1e468fefb
@ -1,84 +1,154 @@
|
|||||||
#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)
|
||||||
|
{
|
||||||
|
val.read_ptr += std::min(get_used_size(val), cnt);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const u32 old = static_cast<u32>(rw_ptr.load() >> 32);
|
u64 simple_ringbuf::push(const void* data, u64 size, bool force)
|
||||||
const u32 to_push = std::min(size, get_free_size());
|
{
|
||||||
auto b_data = static_cast<const u8*>(data);
|
ensure(data != nullptr);
|
||||||
|
|
||||||
if (!to_push) return 0;
|
return rw_ptr.atomic_op([&](ctr_state& val) -> u64
|
||||||
|
{
|
||||||
|
const u64 buf_size = buf.size();
|
||||||
|
const u64 old = val.write_ptr % buf_size;
|
||||||
|
const u64 free_size = get_free_size(val);
|
||||||
|
const u64 to_push = std::min(size, free_size);
|
||||||
|
const auto b_data = static_cast<const u8*>(data);
|
||||||
|
|
||||||
|
if (!to_push || (!force && free_size < size))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (old + to_push > buf_size)
|
if (old + to_push > buf_size)
|
||||||
{
|
{
|
||||||
@ -91,23 +161,28 @@ u32 simple_ringbuf::push(const void *data, u32 size)
|
|||||||
memcpy(&buf[old], b_data, to_push);
|
memcpy(&buf[old], b_data, to_push);
|
||||||
}
|
}
|
||||||
|
|
||||||
rw_ptr.atomic_op([&](u64 &val)
|
val.write_ptr += to_push;
|
||||||
{
|
|
||||||
val = static_cast<u64>((old + to_push) % buf_size) << 32 | static_cast<u32>(val);
|
|
||||||
});
|
|
||||||
|
|
||||||
return to_push;
|
return to_push;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 simple_ringbuf::pop(void *data, u32 size)
|
u64 simple_ringbuf::pop(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);
|
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) return 0;
|
if (!to_pop || (!force && used_size < size))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (old + to_pop > buf_size)
|
if (old + to_pop > buf_size)
|
||||||
{
|
{
|
||||||
@ -120,10 +195,8 @@ u32 simple_ringbuf::pop(void *data, u32 size)
|
|||||||
memcpy(b_data, &buf[old], to_pop);
|
memcpy(b_data, &buf[old], to_pop);
|
||||||
}
|
}
|
||||||
|
|
||||||
rw_ptr.atomic_op([&](u64 &val)
|
val.read_ptr += to_pop;
|
||||||
{
|
|
||||||
val = (old + to_pop) % buf_size | (val & 0xFFFFFFFF'00000000);
|
|
||||||
});
|
|
||||||
|
|
||||||
return to_pop;
|
return to_pop;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
159
Utilities/transactional_storage.h
Normal file
159
Utilities/transactional_storage.h
Normal 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{};
|
||||||
|
};
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
};
|
};
|
||||||
|
@ -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,12 +15,10 @@ 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);
|
m_header = WAVHeader(ch, sample_rate, sample_size);
|
||||||
std::string path = fs::get_cache_dir() + "audio_";
|
std::string path = fs::get_cache_dir() + "audio_";
|
||||||
if (const std::string id = Emu.GetTitleID(); !id.empty())
|
if (const std::string id = Emu.GetTitleID(); !id.empty())
|
||||||
@ -27,16 +27,22 @@ void AudioDumper::Open(u16 ch, u32 sample_rate, u32 sample_size)
|
|||||||
}
|
}
|
||||||
path += date_time::current_time_narrow<'_'>() + ".wav";
|
path += date_time::current_time_narrow<'_'>() + ".wav";
|
||||||
m_output.open(path, fs::rewrite);
|
m_output.open(path, fs::rewrite);
|
||||||
m_output.write(m_header); // write initial file header
|
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)
|
||||||
|
{
|
||||||
|
const u32 blk_size = GetCh() * GetSampleSize();
|
||||||
|
const u32 sample_cnt_per_ch = size / blk_size;
|
||||||
|
|
||||||
|
ensure(size - sample_cnt_per_ch * blk_size == 0);
|
||||||
|
|
||||||
|
if constexpr (std::endian::big == std::endian::native)
|
||||||
|
{
|
||||||
|
std::vector<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(size);
|
|
||||||
ensure(m_output.write(buffer, size) == size);
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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; }
|
||||||
};
|
};
|
||||||
|
@ -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))
|
if (int err = cubeb_stream_stop(m_stream))
|
||||||
{
|
{
|
||||||
Cubeb.error("cubeb_stream_stop() failed: 0x%08x", err);
|
Cubeb.error("cubeb_stream_stop() failed: 0x%08x", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
cubeb_stream_destroy(m_stream);
|
cubeb_stream_destroy(m_stream);
|
||||||
|
m_stream = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (u32 res = FAudioSourceVoice_Stop(m_source_voice, 0, FAUDIO_COMMIT_NOW))
|
||||||
if (res)
|
|
||||||
{
|
{
|
||||||
FAudio_.error("FAudioSourceVoice_Stop() failed(0x%08x)", res);
|
FAudio_.error("FAudioSourceVoice_Stop() failed(0x%08x)", res);
|
||||||
}
|
}
|
||||||
|
|
||||||
FAudioVoice_DestroyVoice(m_source_voice);
|
FAudioVoice_DestroyVoice(m_source_voice);
|
||||||
m_playing = false;
|
|
||||||
m_source_voice = nullptr;
|
m_source_voice = nullptr;
|
||||||
m_data_buf = nullptr;
|
}
|
||||||
m_data_buf_len = 0;
|
|
||||||
|
if (m_master_voice)
|
||||||
|
{
|
||||||
|
FAudioVoice_DestroyVoice(m_master_voice);
|
||||||
|
m_master_voice = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_playing = false;
|
||||||
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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; }
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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{};
|
||||||
|
|
||||||
|
@ -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");
|
||||||
update_config(update_req == audio_backend_update::ALL);
|
{
|
||||||
|
std::lock_guard lock{emu_cfg_upd_m};
|
||||||
|
cfg.raw = cfg.new_raw;
|
||||||
m_update_configuration = audio_backend_update::NONE;
|
m_update_configuration = audio_backend_update::NONE;
|
||||||
}
|
}
|
||||||
|
update_config(update_req == audio_backend_update::ALL);
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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
@ -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);
|
||||||
|
@ -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,9 +135,18 @@ 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));
|
||||||
@ -144,13 +154,15 @@ u64 get_timebased_time()
|
|||||||
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
|
||||||
|
@ -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.hdmi_res_set[1] = hdcp_done_cnt[1] > 0;
|
|
||||||
vuart.add_hdmi_events(UartHdmiEvent::HDCP_DONE, vuart.hdmi_res_set[0], vuart.hdmi_res_set[1]);
|
vuart.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 (hdcp_done_cnt[1])
|
if (vuart.hdmi_res_set[1])
|
||||||
{
|
{
|
||||||
// TBA
|
g_fxo->get<rsx_audio_data>().update_av_mute_state(RsxaudioAvportIdx::HDMI_1, false, true);
|
||||||
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())
|
||||||
{
|
{
|
||||||
|
@ -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();
|
||||||
|
@ -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 };
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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
|
||||||
|
@ -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">
|
||||||
|
@ -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>
|
||||||
|
@ -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"}},
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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">
|
||||||
|
@ -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.");
|
||||||
|
Loading…
Reference in New Issue
Block a user