Skip to content

Commit 2e5e90a

Browse files
committed
audio: Improve port state guards.
1 parent 099e685 commit 2e5e90a

File tree

3 files changed

+100
-82
lines changed

3 files changed

+100
-82
lines changed

src/core/libraries/audio/audioout.cpp

Lines changed: 75 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
// SPDX-License-Identifier: GPL-2.0-or-later
33

44
#include <memory>
5-
#include <mutex>
6-
#include <shared_mutex>
75
#include <magic_enum/magic_enum.hpp>
86

97
#include "common/assert.h"
@@ -18,7 +16,7 @@
1816

1917
namespace Libraries::AudioOut {
2018

21-
std::shared_mutex ports_mutex;
19+
std::mutex port_open_mutex{};
2220
std::array<PortOut, SCE_AUDIO_OUT_NUM_PORTS> ports_out{};
2321

2422
static std::unique_ptr<AudioOutBackend> audio;
@@ -93,17 +91,20 @@ int PS4_SYSV_ABI sceAudioOutClose(s32 handle) {
9391
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
9492
}
9593

96-
std::scoped_lock lock(ports_mutex);
94+
std::unique_lock open_lock{port_open_mutex};
9795
auto& port = ports_out.at(handle - 1);
98-
if (!port.impl) {
99-
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
96+
{
97+
std::unique_lock lock{port.mutex};
98+
if (!port.IsOpen()) {
99+
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
100+
}
101+
std::free(port.output_buffer);
102+
port.output_buffer = nullptr;
103+
port.output_ready = false;
104+
port.impl = nullptr;
100105
}
101-
106+
// Stop outside of port lock scope to prevent deadlocks.
102107
port.output_thread.Stop();
103-
std::free(port.output_buffer);
104-
port.output_buffer = nullptr;
105-
port.output_ready = false;
106-
port.impl = nullptr;
107108
return ORBIS_OK;
108109
}
109110

@@ -172,35 +173,34 @@ int PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* sta
172173
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
173174
}
174175

175-
std::scoped_lock lock(ports_mutex);
176-
const auto& port = ports_out.at(handle - 1);
177-
if (!port.impl) {
178-
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
179-
}
180-
181-
state->rerouteCounter = 0;
182-
state->volume = 127;
183-
184-
switch (port.type) {
185-
case OrbisAudioOutPort::Main:
186-
case OrbisAudioOutPort::Bgm:
187-
case OrbisAudioOutPort::Voice:
188-
state->output = 1;
189-
state->channel = port.format_info.num_channels > 2 ? 2 : port.format_info.num_channels;
190-
break;
191-
case OrbisAudioOutPort::Personal:
192-
case OrbisAudioOutPort::Padspk:
193-
state->output = 4;
194-
state->channel = 1;
195-
break;
196-
case OrbisAudioOutPort::Aux:
197-
state->output = 0;
198-
state->channel = 0;
199-
break;
200-
default:
201-
UNREACHABLE();
176+
auto& port = ports_out.at(handle - 1);
177+
{
178+
std::unique_lock lock{port.mutex};
179+
if (!port.IsOpen()) {
180+
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
181+
}
182+
switch (port.type) {
183+
case OrbisAudioOutPort::Main:
184+
case OrbisAudioOutPort::Bgm:
185+
case OrbisAudioOutPort::Voice:
186+
state->output = 1;
187+
state->channel = port.format_info.num_channels > 2 ? 2 : port.format_info.num_channels;
188+
break;
189+
case OrbisAudioOutPort::Personal:
190+
case OrbisAudioOutPort::Padspk:
191+
state->output = 4;
192+
state->channel = 1;
193+
break;
194+
case OrbisAudioOutPort::Aux:
195+
state->output = 0;
196+
state->channel = 0;
197+
break;
198+
default:
199+
UNREACHABLE();
200+
}
201+
state->rerouteCounter = 0;
202+
state->volume = 127;
202203
}
203-
204204
return ORBIS_OK;
205205
}
206206

@@ -279,15 +279,16 @@ static void AudioOutputThread(PortOut* port, const std::stop_token& stop) {
279279
while (true) {
280280
timer.Start();
281281
{
282-
std::unique_lock lock{port->output_mutex};
283-
Common::CondvarWait(port->output_cv, lock, stop, [&] { return port->output_ready; });
284-
if (stop.stop_requested()) {
285-
break;
282+
std::unique_lock lock{port->mutex};
283+
if (port->output_cv.wait(lock, stop, [&] { return port->output_ready; })) {
284+
port->impl->Output(port->output_buffer);
285+
port->output_ready = false;
286286
}
287-
port->impl->Output(port->output_buffer);
288-
port->output_ready = false;
289287
}
290288
port->output_cv.notify_one();
289+
if (stop.stop_requested()) {
290+
break;
291+
}
291292
timer.End();
292293
}
293294
}
@@ -332,27 +333,30 @@ s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id,
332333
return ORBIS_AUDIO_OUT_ERROR_INVALID_FORMAT;
333334
}
334335

335-
std::scoped_lock lock{ports_mutex};
336+
std::unique_lock open_lock{port_open_mutex};
336337
const auto port =
337338
std::ranges::find_if(ports_out, [&](const PortOut& p) { return p.impl == nullptr; });
338339
if (port == ports_out.end()) {
339340
LOG_ERROR(Lib_AudioOut, "Audio ports are full");
340341
return ORBIS_AUDIO_OUT_ERROR_PORT_FULL;
341342
}
342343

343-
port->type = port_type;
344-
port->format_info = GetFormatInfo(format);
345-
port->sample_rate = sample_rate;
346-
port->buffer_frames = length;
347-
port->volume.fill(SCE_AUDIO_OUT_VOLUME_0DB);
344+
{
345+
std::unique_lock port_lock(port->mutex);
348346

349-
port->impl = audio->Open(*port);
347+
port->type = port_type;
348+
port->format_info = GetFormatInfo(format);
349+
port->sample_rate = sample_rate;
350+
port->buffer_frames = length;
351+
port->volume.fill(SCE_AUDIO_OUT_VOLUME_0DB);
350352

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

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

@@ -367,14 +371,13 @@ s32 PS4_SYSV_ABI sceAudioOutOutput(s32 handle, void* ptr) {
367371
}
368372

369373
auto& port = ports_out.at(handle - 1);
370-
if (!port.impl) {
371-
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
372-
}
373-
374374
{
375-
std::unique_lock lock{port.output_mutex};
375+
std::unique_lock lock{port.mutex};
376+
if (!port.IsOpen()) {
377+
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
378+
}
376379
port.output_cv.wait(lock, [&] { return !port.output_ready; });
377-
if (ptr != nullptr) {
380+
if (ptr != nullptr && port.IsOpen()) {
378381
std::memcpy(port.output_buffer, ptr, port.BufferSize());
379382
port.output_ready = true;
380383
}
@@ -488,19 +491,19 @@ s32 PS4_SYSV_ABI sceAudioOutSetVolume(s32 handle, s32 flag, s32* vol) {
488491
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
489492
}
490493

491-
std::scoped_lock lock(ports_mutex);
492494
auto& port = ports_out.at(handle - 1);
493-
if (!port.impl) {
494-
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
495-
}
496-
497-
for (int i = 0; i < port.format_info.num_channels; i++, flag >>= 1u) {
498-
if (flag & 0x1u) {
499-
port.volume[i] = vol[i];
495+
{
496+
std::unique_lock lock{port.mutex};
497+
if (!port.IsOpen()) {
498+
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
500499
}
500+
for (int i = 0; i < port.format_info.num_channels; i++, flag >>= 1u) {
501+
if (flag & 0x1u) {
502+
port.volume[i] = vol[i];
503+
}
504+
}
505+
port.impl->SetVolume(port.volume);
501506
}
502-
503-
port.impl->SetVolume(port.volume);
504507
return ORBIS_OK;
505508
}
506509

src/core/libraries/audio/audioout.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33

44
#pragma once
55

6+
#include <condition_variable>
67
#include <memory>
8+
#include <mutex>
79

810
#include "common/bit_field.h"
911
#include "core/libraries/kernel/threads.h"
@@ -74,10 +76,10 @@ struct AudioFormatInfo {
7476
};
7577

7678
struct PortOut {
79+
std::mutex mutex;
7780
std::unique_ptr<PortBackend> impl{};
7881

7982
void* output_buffer;
80-
std::mutex output_mutex;
8183
std::condition_variable_any output_cv;
8284
bool output_ready;
8385
Kernel::Thread output_thread{};
@@ -88,6 +90,10 @@ struct PortOut {
8890
u32 buffer_frames;
8991
std::array<s32, 8> volume;
9092

93+
[[nodiscard]] bool IsOpen() const {
94+
return impl != nullptr;
95+
}
96+
9197
[[nodiscard]] u32 BufferSize() const {
9298
return buffer_frames * format_info.FrameSize();
9399
}

src/core/libraries/audio/sdl_audio.cpp

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace Libraries::AudioOut {
1414
class SDLPortBackend : public PortBackend {
1515
public:
1616
explicit SDLPortBackend(const PortOut& port)
17-
: frame_size(port.format_info.FrameSize()), buffer_size(port.BufferSize()) {
17+
: frame_size(port.format_info.FrameSize()), guest_buffer_size(port.BufferSize()) {
1818
// We want the latency for delivering frames out to be as small as possible,
1919
// so set the sample frames hint to the number of frames per buffer.
2020
const auto samples_num_str = std::to_string(port.buffer_frames);
@@ -33,7 +33,7 @@ class SDLPortBackend : public PortBackend {
3333
LOG_ERROR(Lib_AudioOut, "Failed to create SDL audio stream: {}", SDL_GetError());
3434
return;
3535
}
36-
queue_threshold = CalculateQueueThreshold();
36+
CalculateQueueThreshold();
3737
if (!SDL_SetAudioStreamInputChannelMap(stream, port.format_info.channel_layout.data(),
3838
port.format_info.num_channels)) {
3939
LOG_ERROR(Lib_AudioOut, "Failed to configure SDL audio stream channel map: {}",
@@ -71,9 +71,9 @@ class SDLPortBackend : public PortBackend {
7171
queue_threshold);
7272
SDL_ClearAudioStream(stream);
7373
// Recalculate the threshold in case this happened because of a device change.
74-
queue_threshold = CalculateQueueThreshold();
74+
CalculateQueueThreshold();
7575
}
76-
if (!SDL_PutAudioStreamData(stream, ptr, static_cast<int>(buffer_size))) {
76+
if (!SDL_PutAudioStreamData(stream, ptr, static_cast<int>(guest_buffer_size))) {
7777
LOG_ERROR(Lib_AudioOut, "Failed to output to SDL audio stream: {}", SDL_GetError());
7878
}
7979
}
@@ -91,7 +91,7 @@ class SDLPortBackend : public PortBackend {
9191
}
9292

9393
private:
94-
[[nodiscard]] u32 CalculateQueueThreshold() const {
94+
void CalculateQueueThreshold() {
9595
SDL_AudioSpec discard;
9696
int sdl_buffer_frames;
9797
if (!SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(stream), &discard,
@@ -100,13 +100,22 @@ class SDLPortBackend : public PortBackend {
100100
SDL_GetError());
101101
sdl_buffer_frames = 0;
102102
}
103-
return std::max<u32>(buffer_size, sdl_buffer_frames * frame_size) * 4;
103+
const auto sdl_buffer_size = sdl_buffer_frames * frame_size;
104+
const auto new_threshold = std::max(guest_buffer_size, sdl_buffer_size) * 4;
105+
if (host_buffer_size != sdl_buffer_size || queue_threshold != new_threshold) {
106+
host_buffer_size = sdl_buffer_size;
107+
queue_threshold = new_threshold;
108+
LOG_INFO(Lib_AudioOut,
109+
"SDL audio buffers: guest = {} bytes, host = {} bytes, threshold = {} bytes",
110+
guest_buffer_size, host_buffer_size, queue_threshold);
111+
}
104112
}
105113

106114
u32 frame_size;
107-
u32 buffer_size;
108-
u32 queue_threshold;
109-
SDL_AudioStream* stream;
115+
u32 guest_buffer_size;
116+
u32 host_buffer_size{};
117+
u32 queue_threshold{};
118+
SDL_AudioStream* stream{};
110119
};
111120

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

0 commit comments

Comments
 (0)