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

2337 lines
60 KiB
C++

#include "stdafx.h"
#include "Emu/Memory/vm.h"
#include "Emu/IdManager.h"
#include "Emu/System.h"
#include "Emu/system_config.h"
#include "sys_process.h"
#include "sys_rsxaudio.h"
#include <cmath>
#include <bitset>
#include <optional>
#ifdef __linux__
#include <sys/epoll.h>
#include <sys/timerfd.h>
#include <sys/eventfd.h>
#include <unistd.h>
#elif defined(BSD) || defined(__APPLE__)
#include <unistd.h>
#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<s32>(entry_idx_raw, 0, SYS_RSXAUDIO_RINGBUF_SZ);
ring_buf.entries[entry_idx].timestamp = convert_to_timebased_time(timestamp);
}
static std::tuple<bool /*notify*/, u64 /*blk_idx*/, u64 /*timestamp*/> update_status(rsxaudio_shmem::ringbuf_t& ring_buf)
{
const s32 read_idx = std::clamp<s32>(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<s32>(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<bool /*entry_valid*/, u32 /*addr*/> get_addr(const rsxaudio_shmem::ringbuf_t& ring_buf)
{
const s32 read_idx = std::clamp<s32>(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<u64> 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<u64>(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<u64>(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<u32> handle)
{
sys_rsxaudio.trace("sys_rsxaudio_initialize(handle=*0x%x)", handle);
auto& rsxaudio_thread = g_fxo->get<rsx_audio_data>();
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<lv2_obj, lv2_rsxaudio>();
if (!id)
{
rsxaudio_thread.rsxaudio_ctx_allocated = false;
return CELL_ENOMEM;
}
const auto rsxaudio_obj = idm::get<lv2_obj, lv2_rsxaudio>(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<lv2_obj, lv2_rsxaudio>(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<lv2_obj, lv2_rsxaudio>(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<rsx_audio_data>();
{
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);
idm::remove<lv2_obj, lv2_rsxaudio>(handle);
rsxaudio_thread.rsxaudio_ctx_allocated = false;
return CELL_OK;
}
error_code sys_rsxaudio_import_shared_memory(u32 handle, vm::ptr<u64> addr)
{
sys_rsxaudio.trace("sys_rsxaudio_import_shared_memory(handle=0x%x, addr=*0x%x)", handle, addr);
const auto rsxaudio_obj = idm::get<lv2_obj, lv2_rsxaudio>(handle);
if (!rsxaudio_obj)
{
return CELL_ESRCH;
}
std::lock_guard<shared_mutex> 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<u64> addr /* unused */)
{
sys_rsxaudio.trace("sys_rsxaudio_unimport_shared_memory(handle=0x%x, addr=*0x%x)", handle, addr);
const auto rsxaudio_obj = idm::get<lv2_obj, lv2_rsxaudio>(handle);
if (!rsxaudio_obj)
{
return CELL_ESRCH;
}
std::lock_guard<shared_mutex> 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<lv2_obj, lv2_rsxaudio>(handle);
if (!rsxaudio_obj)
{
return CELL_ESRCH;
}
std::lock_guard<shared_mutex> 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<lv2_obj, lv2_event_queue>(sh_page->ctrl.event_queue_1_id))
{
rsxaudio_obj->event_queue[0] = queue1;
if (auto queue2 = idm::get<lv2_obj, lv2_event_queue>(sh_page->ctrl.event_queue_2_id))
{
rsxaudio_obj->event_queue[1] = queue2;
if (auto queue3 = idm::get<lv2_obj, lv2_event_queue>(sh_page->ctrl.event_queue_3_id))
{
rsxaudio_obj->event_queue[2] = queue3;
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<u32>(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<u32>(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<u32>(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<lv2_obj, lv2_rsxaudio>(handle);
if (!rsxaudio_obj)
{
return CELL_ESRCH;
}
std::lock_guard<shared_mutex> lock(rsxaudio_obj->mutex);
if (!rsxaudio_obj->init)
{
return CELL_ESRCH;
}
{
auto& rsxaudio_thread = g_fxo->get<rsx_audio_data>();
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++)
{
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<lv2_obj, lv2_rsxaudio>(handle);
if (!rsxaudio_obj)
{
return CELL_ESRCH;
}
std::lock_guard<shared_mutex> lock(rsxaudio_obj->mutex);
if (!rsxaudio_obj->init)
{
return CELL_ESRCH;
}
auto& rsxaudio_thread = g_fxo->get<rsx_audio_data>();
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<lv2_obj, lv2_rsxaudio>(handle);
if (!rsxaudio_obj)
{
return CELL_ESRCH;
}
std::lock_guard<shared_mutex> 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<rsx_audio_data>();
rsxaudio_thread.update_hw_param([&](auto& param)
{
if (sh_page->ctrl.ringbuf[static_cast<u32>(RsxaudioPort::SERIAL)].active) param.serial.dma_en = true;
if (sh_page->ctrl.ringbuf[static_cast<u32>(RsxaudioPort::SPDIF_0)].active) param.spdif[0].dma_en = true;
if (sh_page->ctrl.ringbuf[static_cast<u32>(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(); queue && sh_page->ctrl.ringbuf[q_idx].active)
{
queue->send(rsxaudio_obj->event_port_name[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<lv2_obj, lv2_rsxaudio>(handle);
if (!rsxaudio_obj)
{
return CELL_ESRCH;
}
std::lock_guard<shared_mutex> lock(rsxaudio_obj->mutex);
if (!rsxaudio_obj->init)
{
return CELL_ESRCH;
}
auto& rsxaudio_thread = g_fxo->get<rsx_audio_data>();
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<u64> 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<lv2_obj, lv2_rsxaudio>(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<u8>(RsxaudioAvportIdx::AVMULTI)] = true;
if (hwp.spdif[0].use_serial_buf)
{
avport_data_avail[static_cast<u8>(RsxaudioAvportIdx::SPDIF_0)] = true;
}
if (hwp.spdif[1].use_serial_buf)
{
avport_data_avail[static_cast<u8>(RsxaudioAvportIdx::SPDIF_1)] = true;
}
}
if (spdif_0_rdy && !hwp.spdif[0].use_serial_buf)
{
avport_data_avail[static_cast<u8>(RsxaudioAvportIdx::SPDIF_0)] = true;
}
if (spdif_1_rdy && !hwp.spdif[1].use_serial_buf)
{
avport_data_avail[static_cast<u8>(RsxaudioAvportIdx::SPDIF_1)] = true;
}
if (hwp.hdmi[0].init)
{
if (hwp.hdmi[0].use_spdif_1)
{
avport_data_avail[static_cast<u8>(RsxaudioAvportIdx::HDMI_0)] = avport_data_avail[static_cast<u8>(RsxaudioAvportIdx::SPDIF_1)];
}
else
{
avport_data_avail[static_cast<u8>(RsxaudioAvportIdx::HDMI_0)] = serial_rdy;
}
hdmi_stream_cnt[0] = static_cast<u8>(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<u8>(RsxaudioAvportIdx::HDMI_1)] = avport_data_avail[static_cast<u8>(RsxaudioAvportIdx::SPDIF_1)];
}
else
{
avport_data_avail[static_cast<u8>(RsxaudioAvportIdx::HDMI_1)] = serial_rdy;
}
hdmi_stream_cnt[1] = static_cast<u8>(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<u8>(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<u8>(avport)])
{
return;
}
data_was_written = true;
auto spdif_filter_map = [&](u8 hdmi_idx)
{
std::array<u8, SYS_RSXAUDIO_SERIAL_MAX_CH> 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<void()> 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<u32>(RsxaudioPort::SERIAL), crnt_time);
timer.vtimer_skip_periods(static_cast<u32>(RsxaudioPort::SPDIF_0), crnt_time);
timer.vtimer_skip_periods(static_cast<u32>(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<shared_mutex> 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<u32>(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())
{
queue->send(rsxaudio_obj->event_port_name[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<u32>(RsxaudioPort::SERIAL), crnt_time))
{
process_rb(RsxaudioPort::SERIAL, hw_cfg->serial.dma_en);
}
if (timer.is_vtimer_behind(static_cast<u32>(RsxaudioPort::SPDIF_0), crnt_time))
{
process_rb(RsxaudioPort::SPDIF_0, hw_cfg->spdif[0].dma_en);
}
if (timer.is_vtimer_behind(static_cast<u32>(RsxaudioPort::SPDIF_1), crnt_time))
{
process_rb(RsxaudioPort::SPDIF_1, hw_cfg->spdif[1].dma_en);
}
}
std::pair<bool, void*> 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<u32>(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<u8*>(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<void(rsxaudio_hw_param_t&)> update_callback)
{
ensure(update_callback);
hw_param_ts.add_op([&]()
{
auto new_hw_param = std::make_shared<rsxaudio_hw_param_t>(*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<rsxaudio_backend_thread::port_config, SYS_RSXAUDIO_AVPORT_CNT> port_cfg{};
port_cfg[static_cast<u8>(RsxaudioAvportIdx::AVMULTI)] = {static_cast<AudioFreq>(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<u8>(RsxaudioAvportIdx::AVMULTI)];
}
else
{
return rsxaudio_backend_thread::port_config{static_cast<AudioFreq>(new_hw_param->spdif_freq_base / new_hw_param->spdif[spdif_idx].freq_div), AudioChannelCnt::STEREO};
}
};
port_cfg[static_cast<u8>(RsxaudioAvportIdx::SPDIF_0)] = gen_spdif_port_cfg(0);
port_cfg[static_cast<u8>(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<u8>(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<u8>(RsxaudioAvportIdx::AVMULTI)].freq, new_hw_param->hdmi[hdmi_idx].ch_cfg.total_ch_cnt};
}
};
port_cfg[static_cast<u8>(RsxaudioAvportIdx::HDMI_0)] = gen_hdmi_port_cfg(0);
port_cfg[static_cast<u8>(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<rsx_audio_backend>().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<u32>(port_cfg[static_cast<u8>(RsxaudioAvportIdx::AVMULTI)].freq) *
static_cast<u8>(new_hw_param->serial.depth) *
SYS_RSXAUDIO_CH_PER_STREAM;
timer.enable_vtimer(static_cast<u32>(RsxaudioPort::SERIAL), new_timer_rate, crnt_time);
}
else
{
timer.disable_vtimer(static_cast<u32>(RsxaudioPort::SERIAL));
}
for (u8 spdif_idx = 0; spdif_idx < SYS_RSXAUDIO_SPDIF_CNT; spdif_idx++)
{
const u32 vtimer_id = static_cast<u32>(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<u32>(port_cfg[static_cast<u8>(RsxaudioAvportIdx::SPDIF_0) + spdif_idx].freq) *
static_cast<u8>(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<rsxaudio_hw_param_t>(*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<u8>(port));
}
}
g_fxo->get<rsx_audio_backend>().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<rsxaudio_hw_param_t>(*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<u8>(avport));
}
}
g_fxo->get<rsx_audio_backend>().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<u8>(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<const be_t<s16>*>(buf_in)[left_ch_src]);
buf_out_r[ch_dst] = pcm_to_float(static_cast<const be_t<s16>*>(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<const be_t<s32>*>(buf_in)[left_ch_src]);
buf_out_r[ch_dst] = pcm_to_float(static_cast<const be_t<s32>*>(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<u8>(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<const be_t<s16>*>(buf_in)[left_ch_src]);
buf_out_r[offset] = pcm_to_float(static_cast<const be_t<s16>*>(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<const be_t<s32>*>(buf_in)[left_ch_src]);
buf_out_r[offset] = pcm_to_float(static_cast<const be_t<s32>*>(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<rsx_audio_backend>();
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
{
extern void configure_rsxaudio()
{
if (g_cfg.audio.provider == audio_provider::rsxaudio && g_fxo->is_init<rsx_audio_backend>())
{
g_fxo->get<rsx_audio_backend>().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<u16>(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->SetStateCallback(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<u16>(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()
{
// Get max supported channel count
AudioChannelCnt out_ch_cnt = AudioBackend::get_max_channel_count(0); // CELL_AUDIO_OUT_PRIMARY
emu_audio_cfg cfg =
{
.audio_device = g_cfg.audio.audio_device,
.desired_buffer_duration = g_cfg.audio.desired_buffer_duration,
.time_stretching_threshold = g_cfg.audio.time_stretching_threshold / 100.0,
.buffering_enabled = static_cast<bool>(g_cfg.audio.enable_buffering),
.convert_to_s16 = static_cast<bool>(g_cfg.audio.convert_to_s16),
.enable_time_stretching = static_cast<bool>(g_cfg.audio.enable_time_stretching),
.dump_to_file = static_cast<bool>(g_cfg.audio.dump_to_file),
.channels = 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 checkDefaultDevice = false;
bool should_service_stream = false;
{
std::unique_lock lock(state_update_m);
for (;;)
{
// Unsafe to access backend under lock (state_changed_callback uses state_update_m -> possible deadlock)
if (thread_ctrl::state() == thread_state::aborting)
{
lock.unlock();
backend_stop();
return;
}
if (backend_device_changed)
{
should_update_backend = true;
checkDefaultDevice = true;
backend_device_changed = false;
}
// Emulated state changed
if (ra_state_changed)
{
const callback_config cb_cfg = callback_cfg.observe();
ra_state_changed = false;
ra_state = new_ra_state;
if (cb_cfg.cfg_changed)
{
should_update_backend = true;
checkDefaultDevice = false;
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;
checkDefaultDevice = false;
}
// Handle backend error notification
if (backend_error_occured)
{
reset_backend = true;
should_update_backend = true;
checkDefaultDevice = false;
backend_error_occured = false;
}
if (should_update_backend)
{
backend_current_cfg.cfg = ra_state.port[static_cast<u8>(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 && (!checkDefaultDevice || backend->DefaultDeviceChanged()))
{
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<u64>(ra_state.port[static_cast<u8>(emu_cfg.avport)].ch_cnt);
const u64 bytes_per_sample = static_cast<u32>(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<f64>(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<f32*>(crnt_buf), static_cast<u32>(samples_avail));
const auto [resampled_data, sample_cnt] = resampler.get_samples(static_cast<u32>(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<u32>(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<port_config, SYS_RSXAUDIO_AVPORT_CNT> &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<u8>(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<SYS_RSXAUDIO_AVPORT_CNT> mute_state{0};
if (avports.hdmi_0) mute_state[static_cast<u8>(RsxaudioAvportIdx::HDMI_0)] = true;
if (avports.hdmi_1) mute_state[static_cast<u8>(RsxaudioAvportIdx::HDMI_1)] = true;
if (avports.avmulti) mute_state[static_cast<u8>(RsxaudioAvportIdx::AVMULTI)] = true;
if (avports.spdif_0) mute_state[static_cast<u8>(RsxaudioAvportIdx::SPDIF_0)] = true;
if (avports.spdif_1) mute_state[static_cast<u8>(RsxaudioAvportIdx::SPDIF_1)] = true;
return static_cast<u8>(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<u8>(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->SetStateCallback(std::bind(&rsxaudio_backend_thread::state_changed_callback, this, std::placeholders::_1));
}
const port_config& port_cfg = ra_state.port[static_cast<u8>(emu_cfg.avport)];
const AudioSampleSize sample_size = emu_cfg.convert_to_s16 ? AudioSampleSize::S16 : AudioSampleSize::FLOAT;
const AudioChannelCnt ch_cnt = static_cast<AudioChannelCnt>(std::min<u32>(static_cast<u32>(port_cfg.ch_cnt), static_cast<u32>(emu_cfg.channels)));
f64 cb_frame_len = 0.0;
u32 backend_ch_cnt = 2;
if (backend->Open(emu_cfg.audio_device, port_cfg.freq, sample_size, ch_cnt))
{
cb_frame_len = backend->GetCallbackFrameLen();
backend_ch_cnt = backend->get_channels();
}
else
{
sys_rsxaudio.error("Failed to open audio backend. Make sure that no other application is running that might block audio access (e.g. Netflix).");
}
static constexpr f64 _10ms = 512.0 / 48000.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<u32>(AudioSampleSize::FLOAT) * static_cast<u32>(port_cfg.ch_cnt) * static_cast<u32>(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<f64>(buffering_len * 0.5, SERVICE_PERIOD_SEC) + cb_frame_len + _10ms;
const u64 frame_len_bytes = static_cast<u64>(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<f64>(buffering_len, cb_frame_len) + _10ms;
ringbuf.set_buf_size(static_cast<u64>(std::round(frame_len * bytes_per_sec)));
thread_tmp_buf.resize(0);
}
callback_tmp_buf.resize(static_cast<usz>((cb_frame_len + _10ms) * static_cast<u32>(AudioSampleSize::FLOAT) * static_cast<u32>(port_cfg.ch_cnt) * static_cast<u32>(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<u32>(port_cfg.freq);
val.input_ch_cnt = static_cast<u32>(port_cfg.ch_cnt);
val.output_ch_cnt = backend_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<SYS_RSXAUDIO_AVPORT_CNT> mute_state{cb_cfg.mute_state};
if (cb_cfg.ready && !mute_state[static_cast<u8>(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<u32>(AudioSampleSize::S16) * static_cast<u32>(AudioSampleSize::FLOAT) : bytes_ch_adjusted;
ensure(callback_tmp_buf.size() * static_cast<u32>(AudioSampleSize::FLOAT) >= bytes_from_rb);
const u32 byte_cnt = static_cast<u32>(ringbuf.pop(callback_tmp_buf.data(), bytes_from_rb, true));
const u32 sample_cnt = byte_cnt / static_cast<u32>(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<u32>(AudioChannelCnt::SURROUND_7_1))
{
if (cb_cfg.output_ch_cnt == static_cast<u32>(AudioChannelCnt::SURROUND_5_1))
{
AudioBackend::downmix<AudioChannelCnt::SURROUND_7_1, AudioChannelCnt::SURROUND_5_1>(sample_cnt, callback_tmp_buf.data(), callback_tmp_buf.data());
}
else if (cb_cfg.output_ch_cnt == static_cast<u32>(AudioChannelCnt::STEREO))
{
AudioBackend::downmix<AudioChannelCnt::SURROUND_7_1, AudioChannelCnt::STEREO>(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<u32>(AudioChannelCnt::SURROUND_5_1))
{
if (cb_cfg.output_ch_cnt == static_cast<u32>(AudioChannelCnt::STEREO))
{
AudioBackend::downmix<AudioChannelCnt::SURROUND_5_1, AudioChannelCnt::STEREO>(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<u16>(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<u32>(AudioSampleSize::S16);
}
AudioBackend::normalize(sample_cnt_out, callback_tmp_buf.data(), static_cast<f32*>(buf));
return sample_cnt_out * static_cast<u32>(AudioSampleSize::FLOAT);
}
ringbuf.reader_flush();
memset(buf, 0, bytes);
return bytes;
}
void rsxaudio_backend_thread::state_changed_callback(AudioStateEvent event)
{
{
std::lock_guard lock(state_update_m);
switch (event)
{
case AudioStateEvent::UNSPECIFIED_ERROR:
{
backend_error_occured = true;
break;
}
case AudioStateEvent::DEFAULT_DEVICE_MAYBE_CHANGED:
{
backend_device_changed = true;
break;
}
default:
{
fmt::throw_exception("Unknown audio state event");
}
}
}
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<s64>(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<void()> &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]{};
int wait_status = 0;
do
{
wait_status = epoll_wait(epoll_fd, event, obj_wait_cnt, -1);
}
while (wait_status == -1 && errno == EINTR);
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]{};
int wait_status = 0;
do
{
wait_status = kevent(kq, nullptr, 0, event, obj_wait_cnt, nullptr);
}
while (wait_status == -1 && errno == EINTR);
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<u64>(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<u64>(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<u64>(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<u64>(std::floor(static_cast<f64>(crnt_time) / blk_time)) + 1;
}
f64 rsxaudio_periodic_tmr::get_blk_time(u32 data_rate) const
{
return static_cast<f64>(SYS_RSXAUDIO_STREAM_SIZE * 1'000'000) / data_rate;
}