Skip to content

Commit

Permalink
Cubeb: rewrite locking
Browse files Browse the repository at this point in the history
  • Loading branch information
Vestrel committed Oct 13, 2022
1 parent cf86b6c commit a40379d
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 85 deletions.
6 changes: 3 additions & 3 deletions rpcs3/Emu/Audio/AudioBackend.h
Expand Up @@ -40,7 +40,7 @@ enum class AudioChannelCnt : u32
enum class AudioStateEvent : u32
{
UNSPECIFIED_ERROR,
DEFAULT_DEVICE_CHANGED,
DEFAULT_DEVICE_MAYBE_CHANGED,
};

class AudioBackend
Expand Down Expand Up @@ -226,10 +226,10 @@ class AudioBackend
AudioFreq m_sampling_rate = AudioFreq::FREQ_48K;
AudioChannelCnt m_channels = AudioChannelCnt::STEREO;

shared_mutex m_cb_mutex{};
std::timed_mutex m_cb_mutex{};
std::function<u32(u32, void *)> m_write_callback{};

std::recursive_mutex m_state_cb_mutex{};
shared_mutex m_state_cb_mutex{};
std::function<void(AudioStateEvent)> m_state_callback{};

bool m_playing = false;
Expand Down
116 changes: 52 additions & 64 deletions rpcs3/Emu/Audio/Cubeb/CubebBackend.cpp
Expand Up @@ -22,16 +22,14 @@ CubebBackend::CubebBackend()
}
#endif

std::lock_guard lock(m_dev_sw_mutex);

if (int err = cubeb_init(&m_ctx, "RPCS3", nullptr))
cubeb *ctx{};
if (int err = cubeb_init(&ctx, "RPCS3", nullptr))
{
Cubeb.error("cubeb_init() failed: %i", err);
m_ctx = nullptr;
return;
}

if (int err = cubeb_register_device_collection_changed(m_ctx, CUBEB_DEVICE_TYPE_OUTPUT, device_collection_changed_cb, this))
if (int err = cubeb_register_device_collection_changed(ctx, CUBEB_DEVICE_TYPE_OUTPUT, device_collection_changed_cb, this))
{
Cubeb.error("cubeb_register_device_collection_changed() failed: %i", err);
}
Expand All @@ -40,7 +38,10 @@ CubebBackend::CubebBackend()
m_dev_collection_cb_enabled = true;
}

Cubeb.notice("Using backend %s", cubeb_get_backend_id(m_ctx));
Cubeb.notice("Using backend %s", cubeb_get_backend_id(ctx));

std::lock_guard cb_lock{m_state_cb_mutex};
m_ctx = ctx;
}

CubebBackend::~CubebBackend()
Expand Down Expand Up @@ -80,8 +81,18 @@ bool CubebBackend::Operational()

bool CubebBackend::DefaultDeviceChanged()
{
std::lock_guard lock{m_dev_sw_mutex};
return !m_reset_req.observe() && m_default_dev_changed;
if (m_default_device.empty() || m_reset_req.observe()) {
return false;
}

device_handle device = GetDevice();
if (!device.handle)
{
Cubeb.error("Selected device not found. Trying alternative approach...");
device = GetDefaultDeviceAlt(m_sampling_rate, m_sample_size, m_channels);
}

return !device.handle || device.id != m_default_device;
}

bool CubebBackend::Open(std::string_view dev_id, AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt)
Expand All @@ -92,10 +103,8 @@ bool CubebBackend::Open(std::string_view dev_id, AudioFreq freq, AudioSampleSize
return false;
}

std::lock_guard lock(m_cb_mutex);
std::lock_guard dev_sw_lock{m_dev_sw_mutex};
std::lock_guard state_cb_lock{m_state_cb_mutex};
CloseUnlocked();
Close();
std::lock_guard lock{m_cb_mutex};

const bool use_default_device = dev_id.empty() || dev_id == audio_device_enumerator::DEFAULT_DEV_ID;

Expand Down Expand Up @@ -128,6 +137,12 @@ bool CubebBackend::Open(std::string_view dev_id, AudioFreq freq, AudioSampleSize
device.ch_cnt = 2;
}

if (use_default_device)
{
std::lock_guard lock{m_state_cb_mutex};
m_default_device = device.id;
}

m_sampling_rate = freq;
m_sample_size = sample_size;
m_channels = static_cast<AudioChannelCnt>(std::min(static_cast<u32>(convert_channel_count(device.ch_cnt)), static_cast<u32>(ch_cnt)));
Expand Down Expand Up @@ -169,7 +184,7 @@ bool CubebBackend::Open(std::string_view dev_id, AudioFreq freq, AudioSampleSize
if (int err = cubeb_stream_start(m_stream))
{
Cubeb.error("cubeb_stream_start() failed: %i", err);
CloseUnlocked();
Close();
return false;
}

Expand All @@ -178,15 +193,10 @@ bool CubebBackend::Open(std::string_view dev_id, AudioFreq freq, AudioSampleSize
Cubeb.error("cubeb_stream_set_volume() failed: %i", err);
}

if (use_default_device)
{
m_default_device = device.id;
}

return true;
}

void CubebBackend::CloseUnlocked()
void CubebBackend::Close()
{
if (m_stream != nullptr)
{
Expand All @@ -196,24 +206,19 @@ void CubebBackend::CloseUnlocked()
}

cubeb_stream_destroy(m_stream);
m_stream = nullptr;
}

m_playing = false;
m_last_sample.fill(0);
{
std::lock_guard lock{m_cb_mutex};
m_stream = nullptr;
m_playing = false;
m_last_sample.fill(0);
}

m_default_dev_changed = false;
std::lock_guard lock{m_state_cb_mutex};
m_default_device.clear();
}

void CubebBackend::Close()
{
std::lock_guard lock(m_cb_mutex);
std::lock_guard dev_sw_lock{m_dev_sw_mutex};
std::lock_guard state_cb_lock(m_state_cb_mutex);
CloseUnlocked();
}

void CubebBackend::Play()
{
if (m_stream == nullptr)
Expand Down Expand Up @@ -427,14 +432,14 @@ long CubebBackend::data_cb(cubeb_stream* stream, void* user_ptr, void const* /*

std::unique_lock lock(cubeb->m_cb_mutex, std::defer_lock);

if (stream != cubeb->m_stream)
if (!cubeb->m_reset_req.observe() && lock.try_lock_for(std::chrono::microseconds{50}) && cubeb->m_write_callback && cubeb->m_playing)
{
Cubeb.error("data_cb called with unknown stream");
return CUBEB_ERROR;
}
if (stream != cubeb->m_stream)
{
// Cubeb.error("data_cb called with unknown stream");
return CUBEB_ERROR;
}

if (!cubeb->m_reset_req.observe() && lock.try_lock() && cubeb->m_write_callback && cubeb->m_playing)
{
const u32 sample_size = cubeb->full_sample_size.observe();
const u32 bytes_req = nframes * sample_size;
u32 written = std::min(cubeb->m_write_callback(bytes_req, output_buffer), bytes_req);
Expand All @@ -454,7 +459,7 @@ long CubebBackend::data_cb(cubeb_stream* stream, void* user_ptr, void const* /*
{
// Stream parameters are modified only after stream_destroy. stream_destroy will return
// only after this callback returns, so it's safe to access full_sample_size here.
memset(output_buffer, 0, nframes * cubeb->full_sample_size.observe());
memset(output_buffer, 0, nframes * cubeb->full_sample_size);
}

return nframes;
Expand Down Expand Up @@ -528,7 +533,12 @@ void CubebBackend::device_collection_changed_cb(cubeb* context, void* user_ptr)
CubebBackend* const cubeb = static_cast<CubebBackend*>(user_ptr);
ensure(cubeb);

std::lock_guard lock{cubeb->m_dev_sw_mutex};
if (cubeb->m_reset_req.observe())
{
return;
}

std::lock_guard cb_lock{cubeb->m_state_cb_mutex};

if (context != cubeb->m_ctx)
{
Expand All @@ -539,34 +549,12 @@ void CubebBackend::device_collection_changed_cb(cubeb* context, void* user_ptr)
// Non default device is used (or default device cannot be detected)
if (cubeb->m_default_device.empty())
{
Cubeb.notice("Skipping default device enumeration.");
Cubeb.notice("Skipping default device notification");
return;
}

device_handle device = cubeb->GetDevice();
if (!device.handle)
{
Cubeb.notice("Selected device not found. Trying alternative approach...");
device = cubeb->GetDefaultDeviceAlt(cubeb->m_sampling_rate, cubeb->m_sample_size, cubeb->m_channels);
}

std::lock_guard cb_lock{cubeb->m_state_cb_mutex};

if (!device.handle)
if (cubeb->m_state_callback)
{
// No devices available
if (!cubeb->m_reset_req.test_and_set() && cubeb->m_state_callback)
{
cubeb->m_state_callback(AudioStateEvent::UNSPECIFIED_ERROR);
}
}
else if (!cubeb->m_reset_req.observe() && device.id != cubeb->m_default_device)
{
cubeb->m_default_dev_changed = true;

if (cubeb->m_state_callback)
{
cubeb->m_state_callback(AudioStateEvent::DEFAULT_DEVICE_CHANGED);
}
cubeb->m_state_callback(AudioStateEvent::DEFAULT_DEVICE_MAYBE_CHANGED);
}
}
5 changes: 1 addition & 4 deletions rpcs3/Emu/Audio/Cubeb/CubebBackend.h
Expand Up @@ -44,9 +44,8 @@ class CubebBackend final : public AudioBackend

atomic_t<bool> m_reset_req = false;

shared_mutex m_dev_sw_mutex{};
// Protected by callback mutex
std::string m_default_device{};
bool m_default_dev_changed = false;

bool m_dev_collection_cb_enabled = false;

Expand All @@ -55,8 +54,6 @@ class CubebBackend final : public AudioBackend
static void state_cb(cubeb_stream* stream, void* user_ptr, cubeb_state state);
static void device_collection_changed_cb(cubeb* context, void* user_ptr);

void CloseUnlocked();

struct device_handle
{
cubeb_devid handle{};
Expand Down
2 changes: 1 addition & 1 deletion rpcs3/Emu/Audio/XAudio2/XAudio2Backend.cpp
Expand Up @@ -404,7 +404,7 @@ HRESULT XAudio2Backend::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWS

if (m_state_callback)
{
m_state_callback(AudioStateEvent::DEFAULT_DEVICE_CHANGED);
m_state_callback(AudioStateEvent::DEFAULT_DEVICE_MAYBE_CHANGED);
}
}
}
Expand Down
10 changes: 9 additions & 1 deletion rpcs3/Emu/Cell/Modules/cellAudio.cpp
Expand Up @@ -158,6 +158,7 @@ audio_ringbuffer::audio_ringbuffer(cell_audio_config& _cfg)

cb_ringbuf.set_buf_size(static_cast<u32>(static_cast<u32>(cfg.backend_ch_cnt) * cfg.audio_sampling_rate * cfg.audio_sample_size * buffer_dur_mult));
backend->SetWriteCallback(std::bind(&audio_ringbuffer::backend_write_callback, this, std::placeholders::_1, std::placeholders::_2));
backend->SetStateCallback(std::bind(&audio_ringbuffer::backend_state_callback, this, std::placeholders::_1));
}

audio_ringbuffer::~audio_ringbuffer()
Expand Down Expand Up @@ -191,6 +192,13 @@ u32 audio_ringbuffer::backend_write_callback(u32 size, void *buf)
return static_cast<u32>(cb_ringbuf.pop(buf, size, true));
}

void audio_ringbuffer::backend_state_callback(AudioStateEvent event)
{
if (event == AudioStateEvent::DEFAULT_DEVICE_MAYBE_CHANGED) {
backend_device_changed = true;
}
}

u64 audio_ringbuffer::get_timestamp()
{
return get_system_time();
Expand Down Expand Up @@ -806,7 +814,7 @@ void cell_audio_thread::operator()()
cellAudio.success("Backend recovered");
m_backend_failed = false;
}

if (!cfg.buffering_enabled)
{
const u64 period_end = (m_counter * cfg.audio_block_period) + m_start_time;
Expand Down
6 changes: 4 additions & 2 deletions rpcs3/Emu/Cell/Modules/cellAudio.h
Expand Up @@ -292,6 +292,7 @@ class audio_ringbuffer
audio_resampler resampler{};

atomic_t<bool> backend_active = false;
atomic_t<bool> backend_device_changed = false;
bool playing = false;

u64 update_timestamp = 0;
Expand All @@ -310,6 +311,7 @@ class audio_ringbuffer

void commit_data(f32* buf, u32 sample_cnt);
u32 backend_write_callback(u32 size, void *buf);
void backend_state_callback(AudioStateEvent event);

public:
audio_ringbuffer(cell_audio_config &cfg);
Expand Down Expand Up @@ -345,9 +347,9 @@ class audio_ringbuffer
return backend->Operational();
}

bool device_changed() const
bool device_changed()
{
return backend->DefaultDeviceChanged();
return backend_device_changed.test_and_reset() && backend->DefaultDeviceChanged();
}

std::string_view get_backend_name() const
Expand Down

0 comments on commit a40379d

Please sign in to comment.