AnyPS5/core/libs/prx/libSceAudioOut/tests/AudioOutLastOutputTime.cpp

205 lines
9.2 KiB
C++

#include "SceTypes.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <atomic>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <thread>
#include <vector>
extern "C" {
int APS5_VABI sceAudioOutOpen(int, int, int, std::uint32_t, std::uint32_t, std::uint32_t);
int APS5_VABI sceAudioOutClose(int);
int APS5_VABI sceAudioOutOutput(int, const void*);
int APS5_VABI sceAudioOutOutputs(AudioOutOutputParam*, std::uint32_t);
int APS5_VABI sceAudioOutGetPortState(int, AudioOutPortState*);
int APS5_VABI sceAudioOutGetLastOutputTime(int, std::uint64_t*);
std::uint64_t APS5_VABI sceKernelGetProcessTime();
}
static void Require(bool value, const char* message) {
if (value) return;
std::fprintf(stderr, "%s\n", message);
std::abort();
}
namespace {
constexpr int user = 0x10000000;
constexpr int portTypeMain = 0;
constexpr int portTypeVibration = 10;
constexpr std::uint32_t formatS16Mono = 0;
constexpr std::uint32_t frames = 256;
constexpr std::uint32_t frequency = 48000;
constexpr std::uint64_t untouched = 0xA5A5A5A5A5A5A5A5ull;
constexpr int invalidPort = static_cast<int>(0x80260003);
constexpr int invalidPointer = static_cast<int>(0x80260004);
int Open(int type) {
const int handle = sceAudioOutOpen(user, type, 0, frames, frequency, formatS16Mono);
Require(handle > 0, "port must open");
return handle;
}
std::uint64_t LastOutputTime(int handle) {
std::uint64_t time = untouched;
Require(sceAudioOutGetLastOutputTime(handle, &time) == 0, "an open port must report its last output time");
return time;
}
void Pause() {
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
std::uint64_t OutputAndCheck(int handle, const std::vector<std::int16_t>& block) {
Pause();
const std::uint64_t before = sceKernelGetProcessTime();
Require(sceAudioOutOutput(handle, block.data()) == static_cast<int>(frames), "output must accept the block");
const std::uint64_t after = sceKernelGetProcessTime();
const std::uint64_t time = LastOutputTime(handle);
Require(time >= before && time <= after, "last output time must be the process time of the output");
return time;
}
void TestOutput(int type) {
const std::vector<std::int16_t> block(frames, 256);
const int handle = Open(type);
Require(LastOutputTime(handle) == 0, "a port that never output must report 0");
const std::uint64_t first = OutputAndCheck(handle, block);
Pause();
Require(LastOutputTime(handle) == first, "the time must not move without an output");
const std::uint64_t second = OutputAndCheck(handle, block);
Require(second > first, "a later output must report a later time");
Pause();
Require(sceAudioOutOutput(handle, nullptr) == static_cast<int>(frames), "waiting for the output must succeed");
Require(LastOutputTime(handle) == second, "waiting without data must not move the time");
Require(sceAudioOutClose(handle) == 0, "port must close");
}
void TestPortsAreIndependent() {
const std::vector<std::int16_t> block(frames, 256);
const int played = Open(portTypeMain);
const int silent = Open(portTypeMain);
Require(played != silent, "two open ports must have different handles");
OutputAndCheck(played, block);
Require(LastOutputTime(silent) == 0, "an output must not move the time of another port");
Require(sceAudioOutClose(played) == 0, "port must close");
Require(sceAudioOutClose(silent) == 0, "port must close");
}
void TestOutputs() {
const std::vector<std::int16_t> block(frames, 256);
const int first = Open(portTypeMain);
const int second = Open(portTypeMain);
const int waiting = Open(portTypeMain);
AudioOutOutputParam params[3] = {{first, block.data()}, {second, block.data()}, {waiting, nullptr}};
Pause();
const std::uint64_t before = sceKernelGetProcessTime();
Require(sceAudioOutOutputs(params, 3) == static_cast<int>(frames), "outputs must accept the blocks");
const std::uint64_t after = sceKernelGetProcessTime();
for (const int handle : {first, second}) {
const std::uint64_t time = LastOutputTime(handle);
Require(time >= before && time <= after, "outputs must set the time of every port that received data");
}
Require(LastOutputTime(waiting) == 0, "outputs must not move the time of a port that received no data");
for (const int handle : {first, second, waiting}) Require(sceAudioOutClose(handle) == 0, "port must close");
}
void TestReopenedPort() {
const std::vector<std::int16_t> block(frames, 256);
const int handle = Open(portTypeMain);
OutputAndCheck(handle, block);
Require(sceAudioOutClose(handle) == 0, "port must close");
const int reopened = Open(portTypeMain);
Require(LastOutputTime(reopened) == 0, "a port opened after a close must not keep the time of the closed one");
Require(sceAudioOutClose(reopened) == 0, "port must close");
}
void TestPortWithoutDeviceKeepsRealTime() {
const std::vector<std::int16_t> block(frames, 256);
const int handle = Open(portTypeVibration);
const std::uint64_t blockUs = 1000000ull * frames / frequency;
const std::uint64_t start = sceKernelGetProcessTime();
for (int i = 0; i < 7; i++) Require(sceAudioOutOutput(handle, block.data()) == static_cast<int>(frames), "output must accept the block");
Require(sceKernelGetProcessTime() - start < 3 * blockUs, "a port without a device must take blocks up to its latency without waiting");
for (int i = 7; i < 60; i++) Require(sceAudioOutOutput(handle, block.data()) == static_cast<int>(frames), "output must accept the block");
const std::uint64_t elapsed = sceKernelGetProcessTime() - start;
Require(elapsed + 50000 >= 60 * blockUs && elapsed < 60 * blockUs + 200000, "a port without a device must consume blocks in real time");
Require(sceAudioOutOutput(handle, nullptr) == static_cast<int>(frames), "waiting for the output must succeed");
Require(sceKernelGetProcessTime() - start + 2000 >= 60 * blockUs, "waiting on a port without a device must last until its queue has played");
Require(sceAudioOutClose(handle) == 0, "port must close");
}
void TestErrors() {
const std::vector<std::int16_t> block(frames, 256);
const int handle = Open(portTypeMain);
const std::uint64_t time = OutputAndCheck(handle, block);
Require(sceAudioOutGetLastOutputTime(handle, nullptr) == invalidPointer, "a null destination must be rejected");
Require(LastOutputTime(handle) == time, "a rejected call must not move the time");
std::uint64_t destination = untouched;
Require(sceAudioOutGetLastOutputTime(0, &destination) == invalidPort, "handle 0 must be rejected");
Require(sceAudioOutGetLastOutputTime(-1, &destination) == invalidPort, "a negative handle must be rejected");
Require(sceAudioOutGetLastOutputTime(handle + 1, &destination) == invalidPort, "a port that is not open must be rejected");
Require(sceAudioOutGetLastOutputTime(1000, &destination) == invalidPort, "an out of range handle must be rejected");
Require(sceAudioOutClose(handle) == 0, "port must close");
Require(sceAudioOutGetLastOutputTime(handle, &destination) == invalidPort, "a closed port must be rejected");
Require(destination == untouched, "a rejected call must not write the destination");
}
void TestQueriesDuringOutput(bool batch, bool drain) {
const std::vector<std::int16_t> block(frequency, 256);
const int handle = sceAudioOutOpen(user, portTypeVibration, 0, frequency, frequency, formatS16Mono);
Require(handle > 0, "port must open");
Require(sceAudioOutOutput(handle, block.data()) == static_cast<int>(frequency), "output must accept the block");
const auto initialTime = LastOutputTime(handle);
std::atomic<bool> started = false;
std::thread output([&] {
AudioOutOutputParam param{handle, drain ? nullptr : block.data()};
started = true;
const int result = batch ? sceAudioOutOutputs(&param, 1) : sceAudioOutOutput(handle, param.ptr);
Require(result == static_cast<int>(frequency), "pending output must complete before close");
});
while (!started) std::this_thread::yield();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
const auto before = std::chrono::steady_clock::now();
AudioOutPortState state{};
Require(sceAudioOutGetPortState(handle, &state) == 0, "pending output must allow port state queries");
Require(LastOutputTime(handle) == initialTime, "pending output must not change its timestamp yet");
Require(std::chrono::steady_clock::now() - before < std::chrono::milliseconds(300), "audio queries must not wait for output pacing or draining");
Require(sceAudioOutClose(handle) == 0, "close must safely wait for pending output");
output.join();
}
}
int main() {
#ifdef _WIN32
_putenv_s("SDL_AUDIODRIVER", "dummy");
#else
setenv("SDL_AUDIODRIVER", "dummy", 1);
#endif
sceKernelGetProcessTime();
TestOutput(portTypeMain);
TestOutput(portTypeVibration);
TestPortsAreIndependent();
TestOutputs();
TestReopenedPort();
TestErrors();
TestPortWithoutDeviceKeepsRealTime();
TestQueriesDuringOutput(false, false);
TestQueriesDuringOutput(false, true);
TestQueriesDuringOutput(true, false);
TestQueriesDuringOutput(true, true);
return 0;
}