mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-23 09:03:22 -05:00
The library registered no shutdown callback, so a guest mixer thread kept calling sceAudioOut2ContextPush while exit ran the static destructors and mixed from the freed port table (#5864). Exports that read the port tables or output audio now hold a guard: once exit starts they park the thread with LibcAwaitExit_nid_postfix, and the shutdown callback waits for the calls already inside. A push waiting for a grain from sceAudioOut2ContextAdvance also wakes on the shutdown token, so the callback does not wait for an advance that is parked.
156 lines
5.7 KiB
C++
156 lines
5.7 KiB
C++
#include "SceTypes.hpp"
|
|
#include "prx/libc/include/general/VabiMacros.hpp"
|
|
#include "prx/libc/include/Shutdown.hpp"
|
|
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
extern "C" {
|
|
int APS5_VABI sceAudioOut2ContextResetParam(AudioOut2ContextParam*);
|
|
int APS5_VABI sceAudioOut2ContextCreate(const AudioOut2ContextParam*, void*, std::size_t, AudioOut2ContextHandle*);
|
|
int APS5_VABI sceAudioOut2ContextAdvance(AudioOut2ContextHandle);
|
|
int APS5_VABI sceAudioOut2ContextPush(AudioOut2ContextHandle, std::uint32_t);
|
|
int APS5_VABI sceAudioOut2PortCreate(AudioOut2ContextHandle, const AudioOut2PortParam*, AudioOut2PortHandle*);
|
|
int APS5_VABI sceAudioOut2PortSetAttributes(AudioOut2PortHandle, const AudioOut2Attribute*, std::uint32_t);
|
|
int APS5_VABI sceAudioOutOpen(int, int, int, std::uint32_t, std::uint32_t, std::uint32_t);
|
|
int APS5_VABI sceAudioOutOutput(int, const void*);
|
|
}
|
|
|
|
namespace {
|
|
|
|
constexpr std::uint32_t grain = 256;
|
|
constexpr std::uint32_t frequency = 48000;
|
|
constexpr std::uint16_t portTypeMain = 0;
|
|
constexpr std::uint32_t attributeData = 0;
|
|
constexpr std::uint32_t formatStereoFloat = 2u << 8;
|
|
constexpr int user = 0x10000000;
|
|
constexpr std::uint32_t formatS16Stereo = 1;
|
|
constexpr std::uint64_t startedOutputs = 3;
|
|
constexpr auto settle = std::chrono::milliseconds(100);
|
|
constexpr auto exitLimit = std::chrono::seconds(3);
|
|
|
|
std::atomic<std::uint64_t> outputs{0};
|
|
std::vector<float> pcm(static_cast<std::size_t>(grain) * 2, 0.25f);
|
|
|
|
void Fail(const char* message) {
|
|
std::fprintf(stderr, "%s\n", message);
|
|
std::fflush(stderr);
|
|
std::_Exit(1);
|
|
}
|
|
|
|
void RequireParked() {
|
|
const auto before = outputs.load();
|
|
std::this_thread::sleep_for(settle);
|
|
if (outputs.load() != before) Fail("the mixer thread kept outputting audio after the audio shutdown");
|
|
std::puts("the mixer thread was parked before the audio tables were destroyed");
|
|
std::fflush(stdout);
|
|
}
|
|
|
|
void DelayExit() {
|
|
std::this_thread::sleep_for(settle);
|
|
}
|
|
|
|
void ReportExit() {
|
|
std::puts("the exit finished while a push waited for an advance");
|
|
std::fflush(stdout);
|
|
}
|
|
|
|
void SetData(AudioOut2PortHandle port) {
|
|
const void* pointer = pcm.data();
|
|
const AudioOut2Attribute attribute{attributeData, 0, &pointer, sizeof(pointer)};
|
|
if (sceAudioOut2PortSetAttributes(port, &attribute, 1) != 0) Fail("a port attribute set returned an error");
|
|
}
|
|
|
|
void CreatePort(AudioOut2ContextHandle& context, AudioOut2PortHandle& port) {
|
|
AudioOut2ContextParam params{};
|
|
if (sceAudioOut2ContextResetParam(¶ms) != 0) Fail("context parameters were not reset");
|
|
params.num_grains = grain;
|
|
if (sceAudioOut2ContextCreate(¶ms, nullptr, 0, &context) != 0) Fail("context was not created");
|
|
AudioOut2PortParam portParams{};
|
|
portParams.port_type = portTypeMain;
|
|
portParams.data_format = formatStereoFloat;
|
|
portParams.sampling_freq = frequency;
|
|
if (sceAudioOut2PortCreate(context, &portParams, &port) != 0) Fail("port was not created");
|
|
}
|
|
|
|
void PushAudioOut2() {
|
|
AudioOut2ContextHandle context = 0;
|
|
AudioOut2PortHandle port = 0;
|
|
CreatePort(context, port);
|
|
std::thread([context, port] {
|
|
for (;;) {
|
|
SetData(port);
|
|
if (sceAudioOut2ContextPush(context, 1) != 0) Fail("a push during exit returned an error");
|
|
outputs++;
|
|
}
|
|
}).detach();
|
|
}
|
|
|
|
void WaitInPushAudioOut2() {
|
|
AudioOut2ContextHandle context = 0;
|
|
AudioOut2PortHandle port = 0;
|
|
CreatePort(context, port);
|
|
SetData(port);
|
|
if (sceAudioOut2ContextAdvance(context) != 0) Fail("an advance returned an error");
|
|
if (sceAudioOut2ContextPush(context, 1) != 0) Fail("a push returned an error");
|
|
std::thread([context] {
|
|
outputs++;
|
|
sceAudioOut2ContextPush(context, 1);
|
|
Fail("a push waiting for an advance returned during exit");
|
|
}).detach();
|
|
}
|
|
|
|
void AdvanceAtExit() {
|
|
AudioOut2ContextHandle context = 0;
|
|
AudioOut2PortHandle port = 0;
|
|
CreatePort(context, port);
|
|
SetData(port);
|
|
LibcRegisterShutdown_nid_postfix(DelayExit);
|
|
LibcRequestExit_nid_postfix(0);
|
|
sceAudioOut2ContextAdvance(context);
|
|
Fail("an advance after the exit request returned");
|
|
}
|
|
|
|
void OutputAudioOut() {
|
|
const int port = sceAudioOutOpen(user, 0, 0, grain, frequency, formatS16Stereo);
|
|
if (port <= 0) Fail("port was not opened");
|
|
static std::vector<std::int16_t> data(static_cast<std::size_t>(grain) * 2, 0x1000);
|
|
std::thread([port] {
|
|
for (;;) {
|
|
if (sceAudioOutOutput(port, data.data()) < 0) Fail("an output during exit returned an error");
|
|
outputs++;
|
|
}
|
|
}).detach();
|
|
}
|
|
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
#ifdef _WIN32
|
|
_putenv_s("SDL_AUDIODRIVER", "dummy");
|
|
#else
|
|
setenv("SDL_AUDIODRIVER", "dummy", 1);
|
|
#endif
|
|
const bool waiting = argc == 2 && std::strcmp(argv[1], "--audio-out2-waiting-push") == 0;
|
|
if (argc == 2 && std::strcmp(argv[1], "--audio-out2") == 0) PushAudioOut2();
|
|
else if (argc == 2 && std::strcmp(argv[1], "--audio-out") == 0) OutputAudioOut();
|
|
else if (waiting) WaitInPushAudioOut2();
|
|
else if (argc == 2 && std::strcmp(argv[1], "--audio-out2-advance") == 0) AdvanceAtExit();
|
|
else Fail("usage: audio_out_shutdown_tests --audio-out2 | --audio-out2-waiting-push | --audio-out2-advance | --audio-out");
|
|
while (outputs.load() < (waiting ? 1 : startedOutputs)) std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
if (waiting) std::this_thread::sleep_for(settle);
|
|
std::thread([] {
|
|
std::this_thread::sleep_for(exitLimit);
|
|
Fail("the exit did not finish");
|
|
}).detach();
|
|
if (std::atexit(waiting ? ReportExit : RequireParked) != 0) Fail("atexit registration failed");
|
|
LibcRequestExit_nid_postfix(0);
|
|
LibcAwaitExit_nid_postfix();
|
|
}
|