1
0
mirror of https://github.com/RPCS3/rpcs3.git synced 2024-11-21 10:12:32 +01:00

sys_rsxaudio: Initial implementation (#11907)

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

View File

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

View File

@ -2,37 +2,53 @@
#include "util/types.hpp"
#include "util/atomic.hpp"
#include <vector>
// Single reader/writer simple ringbuffer.
// Counters are 32-bit.
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:
simple_ringbuf() {};
simple_ringbuf(u32 size);
simple_ringbuf(u64 size = 0);
virtual ~simple_ringbuf();
simple_ringbuf(const simple_ringbuf&) = delete;
simple_ringbuf& operator=(const simple_ringbuf&) = delete;
simple_ringbuf(simple_ringbuf&& other);
simple_ringbuf& operator=(simple_ringbuf&& other);
u32 get_free_size() const;
u32 get_used_size() const;
u32 get_total_size() const;
simple_ringbuf(const simple_ringbuf& other);
simple_ringbuf& operator=(const simple_ringbuf& other);
// Thread unsafe functions.
void set_buf_size(u32 size);
void flush(); // Could be safely called from reader.
simple_ringbuf(simple_ringbuf&& other);
simple_ringbuf& operator=(simple_ringbuf&& other);
void set_buf_size(u64 size);
u32 push(const void *data, u32 size);
u32 pop(void *data, u32 size);
// Helper functions
u64 get_free_size() const;
u64 get_used_size() const;
u64 get_total_size() const;
// Writer functions
u64 push(const void* data, u64 size, bool force = false);
void writer_flush(u64 cnt = umax);
// Reader functions
u64 pop(void* data, u64 size, bool force = false);
void reader_flush(u64 cnt = umax);
private:
struct ctr_state
{
alignas(sizeof(u64) * 2)
u64 read_ptr = 0;
u64 write_ptr = 0;
auto operator<=>(const ctr_state& other) const = default;
};
static_assert(sizeof(ctr_state) == sizeof(u64) * 2);
atomic_t<ctr_state> rw_ptr{};
std::vector<u8> buf{};
u64 get_free_size(ctr_state val) const;
u64 get_used_size(ctr_state val) const;
};

View File

@ -0,0 +1,159 @@
#include "util/types.hpp"
#include <vector>
#include <mutex>
// Thread-safe object pool with garbage collection
class universal_pool
{
public:
universal_pool(u32 gc_interval = 10000) : gc_interval(gc_interval)
{
}
~universal_pool()
{
std::lock_guard lock(mutex);
storage.clear();
}
universal_pool(const universal_pool&) = delete;
universal_pool& operator=(const universal_pool&) = delete;
void set_gc_interval(u32 new_val)
{
gc_interval = new_val;
}
template <typename F>
requires (std::invocable<F&> && std::is_same_v<std::invoke_result_t<F&>, std::shared_ptr<void>>)
void add_op(F func)
{
std::lock_guard lock(mutex);
if (std::shared_ptr<void> new_val = std::invoke(func); new_val)
{
storage.push_back(new_val);
}
delete_unused();
}
void force_gc()
{
std::lock_guard lock(mutex);
delete_unused();
}
private:
void delete_unused()
{
const u32 gc_int = gc_interval.observe();
if (u64 crnt_time = get_system_time(); gc_int == 0 || crnt_time > gc_last_time + gc_int)
{
gc_last_time = crnt_time;
storage.erase
(
std::remove_if(storage.begin(), storage.end(), [](auto& obj) { return obj.use_count() <= 1; }),
storage.end()
);
}
}
shared_mutex mutex{};
std::vector<std::shared_ptr<void>> storage{};
u64 gc_last_time = get_system_time();
atomic_t<u32> gc_interval = 0;
};
template<typename T>
class transactional_storage
{
public:
transactional_storage(std::shared_ptr<universal_pool> pool, std::shared_ptr<T> obj = std::make_shared<T>())
{
ensure(pool && obj);
this->pool = pool;
add(obj);
}
transactional_storage(const transactional_storage&) = delete;
transactional_storage& operator=(const transactional_storage&) = delete;
transactional_storage(transactional_storage&& other)
{
pool = std::move(other.pool);
std::unique_lock lock_other{other.current_mutex};
const std::shared_ptr<T> other_current = other.current;
other.current = nullptr;
lock_other.unlock();
std::lock_guard lock{current_mutex};
current = other_current;
}
transactional_storage& operator=(transactional_storage&& other)
{
if (this == &other) return *this;
pool = std::move(other.pool);
std::unique_lock lock_other{other.current_mutex};
const std::shared_ptr<T> other_current = other.current;
other.current = nullptr;
lock_other.unlock();
std::lock_guard lock{current_mutex};
current = other_current;
return *this;
}
std::shared_ptr<const T> get_current()
{
reader_lock lock(current_mutex);
return current;
}
void add(std::shared_ptr<T> obj)
{
if (!obj)
{
return;
}
pool->add_op([&]() -> std::shared_ptr<void>
{
{
std::lock_guard lock{current_mutex};
current = obj;
}
return std::move(obj);
});
}
template <typename F>
requires (std::invocable<F&> && std::is_same_v<std::invoke_result_t<F&>, std::shared_ptr<T>>)
void add_op(F func)
{
pool->add_op([&]() -> std::shared_ptr<void>
{
std::shared_ptr<T> obj = std::invoke(func);
if (obj)
{
std::lock_guard lock{current_mutex};
current = obj;
}
return obj;
});
}
private:
shared_mutex current_mutex{};
std::shared_ptr<T> current{};
std::shared_ptr<universal_pool> pool{};
};

View File

@ -4,6 +4,12 @@
AudioBackend::AudioBackend() {}
void AudioBackend::SetErrorCallback(std::function<void()> cb)
{
std::lock_guard lock(m_error_cb_mutex);
m_error_callback = cb;
}
/*
* Helper methods
*/
@ -29,8 +35,65 @@ bool AudioBackend::get_convert_to_s16() const
void AudioBackend::convert_to_s16(u32 cnt, const f32* src, void* dst)
{
for (usz i = 0; i < cnt; i++)
for (u32 i = 0; i < cnt; i++)
{
static_cast<s16 *>(dst)[i] = static_cast<s16>(std::clamp(src[i] * 32768.5f, -32768.0f, 32767.0f));
static_cast<s16*>(dst)[i] = static_cast<s16>(std::clamp(src[i] * 32768.5f, -32768.0f, 32767.0f));
}
}
f32 AudioBackend::apply_volume(const VolumeParam& param, u32 sample_cnt, const f32* src, f32* dst)
{
ensure(param.ch_cnt > 1 && param.ch_cnt % 2 == 0); // Tends to produce faster code
const f32 vol_incr = (param.target_volume - param.initial_volume) / (VOLUME_CHANGE_DURATION * param.freq);
f32 crnt_vol = param.current_volume;
u32 sample_idx = 0;
if (vol_incr >= 0)
{
for (sample_idx = 0; sample_idx < sample_cnt && crnt_vol != param.target_volume; sample_idx += param.ch_cnt)
{
crnt_vol = std::min(param.current_volume + (sample_idx + 1) / param.ch_cnt * vol_incr, param.target_volume);
for (u32 i = 0; i < param.ch_cnt; i++)
{
dst[sample_idx + i] = src[sample_idx + i] * crnt_vol;
}
}
}
else
{
for (sample_idx = 0; sample_idx < sample_cnt && crnt_vol != param.target_volume; sample_idx += param.ch_cnt)
{
crnt_vol = std::max(param.current_volume + (sample_idx + 1) / param.ch_cnt * vol_incr, param.target_volume);
for (u32 i = 0; i < param.ch_cnt; i++)
{
dst[sample_idx + i] = src[sample_idx + i] * crnt_vol;
}
}
}
if (sample_cnt > sample_idx)
{
apply_volume_static(param.target_volume, sample_cnt - sample_idx, &src[sample_idx], &dst[sample_idx]);
}
return crnt_vol;
}
void AudioBackend::apply_volume_static(f32 vol, u32 sample_cnt, const f32* src, f32* dst)
{
for (u32 i = 0; i < sample_cnt; i++)
{
dst[i] = src[i] * vol;
}
}
void AudioBackend::normalize(u32 sample_cnt, const f32* src, f32* dst)
{
for (u32 i = 0; i < sample_cnt; i++)
{
dst[i] = std::clamp<f32>(src[i], -1.0f, 1.0f);
}
}

View File

@ -1,7 +1,9 @@
#pragma once
#include "util/types.hpp"
#include "Utilities/mutex.h"
#include "Utilities/StrFmt.h"
#include <numbers>
enum : u32
{
@ -38,32 +40,54 @@ enum class AudioChannelCnt : u32
class AudioBackend
{
public:
struct VolumeParam
{
f32 initial_volume = 1.0f;
f32 current_volume = 1.0f;
f32 target_volume = 1.0f;
u32 freq = 48000;
u32 ch_cnt = 2;
};
AudioBackend();
virtual ~AudioBackend() = default;
/*
* Pure virtual methods
* Virtual methods
*/
virtual std::string_view GetName() const = 0;
virtual void Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) = 0;
// (Re)create output stream with new parameters. Blocks until data callback returns.
// Should return 'true' on success.
virtual bool Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) = 0;
// Reset backend state. Blocks until data callback returns.
virtual void Close() = 0;
// Sets write callback. It's called when backend requests new data to be sent
// Callback should return number of submitted bytes. Calling other backend functions from callback is unsafe
virtual void SetWriteCallback(std::function<u32(u32 /* byte_cnt */, void * /* buffer */)> cb) = 0;
// Sets write callback. It's called when backend requests new data to be sent.
// Callback should return number of submitted bytes. Calling other backend functions from callback is unsafe.
virtual void SetWriteCallback(std::function<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;
// Returns true if audio is currently being played, false otherwise
virtual bool IsPlaying() = 0;
// Returns true if audio is currently being played, false otherwise. Reflects end result of Play() and Pause() calls.
virtual bool IsPlaying() { return m_playing; }
// Start playing enqueued data
// Start playing enqueued data.
virtual void Play() = 0;
// Pause playing enqueued data
// Pause playing enqueued data. No additional callbacks will be issued. Blocks until data callback returns.
virtual void Pause() = 0;
/*
@ -93,8 +117,93 @@ public:
*/
static void convert_to_s16(u32 cnt, const f32* src, void* dst);
/*
* Apply volume parameters to the buffer. Gradually changes volume. src and dst could be the same.
* Number of channels must be >1 and multiple of 2.
* sample_cnt is number of buffer elements. Returns current volume.
*/
static f32 apply_volume(const VolumeParam& param, u32 sample_cnt, const f32* src, f32* dst);
/*
* Apply volume value to the buffer. src and dst could be the same. sample_cnt is number of buffer elements.
* Returns current volume.
*/
static void apply_volume_static(f32 vol, u32 sample_cnt, const f32* src, f32* dst);
/*
* Downmix audio stream.
*/
template<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:
AudioSampleSize m_sample_size = AudioSampleSize::FLOAT;
AudioFreq m_sampling_rate = AudioFreq::FREQ_48K;
AudioChannelCnt m_channels = AudioChannelCnt::STEREO;
shared_mutex m_error_cb_mutex{};
std::function<void()> m_error_callback{};
bool m_playing = false;
private:
static constexpr f32 VOLUME_CHANGE_DURATION = 0.016f; // sec
};

View File

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

View File

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

View File

@ -56,12 +56,17 @@ bool CubebBackend::Initialized()
bool CubebBackend::Operational()
{
return m_ctx != nullptr && m_stream != nullptr && !m_reset_req.observe();
std::lock_guard lock(m_error_cb_mutex);
return m_stream != nullptr && !m_reset_req;
}
void CubebBackend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt)
bool CubebBackend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt)
{
if (m_ctx == nullptr) return;
if (!Initialized())
{
Cubeb.error("Open() called uninitialized");
return false;
}
std::lock_guard lock(m_cb_mutex);
CloseUnlocked();
@ -92,46 +97,48 @@ void CubebBackend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChanne
if (int err = cubeb_get_min_latency(m_ctx, &stream_param, &min_latency))
{
Cubeb.error("cubeb_get_min_latency() failed: 0x%08x", err);
min_latency = 0;
}
const u32 stream_latency = std::max(static_cast<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))
{
m_stream = nullptr;
Cubeb.error("cubeb_stream_init() failed: 0x%08x", err);
m_stream = nullptr;
}
else if (int err = cubeb_stream_start(m_stream))
{
Cubeb.error("cubeb_stream_start() failed: 0x%08x", err);
CloseUnlocked();
}
else if (int err = cubeb_stream_set_volume(m_stream, 1.0))
{
Cubeb.error("cubeb_stream_set_volume() failed: 0x%08x", err);
}
if (m_stream == nullptr)
{
Cubeb.error("Failed to open audio backend. Make sure that no other application is running that might block audio access (e.g. Netflix).");
CloseUnlocked();
return;
return false;
}
if (int err = cubeb_stream_set_volume(m_stream, 1.0))
{
Cubeb.error("cubeb_stream_set_volume() failed: 0x%08x", err);
}
if (int err = cubeb_stream_start(m_stream))
{
Cubeb.error("cubeb_stream_start() failed: 0x%08x", err);
}
return true;
}
void CubebBackend::CloseUnlocked()
{
if (m_stream == nullptr) return;
if (int err = cubeb_stream_stop(m_stream))
if (m_stream != nullptr)
{
Cubeb.error("cubeb_stream_stop() failed: 0x%08x", err);
if (int err = cubeb_stream_stop(m_stream))
{
Cubeb.error("cubeb_stream_stop() failed: 0x%08x", err);
}
cubeb_stream_destroy(m_stream);
m_stream = nullptr;
}
cubeb_stream_destroy(m_stream);
m_playing = false;
m_stream = nullptr;
m_last_sample.fill(0);
}
@ -143,6 +150,12 @@ void CubebBackend::Close()
void CubebBackend::Play()
{
if (m_stream == nullptr)
{
Cubeb.error("Play() called uninitialized");
return;
}
if (m_playing) return;
std::lock_guard lock(m_cb_mutex);
@ -151,16 +164,19 @@ void CubebBackend::Play()
void CubebBackend::Pause()
{
if (m_stream == nullptr)
{
Cubeb.error("Pause() called uninitialized");
return;
}
if (!m_playing) return;
std::lock_guard lock(m_cb_mutex);
m_playing = false;
m_last_sample.fill(0);
}
bool CubebBackend::IsPlaying()
{
return m_playing;
}
void CubebBackend::SetWriteCallback(std::function<u32(u32, void *)> cb)
{
std::lock_guard lock(m_cb_mutex);
@ -169,10 +185,17 @@ void CubebBackend::SetWriteCallback(std::function<u32(u32, void *)> cb)
f64 CubebBackend::GetCallbackFrameLen()
{
if (m_stream == nullptr)
{
Cubeb.error("GetCallbackFrameLen() called uninitialized");
return AUDIO_MIN_LATENCY;
}
u32 stream_latency{};
if (int err = cubeb_stream_get_latency(m_stream, &stream_latency))
{
Cubeb.error("cubeb_stream_get_latency() failed: 0x%08x", err);
stream_latency = 0;
}
return std::max<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)
{
Cubeb.error("Stream entered error state");
std::lock_guard lock(cubeb->m_error_cb_mutex);
cubeb->m_reset_req = true;
if (cubeb->m_error_callback)
{
cubeb->m_error_callback();
}
}
}

View File

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

View File

@ -14,27 +14,17 @@ FAudioBackend::FAudioBackend()
{
FAudio *instance;
u32 res = FAudioCreate(&instance, 0, FAUDIO_DEFAULT_PROCESSOR);
if (res)
if (u32 res = FAudioCreate(&instance, 0, FAUDIO_DEFAULT_PROCESSOR))
{
FAudio_.error("FAudioCreate() failed(0x%08x)", res);
return;
}
res = FAudio_CreateMasteringVoice(instance, &m_master_voice, FAUDIO_DEFAULT_CHANNELS, FAUDIO_DEFAULT_SAMPLERATE, 0, 0, nullptr);
if (res)
{
FAudio_.error("FAudio_CreateMasteringVoice() failed(0x%08x)", res);
FAudio_StopEngine(instance);
return;
}
OnProcessingPassStart = nullptr;
OnProcessingPassEnd = nullptr;
OnCriticalError = OnCriticalError_func;
res = FAudio_RegisterForCallbacks(instance, this);
if (res)
if (u32 res = FAudio_RegisterForCallbacks(instance, this))
{
// Some error recovery functionality will be lost, but otherwise backend is operational
FAudio_.error("FAudio_RegisterForCallbacks() failed(0x%08x)", res);
@ -48,11 +38,6 @@ FAudioBackend::~FAudioBackend()
{
Close();
if (m_master_voice != nullptr)
{
FAudioVoice_DestroyVoice(m_master_voice);
}
if (m_instance != nullptr)
{
FAudio_StopEngine(m_instance);
@ -70,56 +55,52 @@ void FAudioBackend::Play()
if (m_playing) return;
const u32 res = FAudioSourceVoice_Start(m_source_voice, 0, FAUDIO_COMMIT_NOW);
if (res)
{
FAudio_.error("FAudioSourceVoice_Start() failed(0x%08x)", res);
}
std::lock_guard lock(m_cb_mutex);
m_playing = true;
}
void FAudioBackend::Pause()
{
if (m_source_voice)
{
u32 res = FAudioSourceVoice_Stop(m_source_voice, 0, FAUDIO_COMMIT_NOW);
if (res)
{
FAudio_.error("FAudioSourceVoice_Stop() failed(0x%08x)", res);
}
if ((res = FAudioSourceVoice_FlushSourceBuffers(m_source_voice)))
{
FAudio_.error("FAudioSourceVoice_FlushSourceBuffers() failed(0x%08x)", res);
}
}
else
if (m_source_voice == nullptr)
{
FAudio_.error("Pause() called uninitialized");
return;
}
std::lock_guard lock(m_cb_mutex);
m_playing = false;
m_last_sample.fill(0);
if (!m_playing) return;
{
std::lock_guard lock(m_cb_mutex);
m_playing = false;
m_last_sample.fill(0);
}
if (u32 res = FAudioSourceVoice_FlushSourceBuffers(m_source_voice))
{
FAudio_.error("FAudioSourceVoice_FlushSourceBuffers() failed(0x%08x)", res);
}
}
void FAudioBackend::CloseUnlocked()
{
if (m_source_voice == nullptr) return;
const u32 res = FAudioSourceVoice_Stop(m_source_voice, 0, FAUDIO_COMMIT_NOW);
if (res)
if (m_source_voice != nullptr)
{
FAudio_.error("FAudioSourceVoice_Stop() failed(0x%08x)", res);
if (u32 res = FAudioSourceVoice_Stop(m_source_voice, 0, FAUDIO_COMMIT_NOW))
{
FAudio_.error("FAudioSourceVoice_Stop() failed(0x%08x)", res);
}
FAudioVoice_DestroyVoice(m_source_voice);
m_source_voice = nullptr;
}
if (m_master_voice)
{
FAudioVoice_DestroyVoice(m_master_voice);
m_master_voice = nullptr;
}
FAudioVoice_DestroyVoice(m_source_voice);
m_playing = false;
m_source_voice = nullptr;
m_data_buf = nullptr;
m_data_buf_len = 0;
m_last_sample.fill(0);
}
@ -136,12 +117,17 @@ bool FAudioBackend::Initialized()
bool FAudioBackend::Operational()
{
return m_instance != nullptr && m_source_voice != nullptr && !m_reset_req.observe();
std::lock_guard lock(m_error_cb_mutex);
return m_source_voice != nullptr && !m_reset_req;
}
void FAudioBackend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt)
bool FAudioBackend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt)
{
if (m_instance == nullptr) return;
if (!Initialized())
{
FAudio_.error("Open() called uninitialized");
return false;
}
std::lock_guard lock(m_cb_mutex);
CloseUnlocked();
@ -167,27 +153,35 @@ void FAudioBackend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChann
OnLoopEnd = nullptr;
OnVoiceError = nullptr;
const u32 res = FAudio_CreateSourceVoice(m_instance, &m_source_voice, &waveformatex, 0, FAUDIO_DEFAULT_FREQ_RATIO, this, nullptr, nullptr);
if (res)
if (u32 res = FAudio_CreateMasteringVoice(m_instance, &m_master_voice, FAUDIO_DEFAULT_CHANNELS, FAUDIO_DEFAULT_SAMPLERATE, 0, 0, nullptr))
{
FAudio_.error("FAudio_CreateMasteringVoice() failed(0x%08x)", res);
m_master_voice = nullptr;
}
else if (u32 res = FAudio_CreateSourceVoice(m_instance, &m_source_voice, &waveformatex, 0, FAUDIO_DEFAULT_FREQ_RATIO, this, nullptr, nullptr))
{
FAudio_.error("FAudio_CreateSourceVoice() failed(0x%08x)", res);
CloseUnlocked();
}
else if (u32 res = FAudioSourceVoice_Start(m_source_voice, 0, FAUDIO_COMMIT_NOW))
{
FAudio_.error("FAudioSourceVoice_Start() failed(0x%08x)", res);
CloseUnlocked();
}
else if (u32 res = FAudioVoice_SetVolume(m_source_voice, 1.0f, FAUDIO_COMMIT_NOW))
{
FAudio_.error("FAudioVoice_SetVolume() failed(0x%08x)", res);
}
if (m_source_voice == nullptr)
{
FAudio_.fatal("Failed to open audio backend. Make sure that no other application is running that might block audio access (e.g. Netflix).");
return;
FAudio_.error("Failed to open audio backend. Make sure that no other application is running that might block audio access (e.g. Netflix).");
return false;
}
FAudioVoice_SetVolume(m_source_voice, 1.0f, FAUDIO_COMMIT_NOW);
m_data_buf.resize(get_sampling_rate() * get_sample_size() * get_channels() * INTERNAL_BUF_SIZE_MS * static_cast<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;
m_data_buf = std::make_unique<u8[]>(m_data_buf_len);
}
bool FAudioBackend::IsPlaying()
{
return m_playing;
return true;
}
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);
if (BytesRequired && lock.try_lock() && faudio->m_write_callback && faudio->m_playing)
{
ensure(BytesRequired <= faudio->m_data_buf_len, "FAudio internal buffer is too small. Report to developers!");
ensure(BytesRequired <= faudio->m_data_buf.size(), "FAudio internal buffer is too small. Report to developers!");
const u32 sample_size = faudio->get_sample_size() * faudio->get_channels();
u32 written = std::min(faudio->m_write_callback(BytesRequired, faudio->m_data_buf.get()), BytesRequired);
u32 written = std::min(faudio->m_write_callback(BytesRequired, faudio->m_data_buf.data()), BytesRequired);
written -= written % sample_size;
if (written >= sample_size)
{
memcpy(faudio->m_last_sample.data(), faudio->m_data_buf.get() + written - sample_size, sample_size);
memcpy(faudio->m_last_sample.data(), faudio->m_data_buf.data() + written - sample_size, sample_size);
}
for (u32 i = written; i < BytesRequired; i += sample_size)
{
memcpy(faudio->m_data_buf.get() + i, faudio->m_last_sample.data(), sample_size);
memcpy(faudio->m_data_buf.data() + i, faudio->m_last_sample.data(), sample_size);
}
FAudioBuffer buffer{};
buffer.AudioBytes = BytesRequired;
buffer.LoopBegin = FAUDIO_NO_LOOP_REGION;
buffer.pAudioData = static_cast<const u8*>(faudio->m_data_buf.get());
const u32 res = FAudioSourceVoice_SubmitSourceBuffer(faudio->m_source_voice, &buffer, nullptr);
if (res)
{
FAudio_.error("FAudioSourceVoice_SubmitSourceBuffer() failed(0x%08x)", res);
}
buffer.pAudioData = static_cast<const u8*>(faudio->m_data_buf.data());
// Avoid logging in callback and assume that this always succeeds, all errors are caught by error callback anyway
FAudioSourceVoice_SubmitSourceBuffer(faudio->m_source_voice, &buffer, nullptr);
}
}
@ -260,5 +249,12 @@ void FAudioBackend::OnCriticalError_func(FAudioEngineCallback *cb_obj, u32 Error
FAudio_.error("OnCriticalError() failed(0x%08x)", Error);
FAudioBackend *faudio = static_cast<FAudioBackend *>(cb_obj);
std::lock_guard lock(faudio->m_error_cb_mutex);
faudio->m_reset_req = true;
if (faudio->m_error_callback)
{
faudio->m_error_callback();
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -73,16 +73,15 @@ void cell_audio_config::reset(bool backend_changed)
}
}();
backend->Open(freq, sample_size, ch_cnt);
const f64 cb_frame_len = backend->Open(freq, sample_size, ch_cnt) ? backend->GetCallbackFrameLen() : 0.0;
audio_channels = backend->get_channels();
audio_sampling_rate = backend->get_sampling_rate();
audio_channels = static_cast<u32>(ch_cnt);
audio_sampling_rate = static_cast<u32>(freq);
audio_block_period = AUDIO_BUFFER_SAMPLES * 1'000'000 / audio_sampling_rate;
audio_sample_size = backend->get_sample_size();
audio_min_buffer_duration = backend->GetCallbackFrameLen() + u32{AUDIO_BUFFER_SAMPLES} * 2.0 / audio_sampling_rate; // Add 2 blocks to allow jitter compensation
audio_sample_size = static_cast<u32>(sample_size);
audio_min_buffer_duration = cb_frame_len + u32{AUDIO_BUFFER_SAMPLES} * 2.0 / audio_sampling_rate; // Add 2 blocks to allow jitter compensation
audio_buffer_length = AUDIO_BUFFER_SAMPLES * audio_channels;
audio_buffer_size = audio_buffer_length * audio_sample_size;
desired_buffer_duration = std::max(static_cast<s64>(audio_min_buffer_duration * 1000), raw.desired_buffer_duration) * 1000llu;
buffering_enabled = raw.buffering_enabled && raw.renderer != audio_renderer::null;
@ -132,11 +131,11 @@ audio_ringbuffer::audio_ringbuffer(cell_audio_config& _cfg)
// Init audio dumper if enabled
if (cfg.raw.dump_to_file)
{
m_dump.Open(cfg.audio_channels, cfg.audio_sampling_rate, cfg.audio_sample_size);
m_dump.Open(static_cast<AudioChannelCnt>(cfg.audio_channels), static_cast<AudioFreq>(cfg.audio_sampling_rate), AudioSampleSize::FLOAT);
}
// 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);
const f64 buffer_dur_mult = [&]()
@ -165,7 +164,7 @@ audio_ringbuffer::~audio_ringbuffer()
f32 audio_ringbuffer::set_frequency_ratio(f32 new_ratio)
{
frequency_ratio = resampler.set_tempo(new_ratio);
frequency_ratio = static_cast<f32>(resampler.set_tempo(new_ratio));
return frequency_ratio;
}
@ -181,7 +180,7 @@ u32 audio_ringbuffer::backend_write_callback(u32 size, void *buf)
{
if (!backend_active.observe()) backend_active = true;
return cb_ringbuf.pop(buf, size);
return static_cast<u32>(cb_ringbuf.pop(buf, size, true));
}
u64 audio_ringbuffer::get_timestamp()
@ -228,9 +227,6 @@ void audio_ringbuffer::enqueue(bool enqueue_silence, bool force)
cur_pos = (cur_pos + 1) % cfg.num_allocated_buffers;
}
// Dump audio if enabled
m_dump.WriteData(buf, cfg.audio_buffer_size);
if (!backend_active.observe() && !force)
{
// backend is not ready yet
@ -261,7 +257,7 @@ void audio_ringbuffer::process_resampled_data()
{
if (!cfg.time_stretching_enabled) return;
const auto samples = resampler.get_samples(cb_ringbuf.get_free_size() / (cfg.audio_sample_size * cfg.audio_channels));
const auto samples = resampler.get_samples(static_cast<u32>(cb_ringbuf.get_free_size() / (cfg.audio_sample_size * cfg.audio_channels)));
commit_data(samples.first, samples.second);
}
@ -269,17 +265,15 @@ void audio_ringbuffer::commit_data(f32* buf, u32 sample_cnt)
{
sample_cnt *= cfg.audio_channels;
// Dump audio if enabled
m_dump.WriteData(buf, sample_cnt * static_cast<u32>(AudioSampleSize::FLOAT));
if (cfg.backend->get_convert_to_s16())
{
AudioBackend::convert_to_s16(sample_cnt, buf, buf);
}
sample_cnt *= cfg.audio_sample_size;
if (cb_ringbuf.get_free_size() >= sample_cnt)
{
cb_ringbuf.push(buf, sample_cnt);
}
cb_ringbuf.push(buf, sample_cnt * cfg.audio_sample_size);
}
void audio_ringbuffer::play()
@ -297,7 +291,7 @@ void audio_ringbuffer::play()
void audio_ringbuffer::flush()
{
backend->Pause();
cb_ringbuf.flush();
cb_ringbuf.writer_flush();
resampler.flush();
backend_active = false;
playing = false;
@ -566,7 +560,8 @@ namespace audio
raw.renderer != new_raw.renderer ||
raw.dump_to_file != new_raw.dump_to_file)
{
g_audio.cfg.raw = new_raw;
std::lock_guard lock{g_audio.emu_cfg_upd_m};
g_audio.cfg.new_raw = new_raw;
g_audio.m_update_configuration = raw.renderer != new_raw.renderer ? audio_backend_update::ALL : audio_backend_update::PARAM;
}
}
@ -635,18 +630,24 @@ void cell_audio_thread::operator()()
if (update_req != audio_backend_update::NONE)
{
cellAudio.warning("Updating cell_audio_thread configuration");
{
std::lock_guard lock{emu_cfg_upd_m};
cfg.raw = cfg.new_raw;
m_update_configuration = audio_backend_update::NONE;
}
update_config(update_req == audio_backend_update::ALL);
m_update_configuration = audio_backend_update::NONE;
}
if (!ringbuffer->get_operational_status())
{
cellAudio.warning("Backend stopped unexpectedly (likely device change). Attempting to recover...");
if (m_backend_failed)
{
thread_ctrl::wait_for(500 * 1000);
}
else
{
cellAudio.warning("Backend stopped unexpectedly (likely device change). Attempting to recover...");
}
update_config(true);
m_backend_failed = true;
@ -743,12 +744,10 @@ void cell_audio_thread::operator()()
if (desired_duration_rate < cfg.time_stretching_threshold)
{
const f32 normalized_desired_duration_rate = desired_duration_rate / cfg.time_stretching_threshold;
const f32 request_ratio = normalized_desired_duration_rate * cfg.time_stretching_scale;
AUDIT(request_ratio <= RESAMPLER_MAX_FREQ_VAL);
// change frequency ratio in steps
const f32 req_time_stretching_step = (request_ratio + frequency_ratio) / 2.0f;
if (req_time_stretching_step > cfg.time_stretching_step)
const f32 req_time_stretching_step = (normalized_desired_duration_rate + frequency_ratio) / 2.0f;
if (std::abs(req_time_stretching_step - frequency_ratio) > cfg.time_stretching_step)
{
ringbuffer->set_frequency_ratio(req_time_stretching_step);
}

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,41 @@
#pragma once
#include "sys_sync.h"
#include "sys_event.h"
#include "Utilities/Timer.h"
#include "Utilities/simple_ringbuf.h"
#include "Utilities/transactional_storage.h"
#include "Utilities/cond.h"
#include "Emu/Memory/vm_ptr.h"
#include "Emu/Cell/ErrorCodes.h"
#include "Emu/Audio/AudioDumper.h"
#include "Emu/Audio/AudioBackend.h"
#include "Emu/Audio/audio_resampler.h"
#if defined(unix) || defined(__unix) || defined(__unix__)
// For BSD detection
#include <sys/param.h>
#endif
#ifdef _WIN32
#include <windows.h>
#elif defined(BSD) || defined(__APPLE__)
#include <sys/event.h>
#endif
enum : u32
{
SYS_RSXAUDIO_SERIAL_STREAM_CNT = 4,
SYS_RSXAUDIO_STREAM_SIZE = 1024,
SYS_RSXAUDIO_STREAM_DATA_BLK_CNT = 4,
SYS_RSXAUDIO_DATA_BLK_SIZE = 256,
SYS_RSXAUDIO_STREAM_SIZE = SYS_RSXAUDIO_DATA_BLK_SIZE * SYS_RSXAUDIO_STREAM_DATA_BLK_CNT,
SYS_RSXAUDIO_CH_PER_STREAM = 2,
SYS_RSXAUDIO_SERIAL_MAX_CH = 8,
SYS_RSXAUDIO_SPDIF_MAX_CH = 2,
SYS_RSXAUDIO_STREAM_SAMPLE_CNT = SYS_RSXAUDIO_STREAM_SIZE / SYS_RSXAUDIO_CH_PER_STREAM / sizeof(f32),
SYS_RSXAUDIO_RINGBUF_BLK_SZ_SERIAL = 0x1000,
SYS_RSXAUDIO_RINGBUF_BLK_SZ_SPDIF = 0x400,
SYS_RSXAUDIO_RINGBUF_BLK_SZ_SERIAL = SYS_RSXAUDIO_STREAM_SIZE * SYS_RSXAUDIO_SERIAL_STREAM_CNT,
SYS_RSXAUDIO_RINGBUF_BLK_SZ_SPDIF = SYS_RSXAUDIO_STREAM_SIZE,
SYS_RSXAUDIO_RINGBUF_SZ = 16,
@ -19,10 +44,6 @@ enum : u32
SYS_RSXAUDIO_FREQ_BASE_384K = 384000,
SYS_RSXAUDIO_FREQ_BASE_352K = 352800,
SYS_RSXAUDIO_PORT_SERIAL = 0,
SYS_RSXAUDIO_PORT_SPDIF_0 = 1,
SYS_RSXAUDIO_PORT_SPDIF_1 = 2,
SYS_RSXAUDIO_PORT_INVALID = 0xFF,
SYS_RSXAUDIO_PORT_CNT = 3,
SYS_RSXAUDIO_SPDIF_CNT = 2,
@ -37,12 +58,563 @@ enum class RsxaudioAvportIdx : u8
SPDIF_1 = 4,
};
enum class RsxaudioPort : u8
{
SERIAL = 0,
SPDIF_0 = 1,
SPDIF_1 = 2,
INVALID = 0xFF,
};
enum class RsxaudioSampleSize : u8
{
_16BIT = 2,
_32BIT = 4,
};
struct rsxaudio_shmem
{
struct ringbuf_t
{
struct entry_t
{
be_t<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
@ -50,3 +622,9 @@ error_code sys_rsxaudio_initialize(vm::ptr<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_unimport_shared_memory(u32 handle, vm::ptr<u64> addr);
error_code sys_rsxaudio_create_connection(u32 handle);
error_code sys_rsxaudio_close_connection(u32 handle);
error_code sys_rsxaudio_prepare_process(u32 handle);
error_code sys_rsxaudio_start_process(u32 handle);
error_code sys_rsxaudio_stop_process(u32 handle);
error_code sys_rsxaudio_get_dma_param(u32 handle, u32 flag, vm::ptr<u64> out);

View File

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

View File

@ -420,7 +420,7 @@ struct video_disable_signal_cmd : public ps3av_cmd
if (pkt->avport <= static_cast<u16>(UartAudioAvport::HDMI_1))
{
// 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)
{
@ -499,7 +499,7 @@ struct av_audio_mute_cmd : public ps3av_cmd
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);
}
@ -833,7 +833,7 @@ struct audio_init_cmd : public ps3av_cmd
return;
}
// TBA
g_fxo->get<rsx_audio_data>().reset_hw();
vuart.write_resp(pkt->cid, PS3AV_STATUS_SUCCESS);
}
@ -871,7 +871,7 @@ private:
bool set_mode(const ps3av_pkt_audio_mode& pkt)
{
bool spdif_use_serial_buf = false;
u8 avport_src, rsxaudio_port;
RsxaudioPort avport_src, rsxaudio_port;
RsxaudioAvportIdx avport_idx;
switch (pkt.avport)
@ -881,11 +881,11 @@ private:
avport_idx = RsxaudioAvportIdx::HDMI_0;
if (pkt.audio_source == UartAudioSource::SPDIF)
{
avport_src = rsxaudio_port = SYS_RSXAUDIO_PORT_SPDIF_1;
avport_src = rsxaudio_port = RsxaudioPort::SPDIF_1;
}
else
{
avport_src = rsxaudio_port = SYS_RSXAUDIO_PORT_SERIAL;
avport_src = rsxaudio_port = RsxaudioPort::SERIAL;
}
break;
}
@ -894,32 +894,32 @@ private:
avport_idx = RsxaudioAvportIdx::HDMI_1;
if (pkt.audio_source == UartAudioSource::SPDIF)
{
avport_src = rsxaudio_port = SYS_RSXAUDIO_PORT_SPDIF_1;
avport_src = rsxaudio_port = RsxaudioPort::SPDIF_1;
}
else
{
avport_src = rsxaudio_port = SYS_RSXAUDIO_PORT_SERIAL;
avport_src = rsxaudio_port = RsxaudioPort::SERIAL;
}
break;
}
case UartAudioAvport::AVMULTI_0:
{
avport_idx = RsxaudioAvportIdx::AVMULTI;
avport_src = rsxaudio_port = SYS_RSXAUDIO_PORT_SERIAL;
avport_src = rsxaudio_port = RsxaudioPort::SERIAL;
break;
}
case UartAudioAvport::SPDIF_0:
{
avport_idx = RsxaudioAvportIdx::SPDIF_0;
rsxaudio_port = SYS_RSXAUDIO_PORT_SPDIF_0;
rsxaudio_port = RsxaudioPort::SPDIF_0;
if (pkt.audio_source == UartAudioSource::SERIAL)
{
spdif_use_serial_buf = true;
avport_src = SYS_RSXAUDIO_PORT_SERIAL;
avport_src = RsxaudioPort::SERIAL;
}
else
{
avport_src = SYS_RSXAUDIO_PORT_SPDIF_0;
avport_src = RsxaudioPort::SPDIF_0;
}
break;
@ -927,15 +927,15 @@ private:
case UartAudioAvport::SPDIF_1:
{
avport_idx = RsxaudioAvportIdx::SPDIF_1;
rsxaudio_port = SYS_RSXAUDIO_PORT_SPDIF_1;
rsxaudio_port = RsxaudioPort::SPDIF_1;
if (pkt.audio_source == UartAudioSource::SERIAL)
{
spdif_use_serial_buf = true;
avport_src = SYS_RSXAUDIO_PORT_SERIAL;
avport_src = RsxaudioPort::SERIAL;
}
else
{
avport_src = SYS_RSXAUDIO_PORT_SPDIF_1;
avport_src = RsxaudioPort::SPDIF_1;
}
break;
@ -946,25 +946,11 @@ private:
}
}
const u32 freq = [&]()
{
switch (pkt.audio_fs)
{
case UartAudioFreq::_44K: return 44100;
case UartAudioFreq::_48K: return 48000;
case UartAudioFreq::_88K: return 88200;
case UartAudioFreq::_96K: return 96000;
case UartAudioFreq::_176K: return 176400;
case UartAudioFreq::_192K: return 192000;
default: return 0;
}
}();
if (freq == 0) return false;
if (static_cast<u32>(pkt.audio_fs.value()) > static_cast<u32>(UartAudioFreq::_192K)) return false;
const auto bit_cnt = [&]()
{
if ((rsxaudio_port != SYS_RSXAUDIO_PORT_SERIAL && pkt.audio_format != UartAudioFormat::PCM) ||
if ((rsxaudio_port != RsxaudioPort::SERIAL && pkt.audio_format != UartAudioFormat::PCM) ||
pkt.audio_word_bits == UartAudioSampleSize::_16BIT)
{
return UartAudioSampleSize::_16BIT;
@ -975,29 +961,58 @@ private:
}
}();
return commit_param(rsxaudio_port, avport_idx, avport_src, freq, bit_cnt, spdif_use_serial_buf, pkt.audio_cs_info);
return commit_param(rsxaudio_port, avport_idx, avport_src, pkt.audio_fs, bit_cnt, spdif_use_serial_buf, pkt.audio_cs_info);
}
bool commit_param(u8 rsxaudio_port, RsxaudioAvportIdx avport, [[maybe_unused]] u8 avport_src, [[maybe_unused]] u32 freq,
UartAudioSampleSize bit_cnt, [[maybe_unused]] bool spdif_use_serial_buf, [[maybe_unused]] const u8 *cs_data)
bool commit_param(RsxaudioPort rsxaudio_port, RsxaudioAvportIdx avport, RsxaudioPort avport_src, UartAudioFreq freq,
UartAudioSampleSize bit_cnt, bool spdif_use_serial_buf, const u8 *cs_data)
{
// TBA
[[maybe_unused]] const auto avport_idx = static_cast<std::underlying_type_t<decltype(avport)>>(avport);
[[maybe_unused]] const auto rsxaudio_word_depth = bit_cnt == UartAudioSampleSize::_16BIT ? RsxaudioSampleSize::_16BIT : RsxaudioSampleSize::_32BIT;
auto& rsxaudio_thread = g_fxo->get<rsx_audio_data>();
const auto avport_idx = static_cast<std::underlying_type_t<decltype(avport)>>(avport);
const auto rsxaudio_word_depth = bit_cnt == UartAudioSampleSize::_16BIT ? RsxaudioSampleSize::_16BIT : RsxaudioSampleSize::_32BIT;
const auto freq_param = [&]()
{
switch (freq)
{
case UartAudioFreq::_44K: return std::make_pair(8, SYS_RSXAUDIO_FREQ_BASE_352K);
default:
case UartAudioFreq::_48K: return std::make_pair(8, SYS_RSXAUDIO_FREQ_BASE_384K);
case UartAudioFreq::_88K: return std::make_pair(4, SYS_RSXAUDIO_FREQ_BASE_352K);
case UartAudioFreq::_96K: return std::make_pair(4, SYS_RSXAUDIO_FREQ_BASE_384K);
case UartAudioFreq::_176K: return std::make_pair(2, SYS_RSXAUDIO_FREQ_BASE_352K);
case UartAudioFreq::_192K: return std::make_pair(2, SYS_RSXAUDIO_FREQ_BASE_384K);
}
}();
switch (rsxaudio_port)
{
case SYS_RSXAUDIO_PORT_SERIAL:
case RsxaudioPort::SERIAL:
{
// TBA
rsxaudio_thread.update_hw_param([&](auto& obj)
{
obj.serial_freq_base = freq_param.second;
obj.serial.freq_div = freq_param.first;
obj.serial.depth = rsxaudio_word_depth;
obj.serial.buf_empty_en = true;
obj.avport_src[avport_idx] = avport_src;
});
break;
}
case SYS_RSXAUDIO_PORT_SPDIF_0:
case SYS_RSXAUDIO_PORT_SPDIF_1:
case RsxaudioPort::SPDIF_0:
case RsxaudioPort::SPDIF_1:
{
[[maybe_unused]] const u8 spdif_idx = rsxaudio_port == SYS_RSXAUDIO_PORT_SPDIF_1;
const u8 spdif_idx = rsxaudio_port == RsxaudioPort::SPDIF_1;
// TBA
rsxaudio_thread.update_hw_param([&](auto& obj)
{
obj.spdif_freq_base = freq_param.second;
obj.spdif[spdif_idx].freq_div = freq_param.first;
obj.spdif[spdif_idx].depth = rsxaudio_word_depth;
obj.spdif[spdif_idx].use_serial_buf = spdif_use_serial_buf;
obj.spdif[spdif_idx].buf_empty_en = true;
obj.avport_src[avport_idx] = avport_src;
memcpy(obj.spdif[spdif_idx].cs_data.data(), cs_data, sizeof(obj.spdif[spdif_idx].cs_data));
});
break;
}
default:
@ -1034,13 +1049,13 @@ struct audio_mute_cmd : public ps3av_cmd
case UartAudioAvport::HDMI_1:
case UartAudioAvport::AVMULTI_0:
case UartAudioAvport::AVMULTI_1:
// TBA
g_fxo->get<rsx_audio_data>().update_mute_state(RsxaudioPort::SERIAL, pkt->mute);
break;
case UartAudioAvport::SPDIF_0:
// TBA
g_fxo->get<rsx_audio_data>().update_mute_state(RsxaudioPort::SPDIF_0, pkt->mute);
break;
case UartAudioAvport::SPDIF_1:
// TBA
g_fxo->get<rsx_audio_data>().update_mute_state(RsxaudioPort::SPDIF_1, pkt->mute);
break;
default:
break;
@ -1067,7 +1082,7 @@ struct audio_set_active_cmd : public ps3av_cmd
return;
}
[[maybe_unused]] const bool requested_avports[SYS_RSXAUDIO_AVPORT_CNT] =
const bool requested_avports[SYS_RSXAUDIO_AVPORT_CNT] =
{
(pkt->audio_port & PS3AV_AUDIO_PORT_HDMI_0) != 0U,
(pkt->audio_port & PS3AV_AUDIO_PORT_HDMI_1) != 0U,
@ -1076,7 +1091,36 @@ struct audio_set_active_cmd : public ps3av_cmd
(pkt->audio_port & PS3AV_AUDIO_PORT_SPDIF_1) != 0U
};
// TBA
g_fxo->get<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);
}
@ -1099,7 +1143,24 @@ struct audio_set_inactive_cmd : public ps3av_cmd
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);
}
@ -1122,7 +1183,7 @@ struct audio_spdif_bit_cmd : public ps3av_cmd
return;
}
[[maybe_unused]] const bool requested_avports[SYS_RSXAUDIO_AVPORT_CNT] =
const bool requested_avports[SYS_RSXAUDIO_AVPORT_CNT] =
{
(pkt->audio_port & PS3AV_AUDIO_PORT_HDMI_0) != 0U,
(pkt->audio_port & PS3AV_AUDIO_PORT_HDMI_1) != 0U,
@ -1131,7 +1192,21 @@ struct audio_spdif_bit_cmd : public ps3av_cmd
(pkt->audio_port & PS3AV_AUDIO_PORT_SPDIF_1) != 0U
};
// TBA
g_fxo->get<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);
}
@ -1243,7 +1318,7 @@ struct inc_avset_cmd : public ps3av_cmd
return;
}
u8 hdcp_done_cnt[2]{};
bool hdcp_done[2]{};
// AV Video
for (u32 video_av_pkt_idx = 0; video_av_pkt_idx < pkt->num_of_av_video_pkt; video_av_pkt_idx++)
@ -1259,29 +1334,24 @@ struct inc_avset_cmd : public ps3av_cmd
if (syscon_check_passed)
{
if (subcmd_resp.hdcp_done_event[0]) hdcp_done_cnt[0]++;
if (subcmd_resp.hdcp_done_event[1]) hdcp_done_cnt[1]++;
hdcp_done[0] |= subcmd_resp.hdcp_done_event[0];
hdcp_done[1] |= subcmd_resp.hdcp_done_event[1];
}
pkt_data_addr += av_video_pkt->hdr.length + 4ULL;
}
while (hdcp_done_cnt[0] || hdcp_done_cnt[1])
{
vuart.hdmi_res_set[0] = hdcp_done_cnt[0] > 0;
vuart.hdmi_res_set[1] = hdcp_done_cnt[1] > 0;
vuart.add_hdmi_events(UartHdmiEvent::HDCP_DONE, vuart.hdmi_res_set[0], vuart.hdmi_res_set[1]);
vuart.hdmi_res_set[0] = hdcp_done[0];
vuart.hdmi_res_set[1] = hdcp_done[1];
vuart.add_hdmi_events(UartHdmiEvent::HDCP_DONE, vuart.hdmi_res_set[0], vuart.hdmi_res_set[1]);
if (hdcp_done_cnt[0])
{
// TBA
hdcp_done_cnt[0]--;
}
if (hdcp_done_cnt[1])
{
// TBA
hdcp_done_cnt[1]--;
}
if (vuart.hdmi_res_set[0])
{
g_fxo->get<rsx_audio_data>().update_av_mute_state(RsxaudioAvportIdx::HDMI_0, false, true);
}
if (vuart.hdmi_res_set[1])
{
g_fxo->get<rsx_audio_data>().update_av_mute_state(RsxaudioAvportIdx::HDMI_1, false, true);
}
bool valid_av_audio_pkt = false;
@ -1301,9 +1371,43 @@ struct inc_avset_cmd : public ps3av_cmd
break;
}
[[maybe_unused]] const u8 hdmi_idx = av_audio_pkt->avport == static_cast<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;
@ -1342,7 +1446,7 @@ private:
u32 video_pkt_parse(const ps3av_pkt_video_mode &video_head_cfg)
{
constexpr video_sce_param sce_param_arr[28] =
static constexpr video_sce_param sce_param_arr[28] =
{
{ 0, 0, 0 },
{ 4, 2880, 480 },
@ -1430,7 +1534,7 @@ private:
((1ULL << video_head_cfg.video_out_format) & 0x1CE07) == 0U ||
video_head_cfg.unk2 > 3 ||
video_head_cfg.pitch & 7 ||
video_head_cfg.pitch >> 16 ||
video_head_cfg.pitch > UINT16_MAX ||
(video_head_cfg.width != 1280U && ((video_head_cfg.width & 7) != 0U || video_head_cfg.width > UINT16_MAX)) ||
(sce_param.width != 720 && video_head_cfg.width > sce_param.width / sce_param.width_div) ||
!((video_head_cfg.height == 1470U && (sce_param.height == 721 || sce_param.height == 481 || sce_param.height == 577)) || (video_head_cfg.height <= sce_param.height && video_head_cfg.height <= UINT16_MAX)))
@ -1486,6 +1590,48 @@ private:
return {};
}
static rsxaudio_hw_param_t::hdmi_param_t::hdmi_ch_cfg_t hdmi_param_conv(const std::array<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
@ -1622,7 +1768,7 @@ error_code sys_uart_receive(ppu_thread &ppu, vm::ptr<void> buffer, u64 size, u32
read_size = read_result;
}
if (!buffer || !vm::check_addr(buffer.addr(), vm::page_writable, read_size))
if (!vm::check_addr(buffer.addr(), vm::page_writable, read_size))
{
return CELL_EFAULT;
}
@ -1781,7 +1927,7 @@ error_code sys_uart_get_params(vm::ptr<vuart_params> buffer)
return CELL_ESRCH;
}
if (!buffer || !vm::check_addr(buffer.addr(), vm::page_writable, sizeof(vuart_params)))
if (!vm::check_addr(buffer.addr(), vm::page_writable, sizeof(vuart_params)))
{
return CELL_EFAULT;
}
@ -1913,6 +2059,9 @@ void vuart_av_thread::parse_tx_buffer(u32 buf_size)
vuart_av_thread &vuart_av_thread::operator=(thread_state)
{
{
std::lock_guard lock(tx_wake_m);
}
tx_wake_c.notify_all();
return *this;
}
@ -1920,7 +2069,7 @@ vuart_av_thread &vuart_av_thread::operator=(thread_state)
u32 vuart_av_thread::enque_tx_data(const void *data, u32 data_sz)
{
std::unique_lock<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();
tx_wake_c.notify_all();
@ -1932,18 +2081,18 @@ u32 vuart_av_thread::enque_tx_data(const void *data, u32 data_sz)
u32 vuart_av_thread::get_tx_bytes()
{
return tx_buf.get_used_size();
return static_cast<u32>(tx_buf.get_used_size());
}
u32 vuart_av_thread::read_rx_data(void *data, u32 data_sz)
{
return rx_buf.pop(data, data_sz);
return static_cast<u32>(rx_buf.pop(data, data_sz, true));
}
u32 vuart_av_thread::read_tx_data(void *data, u32 data_sz)
{
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();
tx_rdy_c.notify_all();
@ -2042,7 +2191,7 @@ void vuart_av_thread::commit_rx_buf(bool syscon_buf)
temp_buf &buf = syscon_buf ? temp_rx_sc_buf : temp_rx_buf;
std::unique_lock<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;
if (rx_buf.get_used_size())
@ -2127,14 +2276,14 @@ void vuart_av_thread::add_unplug_event(bool hdmi_0, bool hdmi_1)
if (hdmi_0)
{
// TBA
g_fxo->get<rsx_audio_data>().update_av_mute_state(RsxaudioAvportIdx::HDMI_0, false, true);
hdcp_first_auth[0] = true;
commit_event_data(&pkt, sizeof(pkt));
}
if (hdmi_1)
{
// TBA
g_fxo->get<rsx_audio_data>().update_av_mute_state(RsxaudioAvportIdx::HDMI_1, false, true);
hdcp_first_auth[1] = true;
pkt.cid |= 0x10000;
commit_event_data(&pkt, sizeof(pkt));
@ -2152,14 +2301,14 @@ void vuart_av_thread::add_plug_event(bool hdmi_0, bool hdmi_1)
if (hdmi_0)
{
// TBA
g_fxo->get<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));
commit_event_data(&pkt, sizeof(pkt) - 4);
}
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));
pkt.hdr.cid |= 0x10000;
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)
{
// TBA
g_fxo->get<rsx_audio_data>().update_av_mute_state(RsxaudioAvportIdx::HDMI_0, false, true, false);
if (hdcp_first_auth[0])
{
@ -2204,7 +2353,7 @@ void vuart_av_thread::add_hdcp_done_event(bool hdmi_0, bool hdmi_1)
if (hdmi_1)
{
// TBA
g_fxo->get<rsx_audio_data>().update_av_mute_state(RsxaudioAvportIdx::HDMI_1, false, true, false);
if (hdcp_first_auth[1])
{
@ -2226,7 +2375,7 @@ void vuart_av_thread::add_hdcp_done_event(bool hdmi_0, bool hdmi_1)
void vuart_av_thread::commit_event_data(const void *data, u16 data_size)
{
std::unique_lock<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())
{

View File

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

View File

@ -230,7 +230,8 @@ struct cfg_root : cfg::node
node_audio(cfg::node* _this) : cfg::node(_this, "Audio") {}
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 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 };

View File

@ -512,6 +512,24 @@ void fmt_class_string<audio_provider>::format(std::string& out, u64 arg)
});
}
template <>
void fmt_class_string<audio_avport>::format(std::string& out, u64 arg)
{
format_enum(out, arg, [](audio_avport value)
{
switch (value)
{
case audio_avport::hdmi_0: return "HDMI 0";
case audio_avport::hdmi_1: return "HDMI 1";
case audio_avport::avmulti: return "AV multiout";
case audio_avport::spdif_0: return "SPDIF 0";
case audio_avport::spdif_1: return "SPDIF 1";
}
return unknown;
});
}
template <>
void fmt_class_string<audio_downmix>::format(std::string& out, u64 arg)
{

View File

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

View File

@ -446,6 +446,8 @@
<ClInclude Include="..\3rdparty\stblib\include\stb_image.h" />
<ClInclude Include="..\Utilities\address_range.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_resampler.h" />
<ClInclude Include="Emu\Audio\FAudio\FAudioBackend.h">
@ -813,4 +815,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View File

@ -2083,6 +2083,12 @@
<ClInclude Include="Emu\Io\Null\null_music_handler.h">
<Filter>Emu\Io\Null</Filter>
</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">
<Filter>Emu\GPU\RSX\Overlays</Filter>
</ClInclude>
@ -2113,4 +2119,4 @@
<Filter>Emu\GPU\RSX\Common\Interpreter</Filter>
</None>
</ItemGroup>
</Project>
</Project>

View File

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

View File

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

View File

@ -1110,7 +1110,7 @@ void main_window::HandlePupInstallation(const QString& file_path, const QString&
{
gui_log.error("Error while extracting firmware: Failed to mount '%s'", sstr(dir_path));
critical(tr("Firmware extraction failed: VFS mounting failed."));
return;
return;
}
if (!update_files.extract("/pup_extract"))
@ -2789,7 +2789,7 @@ void main_window::CreateFirmwareCache()
Emu.GracefulShutdown(false);
Emu.SetForceBoot(true);
if (const game_boot_result error = Emu.BootGame(g_cfg_vfs.get_dev_flash() + "sys", "", true);
if (const game_boot_result error = Emu.BootGame(g_cfg_vfs.get_dev_flash(), "", true);
error != game_boot_result::no_errors)
{
gui_log.error("Creating firmware cache failed: reason: %s", error);
@ -3004,7 +3004,7 @@ void main_window::dropEvent(QDropEvent* event)
// Refresh game list since we probably unlocked some games now.
m_game_list_frame->Refresh(true);
}
break;
}
case drop_type::drop_psf: // Display PARAM.SFO content

View File

@ -62,6 +62,8 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> gui_settings, std
if (!m_gui_settings->GetValue(gui::m_showDebugTab).toBool())
{
ui->tab_widget_settings->removeTab(9);
ui->audioDump->setVisible(false);
ui->audioDump->setChecked(false);
m_gui_settings->SetValue(gui::m_showDebugTab, false);
}
if (game)
@ -879,6 +881,16 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> gui_settings, std
enable_buffering_options(enabled && ui->enableBuffering->isChecked());
};
const auto enable_avport_option = [this](int index)
{
if (index < 0) return;
const QVariantList var_list = ui->audioProviderBox->itemData(index).toList();
ensure(var_list.size() == 2 && var_list[0].canConvert<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 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
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
m_mics_combo[0] = ui->microphone1Box;
m_mics_combo[1] = ui->microphone2Box;
@ -1014,6 +1033,7 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> gui_settings, std
connect(ui->enableTimeStretching, &QCheckBox::toggled, enable_time_stretching_options);
enable_buffering(ui->audioOutBox->currentIndex());
enable_avport_option(ui->audioProviderBox->currentIndex());
// Sliders

View File

@ -1049,16 +1049,16 @@
</property>
<layout class="QVBoxLayout" name="gb_audio_settings_layout">
<item>
<widget class="QCheckBox" name="audioDump">
<widget class="QCheckBox" name="convert">
<property name="text">
<string>Dump to File</string>
<string>Convert to 16-bit</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="convert">
<widget class="QCheckBox" name="audioDump">
<property name="text">
<string>Convert to 16-bit</string>
<string>Dump to File</string>
</property>
</widget>
</item>
@ -1113,6 +1113,30 @@
</item>
<item>
<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>
<widget class="QGroupBox" name="gb_audio_volume">
<property name="title">

View File

@ -46,6 +46,8 @@ public:
const QString audio_out = tr("Cubeb uses a cross-platform approach and supports audio buffering, so it is the recommended option.\nXAudio2 uses native Windows sounds system, is the next best alternative.");
const QString audio_out_linux = tr("Cubeb uses a cross-platform approach and supports audio buffering, so it is the recommended option.\nIf it's not available, FAudio could be used instead.");
const QString audio_provider = tr("Controls which PS3 audio API is used.\nGames use CellAudio, while VSH requires RSXAudio.");
const QString audio_avport = tr("Controls which avport is used to sample audio data from.");
const QString audio_dump = tr("Saves all audio as a raw wave file. If unsure, leave this unchecked.");
const QString convert = tr("Uses 16-bit audio samples instead of default 32-bit floating point.\nUse with buggy audio drivers if you have no sound or completely broken sound.");
const QString downmix = tr("Uses chosen audio output instead of default 7.1 surround sound.\nUse downmix to stereo with stereo audio devices. Use 5.1 or higher only if you are using a surround sound audio system.");