rpcsx/rpcs3/Emu/Cell/lv2/sys_rsxaudio.h

631 lines
17 KiB
C++

#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_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 = SYS_RSXAUDIO_STREAM_SIZE * SYS_RSXAUDIO_SERIAL_STREAM_CNT,
SYS_RSXAUDIO_RINGBUF_BLK_SZ_SPDIF = SYS_RSXAUDIO_STREAM_SIZE,
SYS_RSXAUDIO_RINGBUF_SZ = 16,
SYS_RSXAUDIO_AVPORT_CNT = 5,
SYS_RSXAUDIO_FREQ_BASE_384K = 384000,
SYS_RSXAUDIO_FREQ_BASE_352K = 352800,
SYS_RSXAUDIO_PORT_CNT = 3,
SYS_RSXAUDIO_SPDIF_CNT = 2,
};
enum class RsxaudioAvportIdx : u8
{
HDMI_0 = 0,
HDMI_1 = 1,
AVMULTI = 2,
SPDIF_0 = 3,
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
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);