mirror of
https://github.com/RPCSX/rpcsx.git
synced 2025-12-06 07:12:14 +01:00
2337 lines
60 KiB
C++
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;
|
|
}
|