diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp index 5db475551..03248d3e0 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp @@ -28,6 +28,9 @@ static constexpr std::uint32_t FORMAT_CHANNELS_SHIFT = 8; static constexpr std::uint32_t FORMAT_CHANNELS_MASK = 0xFu; static constexpr std::uint32_t FORMAT_TYPE_MASK = 0x7Fu; static constexpr std::uint32_t FORMAT_FIELDS_MASK = 0xFFFu; +static constexpr std::uint16_t PORT_TYPE_MAIN = 0; +static constexpr int PORT_ERROR_FORMAT_FLAG_WITHOUT_8_CHANNELS = static_cast(0x80268001); +static constexpr int PORT_ERROR_UNSUPPORTED_FORMAT = static_cast(0x8026800E); static std::mutex g_portsLock; // Grows on demand: the title opens its bed ports plus max_object_ports object ports at once. @@ -158,6 +161,19 @@ extern "C" { int APS5_VABI sceAudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut2PortParam* params, AudioOut2PortHandle* port) { if (!ctx) return SCE_AUDIO_OUT2_ERROR_INVALID_HANDLE; if (!params || !port) return SCE_AUDIO_OUT2_ERROR_INVALID_ARGUMENT; + const auto channels = (params->data_format >> FORMAT_CHANNELS_SHIFT) & FORMAT_CHANNELS_MASK; + const auto sampleType = params->data_format & FORMAT_TYPE_MASK; + const auto* fold = StereoFoldFor(channels); + if (params->port_type == PORT_TYPE_MAIN) { + if ((params->data_format & 0x80u) != 0 && channels != 8) { + *port = static_cast(-1); + return PORT_ERROR_FORMAT_FLAG_WITHOUT_8_CHANNELS; + } + if (fold == nullptr || channels == 6 || sampleType > 1) { + *port = static_cast(-1); + return PORT_ERROR_UNSUPPORTED_FORMAT; + } + } std::lock_guard lock(g_portsLock); std::size_t index = 0; while (index < g_ports.size() && g_ports[index].used) index++; @@ -171,10 +187,9 @@ int APS5_VABI sceAudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut2 entry.dataFormat = params->data_format; entry.samplingFreq = params->sampling_freq; entry.flags = params->flags; - entry.channels = (params->data_format >> FORMAT_CHANNELS_SHIFT) & FORMAT_CHANNELS_MASK; - const auto sampleType = params->data_format & FORMAT_TYPE_MASK; - entry.fold = StereoFoldFor(entry.channels); - if (entry.fold == nullptr || sampleType > 1 || (params->data_format & ~FORMAT_FIELDS_MASK) != 0) { + entry.channels = channels; + entry.fold = fold; + if (entry.fold == nullptr || sampleType > 1 || (params->port_type != PORT_TYPE_MAIN && (params->data_format & ~FORMAT_FIELDS_MASK) != 0)) { entry = AudioOut2Port{}; throw std::runtime_error("sceAudioOut2PortCreate: data format 0x" + [&] { char text[16]; std::snprintf(text, sizeof(text), "%x", params->data_format); return std::string(text); }() + " is not implemented"); } diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOut2PortLayouts.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOut2PortLayouts.cpp index 24faddb21..56648e1b0 100644 --- a/core/libs/prx/libSceAudioOut/tests/AudioOut2PortLayouts.cpp +++ b/core/libs/prx/libSceAudioOut/tests/AudioOut2PortLayouts.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include @@ -33,16 +32,6 @@ static void SetEnvironment(const char* name, const std::string& value) { #endif } -template -static bool ThrowsRuntimeError(TFunction function) { - try { - function(); - } catch (const std::runtime_error&) { - return true; - } - return false; -} - namespace { constexpr std::uint32_t grain = 256; @@ -65,7 +54,6 @@ struct Layout { const Layout mono{1, {1.0f}, {1.0f}}; const Layout stereo{2, {1.0f, 0.0f}, {0.0f, 1.0f}}; -const Layout surround51{6, {1.0f, 0.0f, fold, 0.0f, fold, 0.0f}, {0.0f, 1.0f, fold, 0.0f, 0.0f, fold}}; const Layout surround71{8, {1.0f, 0.0f, fold, 0.0f, fold, 0.0f, fold, 0.0f}, {0.0f, 1.0f, fold, 0.0f, 0.0f, fold, 0.0f, fold}}; const Layout surround714{12, {1.0f, 0.0f, fold, 0.0f, fold, 0.0f, fold, 0.0f, fold, 0.0f, fold * fold, 0.0f}, {0.0f, 1.0f, fold, 0.0f, 0.0f, fold, 0.0f, fold, 0.0f, fold, 0.0f, fold * fold}}; @@ -216,20 +204,49 @@ void TestDroppedLfe() { RequireFold(Play(Format(surround714.channels, formatFloat), data.data(), volume), surround714, expected, volume, 1.0f); } -void TestRejectedLayouts() { +void TestMeasuredMainPortFormats() { + struct Case { + std::uint32_t format; + std::uint32_t result; + }; + constexpr Case cases[] = { + {0x000, 0x8026800E}, {0x001, 0x8026800E}, {0x080, 0x80268001}, + {0x100, 0}, {0x101, 0}, {0x102, 0x8026800E}, {0x17F, 0x8026800E}, + {0x180, 0x80268001}, {0x181, 0x80268001}, {0x1FF, 0x80268001}, + {0x200, 0}, {0x201, 0}, {0x202, 0x8026800E}, {0x27F, 0x8026800E}, + {0x280, 0x80268001}, {0x281, 0x80268001}, {0x2FF, 0x80268001}, + {0x300, 0x8026800E}, {0x301, 0x8026800E}, {0x380, 0x80268001}, + {0x400, 0x8026800E}, {0x401, 0x8026800E}, {0x500, 0x8026800E}, + {0x600, 0x8026800E}, {0x601, 0x8026800E}, {0x680, 0x80268001}, + {0x700, 0x8026800E}, {0x701, 0x8026800E}, {0x780, 0x80268001}, + {0x800, 0}, {0x801, 0}, {0x802, 0x8026800E}, {0x87F, 0x8026800E}, + {0x880, 0}, {0x881, 0}, {0x882, 0x8026800E}, {0x8FF, 0x8026800E}, + {0x900, 0x8026800E}, {0x901, 0x8026800E}, {0x980, 0x80268001}, + {0xA00, 0x8026800E}, {0xB00, 0x8026800E}, {0xC00, 0}, {0xC01, 0}, + {0xC02, 0x8026800E}, {0xC7F, 0x8026800E}, {0xC80, 0x80268001}, + {0xC81, 0x80268001}, {0xD00, 0x8026800E}, {0xE00, 0x8026800E}, + {0xF00, 0x8026800E}, {0xFFF, 0x80268001}, + {0xFFFFF100, 0}, {0xFFFFF201, 0}, {0xFFFFF800, 0}, {0xFFFFFC01, 0}, + {0xFFFFF600, 0x8026800E}, {0xFFFFF202, 0x8026800E}, {0xFFFFF280, 0x80268001}, + }; const auto context = CreateContext(); - for (const std::uint32_t channels : {0u, 3u, 4u, 5u, 7u, 9u, 10u, 11u, 13u, 15u}) { - AudioOut2PortHandle port = 0; - Require(ThrowsRuntimeError([&] { CreatePort(context, Format(channels, formatFloat), &port); })); - Require(ThrowsRuntimeError([&] { CreatePort(context, Format(channels, formatS16), &port); })); + for (const auto& test : cases) { + AudioOut2PortHandle port = 0xAAAAAAAAAAAAAAAAull; + Require(static_cast(CreatePort(context, test.format, &port)) == test.result); + if (test.result == 0) { + Require(port != 0 && port != static_cast(-1)); + Require(sceAudioOut2PortDestroy(port) == 0); + } else { + Require(port == static_cast(-1)); + } + } + for (const std::uint32_t format : {0x100u, 0x201u, 0x800u, 0xC01u}) { + for (std::uint32_t bit = 12; bit < 32; bit++) { + AudioOut2PortHandle port = 0; + Require(CreatePort(context, format | (1u << bit), &port) == 0); + Require(sceAudioOut2PortDestroy(port) == 0); + } } - AudioOut2PortHandle port = 0; - Require(ThrowsRuntimeError([&] { CreatePort(context, Format(12, 2), &port); })); - Require(ThrowsRuntimeError([&] { CreatePort(context, Format(12, formatFloat) | 0x1000, &port); })); - Require(ThrowsRuntimeError([&] { CreatePort(context, Format(12, formatFloat) | 0x10000, &port); })); - Require(CreatePort(context, Format(12, formatFloat), &port) == 0); - Require(port != 0); - Require(sceAudioOut2PortDestroy(port) == 0); Require(sceAudioOut2ContextDestroy(context) == 0); } @@ -237,7 +254,7 @@ void TestRejectedLayouts() { int main() { SetEnvironment("SDL_AUDIODRIVER", "disk"); - for (const Layout* layout : {&mono, &stereo, &surround51, &surround71, &surround714}) { + for (const Layout* layout : {&mono, &stereo, &surround71, &surround714}) { TestFloatLayout(*layout); TestS16Layout(*layout); } @@ -246,8 +263,13 @@ int main() { const auto volume = Volume(8); RequireFold(Play(Format(8, formatFloat) | 0x80, data.data(), volume), surround71, data, volume, 1.0f); } + { + const auto data = FloatGrain(2); + const auto volume = Volume(2); + RequireFold(Play(0xFFFFF200u, data.data(), volume), stereo, data, volume, 1.0f); + } TestHeightChannels(); TestDroppedLfe(); - TestRejectedLayouts(); + TestMeasuredMainPortFormats(); return 0; } diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 140718841..650d403fc 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -144,7 +144,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceAudioOutSetMixLevelPadSpk](../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - effect of a negative mix level unknown; only 0 to 32768 (0 dB) is accepted - [sceAudioOutSetMixLevelPadSpk](../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - no source applies the level (shadPS4 only stores it); it is assumed to be a linear gain on the port's own output, and the -9 dB default (11626) is applied to every pad speaker port, including those of titles that never call the function - [sceAudioOutGetLastOutputTime](../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - signature and behaviour taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) and [fpPS4](https://github.com/red-prig/fpPS4) reimplementations of the PS4 library, not confirmed on a PS5 title: the time is the process time at which `sceAudioOutOutput` or `sceAudioOutOutputs` accepted the port's last block, not the time the block reached the device -- [sceAudioOut2PortCreate](../../core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp) (libSceAudioOut) - `data_format` is read as the channel count (bits 8-11: only 1, 2, 6, 8 or 12) and sample type (bits 0-6: 0 float, 1 int16); bit 7 and the port `flags` are not decoded, and bits 12-31 throw +- [sceAudioOut2PortCreate](../../core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp) (libSceAudioOut) - MAIN (type 0) format validation follows PS5 firmware 13.60 measurements: bits 8-11 accept 1, 2, 8 or 12, bits 0-6 accept 0 or 1, bit 7 requires 8 channels, and bits 12-31 do not affect validation. The sample types are still read as float and int16; bit 7's layout, the PCM effect of bits 12-31 and the port `flags` are unverified. Other port types retain unverified format validation, including 6 channels and rejection of bits 12-31 - [sceAudioOut2PortCreate](../../core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp) (libSceAudioOut) - 12 channels are assumed to be L R C LFE Ls Rs Lb Rb Ltf Rtf Ltb Rtb as in KytyPS5's 12-to-8 fold, and 6 and 8 channels to be its first 6 and 8; no title or SDK header confirms the order - [sceAudioOut2PortCreate](../../core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp) (libSceAudioOut) - the stereo fold is assumed, not the console's downmix: C at -3 dB into both sides, Ls/Rs, Lb/Rb and Ltf/Rtf at -3 dB and Ltb/Rtb at -6 dB into their own side, LFE dropped - [scePsmlMfsrGetContextBufferRequirement1100, scePsmlMfsrCreateContext1100, scePsmlMfsrGetDispatchMfsrPacket1100](../../core/libs/prx/libScePsml_debug/Export.cpp) (libScePsml_debug) - argument layouts unknown; only 0x8A810001 (not initialized) is returned