mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-19 18:36:53 +02:00
audio: fix pacing and AudioOut2 port lifetime
Extracted from 6a60f1b17481a0e5e14242c0fb4dc22f963545e1 in KytyPS5/KytyPS5#147.
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
#include "libs/audio.h"
|
||||
#include "libs/audio_internal.h"
|
||||
#include "libs/errno.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
namespace AudioOut2 = Libs::Audio::AudioOut2;
|
||||
|
||||
std::mutex g_device_mutex;
|
||||
std::condition_variable g_device_cv;
|
||||
std::vector<int> g_live_devices;
|
||||
int g_next_device = 1;
|
||||
int g_open_waiters = 0;
|
||||
bool g_block_opens = false;
|
||||
|
||||
void Check(bool value, const char* text) {
|
||||
if (!value) {
|
||||
std::fprintf(stderr, "AudioOut2PortTests: failed: %s\n", text);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
struct PortParam {
|
||||
uint16_t port_type;
|
||||
uint16_t pad;
|
||||
uint32_t data_format;
|
||||
uint32_t sampling_freq;
|
||||
uint32_t flags;
|
||||
uint64_t user_handle;
|
||||
uint32_t reserved[10];
|
||||
};
|
||||
|
||||
struct ContextParam {
|
||||
uint32_t max_ports;
|
||||
uint32_t max_object_ports;
|
||||
uint32_t guarantee_object_ports;
|
||||
uint32_t queue_depth;
|
||||
uint32_t num_grains;
|
||||
uint32_t flags;
|
||||
uint32_t reserved[10];
|
||||
};
|
||||
|
||||
struct PortState {
|
||||
uint16_t output;
|
||||
uint8_t num_channels;
|
||||
uint8_t pad1;
|
||||
int16_t volume;
|
||||
uint16_t reroute_counter;
|
||||
uint32_t flags;
|
||||
uint32_t pad2;
|
||||
uint64_t reserved[6];
|
||||
};
|
||||
|
||||
const auto* AsParam(const PortParam* param) {
|
||||
return reinterpret_cast<const AudioOut2::AudioOut2PortParam*>(param);
|
||||
}
|
||||
|
||||
const auto* AsParam(const ContextParam* param) {
|
||||
return reinterpret_cast<const AudioOut2::AudioOut2ContextParam*>(param);
|
||||
}
|
||||
|
||||
auto* AsState(PortState* state) {
|
||||
return reinterpret_cast<AudioOut2::AudioOut2PortState*>(state);
|
||||
}
|
||||
|
||||
PortParam MakeParam(uint32_t data_format = 0x200) {
|
||||
PortParam param {};
|
||||
param.data_format = data_format;
|
||||
param.sampling_freq = 48000;
|
||||
return param;
|
||||
}
|
||||
|
||||
AudioOut2::AudioOut2ContextHandle CreateContext() {
|
||||
ContextParam param {};
|
||||
param.queue_depth = 4;
|
||||
param.num_grains = 512;
|
||||
AudioOut2::AudioOut2ContextHandle context = 0;
|
||||
Check(AudioOut2::AudioOut2ContextCreate(AsParam(¶m), nullptr, 0, &context) == OK,
|
||||
"context create failed");
|
||||
return context;
|
||||
}
|
||||
|
||||
void BlockDeviceOpens() {
|
||||
std::lock_guard lock(g_device_mutex);
|
||||
g_open_waiters = 0;
|
||||
g_block_opens = true;
|
||||
}
|
||||
|
||||
void WaitForDeviceOpens(int count) {
|
||||
std::unique_lock lock(g_device_mutex);
|
||||
g_device_cv.wait(lock, [count]() { return g_open_waiters >= count; });
|
||||
}
|
||||
|
||||
void ReleaseDeviceOpens() {
|
||||
std::lock_guard lock(g_device_mutex);
|
||||
g_block_opens = false;
|
||||
g_device_cv.notify_all();
|
||||
}
|
||||
|
||||
int LiveDeviceCount() {
|
||||
std::lock_guard lock(g_device_mutex);
|
||||
return static_cast<int>(g_live_devices.size());
|
||||
}
|
||||
|
||||
void TestSlotReuse() {
|
||||
const auto context = CreateContext();
|
||||
const auto param = MakeParam();
|
||||
for (int i = 0; i < 300; i++) {
|
||||
AudioOut2::AudioOut2PortHandle port = 0;
|
||||
Check(AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &port) == OK,
|
||||
"port slot was not reusable");
|
||||
Check(port != 0, "port handle is zero");
|
||||
AudioOut2::AudioOut2PortDestroy(port);
|
||||
}
|
||||
AudioOut2::AudioOut2ContextDestroy(context);
|
||||
}
|
||||
|
||||
void TestFullTableRecovers() {
|
||||
const auto context = CreateContext();
|
||||
const auto param = MakeParam();
|
||||
std::vector<AudioOut2::AudioOut2PortHandle> ports;
|
||||
ports.reserve(256);
|
||||
|
||||
for (int i = 0; i < 256; i++) {
|
||||
AudioOut2::AudioOut2PortHandle port = 0;
|
||||
Check(AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &port) == OK,
|
||||
"port table filled early");
|
||||
ports.push_back(port);
|
||||
}
|
||||
|
||||
AudioOut2::AudioOut2PortHandle overflow = 0;
|
||||
Check(AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &overflow) != OK,
|
||||
"full port table accepted another port");
|
||||
|
||||
for (auto port: ports) {
|
||||
AudioOut2::AudioOut2PortDestroy(port);
|
||||
}
|
||||
|
||||
AudioOut2::AudioOut2PortHandle port = 0;
|
||||
Check(AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &port) == OK,
|
||||
"port table did not recover");
|
||||
AudioOut2::AudioOut2PortDestroy(port);
|
||||
AudioOut2::AudioOut2ContextDestroy(context);
|
||||
}
|
||||
|
||||
void TestConcurrentCreates() {
|
||||
constexpr int thread_count = 8;
|
||||
const auto context = CreateContext();
|
||||
const auto param = MakeParam(0x800);
|
||||
std::vector<AudioOut2::AudioOut2PortHandle> ports(thread_count);
|
||||
std::vector<int> results(thread_count);
|
||||
std::vector<std::thread> threads;
|
||||
|
||||
BlockDeviceOpens();
|
||||
for (int i = 0; i < thread_count; i++) {
|
||||
threads.emplace_back([&, i]() {
|
||||
results[i] = AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &ports[i]);
|
||||
});
|
||||
}
|
||||
WaitForDeviceOpens(thread_count);
|
||||
ReleaseDeviceOpens();
|
||||
for (auto& thread: threads) {
|
||||
thread.join();
|
||||
}
|
||||
|
||||
for (int i = 0; i < thread_count; i++) {
|
||||
Check(results[i] == OK, "concurrent port create failed");
|
||||
PortState state {};
|
||||
AudioOut2::AudioOut2PortGetState(ports[i], AsState(&state));
|
||||
Check(state.num_channels == 8, "concurrent create lost its reserved slot");
|
||||
AudioOut2::AudioOut2PortDestroy(ports[i]);
|
||||
}
|
||||
Check(LiveDeviceCount() == 0, "concurrent create leaked a device");
|
||||
AudioOut2::AudioOut2ContextDestroy(context);
|
||||
}
|
||||
|
||||
void TestContextDestroyCancelsPendingCreate() {
|
||||
const auto context = CreateContext();
|
||||
const auto param = MakeParam();
|
||||
AudioOut2::AudioOut2PortHandle port = 0;
|
||||
int result = OK;
|
||||
|
||||
BlockDeviceOpens();
|
||||
std::thread creator(
|
||||
[&]() { result = AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &port); });
|
||||
WaitForDeviceOpens(1);
|
||||
AudioOut2::AudioOut2ContextDestroy(context);
|
||||
ReleaseDeviceOpens();
|
||||
creator.join();
|
||||
|
||||
Check(result != OK, "destroyed context retained a pending port create");
|
||||
Check(LiveDeviceCount() == 0, "cancelled port create leaked a device");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Libs::Audio::AudioInternal {
|
||||
|
||||
int AudioOutOpen(int /*type*/, uint32_t /*samples_num*/, uint32_t /*freq*/, Format /*format*/) {
|
||||
std::unique_lock lock(g_device_mutex);
|
||||
const int handle = g_next_device++;
|
||||
g_live_devices.push_back(handle);
|
||||
g_open_waiters++;
|
||||
g_device_cv.notify_all();
|
||||
g_device_cv.wait(lock, []() { return !g_block_opens; });
|
||||
return handle;
|
||||
}
|
||||
|
||||
void AudioOutClose(int handle) {
|
||||
std::lock_guard lock(g_device_mutex);
|
||||
const auto it = std::find(g_live_devices.begin(), g_live_devices.end(), handle);
|
||||
if (it != g_live_devices.end()) {
|
||||
g_live_devices.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t AudioOutOutputs(const OutputParam* /*params*/, uint32_t /*num*/, bool /*blocking*/) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace Libs::Audio::AudioInternal
|
||||
|
||||
namespace Libs::LibKernel {
|
||||
|
||||
uint64_t KYTY_SYSV_ABI KernelGetProcessTime() {
|
||||
static std::atomic_uint64_t now {0};
|
||||
return now.fetch_add(1000);
|
||||
}
|
||||
|
||||
} // namespace Libs::LibKernel
|
||||
|
||||
int main() {
|
||||
TestSlotReuse();
|
||||
TestFullTableRecovers();
|
||||
TestConcurrentCreates();
|
||||
TestContextDestroyCancelsPendingCreate();
|
||||
std::printf("AudioOut2PortTests: all cases passed\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user