mirror of
https://github.com/RPCSX/rpcsx.git
synced 2026-01-10 02:30:12 +01:00
Replaces `std::shared_pointer` with `stx::atomic_ptr` and `stx::shared_ptr`. Notes to programmers: * This pr kills the use of `dynamic_cast`, `std::dynamic_pointer_cast` and `std::weak_ptr` on IDM objects, possible replacement is to save the object ID on the base object, then use idm::check/get_unlocked to the destination type via the saved ID which may be null. Null pointer check is how you can tell type mismatch (as dynamic cast) or object destruction (as weak_ptr locking). * Double-inheritance on IDM objects should be used with care, `stx::shared_ptr` does not support constant-evaluated pointer offsetting to parent/child type. * `idm::check/get_unlocked` can now be used anywhere. Misc fixes: * Fixes some segfaults with RPCN with interaction with IDM. * Fix deadlocks in access violation handler due locking recursion. * Fixes race condition in process exit-spawn on memory containers read. * Fix bug that theoretically can prevent RPCS3 from booting - fix `id_manager::typeinfo` comparison to compare members instead of `memcmp` which can fail spuriously on padding bytes. * Ensure all IDM inherited types of base, either has `id_base` or `id_type` defined locally, this allows to make getters such as `idm::get_unlocked<lv2_socket, lv2_socket_raw>()` which were broken before. (requires save-states invalidation) * Removes broken operator[] overload of `stx::shared_ptr` and `stx::single_ptr` for non-array types.
2363 lines
61 KiB
C++
2363 lines
61 KiB
C++
#include "stdafx.h"
|
|
#include "Emu/Memory/vm.h"
|
|
#include "Emu/IdManager.h"
|
|
#include "Emu/System.h"
|
|
#include "Emu/system_config.h"
|
|
#include "Emu//Cell/Modules/cellAudioOut.h"
|
|
#include "util/video_provider.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);
|
|
|
|
extern atomic_t<recording_mode> g_recording_mode;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
lv2_rsxaudio::lv2_rsxaudio(utils::serial& ar) noexcept
|
|
: lv2_obj{1}
|
|
, init(ar)
|
|
{
|
|
if (init)
|
|
{
|
|
ar(shmem);
|
|
|
|
for (auto& port : event_queue)
|
|
{
|
|
port = lv2_event_queue::load_ptr(ar, port, "rsxaudio");
|
|
}
|
|
}
|
|
}
|
|
|
|
void lv2_rsxaudio::save(utils::serial& ar)
|
|
{
|
|
USING_SERIALIZATION_VERSION(LLE);
|
|
|
|
ar(init);
|
|
|
|
if (init)
|
|
{
|
|
ar(shmem);
|
|
|
|
for (const auto& port : event_queue)
|
|
{
|
|
lv2_event_queue::save_ptr(ar, port.get());
|
|
}
|
|
}
|
|
}
|
|
|
|
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_unlocked<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_unlocked<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 = null_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_unlocked<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_unlocked<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_unlocked<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_unlocked<lv2_obj, lv2_event_queue>(sh_page->ctrl.event_queue_1_id))
|
|
{
|
|
rsxaudio_obj->event_queue[0] = queue1;
|
|
|
|
if (auto queue2 = idm::get_unlocked<lv2_obj, lv2_event_queue>(sh_page->ctrl.event_queue_2_id))
|
|
{
|
|
rsxaudio_obj->event_queue[1] = queue2;
|
|
|
|
if (auto queue3 = idm::get_unlocked<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_unlocked<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 = null_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_unlocked<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_unlocked<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 (const auto& queue = rsxaudio_obj->event_queue[q_idx]; 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_unlocked<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_unlocked<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 (const auto& queue = rsxaudio_obj->event_queue[dst_raw])
|
|
{
|
|
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)];
|
|
}
|
|
|
|
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};
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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()
|
|
{
|
|
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;
|
|
});
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
u32 rsxaudio_backend_thread::get_sample_rate() const
|
|
{
|
|
return callback_cfg.load().freq;
|
|
}
|
|
|
|
u8 rsxaudio_backend_thread::get_channel_count() const
|
|
{
|
|
return callback_cfg.load().input_ch_cnt;
|
|
}
|
|
|
|
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,
|
|
.channel_layout = g_cfg.audio.channel_layout,
|
|
.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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// Dump audio if enabled
|
|
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;
|
|
audio_channel_layout backend_channel_layout = audio_channel_layout::stereo;
|
|
|
|
if (backend->Open(emu_cfg.audio_device, port_cfg.freq, sample_size, ch_cnt, emu_cfg.channel_layout))
|
|
{
|
|
cb_frame_len = backend->GetCallbackFrameLen();
|
|
backend_channel_layout = backend->get_channel_layout();
|
|
sys_rsxaudio.notice("Opened audio backend (sampling_rate=%d, sample_size=%d, channels=%d, layout=%s)", backend->get_sampling_rate(), backend->get_sample_size(), backend->get_channels(), backend->get_channel_layout());
|
|
}
|
|
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_channel_layout = static_cast<u8>(backend_channel_layout);
|
|
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 audio_channel_layout output_channel_layout = static_cast<audio_channel_layout>(cb_cfg.output_channel_layout);
|
|
const u32 output_ch_cnt = AudioBackend::default_layout_channel_count(output_channel_layout);
|
|
const u32 bytes_ch_adjusted = bytes / 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 * 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;
|
|
}
|
|
|
|
// Record audio if enabled
|
|
if (g_recording_mode != recording_mode::stopped)
|
|
{
|
|
utils::video_provider& provider = g_fxo->get<utils::video_provider>();
|
|
provider.present_samples(reinterpret_cast<u8*>(callback_tmp_buf.data()), sample_cnt / cb_cfg.input_ch_cnt, cb_cfg.input_ch_cnt);
|
|
}
|
|
|
|
// Downmix if necessary
|
|
AudioBackend::downmix(sample_cnt, cb_cfg.input_ch_cnt, output_channel_layout, callback_tmp_buf.data(), callback_tmp_buf.data());
|
|
|
|
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;
|
|
}
|
|
|
|
if (timeout)
|
|
{
|
|
return wait_result::TIMEOUT;
|
|
}
|
|
|
|
if (wait_canceled)
|
|
{
|
|
sched_timer();
|
|
return wait_result::TIMER_CANCELED;
|
|
}
|
|
|
|
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
|
|
{
|
|
ensure(vtimer_id < VTIMER_MAX);
|
|
|
|
const vtimer& vtimer = vtmr_pool[vtimer_id];
|
|
|
|
return is_vtimer_behind(vtimer, crnt_time);
|
|
}
|
|
|
|
void rsxaudio_periodic_tmr::vtimer_skip_periods(u32 vtimer_id, u64 crnt_time)
|
|
{
|
|
ensure(vtimer_id < VTIMER_MAX);
|
|
|
|
vtimer& vtimer = vtmr_pool[vtimer_id];
|
|
|
|
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)
|
|
{
|
|
ensure(vtimer_id < VTIMER_MAX);
|
|
|
|
vtimer& vtimer = vtmr_pool[vtimer_id];
|
|
|
|
if (is_vtimer_behind(vtimer, crnt_time))
|
|
{
|
|
vtimer.blk_cnt++;
|
|
}
|
|
}
|
|
|
|
bool rsxaudio_periodic_tmr::is_vtimer_active(u32 vtimer_id) const
|
|
{
|
|
ensure(vtimer_id < VTIMER_MAX);
|
|
|
|
const vtimer& vtimer = vtmr_pool[vtimer_id];
|
|
|
|
return vtimer.active;
|
|
}
|
|
|
|
u64 rsxaudio_periodic_tmr::vtimer_get_sched_time(u32 vtimer_id) const
|
|
{
|
|
ensure(vtimer_id < VTIMER_MAX);
|
|
|
|
const vtimer& vtimer = vtmr_pool[vtimer_id];
|
|
|
|
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;
|
|
}
|