mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-08-03 11:23:49 +00:00
Extracted from 6a60f1b17481a0e5e14242c0fb4dc22f963545e1 in KytyPS5/KytyPS5#147.
250 lines
6.8 KiB
C++
250 lines
6.8 KiB
C++
#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;
|
|
}
|