Skip to content

Commit

Permalink
audio: Improve port state guards. (#1998)
Browse files Browse the repository at this point in the history
  • Loading branch information
squidbus authored Jan 2, 2025
1 parent 596f4cd commit 55b5017
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 83 deletions.
151 changes: 78 additions & 73 deletions src/core/libraries/audio/audioout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@

#include <memory>
#include <mutex>
#include <shared_mutex>
#include <stop_token>
#include <thread>
#include <magic_enum/magic_enum.hpp>

#include "common/assert.h"
#include "common/config.h"
#include "common/logging/log.h"
#include "common/polyfill_thread.h"
#include "common/thread.h"
#include "core/libraries/audio/audioout.h"
#include "core/libraries/audio/audioout_backend.h"
Expand All @@ -18,7 +18,7 @@

namespace Libraries::AudioOut {

std::shared_mutex ports_mutex;
std::mutex port_open_mutex{};
std::array<PortOut, SCE_AUDIO_OUT_NUM_PORTS> ports_out{};

static std::unique_ptr<AudioOutBackend> audio;
Expand Down Expand Up @@ -93,17 +93,20 @@ int PS4_SYSV_ABI sceAudioOutClose(s32 handle) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
}

std::scoped_lock lock(ports_mutex);
std::unique_lock open_lock{port_open_mutex};
auto& port = ports_out.at(handle - 1);
if (!port.impl) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
{
std::unique_lock lock{port.mutex};
if (!port.IsOpen()) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
}
std::free(port.output_buffer);
port.output_buffer = nullptr;
port.output_ready = false;
port.impl = nullptr;
}

// Stop outside of port lock scope to prevent deadlocks.
port.output_thread.Stop();
std::free(port.output_buffer);
port.output_buffer = nullptr;
port.output_ready = false;
port.impl = nullptr;
return ORBIS_OK;
}

Expand Down Expand Up @@ -172,35 +175,34 @@ int PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* sta
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
}

std::scoped_lock lock(ports_mutex);
const auto& port = ports_out.at(handle - 1);
if (!port.impl) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
}

state->rerouteCounter = 0;
state->volume = 127;

switch (port.type) {
case OrbisAudioOutPort::Main:
case OrbisAudioOutPort::Bgm:
case OrbisAudioOutPort::Voice:
state->output = 1;
state->channel = port.format_info.num_channels > 2 ? 2 : port.format_info.num_channels;
break;
case OrbisAudioOutPort::Personal:
case OrbisAudioOutPort::Padspk:
state->output = 4;
state->channel = 1;
break;
case OrbisAudioOutPort::Aux:
state->output = 0;
state->channel = 0;
break;
default:
UNREACHABLE();
auto& port = ports_out.at(handle - 1);
{
std::unique_lock lock{port.mutex};
if (!port.IsOpen()) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
}
switch (port.type) {
case OrbisAudioOutPort::Main:
case OrbisAudioOutPort::Bgm:
case OrbisAudioOutPort::Voice:
state->output = 1;
state->channel = port.format_info.num_channels > 2 ? 2 : port.format_info.num_channels;
break;
case OrbisAudioOutPort::Personal:
case OrbisAudioOutPort::Padspk:
state->output = 4;
state->channel = 1;
break;
case OrbisAudioOutPort::Aux:
state->output = 0;
state->channel = 0;
break;
default:
UNREACHABLE();
}
state->rerouteCounter = 0;
state->volume = 127;
}

return ORBIS_OK;
}

Expand Down Expand Up @@ -279,15 +281,16 @@ static void AudioOutputThread(PortOut* port, const std::stop_token& stop) {
while (true) {
timer.Start();
{
std::unique_lock lock{port->output_mutex};
Common::CondvarWait(port->output_cv, lock, stop, [&] { return port->output_ready; });
if (stop.stop_requested()) {
break;
std::unique_lock lock{port->mutex};
if (port->output_cv.wait(lock, stop, [&] { return port->output_ready; })) {
port->impl->Output(port->output_buffer);
port->output_ready = false;
}
port->impl->Output(port->output_buffer);
port->output_ready = false;
}
port->output_cv.notify_one();
if (stop.stop_requested()) {
break;
}
timer.End();
}
}
Expand Down Expand Up @@ -332,27 +335,30 @@ s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id,
return ORBIS_AUDIO_OUT_ERROR_INVALID_FORMAT;
}

std::scoped_lock lock{ports_mutex};
std::unique_lock open_lock{port_open_mutex};
const auto port =
std::ranges::find_if(ports_out, [&](const PortOut& p) { return p.impl == nullptr; });
std::ranges::find_if(ports_out, [&](const PortOut& p) { return !p.IsOpen(); });
if (port == ports_out.end()) {
LOG_ERROR(Lib_AudioOut, "Audio ports are full");
return ORBIS_AUDIO_OUT_ERROR_PORT_FULL;
}

port->type = port_type;
port->format_info = GetFormatInfo(format);
port->sample_rate = sample_rate;
port->buffer_frames = length;
port->volume.fill(SCE_AUDIO_OUT_VOLUME_0DB);
{
std::unique_lock port_lock(port->mutex);

port->impl = audio->Open(*port);
port->type = port_type;
port->format_info = GetFormatInfo(format);
port->sample_rate = sample_rate;
port->buffer_frames = length;
port->volume.fill(SCE_AUDIO_OUT_VOLUME_0DB);

port->output_buffer = std::malloc(port->BufferSize());
port->output_ready = false;
port->output_thread.Run(
[port](const std::stop_token& stop) { AudioOutputThread(&*port, stop); });
port->impl = audio->Open(*port);

port->output_buffer = std::malloc(port->BufferSize());
port->output_ready = false;
port->output_thread.Run(
[port](const std::stop_token& stop) { AudioOutputThread(&*port, stop); });
}
return std::distance(ports_out.begin(), port) + 1;
}

Expand All @@ -367,14 +373,13 @@ s32 PS4_SYSV_ABI sceAudioOutOutput(s32 handle, void* ptr) {
}

auto& port = ports_out.at(handle - 1);
if (!port.impl) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
}

{
std::unique_lock lock{port.output_mutex};
std::unique_lock lock{port.mutex};
if (!port.IsOpen()) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
}
port.output_cv.wait(lock, [&] { return !port.output_ready; });
if (ptr != nullptr) {
if (ptr != nullptr && port.IsOpen()) {
std::memcpy(port.output_buffer, ptr, port.BufferSize());
port.output_ready = true;
}
Expand Down Expand Up @@ -488,19 +493,19 @@ s32 PS4_SYSV_ABI sceAudioOutSetVolume(s32 handle, s32 flag, s32* vol) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
}

std::scoped_lock lock(ports_mutex);
auto& port = ports_out.at(handle - 1);
if (!port.impl) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
}

for (int i = 0; i < port.format_info.num_channels; i++, flag >>= 1u) {
if (flag & 0x1u) {
port.volume[i] = vol[i];
{
std::unique_lock lock{port.mutex};
if (!port.IsOpen()) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
}
for (int i = 0; i < port.format_info.num_channels; i++, flag >>= 1u) {
if (flag & 0x1u) {
port.volume[i] = vol[i];
}
}
port.impl->SetVolume(port.volume);
}

port.impl->SetVolume(port.volume);
return ORBIS_OK;
}

Expand Down
8 changes: 7 additions & 1 deletion src/core/libraries/audio/audioout.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

#pragma once

#include <condition_variable>
#include <memory>
#include <mutex>

#include "common/bit_field.h"
#include "core/libraries/kernel/threads.h"
Expand Down Expand Up @@ -74,10 +76,10 @@ struct AudioFormatInfo {
};

struct PortOut {
std::mutex mutex;
std::unique_ptr<PortBackend> impl{};

void* output_buffer;
std::mutex output_mutex;
std::condition_variable_any output_cv;
bool output_ready;
Kernel::Thread output_thread{};
Expand All @@ -88,6 +90,10 @@ struct PortOut {
u32 buffer_frames;
std::array<s32, 8> volume;

[[nodiscard]] bool IsOpen() const {
return impl != nullptr;
}

[[nodiscard]] u32 BufferSize() const {
return buffer_frames * format_info.FrameSize();
}
Expand Down
27 changes: 18 additions & 9 deletions src/core/libraries/audio/sdl_audio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace Libraries::AudioOut {
class SDLPortBackend : public PortBackend {
public:
explicit SDLPortBackend(const PortOut& port)
: frame_size(port.format_info.FrameSize()), buffer_size(port.BufferSize()) {
: frame_size(port.format_info.FrameSize()), guest_buffer_size(port.BufferSize()) {
// We want the latency for delivering frames out to be as small as possible,
// so set the sample frames hint to the number of frames per buffer.
const auto samples_num_str = std::to_string(port.buffer_frames);
Expand All @@ -33,7 +33,7 @@ class SDLPortBackend : public PortBackend {
LOG_ERROR(Lib_AudioOut, "Failed to create SDL audio stream: {}", SDL_GetError());
return;
}
queue_threshold = CalculateQueueThreshold();
CalculateQueueThreshold();
if (!SDL_SetAudioStreamInputChannelMap(stream, port.format_info.channel_layout.data(),
port.format_info.num_channels)) {
LOG_ERROR(Lib_AudioOut, "Failed to configure SDL audio stream channel map: {}",
Expand Down Expand Up @@ -71,9 +71,9 @@ class SDLPortBackend : public PortBackend {
queue_threshold);
SDL_ClearAudioStream(stream);
// Recalculate the threshold in case this happened because of a device change.
queue_threshold = CalculateQueueThreshold();
CalculateQueueThreshold();
}
if (!SDL_PutAudioStreamData(stream, ptr, static_cast<int>(buffer_size))) {
if (!SDL_PutAudioStreamData(stream, ptr, static_cast<int>(guest_buffer_size))) {
LOG_ERROR(Lib_AudioOut, "Failed to output to SDL audio stream: {}", SDL_GetError());
}
}
Expand All @@ -91,7 +91,7 @@ class SDLPortBackend : public PortBackend {
}

private:
[[nodiscard]] u32 CalculateQueueThreshold() const {
void CalculateQueueThreshold() {
SDL_AudioSpec discard;
int sdl_buffer_frames;
if (!SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(stream), &discard,
Expand All @@ -100,13 +100,22 @@ class SDLPortBackend : public PortBackend {
SDL_GetError());
sdl_buffer_frames = 0;
}
return std::max<u32>(buffer_size, sdl_buffer_frames * frame_size) * 4;
const auto sdl_buffer_size = sdl_buffer_frames * frame_size;
const auto new_threshold = std::max(guest_buffer_size, sdl_buffer_size) * 4;
if (host_buffer_size != sdl_buffer_size || queue_threshold != new_threshold) {
host_buffer_size = sdl_buffer_size;
queue_threshold = new_threshold;
LOG_INFO(Lib_AudioOut,
"SDL audio buffers: guest = {} bytes, host = {} bytes, threshold = {} bytes",
guest_buffer_size, host_buffer_size, queue_threshold);
}
}

u32 frame_size;
u32 buffer_size;
u32 queue_threshold;
SDL_AudioStream* stream;
u32 guest_buffer_size;
u32 host_buffer_size{};
u32 queue_threshold{};
SDL_AudioStream* stream{};
};

std::unique_ptr<PortBackend> SDLAudioOut::Open(PortOut& port) {
Expand Down

0 comments on commit 55b5017

Please sign in to comment.