#include "stdafx.h" #include "Emu/Memory/vm.h" #include "Emu/IdManager.h" #include "Emu/System.h" #include "sys_process.h" #include "sys_rsxaudio.h" #include #include #include #ifdef __linux__ #include #include #include #include #elif defined(BSD) || defined(__APPLE__) #include #endif LOG_CHANNEL(sys_rsxaudio); namespace rsxaudio_ringbuf_reader { static constexpr void clean_buf(rsxaudio_shmem::ringbuf_t& ring_buf) { ring_buf.unk2 = 100; ring_buf.read_idx = 0; ring_buf.write_idx = 0; ring_buf.queue_notify_idx = 0; ring_buf.next_blk_idx = 0; for (auto& ring_entry : ring_buf.entries) { ring_entry.valid = 0; ring_entry.audio_blk_idx = 0; ring_entry.timestamp = 0; } } static void set_timestamp(rsxaudio_shmem::ringbuf_t& ring_buf, u64 timestamp) { const s32 entry_idx_raw = (ring_buf.read_idx + ring_buf.rw_max_idx - (ring_buf.rw_max_idx > 2) - 1) % ring_buf.rw_max_idx; const s32 entry_idx = std::clamp(entry_idx_raw, 0, SYS_RSXAUDIO_RINGBUF_SZ); ring_buf.entries[entry_idx].timestamp = convert_to_timebased_time(timestamp); } static std::tuple update_status(rsxaudio_shmem::ringbuf_t& ring_buf) { const s32 read_idx = std::clamp(ring_buf.read_idx, 0, SYS_RSXAUDIO_RINGBUF_SZ); if ((ring_buf.entries[read_idx].valid & 1) == 0U) { return {}; } const s32 entry_idx_raw = (ring_buf.read_idx + ring_buf.rw_max_idx - (ring_buf.rw_max_idx > 2)) % ring_buf.rw_max_idx; const s32 entry_idx = std::clamp(entry_idx_raw, 0, SYS_RSXAUDIO_RINGBUF_SZ); ring_buf.entries[read_idx].valid = 0; ring_buf.queue_notify_idx = (ring_buf.queue_notify_idx + 1) % ring_buf.queue_notify_step; ring_buf.read_idx = (ring_buf.read_idx + 1) % ring_buf.rw_max_idx; return std::make_tuple(((ring_buf.rw_max_idx > 2) ^ ring_buf.queue_notify_idx) == 0, ring_buf.entries[entry_idx].audio_blk_idx, ring_buf.entries[entry_idx].timestamp); } static std::pair get_addr(const rsxaudio_shmem::ringbuf_t& ring_buf) { const s32 read_idx = std::clamp(ring_buf.read_idx, 0, SYS_RSXAUDIO_RINGBUF_SZ); if (ring_buf.entries[read_idx].valid & 1) { return std::make_pair(true, ring_buf.entries[read_idx].dma_addr); } return std::make_pair(false, ring_buf.dma_silence_addr); } [[maybe_unused]] static std::optional get_spdif_channel_data(RsxaudioPort dst, rsxaudio_shmem& shmem) { if (dst == RsxaudioPort::SPDIF_0) { if (shmem.ctrl.spdif_ch0_channel_data_tx_cycles) { shmem.ctrl.spdif_ch0_channel_data_tx_cycles--; return static_cast(shmem.ctrl.spdif_ch0_channel_data_hi) << 32 | shmem.ctrl.spdif_ch0_channel_data_lo; } } else { if (shmem.ctrl.spdif_ch1_channel_data_tx_cycles) { shmem.ctrl.spdif_ch1_channel_data_tx_cycles--; return static_cast(shmem.ctrl.spdif_ch1_channel_data_hi) << 32 | shmem.ctrl.spdif_ch1_channel_data_lo; } } return std::nullopt; } } error_code sys_rsxaudio_initialize(vm::ptr handle) { sys_rsxaudio.trace("sys_rsxaudio_initialize(handle=*0x%x)", handle); auto& rsxaudio_thread = g_fxo->get(); if (rsxaudio_thread.rsxaudio_ctx_allocated.test_and_set()) { return CELL_EINVAL; } if (!vm::check_addr(handle.addr(), vm::page_writable, sizeof(u32))) { rsxaudio_thread.rsxaudio_ctx_allocated = false; return CELL_EFAULT; } const u32 id = idm::make(); if (!id) { rsxaudio_thread.rsxaudio_ctx_allocated = false; return CELL_ENOMEM; } const auto rsxaudio_obj = idm::get(id); std::lock_guard lock(rsxaudio_obj->mutex); rsxaudio_obj->shmem = vm::addr_t{vm::alloc(sizeof(rsxaudio_shmem), vm::main)}; if (!rsxaudio_obj->shmem) { idm::remove(id); rsxaudio_thread.rsxaudio_ctx_allocated = false; return CELL_ENOMEM; } rsxaudio_obj->page_lock(); rsxaudio_shmem* sh_page = rsxaudio_obj->get_rw_shared_page(); sh_page->ctrl = {}; for (auto& uf : sh_page->ctrl.channel_uf) { uf.uf_event_cnt = 0; uf.unk1 = 0; } sh_page->ctrl.unk4 = 0x8000; sh_page->ctrl.intr_thread_prio = 0xDEADBEEF; sh_page->ctrl.unk5 = 0; rsxaudio_obj->init = true; *handle = id; return CELL_OK; } error_code sys_rsxaudio_finalize(u32 handle) { sys_rsxaudio.trace("sys_rsxaudio_finalize(handle=0x%x)", handle); const auto rsxaudio_obj = idm::get(handle); if (!rsxaudio_obj) { return CELL_ESRCH; } std::lock_guard lock(rsxaudio_obj->mutex); if (!rsxaudio_obj->init) { return CELL_ESRCH; } auto& rsxaudio_thread = g_fxo->get(); { std::lock_guard ra_obj_lock{rsxaudio_thread.rsxaudio_obj_upd_m}; rsxaudio_thread.rsxaudio_obj_ptr = {}; } rsxaudio_obj->init = false; vm::dealloc(rsxaudio_obj->shmem, vm::main); for (const auto port : rsxaudio_obj->event_port) { idm::remove(port); } idm::remove(handle); rsxaudio_thread.rsxaudio_ctx_allocated = false; return CELL_OK; } error_code sys_rsxaudio_import_shared_memory(u32 handle, vm::ptr addr) { sys_rsxaudio.trace("sys_rsxaudio_import_shared_memory(handle=0x%x, addr=*0x%x)", handle, addr); const auto rsxaudio_obj = idm::get(handle); if (!rsxaudio_obj) { return CELL_ESRCH; } std::lock_guard lock(rsxaudio_obj->mutex); if (!rsxaudio_obj->init) { return CELL_ESRCH; } if (!vm::check_addr(addr.addr(), vm::page_writable, sizeof(u64))) { return CELL_EFAULT; } *addr = rsxaudio_obj->shmem; rsxaudio_obj->page_unlock(); return CELL_OK; } error_code sys_rsxaudio_unimport_shared_memory(u32 handle, vm::ptr addr /* unused */) { sys_rsxaudio.trace("sys_rsxaudio_unimport_shared_memory(handle=0x%x, addr=*0x%x)", handle, addr); const auto rsxaudio_obj = idm::get(handle); if (!rsxaudio_obj) { return CELL_ESRCH; } std::lock_guard lock(rsxaudio_obj->mutex); if (!rsxaudio_obj->init) { return CELL_ESRCH; } rsxaudio_obj->page_lock(); return CELL_OK; } error_code sys_rsxaudio_create_connection(u32 handle) { sys_rsxaudio.trace("sys_rsxaudio_create_connection(handle=0x%x)", handle); const auto rsxaudio_obj = idm::get(handle); if (!rsxaudio_obj) { return CELL_ESRCH; } std::lock_guard lock(rsxaudio_obj->mutex); if (!rsxaudio_obj->init) { return CELL_ESRCH; } rsxaudio_shmem* sh_page = rsxaudio_obj->get_rw_shared_page(); const error_code port_create_status = [&]() -> error_code { if (auto queue1 = idm::get(sh_page->ctrl.event_queue_1_id)) { rsxaudio_obj->event_queue[0] = queue1; if (auto queue2 = idm::get(sh_page->ctrl.event_queue_2_id)) { rsxaudio_obj->event_queue[1] = queue2; if (auto queue3 = idm::get(sh_page->ctrl.event_queue_3_id)) { rsxaudio_obj->event_queue[2] = queue3; if (auto port1 = idm::make(SYS_EVENT_PORT_LOCAL, 0)) { idm::remove(rsxaudio_obj->event_port[0]); rsxaudio_obj->event_port[0] = port1; if (auto port2 = idm::make(SYS_EVENT_PORT_LOCAL, 0)) { idm::remove(rsxaudio_obj->event_port[1]); rsxaudio_obj->event_port[1] = port2; if (auto port3 = idm::make(SYS_EVENT_PORT_LOCAL, 0)) { idm::remove(rsxaudio_obj->event_port[2]); rsxaudio_obj->event_port[2] = port3; return CELL_OK; } idm::remove(port2); rsxaudio_obj->event_port[1] = 0; } idm::remove(port1); rsxaudio_obj->event_port[0] = 0; } return CELL_OK; } } } return CELL_ESRCH; }(); if (port_create_status != CELL_OK) { return port_create_status; } for (auto& rb : sh_page->ctrl.ringbuf) { rb.dma_silence_addr = rsxaudio_obj->dma_io_base + offsetof(rsxaudio_shmem, dma_silence_region); rb.unk2 = 100; } for (u32 entry_idx = 0; entry_idx < SYS_RSXAUDIO_RINGBUF_SZ; entry_idx++) { sh_page->ctrl.ringbuf[static_cast(RsxaudioPort::SERIAL)].entries[entry_idx].dma_addr = rsxaudio_obj->dma_io_base + u32{offsetof(rsxaudio_shmem, dma_serial_region)} + SYS_RSXAUDIO_RINGBUF_BLK_SZ_SERIAL * entry_idx; sh_page->ctrl.ringbuf[static_cast(RsxaudioPort::SPDIF_0)].entries[entry_idx].dma_addr = rsxaudio_obj->dma_io_base + u32{offsetof(rsxaudio_shmem, dma_spdif_0_region)} + SYS_RSXAUDIO_RINGBUF_BLK_SZ_SPDIF * entry_idx; sh_page->ctrl.ringbuf[static_cast(RsxaudioPort::SPDIF_1)].entries[entry_idx].dma_addr = rsxaudio_obj->dma_io_base + u32{offsetof(rsxaudio_shmem, dma_spdif_1_region)} + SYS_RSXAUDIO_RINGBUF_BLK_SZ_SPDIF * entry_idx; } return CELL_OK; } error_code sys_rsxaudio_close_connection(u32 handle) { sys_rsxaudio.trace("sys_rsxaudio_close_connection(handle=0x%x)", handle); const auto rsxaudio_obj = idm::get(handle); if (!rsxaudio_obj) { return CELL_ESRCH; } std::lock_guard lock(rsxaudio_obj->mutex); if (!rsxaudio_obj->init) { return CELL_ESRCH; } { auto& rsxaudio_thread = g_fxo->get(); std::lock_guard ra_obj_lock{rsxaudio_thread.rsxaudio_obj_upd_m}; rsxaudio_thread.rsxaudio_obj_ptr = {}; } for (u32 q_idx = 0; q_idx < SYS_RSXAUDIO_PORT_CNT; q_idx++) { idm::remove(rsxaudio_obj->event_port[q_idx]); rsxaudio_obj->event_port[q_idx] = 0; rsxaudio_obj->event_queue[q_idx].reset(); } return CELL_OK; } error_code sys_rsxaudio_prepare_process(u32 handle) { sys_rsxaudio.trace("sys_rsxaudio_prepare_process(handle=0x%x)", handle); const auto rsxaudio_obj = idm::get(handle); if (!rsxaudio_obj) { return CELL_ESRCH; } std::lock_guard lock(rsxaudio_obj->mutex); if (!rsxaudio_obj->init) { return CELL_ESRCH; } auto& rsxaudio_thread = g_fxo->get(); std::lock_guard ra_obj_lock{rsxaudio_thread.rsxaudio_obj_upd_m}; if (rsxaudio_thread.rsxaudio_obj_ptr) { return -1; } rsxaudio_thread.rsxaudio_obj_ptr = rsxaudio_obj; return CELL_OK; } error_code sys_rsxaudio_start_process(u32 handle) { sys_rsxaudio.trace("sys_rsxaudio_start_process(handle=0x%x)", handle); const auto rsxaudio_obj = idm::get(handle); if (!rsxaudio_obj) { return CELL_ESRCH; } std::lock_guard lock(rsxaudio_obj->mutex); if (!rsxaudio_obj->init) { return CELL_ESRCH; } rsxaudio_shmem* sh_page = rsxaudio_obj->get_rw_shared_page(); for (auto& rb : sh_page->ctrl.ringbuf) { if (rb.active) rsxaudio_ringbuf_reader::clean_buf(rb); } for (auto& uf : sh_page->ctrl.channel_uf) { uf.uf_event_cnt = 0; uf.unk1 = 0; } auto& rsxaudio_thread = g_fxo->get(); rsxaudio_thread.update_hw_param([&](auto& param) { if (sh_page->ctrl.ringbuf[static_cast(RsxaudioPort::SERIAL)].active) param.serial.dma_en = true; if (sh_page->ctrl.ringbuf[static_cast(RsxaudioPort::SPDIF_0)].active) param.spdif[0].dma_en = true; if (sh_page->ctrl.ringbuf[static_cast(RsxaudioPort::SPDIF_1)].active) param.spdif[1].dma_en = true; }); for (u32 q_idx = 0; q_idx < SYS_RSXAUDIO_PORT_CNT; q_idx++) { if (auto queue = rsxaudio_obj->event_queue[q_idx].lock(); rsxaudio_obj->event_port[q_idx] && sh_page->ctrl.ringbuf[q_idx].active) { queue->send(s64{process_getpid()} << 32 | u64{rsxaudio_obj->event_port[q_idx]}, q_idx, 0, 0); } } return CELL_OK; } error_code sys_rsxaudio_stop_process(u32 handle) { sys_rsxaudio.trace("sys_rsxaudio_stop_process(handle=0x%x)", handle); const auto rsxaudio_obj = idm::get(handle); if (!rsxaudio_obj) { return CELL_ESRCH; } std::lock_guard lock(rsxaudio_obj->mutex); if (!rsxaudio_obj->init) { return CELL_ESRCH; } auto& rsxaudio_thread = g_fxo->get(); rsxaudio_thread.update_hw_param([&](auto& param) { param.serial.dma_en = false; param.serial.muted = true; param.serial.en = false; for (auto& spdif : param.spdif) { spdif.dma_en = false; if (!spdif.use_serial_buf) { spdif.en = false; } } param.spdif[1].muted = true; }); rsxaudio_shmem* sh_page = rsxaudio_obj->get_rw_shared_page(); for (auto& rb : sh_page->ctrl.ringbuf) { if (rb.active) rsxaudio_ringbuf_reader::clean_buf(rb); } return CELL_OK; } error_code sys_rsxaudio_get_dma_param(u32 handle, u32 flag, vm::ptr out) { sys_rsxaudio.trace("sys_rsxaudio_get_dma_param(handle=0x%x, flag=0x%x, out=0x%x)", handle, flag, out); const auto rsxaudio_obj = idm::get(handle); if (!rsxaudio_obj) { return CELL_ESRCH; } std::lock_guard lock(rsxaudio_obj->mutex); if (!rsxaudio_obj->init) { return CELL_ESRCH; } if (!vm::check_addr(out.addr(), vm::page_writable, sizeof(u64))) { return CELL_EFAULT; } if (flag == rsxaudio_dma_flag::IO_ID) { *out = rsxaudio_obj->dma_io_id; } else if (flag == rsxaudio_dma_flag::IO_BASE) { *out = rsxaudio_obj->dma_io_base; } return CELL_OK; } rsxaudio_data_container::rsxaudio_data_container(const rsxaudio_hw_param_t& hw_param, const buf_t& buf, bool serial_rdy, bool spdif_0_rdy, bool spdif_1_rdy) : hwp(hw_param), out_buf(buf) { if (serial_rdy) { avport_data_avail[static_cast(RsxaudioAvportIdx::AVMULTI)] = true; if (hwp.spdif[0].use_serial_buf) { avport_data_avail[static_cast(RsxaudioAvportIdx::SPDIF_0)] = true; } if (hwp.spdif[1].use_serial_buf) { avport_data_avail[static_cast(RsxaudioAvportIdx::SPDIF_1)] = true; } } if (spdif_0_rdy && !hwp.spdif[0].use_serial_buf) { avport_data_avail[static_cast(RsxaudioAvportIdx::SPDIF_0)] = true; } if (spdif_1_rdy && !hwp.spdif[1].use_serial_buf) { avport_data_avail[static_cast(RsxaudioAvportIdx::SPDIF_1)] = true; } if (hwp.hdmi[0].init) { if (hwp.hdmi[0].use_spdif_1) { avport_data_avail[static_cast(RsxaudioAvportIdx::HDMI_0)] = avport_data_avail[static_cast(RsxaudioAvportIdx::SPDIF_1)]; } else { avport_data_avail[static_cast(RsxaudioAvportIdx::HDMI_0)] = serial_rdy; } hdmi_stream_cnt[0] = static_cast(hwp.hdmi[0].ch_cfg.total_ch_cnt) / SYS_RSXAUDIO_CH_PER_STREAM; } if (hwp.hdmi[1].init) { if (hwp.hdmi[1].use_spdif_1) { avport_data_avail[static_cast(RsxaudioAvportIdx::HDMI_1)] = avport_data_avail[static_cast(RsxaudioAvportIdx::SPDIF_1)]; } else { avport_data_avail[static_cast(RsxaudioAvportIdx::HDMI_1)] = serial_rdy; } hdmi_stream_cnt[1] = static_cast(hwp.hdmi[1].ch_cfg.total_ch_cnt) / SYS_RSXAUDIO_CH_PER_STREAM; } } u32 rsxaudio_data_container::get_data_size(RsxaudioAvportIdx avport) { if (!avport_data_avail[static_cast(avport)]) { return 0; } switch (avport) { case RsxaudioAvportIdx::HDMI_0: { const RsxaudioSampleSize depth = hwp.hdmi[0].use_spdif_1 ? hwp.spdif[1].depth : hwp.serial.depth; return (depth == RsxaudioSampleSize::_16BIT ? SYS_RSXAUDIO_STREAM_SIZE * 2 : SYS_RSXAUDIO_STREAM_SIZE) * hdmi_stream_cnt[0]; } case RsxaudioAvportIdx::HDMI_1: { const RsxaudioSampleSize depth = hwp.hdmi[1].use_spdif_1 ? hwp.spdif[1].depth : hwp.serial.depth; return (depth == RsxaudioSampleSize::_16BIT ? SYS_RSXAUDIO_STREAM_SIZE * 2 : SYS_RSXAUDIO_STREAM_SIZE) * hdmi_stream_cnt[1]; } case RsxaudioAvportIdx::AVMULTI: { return hwp.serial.depth == RsxaudioSampleSize::_16BIT ? SYS_RSXAUDIO_STREAM_SIZE * 2 : SYS_RSXAUDIO_STREAM_SIZE; } case RsxaudioAvportIdx::SPDIF_0: { const RsxaudioSampleSize depth = hwp.spdif[0].use_serial_buf ? hwp.serial.depth : hwp.spdif[0].depth; return depth == RsxaudioSampleSize::_16BIT ? SYS_RSXAUDIO_STREAM_SIZE * 2 : SYS_RSXAUDIO_STREAM_SIZE; } case RsxaudioAvportIdx::SPDIF_1: { const RsxaudioSampleSize depth = hwp.spdif[1].use_serial_buf ? hwp.serial.depth : hwp.spdif[1].depth; return depth == RsxaudioSampleSize::_16BIT ? SYS_RSXAUDIO_STREAM_SIZE * 2 : SYS_RSXAUDIO_STREAM_SIZE; } default: { return 0; } } } void rsxaudio_data_container::get_data(RsxaudioAvportIdx avport, data_blk_t& data_out) { if (!avport_data_avail[static_cast(avport)]) { return; } data_was_written = true; auto spdif_filter_map = [&](u8 hdmi_idx) { std::array result; for (u64 i = 0; i < SYS_RSXAUDIO_SERIAL_MAX_CH; i++) { const u8 old_val = hwp.hdmi[hdmi_idx].ch_cfg.map[i]; result[i] = old_val >= SYS_RSXAUDIO_SPDIF_MAX_CH ? rsxaudio_hw_param_t::hdmi_param_t::MAP_SILENT_CH : old_val; } return result; }; switch (avport) { case RsxaudioAvportIdx::HDMI_0: case RsxaudioAvportIdx::HDMI_1: { const u8 hdmi_idx = avport == RsxaudioAvportIdx::HDMI_1; switch (hdmi_stream_cnt[hdmi_idx]) { default: case 0: { return; } case 1: { if (hwp.hdmi[hdmi_idx].use_spdif_1) { if (hwp.spdif[1].use_serial_buf) { mix<2>(spdif_filter_map(hdmi_idx), hwp.serial.depth, out_buf.serial, data_out); } else { mix<2>(hwp.hdmi[hdmi_idx].ch_cfg.map, hwp.spdif[1].depth, out_buf.spdif[1], data_out); } } else { mix<2>(hwp.hdmi[hdmi_idx].ch_cfg.map, hwp.serial.depth, out_buf.serial, data_out); } break; } case 3: { if (hwp.hdmi[hdmi_idx].use_spdif_1) { if (hwp.spdif[1].use_serial_buf) { mix<6>(spdif_filter_map(hdmi_idx), hwp.serial.depth, out_buf.serial, data_out); } else { mix<6>(hwp.hdmi[hdmi_idx].ch_cfg.map, hwp.spdif[1].depth, out_buf.spdif[1], data_out); } } else { mix<6>(hwp.hdmi[hdmi_idx].ch_cfg.map, hwp.serial.depth, out_buf.serial, data_out); } break; } case 4: { if (hwp.hdmi[hdmi_idx].use_spdif_1) { if (hwp.spdif[1].use_serial_buf) { mix<8>(spdif_filter_map(hdmi_idx), hwp.serial.depth, out_buf.serial, data_out); } else { mix<8>(hwp.hdmi[hdmi_idx].ch_cfg.map, hwp.spdif[1].depth, out_buf.spdif[1], data_out); } } else { mix<8>(hwp.hdmi[hdmi_idx].ch_cfg.map, hwp.serial.depth, out_buf.serial, data_out); } break; } } break; } case RsxaudioAvportIdx::AVMULTI: { mix<2>({2, 3}, hwp.serial.depth, out_buf.serial, data_out); break; } case RsxaudioAvportIdx::SPDIF_0: case RsxaudioAvportIdx::SPDIF_1: { const u8 spdif_idx = avport == RsxaudioAvportIdx::SPDIF_1; if (hwp.spdif[spdif_idx].use_serial_buf) { mix<2>({0, 1}, hwp.serial.depth, out_buf.serial, data_out); } else { mix<2>({0, 1}, hwp.spdif[spdif_idx].depth, out_buf.spdif[spdif_idx], data_out); } break; } default: { return; } } } bool rsxaudio_data_container::data_was_used() { return data_was_written; } rsxaudio_data_thread::rsxaudio_data_thread() {} void rsxaudio_data_thread::operator()() { thread_ctrl::scoped_priority high_prio(+1); while (thread_ctrl::state() != thread_state::aborting) { static const std::function tmr_callback = [this]() { extract_audio_data(); }; switch (timer.wait(tmr_callback)) { case rsxaudio_periodic_tmr::wait_result::SUCCESS: case rsxaudio_periodic_tmr::wait_result::TIMEOUT: case rsxaudio_periodic_tmr::wait_result::TIMER_CANCELED: { continue; } case rsxaudio_periodic_tmr::wait_result::INVALID_PARAM: case rsxaudio_periodic_tmr::wait_result::TIMER_ERROR: default: { fmt::throw_exception("rsxaudio_periodic_tmr::wait() failed"); } } } } rsxaudio_data_thread& rsxaudio_data_thread::operator=(thread_state /* state */) { timer.cancel_wait(); return *this; } void rsxaudio_data_thread::advance_all_timers() { const u64 crnt_time = get_system_time(); timer.vtimer_skip_periods(static_cast(RsxaudioPort::SERIAL), crnt_time); timer.vtimer_skip_periods(static_cast(RsxaudioPort::SPDIF_0), crnt_time); timer.vtimer_skip_periods(static_cast(RsxaudioPort::SPDIF_1), crnt_time); } void rsxaudio_data_thread::extract_audio_data() { // Accessing timer state is safe here, since we're in timer::wait() const auto rsxaudio_obj = [&]() { std::lock_guard ra_obj_lock{rsxaudio_obj_upd_m}; return rsxaudio_obj_ptr; }(); if (Emu.IsPaused() || !rsxaudio_obj) { advance_all_timers(); return; } std::lock_guard rsxaudio_lock(rsxaudio_obj->mutex); if (!rsxaudio_obj->init) { advance_all_timers(); return; } rsxaudio_shmem* sh_page = rsxaudio_obj->get_rw_shared_page(); const auto hw_cfg = hw_param_ts.get_current(); const u64 crnt_time = get_system_time(); auto process_rb = [&](RsxaudioPort dst, bool dma_en) { // SPDIF channel data and underflow events are always disabled by lv1 const u32 dst_raw = static_cast(dst); rsxaudio_ringbuf_reader::set_timestamp(sh_page->ctrl.ringbuf[dst_raw], timer.vtimer_get_sched_time(dst_raw)); const auto [data_present, rb_addr] = get_ringbuf_addr(dst, *rsxaudio_obj); bool reset_periods = !enqueue_data(dst, rb_addr == nullptr, rb_addr, *hw_cfg); if (dma_en) { if (const auto [notify, blk_idx, timestamp] = rsxaudio_ringbuf_reader::update_status(sh_page->ctrl.ringbuf[dst_raw]); notify) { // Too late to recover reset_periods = true; if (auto queue = rsxaudio_obj->event_queue[dst_raw].lock(); rsxaudio_obj->event_port[dst_raw]) { queue->send(s64{process_getpid()} << 32 | u64{rsxaudio_obj->event_port[dst_raw]}, dst_raw, blk_idx, timestamp); } } } if (reset_periods) { timer.vtimer_skip_periods(dst_raw, crnt_time); } else { timer.vtimer_incr(dst_raw, crnt_time); } }; if (timer.is_vtimer_behind(static_cast(RsxaudioPort::SERIAL), crnt_time)) { process_rb(RsxaudioPort::SERIAL, hw_cfg->serial.dma_en); } if (timer.is_vtimer_behind(static_cast(RsxaudioPort::SPDIF_0), crnt_time)) { process_rb(RsxaudioPort::SPDIF_0, hw_cfg->spdif[0].dma_en); } if (timer.is_vtimer_behind(static_cast(RsxaudioPort::SPDIF_1), crnt_time)) { process_rb(RsxaudioPort::SPDIF_1, hw_cfg->spdif[1].dma_en); } } std::pair rsxaudio_data_thread::get_ringbuf_addr(RsxaudioPort dst, const lv2_rsxaudio& rsxaudio_obj) { ensure(dst <= RsxaudioPort::SPDIF_1); rsxaudio_shmem* sh_page = rsxaudio_obj.get_rw_shared_page(); const auto [data_present, addr] = rsxaudio_ringbuf_reader::get_addr(sh_page->ctrl.ringbuf[static_cast(dst)]); const u32 buf_size = dst == RsxaudioPort::SERIAL ? SYS_RSXAUDIO_RINGBUF_BLK_SZ_SERIAL : SYS_RSXAUDIO_RINGBUF_BLK_SZ_SPDIF; if (addr >= rsxaudio_obj.dma_io_base && addr < rsxaudio_obj.dma_io_base + sizeof(rsxaudio_shmem) - buf_size) { return std::make_pair(data_present, reinterpret_cast(rsxaudio_obj.get_rw_shared_page()) + addr - rsxaudio_obj.dma_io_base); } // Buffer address is invalid return std::make_pair(false, nullptr); } void rsxaudio_data_thread::reset_hw() { update_hw_param([&](rsxaudio_hw_param_t& current) { const bool serial_dma_en = current.serial.dma_en; current.serial = {}; current.serial.dma_en = serial_dma_en; for (auto& spdif : current.spdif) { const bool spdif_dma_en = spdif.dma_en; spdif = {}; spdif.dma_en = spdif_dma_en; } current.serial_freq_base = SYS_RSXAUDIO_FREQ_BASE_384K; current.spdif_freq_base = SYS_RSXAUDIO_FREQ_BASE_352K; current.avport_src.fill(RsxaudioPort::INVALID); }); } void rsxaudio_data_thread::update_hw_param(std::function update_callback) { ensure(update_callback); hw_param_ts.add_op([&]() { auto new_hw_param = std::make_shared(*hw_param_ts.get_current()); update_callback(*new_hw_param); const bool serial_active = calc_port_active_state(RsxaudioPort::SERIAL, *new_hw_param); const bool spdif_active[SYS_RSXAUDIO_SPDIF_CNT] = { calc_port_active_state(RsxaudioPort::SPDIF_0, *new_hw_param), calc_port_active_state(RsxaudioPort::SPDIF_1, *new_hw_param) }; std::array port_cfg{}; port_cfg[static_cast(RsxaudioAvportIdx::AVMULTI)] = {static_cast(new_hw_param->serial_freq_base / new_hw_param->serial.freq_div), AudioChannelCnt::STEREO}; auto gen_spdif_port_cfg = [&](u8 spdif_idx) { if (new_hw_param->spdif[spdif_idx].use_serial_buf) { return port_cfg[static_cast(RsxaudioAvportIdx::AVMULTI)]; } else { return rsxaudio_backend_thread::port_config{static_cast(new_hw_param->spdif_freq_base / new_hw_param->spdif[spdif_idx].freq_div), AudioChannelCnt::STEREO}; } }; port_cfg[static_cast(RsxaudioAvportIdx::SPDIF_0)] = gen_spdif_port_cfg(0); port_cfg[static_cast(RsxaudioAvportIdx::SPDIF_1)] = gen_spdif_port_cfg(1); auto gen_hdmi_port_cfg = [&](u8 hdmi_idx) { if (new_hw_param->hdmi[hdmi_idx].use_spdif_1) { return rsxaudio_backend_thread::port_config{port_cfg[static_cast(RsxaudioAvportIdx::SPDIF_1)].freq, new_hw_param->hdmi[hdmi_idx].ch_cfg.total_ch_cnt}; } else { return rsxaudio_backend_thread::port_config{port_cfg[static_cast(RsxaudioAvportIdx::AVMULTI)].freq, new_hw_param->hdmi[hdmi_idx].ch_cfg.total_ch_cnt}; } }; port_cfg[static_cast(RsxaudioAvportIdx::HDMI_0)] = gen_hdmi_port_cfg(0); port_cfg[static_cast(RsxaudioAvportIdx::HDMI_1)] = gen_hdmi_port_cfg(1); // TODO: ideally, old data must be flushed from backend buffers if channel became inactive or its src changed g_fxo->get().set_new_stream_param(port_cfg, calc_avport_mute_state(*new_hw_param)); timer.vtimer_access_sec([&]() { const u64 crnt_time = get_system_time(); if (serial_active) { // 2 channels per stream, streams go in parallel const u32 new_timer_rate = static_cast(port_cfg[static_cast(RsxaudioAvportIdx::AVMULTI)].freq) * static_cast(new_hw_param->serial.depth) * SYS_RSXAUDIO_CH_PER_STREAM; timer.enable_vtimer(static_cast(RsxaudioPort::SERIAL), new_timer_rate, crnt_time); } else { timer.disable_vtimer(static_cast(RsxaudioPort::SERIAL)); } for (u8 spdif_idx = 0; spdif_idx < SYS_RSXAUDIO_SPDIF_CNT; spdif_idx++) { const u32 vtimer_id = static_cast(RsxaudioPort::SPDIF_0) + spdif_idx; if (spdif_active[spdif_idx] && !new_hw_param->spdif[spdif_idx].use_serial_buf) { // 2 channels per stream, single stream const u32 new_timer_rate = static_cast(port_cfg[static_cast(RsxaudioAvportIdx::SPDIF_0) + spdif_idx].freq) * static_cast(new_hw_param->spdif[spdif_idx].depth) * SYS_RSXAUDIO_CH_PER_STREAM; timer.enable_vtimer(vtimer_id, new_timer_rate, crnt_time); } else { timer.disable_vtimer(vtimer_id); } } }); return new_hw_param; }); } void rsxaudio_data_thread::update_mute_state(RsxaudioPort port, bool muted) { hw_param_ts.add_op([&]() { auto new_hw_param = std::make_shared(*hw_param_ts.get_current()); switch (port) { case RsxaudioPort::SERIAL: { new_hw_param->serial.muted = muted; break; } case RsxaudioPort::SPDIF_0: { new_hw_param->spdif[0].muted = muted; break; } case RsxaudioPort::SPDIF_1: { new_hw_param->spdif[1].muted = muted; break; } default: { fmt::throw_exception("Invalid RSXAudio port: %u", static_cast(port)); } } g_fxo->get().set_mute_state(calc_avport_mute_state(*new_hw_param)); return new_hw_param; }); } void rsxaudio_data_thread::update_av_mute_state(RsxaudioAvportIdx avport, bool muted, bool force_mute, bool set) { hw_param_ts.add_op([&]() { auto new_hw_param = std::make_shared(*hw_param_ts.get_current()); switch (avport) { case RsxaudioAvportIdx::HDMI_0: case RsxaudioAvportIdx::HDMI_1: { const u32 hdmi_idx = avport == RsxaudioAvportIdx::HDMI_1; if (muted) { new_hw_param->hdmi[hdmi_idx].muted = set; } if (force_mute) { new_hw_param->hdmi[hdmi_idx].force_mute = set; } break; } case RsxaudioAvportIdx::AVMULTI: { if (muted) { new_hw_param->avmulti_av_muted = set; } break; } default: { fmt::throw_exception("Invalid RSXAudio avport: %u", static_cast(avport)); } } g_fxo->get().set_mute_state(calc_avport_mute_state(*new_hw_param)); return new_hw_param; }); } rsxaudio_backend_thread::avport_bit rsxaudio_data_thread::calc_avport_mute_state(const rsxaudio_hw_param_t& hwp) { const bool serial_active = calc_port_active_state(RsxaudioPort::SERIAL, hwp); const bool spdif_active[SYS_RSXAUDIO_SPDIF_CNT] = { calc_port_active_state(RsxaudioPort::SPDIF_0, hwp), calc_port_active_state(RsxaudioPort::SPDIF_1, hwp) }; const bool avmulti = !serial_active || hwp.serial.muted || hwp.avmulti_av_muted; auto spdif_muted = [&](u8 spdif_idx) { const u8 spdif_port = spdif_idx == 1; if (hwp.spdif[spdif_port].use_serial_buf) { // TODO: HW test if both serial and spdif mutes are used in serial mode for spdif return !serial_active || hwp.spdif[spdif_port].freq_div != hwp.serial.freq_div || hwp.serial.muted || hwp.spdif[spdif_port].muted; } else { return !spdif_active[spdif_port] || hwp.spdif[spdif_port].muted; } }; auto hdmi_muted = [&](u8 hdmi_idx) { const u8 hdmi_port = hdmi_idx == 1; if (hwp.hdmi[hdmi_idx].use_spdif_1) { return spdif_muted(1) || hwp.hdmi[hdmi_port].muted || hwp.hdmi[hdmi_port].force_mute || !hwp.hdmi[hdmi_port].init; } else { return !serial_active || hwp.serial.muted || hwp.hdmi[hdmi_port].muted || hwp.hdmi[hdmi_port].force_mute || !hwp.hdmi[hdmi_port].init; } }; return { hdmi_muted(0), hdmi_muted(1), avmulti, spdif_muted(0), spdif_muted(1) }; } bool rsxaudio_data_thread::calc_port_active_state(RsxaudioPort port, const rsxaudio_hw_param_t& hwp) { auto gen_serial_active = [&]() { return hwp.serial.dma_en && hwp.serial.buf_empty_en && hwp.serial.en; }; auto gen_spdif_active = [&](u8 spdif_idx) { if (hwp.spdif[spdif_idx].use_serial_buf) { return gen_serial_active() && (hwp.spdif[spdif_idx].freq_div == hwp.serial.freq_div); } else { return hwp.spdif[spdif_idx].dma_en && hwp.spdif[spdif_idx].buf_empty_en && hwp.spdif[spdif_idx].en; } }; switch (port) { case RsxaudioPort::SERIAL: { return gen_serial_active(); } case RsxaudioPort::SPDIF_0: { return gen_spdif_active(0); } case RsxaudioPort::SPDIF_1: { return gen_spdif_active(1); } default: { return false; } } } f32 rsxaudio_data_thread::pcm_to_float(s32 sample) { return sample * (1.0f / 2147483648.0f); } f32 rsxaudio_data_thread::pcm_to_float(s16 sample) { return sample * (1.0f / 32768.0f); } void rsxaudio_data_thread::pcm_serial_process_channel(RsxaudioSampleSize word_bits, ra_stream_blk_t& buf_out_l, ra_stream_blk_t& buf_out_r, const void* buf_in, u8 src_stream) { const u8 input_word_sz = static_cast(word_bits); u64 ch_dst = 0; for (u64 blk_idx = 0; blk_idx < SYS_RSXAUDIO_STREAM_DATA_BLK_CNT; blk_idx++) { for (u64 offset = 0; offset < SYS_RSXAUDIO_DATA_BLK_SIZE / 2; offset += input_word_sz, ch_dst++) { const u64 left_ch_src = (blk_idx * SYS_RSXAUDIO_STREAM_SIZE + src_stream * SYS_RSXAUDIO_DATA_BLK_SIZE + offset) / input_word_sz; const u64 right_ch_src = left_ch_src + (SYS_RSXAUDIO_DATA_BLK_SIZE / 2) / input_word_sz; if (word_bits == RsxaudioSampleSize::_16BIT) { buf_out_l[ch_dst] = pcm_to_float(static_cast*>(buf_in)[left_ch_src]); buf_out_r[ch_dst] = pcm_to_float(static_cast*>(buf_in)[right_ch_src]); } else { // Looks like rsx treats 20bit/24bit samples as 32bit ones buf_out_l[ch_dst] = pcm_to_float(static_cast*>(buf_in)[left_ch_src]); buf_out_r[ch_dst] = pcm_to_float(static_cast*>(buf_in)[right_ch_src]); } } } } void rsxaudio_data_thread::pcm_spdif_process_channel(RsxaudioSampleSize word_bits, ra_stream_blk_t& buf_out_l, ra_stream_blk_t& buf_out_r, const void* buf_in) { const u8 input_word_sz = static_cast(word_bits); for (u64 offset = 0; offset < SYS_RSXAUDIO_RINGBUF_BLK_SZ_SPDIF / (input_word_sz * SYS_RSXAUDIO_SPDIF_MAX_CH); offset++) { const u64 left_ch_src = offset * SYS_RSXAUDIO_SPDIF_MAX_CH; const u64 right_ch_src = left_ch_src + 1; if (word_bits == RsxaudioSampleSize::_16BIT) { buf_out_l[offset] = pcm_to_float(static_cast*>(buf_in)[left_ch_src]); buf_out_r[offset] = pcm_to_float(static_cast*>(buf_in)[right_ch_src]); } else { // Looks like rsx treats 20bit/24bit samples as 32bit ones buf_out_l[offset] = pcm_to_float(static_cast*>(buf_in)[left_ch_src]); buf_out_r[offset] = pcm_to_float(static_cast*>(buf_in)[right_ch_src]); } } } bool rsxaudio_data_thread::enqueue_data(RsxaudioPort dst, bool silence, const void* src_addr, const rsxaudio_hw_param_t& hwp) { auto& backend_thread = g_fxo->get(); if (dst == RsxaudioPort::SERIAL) { if (!silence) { for (u8 stream_idx = 0; stream_idx < SYS_RSXAUDIO_SERIAL_STREAM_CNT; stream_idx++) { pcm_serial_process_channel(hwp.serial.depth, output_buf.serial[stream_idx * 2], output_buf.serial[stream_idx * 2 + 1], src_addr, stream_idx); } } else { output_buf.serial.fill({}); } rsxaudio_data_container cont{hwp, output_buf, true, false, false}; backend_thread.add_data(cont); return cont.data_was_used(); } else if (dst == RsxaudioPort::SPDIF_0) { if (!silence) { pcm_spdif_process_channel(hwp.spdif[0].depth, output_buf.spdif[0][0], output_buf.spdif[0][1], src_addr); } else { output_buf.spdif[0].fill({}); } rsxaudio_data_container cont{hwp, output_buf, false, true, false}; backend_thread.add_data(cont); return cont.data_was_used(); } else if (dst == RsxaudioPort::SPDIF_1) { if (!silence) { pcm_spdif_process_channel(hwp.spdif[1].depth, output_buf.spdif[1][0], output_buf.spdif[1][1], src_addr); } else { output_buf.spdif[1].fill({}); } rsxaudio_data_container cont{hwp, output_buf, false, false, true}; backend_thread.add_data(cont); return cont.data_was_used(); } return false; } namespace audio { void configure_rsxaudio() { if (g_cfg.audio.provider == audio_provider::rsxaudio) { g_fxo->get().update_emu_cfg(); } } } rsxaudio_backend_thread::rsxaudio_backend_thread() { const u64 new_vol = g_cfg.audio.volume; callback_cfg.atomic_op([&](callback_config& val) { val.target_volume = static_cast(new_vol / 100.0 * callback_config::VOL_NOMINAL); val.initial_volume = val.current_volume; }); } rsxaudio_backend_thread::~rsxaudio_backend_thread() { if (backend) { backend->Close(); backend->SetWriteCallback(nullptr); backend->SetErrorCallback(nullptr); backend = nullptr; } } void rsxaudio_backend_thread::update_emu_cfg() { std::unique_lock lock(state_update_m); const emu_audio_cfg _new_emu_cfg = get_emu_cfg(); const u64 new_vol = g_cfg.audio.volume; callback_cfg.atomic_op([&](callback_config& val) { val.target_volume = static_cast(new_vol / 100.0 * callback_config::VOL_NOMINAL); val.initial_volume = val.current_volume; }); if (new_emu_cfg != _new_emu_cfg) { new_emu_cfg = _new_emu_cfg; emu_cfg_changed = true; lock.unlock(); state_update_c.notify_all(); } } rsxaudio_backend_thread::emu_audio_cfg rsxaudio_backend_thread::get_emu_cfg() { const AudioChannelCnt out_ch_cnt = [&]() { switch (g_cfg.audio.audio_channel_downmix) { case audio_downmix::use_application_settings: case audio_downmix::downmix_to_stereo: return AudioChannelCnt::STEREO; case audio_downmix::downmix_to_5_1: return AudioChannelCnt::SURROUND_5_1; case audio_downmix::no_downmix: return AudioChannelCnt::SURROUND_7_1; default: { fmt::throw_exception("Unsupported downmix level: %u", static_cast(g_cfg.audio.audio_channel_downmix.get())); } } }(); emu_audio_cfg cfg = { .desired_buffer_duration = g_cfg.audio.desired_buffer_duration, .time_stretching_threshold = g_cfg.audio.time_stretching_threshold / 100.0, .buffering_enabled = static_cast(g_cfg.audio.enable_buffering), .convert_to_s16 = static_cast(g_cfg.audio.convert_to_s16), .enable_time_stretching = static_cast(g_cfg.audio.enable_time_stretching), .dump_to_file = static_cast(g_cfg.audio.dump_to_file), .downmix = out_ch_cnt, .renderer = g_cfg.audio.renderer, .provider = g_cfg.audio.provider, .avport = convert_avport(g_cfg.audio.rsxaudio_port) }; cfg.buffering_enabled = cfg.buffering_enabled && cfg.renderer != audio_renderer::null; cfg.enable_time_stretching = cfg.buffering_enabled && cfg.enable_time_stretching && cfg.time_stretching_threshold > 0.0; return cfg; } void rsxaudio_backend_thread::operator()() { if (g_cfg.audio.provider != audio_provider::rsxaudio) { return; } static rsxaudio_state ra_state{}; static emu_audio_cfg emu_cfg{}; static bool backend_failed = false; for (;;) { bool should_update_backend = false; bool reset_backend = false; bool should_service_stream = false; { std::unique_lock lock(state_update_m); for (;;) { // Unsafe to access backend under lock (error_callback uses state_update_m -> possible deadlock) if (thread_ctrl::state() == thread_state::aborting) { lock.unlock(); backend_stop(); return; } // Emulated state changed if (ra_state_changed) { const callback_config cb_cfg = callback_cfg.observe(); should_update_backend |= cb_cfg.cfg_changed; ra_state_changed = false; ra_state = new_ra_state; if (cb_cfg.cfg_changed) { callback_cfg.atomic_op([&](callback_config& val) { val.cfg_changed = false; // Acknowledge cfg update }); } } // Update emu config if (emu_cfg_changed) { reset_backend |= emu_cfg.renderer != new_emu_cfg.renderer; emu_cfg_changed = false; emu_cfg = new_emu_cfg; should_update_backend = true; } // Handle backend error notification if (backend_error_occured) { reset_backend = true; should_update_backend = true; backend_error_occured = false; } if (should_update_backend) { backend_current_cfg.cfg = ra_state.port[static_cast(emu_cfg.avport)]; backend_current_cfg.avport = emu_cfg.avport; break; } if (backend_failed) { state_update_c.wait(state_update_m, ERROR_SERVICE_PERIOD); break; } else if (use_aux_ringbuf) { const u64 next_period_time = get_time_until_service(); should_service_stream = next_period_time <= SERVICE_THRESHOLD; if (should_service_stream) { break; } state_update_c.wait(state_update_m, next_period_time); } else { // Nothing to do - wait for events state_update_c.wait(state_update_m, umax); } } } if (should_update_backend) { backend_init(ra_state, emu_cfg, reset_backend); if (emu_cfg.enable_time_stretching) { resampler.set_params(backend_current_cfg.cfg.ch_cnt, backend_current_cfg.cfg.freq); resampler.set_tempo(RESAMPLER_MAX_FREQ_VAL); } if (emu_cfg.dump_to_file) { dumper.Open(backend_current_cfg.cfg.ch_cnt, backend_current_cfg.cfg.freq, AudioSampleSize::FLOAT); } else { dumper.Close(); } } if (!backend->Operational()) { if (!backend_failed) { sys_rsxaudio.warning("Backend stopped unexpectedly (likely device change). Attempting to recover..."); } backend_init(ra_state, emu_cfg); backend_failed = true; continue; } if (backend_failed) { sys_rsxaudio.warning("Backend recovered"); backend_failed = false; } if (!Emu.IsPaused() || !use_aux_ringbuf) // Don't pause if thread is in direct mode { if (!backend_playing()) { backend_start(); reset_service_time(); continue; } if (should_service_stream) { void* crnt_buf = thread_tmp_buf.data(); const u64 bytes_req = ringbuf.get_free_size(); const u64 bytes_read = aux_ringbuf.pop(crnt_buf, bytes_req, true); u64 crnt_buf_size = bytes_read; if (emu_cfg.enable_time_stretching) { const u64 input_ch_cnt = static_cast(ra_state.port[static_cast(emu_cfg.avport)].ch_cnt); const u64 bytes_per_sample = static_cast(AudioSampleSize::FLOAT) * input_ch_cnt; const u64 samples_req = bytes_req / bytes_per_sample; const u64 samples_avail = crnt_buf_size / bytes_per_sample; const f64 resampler_ratio = resampler.get_resample_ratio(); f64 fullness_ratio = static_cast(samples_avail + resampler.samples_available()) / samples_req; if (fullness_ratio < emu_cfg.time_stretching_threshold) { fullness_ratio /= emu_cfg.time_stretching_threshold; const f64 new_resampler_ratio = (resampler_ratio + fullness_ratio) / 2.0; if (std::abs(new_resampler_ratio - resampler_ratio) >= TIME_STRETCHING_STEP) { resampler.set_tempo(new_resampler_ratio); } } else if (resampler_ratio != RESAMPLER_MAX_FREQ_VAL) { resampler.set_tempo(RESAMPLER_MAX_FREQ_VAL); } resampler.put_samples(static_cast(crnt_buf), static_cast(samples_avail)); const auto [resampled_data, sample_cnt] = resampler.get_samples(static_cast(samples_req)); crnt_buf = resampled_data; crnt_buf_size = sample_cnt * bytes_per_sample; } if (emu_cfg.dump_to_file) { dumper.WriteData(crnt_buf, static_cast(crnt_buf_size)); } ringbuf.push(crnt_buf, crnt_buf_size); update_service_time(); } } else { if (backend_playing()) { backend_stop(); } if (should_service_stream) { update_service_time(); } } } } rsxaudio_backend_thread& rsxaudio_backend_thread::operator=(thread_state /* state */) { { std::lock_guard lock(state_update_m); } state_update_c.notify_all(); return *this; } void rsxaudio_backend_thread::set_new_stream_param(const std::array &cfg, avport_bit muted_avports) { std::unique_lock lock(state_update_m); const auto new_mute_state = gen_mute_state(muted_avports); const bool should_update = backend_current_cfg.cfg != cfg[static_cast(backend_current_cfg.avport)]; callback_cfg.atomic_op([&](callback_config& val) { val.mute_state = new_mute_state; if (should_update) { val.ready = false; // Prevent audio playback until backend is reconfigured val.cfg_changed = true; } }); if (new_ra_state.port != cfg) { new_ra_state.port = cfg; ra_state_changed = true; lock.unlock(); state_update_c.notify_all(); } } void rsxaudio_backend_thread::set_mute_state(avport_bit muted_avports) { const auto new_mute_state = gen_mute_state(muted_avports); callback_cfg.atomic_op([&](callback_config& val) { val.mute_state = new_mute_state; }); } u8 rsxaudio_backend_thread::gen_mute_state(avport_bit avports) { std::bitset mute_state{0}; if (avports.hdmi_0) mute_state[static_cast(RsxaudioAvportIdx::HDMI_0)] = true; if (avports.hdmi_1) mute_state[static_cast(RsxaudioAvportIdx::HDMI_1)] = true; if (avports.avmulti) mute_state[static_cast(RsxaudioAvportIdx::AVMULTI)] = true; if (avports.spdif_0) mute_state[static_cast(RsxaudioAvportIdx::SPDIF_0)] = true; if (avports.spdif_1) mute_state[static_cast(RsxaudioAvportIdx::SPDIF_1)] = true; return static_cast(mute_state.to_ulong()); } void rsxaudio_backend_thread::add_data(rsxaudio_data_container& cont) { std::unique_lock lock(ringbuf_mutex, std::try_to_lock); if (!lock.owns_lock()) { return; } const callback_config cb_cfg = callback_cfg.observe(); if (!cb_cfg.ready || !cb_cfg.callback_active) { return; } static rsxaudio_data_container::data_blk_t in_data_blk{}; if (u32 len = cont.get_data_size(cb_cfg.avport_idx)) { if (use_aux_ringbuf) { if (aux_ringbuf.get_free_size() >= len) { cont.get_data(cb_cfg.avport_idx, in_data_blk); aux_ringbuf.push(in_data_blk.data(), len); } } else { if (ringbuf.get_free_size() >= len) { cont.get_data(cb_cfg.avport_idx, in_data_blk); ringbuf.push(in_data_blk.data(), len); } } } } RsxaudioAvportIdx rsxaudio_backend_thread::convert_avport(audio_avport avport) { switch (avport) { case audio_avport::hdmi_0: return RsxaudioAvportIdx::HDMI_0; case audio_avport::hdmi_1: return RsxaudioAvportIdx::HDMI_1; case audio_avport::avmulti: return RsxaudioAvportIdx::AVMULTI; case audio_avport::spdif_0: return RsxaudioAvportIdx::SPDIF_0; case audio_avport::spdif_1: return RsxaudioAvportIdx::SPDIF_1; default: { fmt::throw_exception("Invalid RSXAudio avport: %u", static_cast(avport)); } } } void rsxaudio_backend_thread::backend_init(const rsxaudio_state& ra_state, const emu_audio_cfg& emu_cfg, bool reset_backend) { if (reset_backend || !backend) { backend = nullptr; backend = Emu.GetCallbacks().get_audio(); backend->SetWriteCallback(std::bind(&rsxaudio_backend_thread::write_data_callback, this, std::placeholders::_1, std::placeholders::_2)); backend->SetErrorCallback(std::bind(&rsxaudio_backend_thread::error_callback, this)); } const port_config& port_cfg = ra_state.port[static_cast(emu_cfg.avport)]; const AudioSampleSize sample_size = emu_cfg.convert_to_s16 ? AudioSampleSize::S16 : AudioSampleSize::FLOAT; const AudioChannelCnt ch_cnt = static_cast(std::min(static_cast(port_cfg.ch_cnt), static_cast(emu_cfg.downmix))); static constexpr f64 _10ms = 512.0 / 48000.0; const f64 cb_frame_len = backend->Open(port_cfg.freq, sample_size, ch_cnt) ? backend->GetCallbackFrameLen() : 0.0; const f64 buffering_len = emu_cfg.buffering_enabled ? (emu_cfg.desired_buffer_duration / 1000.0) : 0.0; const u64 bytes_per_sec = static_cast(AudioSampleSize::FLOAT) * static_cast(port_cfg.ch_cnt) * static_cast(port_cfg.freq); { std::lock_guard lock(ringbuf_mutex); use_aux_ringbuf = emu_cfg.enable_time_stretching || emu_cfg.dump_to_file; if (use_aux_ringbuf) { const f64 frame_len = std::max(buffering_len * 0.5, SERVICE_PERIOD_SEC) + cb_frame_len + _10ms; const u64 frame_len_bytes = static_cast(std::round(frame_len * bytes_per_sec)); aux_ringbuf.set_buf_size(frame_len_bytes); ringbuf.set_buf_size(frame_len_bytes); thread_tmp_buf.resize(frame_len_bytes); } else { const f64 frame_len = std::max(buffering_len, cb_frame_len) + _10ms; ringbuf.set_buf_size(static_cast(std::round(frame_len * bytes_per_sec))); thread_tmp_buf.resize(0); } callback_tmp_buf.resize(static_cast((cb_frame_len + _10ms) * static_cast(AudioSampleSize::FLOAT) * static_cast(port_cfg.ch_cnt) * static_cast(port_cfg.freq))); } callback_cfg.atomic_op([&](callback_config& val) { val.callback_active = false; // Backend may take some time to activate. This prevents overflows on input side. if (!val.cfg_changed) { val.freq = static_cast(port_cfg.freq); val.input_ch_cnt = static_cast(port_cfg.ch_cnt); val.output_ch_cnt = static_cast(ch_cnt); val.convert_to_s16 = emu_cfg.convert_to_s16; val.avport_idx = emu_cfg.avport; val.ready = true; } }); } void rsxaudio_backend_thread::backend_start() { ensure(backend != nullptr); if (use_aux_ringbuf) { resampler.set_tempo(RESAMPLER_MAX_FREQ_VAL); resampler.flush(); aux_ringbuf.reader_flush(); } ringbuf.reader_flush(); backend->Play(); } void rsxaudio_backend_thread::backend_stop() { if (backend == nullptr) { return; } backend->Pause(); callback_cfg.atomic_op([&](callback_config& val) { val.callback_active = false; }); } bool rsxaudio_backend_thread::backend_playing() { if (backend == nullptr) { return false; } return backend->IsPlaying(); } u32 rsxaudio_backend_thread::write_data_callback(u32 bytes, void* buf) { const callback_config cb_cfg = callback_cfg.atomic_op([&](callback_config& val) { val.callback_active = true; return val; }); const std::bitset mute_state{cb_cfg.mute_state}; if (cb_cfg.ready && !mute_state[static_cast(cb_cfg.avport_idx)] && Emu.IsRunning()) { const u32 bytes_ch_adjusted = bytes / cb_cfg.output_ch_cnt * cb_cfg.input_ch_cnt; const u32 bytes_from_rb = cb_cfg.convert_to_s16 ? bytes_ch_adjusted / static_cast(AudioSampleSize::S16) * static_cast(AudioSampleSize::FLOAT) : bytes_ch_adjusted; ensure(callback_tmp_buf.size() * static_cast(AudioSampleSize::FLOAT) >= bytes_from_rb); const u32 byte_cnt = static_cast(ringbuf.pop(callback_tmp_buf.data(), bytes_from_rb, true)); const u32 sample_cnt = byte_cnt / static_cast(AudioSampleSize::FLOAT); const u32 sample_cnt_out = sample_cnt / cb_cfg.input_ch_cnt * cb_cfg.output_ch_cnt; // Buffer is in weird state - drop acquired data if (sample_cnt == 0 || sample_cnt % cb_cfg.input_ch_cnt != 0) { memset(buf, 0, bytes); return bytes; } if (cb_cfg.input_ch_cnt > cb_cfg.output_ch_cnt) { if (cb_cfg.input_ch_cnt == static_cast(AudioChannelCnt::SURROUND_7_1)) { if (cb_cfg.output_ch_cnt == static_cast(AudioChannelCnt::SURROUND_5_1)) { AudioBackend::downmix(sample_cnt, callback_tmp_buf.data(), callback_tmp_buf.data()); } else if (cb_cfg.output_ch_cnt == static_cast(AudioChannelCnt::STEREO)) { AudioBackend::downmix(sample_cnt, callback_tmp_buf.data(), callback_tmp_buf.data()); } else { fmt::throw_exception("Invalid downmix combination: %u -> %u", cb_cfg.input_ch_cnt, cb_cfg.output_ch_cnt); } } else if (cb_cfg.input_ch_cnt == static_cast(AudioChannelCnt::SURROUND_5_1)) { if (cb_cfg.output_ch_cnt == static_cast(AudioChannelCnt::STEREO)) { AudioBackend::downmix(sample_cnt, callback_tmp_buf.data(), callback_tmp_buf.data()); } else { fmt::throw_exception("Invalid downmix combination: %u -> %u", cb_cfg.input_ch_cnt, cb_cfg.output_ch_cnt); } } else { fmt::throw_exception("Invalid downmix combination: %u -> %u", cb_cfg.input_ch_cnt, cb_cfg.output_ch_cnt); } } if (cb_cfg.target_volume != cb_cfg.current_volume) { const AudioBackend::VolumeParam param = { .initial_volume = cb_cfg.initial_volume * callback_config::VOL_NOMINAL_INV, .current_volume = cb_cfg.current_volume * callback_config::VOL_NOMINAL_INV, .target_volume = cb_cfg.target_volume * callback_config::VOL_NOMINAL_INV, .freq = cb_cfg.freq, .ch_cnt = cb_cfg.input_ch_cnt }; const u16 new_vol = static_cast(std::round(AudioBackend::apply_volume(param, sample_cnt_out, callback_tmp_buf.data(), callback_tmp_buf.data()) * callback_config::VOL_NOMINAL)); callback_cfg.atomic_op([&](callback_config& val) { if (val.target_volume != cb_cfg.target_volume) { val.initial_volume = new_vol; } // We don't care about proper volume adjustment if underflow has occured val.current_volume = bytes_from_rb != byte_cnt ? val.target_volume : new_vol; }); } else if (cb_cfg.current_volume != callback_config::VOL_NOMINAL) { AudioBackend::apply_volume_static(cb_cfg.current_volume * callback_config::VOL_NOMINAL_INV, sample_cnt_out, callback_tmp_buf.data(), callback_tmp_buf.data()); } if (cb_cfg.convert_to_s16) { AudioBackend::convert_to_s16(sample_cnt_out, callback_tmp_buf.data(), buf); return sample_cnt_out * static_cast(AudioSampleSize::S16); } AudioBackend::normalize(sample_cnt_out, callback_tmp_buf.data(), static_cast(buf)); return sample_cnt_out * static_cast(AudioSampleSize::FLOAT); } ringbuf.reader_flush(); memset(buf, 0, bytes); return bytes; } void rsxaudio_backend_thread::error_callback() { { std::lock_guard lock(state_update_m); backend_error_occured = true; } state_update_c.notify_all(); } u64 rsxaudio_backend_thread::get_time_until_service() { const u64 next_service_time = start_time + time_period_idx * SERVICE_PERIOD; const u64 current_time = get_system_time(); return next_service_time >= current_time ? next_service_time - current_time : 0; } void rsxaudio_backend_thread::update_service_time() { if (get_time_until_service() <= SERVICE_THRESHOLD) time_period_idx++; } void rsxaudio_backend_thread::reset_service_time() { start_time = get_system_time(); time_period_idx = 1; } void rsxaudio_periodic_tmr::sched_timer() { u64 interval = get_rel_next_time(); if (interval == 0) { zero_period = true; } else if (interval == UINT64_MAX) { interval = 0; zero_period = false; } else { zero_period = false; } #if defined(_WIN32) if (interval) { LARGE_INTEGER due_time{}; due_time.QuadPart = -static_cast(interval * 10); ensure(SetWaitableTimerEx(timer_handle, &due_time, 0, nullptr, nullptr, nullptr, 0)); } else { ensure(CancelWaitableTimer(timer_handle)); } #elif defined(__linux__) const time_t secs = interval / 1'000'000; const long nsecs = (interval - secs * 1'000'000) * 1000; const itimerspec tspec = {{}, { secs, nsecs }}; ensure(timerfd_settime(timer_handle, 0, &tspec, nullptr) == 0); #elif defined(BSD) || defined(__APPLE__) handle[TIMER_ID].data = interval * 1000; if (interval) { handle[TIMER_ID].flags = (handle[TIMER_ID].flags & ~EV_DISABLE) | EV_ENABLE; } else { handle[TIMER_ID].flags = (handle[TIMER_ID].flags & ~EV_ENABLE) | EV_DISABLE; } ensure(kevent(kq, &handle[TIMER_ID], 1, nullptr, 0, nullptr) >= 0); #else #error "Implement" #endif } void rsxaudio_periodic_tmr::cancel_timer_unlocked() { zero_period = false; #if defined(_WIN32) ensure(CancelWaitableTimer(timer_handle)); if (in_wait) { ensure(SetEvent(cancel_event)); } #elif defined(__linux__) const itimerspec tspec{}; ensure(timerfd_settime(timer_handle, 0, &tspec, nullptr) == 0); if (in_wait) { const u64 flag = 1; const auto wr_res = write(cancel_event, &flag, sizeof(flag)); ensure(wr_res == sizeof(flag) || wr_res == -EAGAIN); } #elif defined(BSD) || defined(__APPLE__) handle[TIMER_ID].flags = (handle[TIMER_ID].flags & ~EV_ENABLE) | EV_DISABLE; handle[TIMER_ID].data = 0; if (in_wait) { ensure(kevent(kq, handle, 2, nullptr, 0, nullptr) >= 0); } else { ensure(kevent(kq, &handle[TIMER_ID], 1, nullptr, 0, nullptr) >= 0); } #else #error "Implement" #endif } void rsxaudio_periodic_tmr::reset_cancel_flag() { #if defined(_WIN32) ensure(ResetEvent(cancel_event)); #elif defined(__linux__) u64 tmp_buf{}; [[maybe_unused]] const auto nread = read(cancel_event, &tmp_buf, sizeof(tmp_buf)); #elif defined(BSD) || defined(__APPLE__) // Cancel event is reset automatically #else #error "Implement" #endif } rsxaudio_periodic_tmr::rsxaudio_periodic_tmr() { #if defined(_WIN32) ensure(cancel_event = CreateEvent(nullptr, false, false, nullptr)); ensure(timer_handle = CreateWaitableTimer(nullptr, false, nullptr)); #elif defined(__linux__) timer_handle = timerfd_create(CLOCK_MONOTONIC, 0); ensure((epoll_fd = epoll_create(2)) >= 0); epoll_event evnt{ EPOLLIN, {} }; evnt.data.fd = timer_handle; ensure(timer_handle >= 0 && epoll_ctl(epoll_fd, EPOLL_CTL_ADD, timer_handle, &evnt) == 0); cancel_event = eventfd(0, EFD_NONBLOCK); evnt.data.fd = cancel_event; ensure(cancel_event >= 0 && epoll_ctl(epoll_fd, EPOLL_CTL_ADD, cancel_event, &evnt) == 0); #elif defined(BSD) || defined(__APPLE__) #if defined(__APPLE__) static constexpr unsigned int TMR_CFG = NOTE_NSECONDS | NOTE_CRITICAL; #else static constexpr unsigned int TMR_CFG = NOTE_NSECONDS; #endif ensure((kq = kqueue()) >= 0); EV_SET(&handle[TIMER_ID], TIMER_ID, EVFILT_TIMER, EV_ADD | EV_ENABLE | EV_ONESHOT, TMR_CFG, 0, nullptr); EV_SET(&handle[CANCEL_ID], CANCEL_ID, EVFILT_USER, EV_ADD | EV_ENABLE | EV_CLEAR, NOTE_FFNOP, 0, nullptr); ensure(kevent(kq, &handle[CANCEL_ID], 1, nullptr, 0, nullptr) >= 0); handle[CANCEL_ID].fflags |= NOTE_TRIGGER; #else #error "Implement" #endif } rsxaudio_periodic_tmr::~rsxaudio_periodic_tmr() { #if defined(_WIN32) CloseHandle(timer_handle); CloseHandle(cancel_event); #elif defined(__linux__) close(epoll_fd); close(timer_handle); close(cancel_event); #elif defined(BSD) || defined(__APPLE__) close(kq); #else #error "Implement" #endif } rsxaudio_periodic_tmr::wait_result rsxaudio_periodic_tmr::wait(const std::function &callback) { std::unique_lock lock(mutex); if (in_wait || !callback) { return wait_result::INVALID_PARAM; } in_wait = true; bool tmr_error = false; bool timeout = false; bool wait_canceled = false; if (!zero_period) { lock.unlock(); constexpr u8 obj_wait_cnt = 2; #if defined(_WIN32) const HANDLE wait_arr[obj_wait_cnt] = { timer_handle, cancel_event }; const auto wait_status = WaitForMultipleObjects(obj_wait_cnt, wait_arr, false, INFINITE); if (wait_status == WAIT_FAILED || wait_status >= WAIT_ABANDONED_0 && wait_status < WAIT_ABANDONED_0 + obj_wait_cnt) { tmr_error = true; } else if (wait_status == WAIT_TIMEOUT) { timeout = true; } else if (wait_status == WAIT_OBJECT_0 + 1) { wait_canceled = true; } #elif defined(__linux__) epoll_event event[obj_wait_cnt]{}; const auto wait_status = epoll_wait(epoll_fd, event, obj_wait_cnt, -1); if (wait_status < 0 || wait_status > obj_wait_cnt) { tmr_error = true; } else if (wait_status == 0) { timeout = true; } else { for (int i = 0; i < wait_status; i++) { if (event[i].data.fd == cancel_event) { wait_canceled = true; break; } } } #elif defined(BSD) || defined(__APPLE__) struct kevent event[obj_wait_cnt]{}; const auto wait_status = kevent(kq, nullptr, 0, event, obj_wait_cnt, nullptr); if (wait_status < 0 || wait_status > obj_wait_cnt) { tmr_error = true; } else if (wait_status == 0) { timeout = true; } else { for (int i = 0; i < wait_status; i++) { if (event[i].ident == CANCEL_ID) { wait_canceled = true; break; } } } #else #error "Implement" #endif lock.lock(); } else { zero_period = false; } in_wait = false; if (wait_canceled) { reset_cancel_flag(); } if (tmr_error) { return wait_result::TIMER_ERROR; } else if (timeout) { return wait_result::TIMEOUT; } else if (wait_canceled) { sched_timer(); return wait_result::TIMER_CANCELED; } else { callback(); sched_timer(); return wait_result::SUCCESS; } } u64 rsxaudio_periodic_tmr::get_rel_next_time() { const u64 crnt_time = get_system_time(); u64 next_time = UINT64_MAX; for (vtimer& vtimer : vtmr_pool) { if (!vtimer.active) continue; u64 next_blk_time = static_cast(vtimer.blk_cnt * vtimer.blk_time); if (crnt_time >= next_blk_time) { const u64 crnt_blk = get_crnt_blk(crnt_time, vtimer.blk_time); if (crnt_blk > vtimer.blk_cnt + MAX_BURST_PERIODS) { vtimer.blk_cnt = std::max(vtimer.blk_cnt, crnt_blk - MAX_BURST_PERIODS); next_blk_time = static_cast(vtimer.blk_cnt * vtimer.blk_time); } } if (crnt_time >= next_blk_time) { next_time = 0; } else { next_time = std::min(next_time, next_blk_time - crnt_time); } } return next_time; } void rsxaudio_periodic_tmr::cancel_wait() { std::lock_guard lock(mutex); cancel_timer_unlocked(); } void rsxaudio_periodic_tmr::enable_vtimer(u32 vtimer_id, u32 rate, u64 crnt_time) { ensure(vtimer_id < VTIMER_MAX && rate); vtimer& vtimer = vtmr_pool[vtimer_id]; const f64 new_blk_time = get_blk_time(rate); // Avoid timer reset when possible if (!vtimer.active || new_blk_time != vtimer.blk_time) { vtimer.blk_cnt = get_crnt_blk(crnt_time, new_blk_time); } vtimer.blk_time = new_blk_time; vtimer.active = true; } void rsxaudio_periodic_tmr::disable_vtimer(u32 vtimer_id) { ensure(vtimer_id < VTIMER_MAX); vtimer& vtimer = vtmr_pool[vtimer_id]; vtimer.active = false; } bool rsxaudio_periodic_tmr::is_vtimer_behind(u32 vtimer_id, u64 crnt_time) const { const vtimer& vtimer = vtmr_pool[vtimer_id]; ensure(vtimer_id < VTIMER_MAX); return is_vtimer_behind(vtimer, crnt_time); } void rsxaudio_periodic_tmr::vtimer_skip_periods(u32 vtimer_id, u64 crnt_time) { vtimer& vtimer = vtmr_pool[vtimer_id]; ensure(vtimer_id < VTIMER_MAX); if (is_vtimer_behind(vtimer, crnt_time)) { vtimer.blk_cnt = get_crnt_blk(crnt_time, vtimer.blk_time); } } void rsxaudio_periodic_tmr::vtimer_incr(u32 vtimer_id, u64 crnt_time) { vtimer& vtimer = vtmr_pool[vtimer_id]; ensure(vtimer_id < VTIMER_MAX); if (is_vtimer_behind(vtimer, crnt_time)) { vtimer.blk_cnt++; } } bool rsxaudio_periodic_tmr::is_vtimer_active(u32 vtimer_id) const { const vtimer& vtimer = vtmr_pool[vtimer_id]; ensure(vtimer_id < VTIMER_MAX); return vtimer.active; } u64 rsxaudio_periodic_tmr::vtimer_get_sched_time(u32 vtimer_id) const { const vtimer& vtimer = vtmr_pool[vtimer_id]; ensure(vtimer_id < VTIMER_MAX); return static_cast(vtimer.blk_cnt * vtimer.blk_time); } bool rsxaudio_periodic_tmr::is_vtimer_behind(const vtimer& vtimer, u64 crnt_time) const { return vtimer.active && vtimer.blk_cnt < get_crnt_blk(crnt_time, vtimer.blk_time); } u64 rsxaudio_periodic_tmr::get_crnt_blk(u64 crnt_time, f64 blk_time) const { return static_cast(std::floor(static_cast(crnt_time) / blk_time)) + 1; } f64 rsxaudio_periodic_tmr::get_blk_time(u32 data_rate) const { return static_cast(SYS_RSXAUDIO_STREAM_SIZE * 1'000'000) / data_rate; }