Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion core/libs/prx/libSceAudioOut/src/AudioOut2Internal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ struct AudioOut2CapturedPort {

using AudioOut2Grain = std::vector<AudioOut2CapturedPort>;

struct AudioOut2QueuedData {
const void* data = nullptr;
std::vector<std::byte> pcm;
bool reused = false;
};

struct AudioOut2Port {
bool used = false;
std::uint64_t generation = 0;
Expand All @@ -68,9 +74,11 @@ struct AudioOut2Port {
bool int16 = false;
const AudioOut2StereoFold* fold = nullptr;
const void* data = nullptr;
std::deque<const void*> queuedData;
std::deque<AudioOut2QueuedData> queuedData;
const void* lastBuffer = nullptr;
const void* repeatedData = nullptr;
std::vector<std::byte> repeatedPcm;
bool repeatedReused = false;
float volume[AUDIO_OUT2_PORT_CHANNELS_MAX] = {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f};
std::uint64_t dataSets = 0;
std::uint64_t attributeTraces = 0;
Expand Down
37 changes: 33 additions & 4 deletions core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,20 @@ AudioOut2Grain AudioOut2CaptureGrain(const AudioOut2Context& context, bool snaps
auto& port = g_ports[index];
if (!port.used || port.context != &context || port.channels == 0) continue;
const void* data = port.repeatedData;
const std::vector<std::byte>* setPcm = port.repeatedReused ? &port.repeatedPcm : nullptr;
AudioOut2QueuedData queued;
if (!port.queuedData.empty()) {
data = port.queuedData.front();
queued = std::move(port.queuedData.front());
port.queuedData.pop_front();
data = queued.data;
setPcm = queued.reused ? &queued.pcm : nullptr;
}
if (data != nullptr) {
AudioOut2CapturedPort captured{index, port.generation, data};
if (snapshot) {
if (setPcm != nullptr) {
captured.pcm = *setPcm;
std::copy_n(port.volume, port.channels, captured.volume.begin());
} else if (snapshot) {
captured.pcm.resize(static_cast<std::size_t>(context.grain) * port.channels * (port.int16 ? sizeof(std::int16_t) : sizeof(float)));
std::memcpy(captured.pcm.data(), data, captured.pcm.size());
std::copy_n(port.volume, port.channels, captured.volume.begin());
Expand Down Expand Up @@ -159,6 +166,24 @@ void AudioOut2ReleasePorts(const AudioOut2Context& context) {
}
}

static std::vector<std::byte> CopyGrain(const AudioOut2Port& port) {
if (port.data == nullptr || port.context == nullptr) return {};
std::vector<std::byte> pcm(static_cast<std::size_t>(port.context->grain) * port.channels * (port.int16 ? sizeof(std::int16_t) : sizeof(float)));
std::memcpy(pcm.data(), port.data, pcm.size());
return pcm;
}

static void MarkBufferReused(const AudioOut2Port& setter) {
if (setter.data == nullptr) return;
for (auto& port : g_ports) {
if (!port.used || &port == &setter || port.context != setter.context) continue;
for (auto& queued : port.queuedData) {
if (queued.data == setter.data) queued.reused = true;
}
if (port.repeatedData == setter.data) port.repeatedReused = true;
}
}

static void TraceAttribute(AudioOut2PortHandle handle, const AudioOut2Port& port, const AudioOut2Attribute& attribute, const char* verdict) {
if (!AudioOut2TraceEnabled()) return;
char values[128] = "";
Expand Down Expand Up @@ -266,9 +291,13 @@ int APS5_VABI sceAudioOut2PortSetAttributes(AudioOut2PortHandle port, const Audi
verdict = "null value";
} else if (attribute.attribute_id == ATTRIBUTE_DATA && attribute.value_size == sizeof(entry->data)) {
std::memcpy(&entry->data, attribute.value, sizeof(entry->data));
entry->queuedData.push_back(entry->data);
if (entry->queuedData.size() > QUEUED_DATA_MAX) entry->queuedData.pop_front();
MarkBufferReused(*entry);
AudioOut2QueuedData queued{entry->data, CopyGrain(*entry)};
entry->repeatedData = entry->data != nullptr && (entry->lastBuffer == nullptr || entry->lastBuffer == entry->data) ? entry->data : nullptr;
entry->repeatedPcm = queued.pcm;
entry->repeatedReused = false;
entry->queuedData.push_back(std::move(queued));
if (entry->queuedData.size() > QUEUED_DATA_MAX) entry->queuedData.pop_front();
if (entry->data != nullptr) entry->lastBuffer = entry->data;
entry->dataSets++;
verdict = "pcm data pointer";
Expand Down
24 changes: 24 additions & 0 deletions core/libs/prx/libSceAudioOut/tests/AudioOut2PortBuffers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,28 @@ void TestRenderingSizedByLevelStaysWithinQueueDepth() {
RequirePlayed(played, order);
}

void TestBufferReusedAcrossPortsKeepsEachPortsGrain(bool advanced) {
const auto voice = Grain(0.01f);
const std::vector<float> silence(voice.size(), 0.0f);
auto shared = voice;
const auto played = Play([&](AudioOut2ContextHandle context, AudioOut2PortHandle port) {
AudioOut2PortParam portParams{};
portParams.port_type = portTypeMain;
portParams.data_format = formatStereoFloat;
portParams.sampling_freq = frequency;
AudioOut2PortHandle other = 0;
Require(sceAudioOut2PortCreate(context, &portParams, &other) == 0);
SetData(port, shared.data());
shared = silence;
SetData(other, shared.data());
if (advanced) Require(sceAudioOut2ContextAdvance(context) == 0);
Require(sceAudioOut2ContextPush(context, 1) == 0);
SetData(port, nullptr);
SetData(other, nullptr);
}, advanced);
RequirePlayed(played, {&voice});
}

}

int main() {
Expand All @@ -299,5 +321,7 @@ int main() {
TestAdvancingPortWithoutNewBufferPlaysSilence();
TestRewrittenBufferRepeats();
TestRenderingSizedByLevelStaysWithinQueueDepth();
TestBufferReusedAcrossPortsKeepsEachPortsGrain(false);
TestBufferReusedAcrossPortsKeepsEachPortsGrain(true);
return 0;
}
2 changes: 1 addition & 1 deletion docs/dev/debt/libSceAudioOut.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
- [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
- [sceAudioOut2PortSetAttributes](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp) (libSceAudioOut) - each PCM buffer set (attribute 0) is taken as one grain: buffers set between two pushes are queued and played one per grain, at most 16 queued. A grain with no new buffer repeats the last one only when the port has always been given that same buffer (rewritten in place); a port whose buffers move on plays silence instead. Inferred from PPSA12544, whose object port (type 6) is given one grain of a 64-grain ring per set, often two or three between pushes and then none for a few; the console behaviour for several sets per push and for a push without a new buffer is not documented
- [sceAudioOut2PortSetAttributes](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp) (libSceAudioOut) - each PCM buffer set (attribute 0) is taken as one grain: buffers set between two pushes are queued and played one per grain, at most 16 queued. A grain with no new buffer repeats the last one only when the port has always been given that same buffer (rewritten in place); a port whose buffers move on plays silence instead. Each set also copies the grain from the buffer: once another port of the context is given the same buffer, the grains already queued with it (and its repeat) play that copy instead of the buffer's later contents, as PPSA08587 fills one buffer and hands it to each of its three ports in turn before advancing. A buffer no other port is given is still read when the grain is pushed. Inferred from PPSA12544, whose object port (type 6) is given one grain of a 64-grain ring per set, often two or three between pushes and then none for a few; the console behaviour for several sets per push and for a push without a new buffer is not documented

Check warning on line 22 in docs/dev/debt/libSceAudioOut.md

View workflow job for this annotation

GitHub Actions / conventions

debt-length

Technical debt entry over 1000 characters: state the gap first (what differs from the console, what is unverified, what throws) and keep only the behaviour it needs: 1078 characters
- [sceAudioOutDeviceIdOpen](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - imported by PPSA01317, which fails to load without it; declared without parameters, signature unknown. It opens a port on a given audio device; which device ids exist and how they map to host outputs is unknown, so it throws
- [sceAudioOut2PortGetState](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp) and [sceAudioOut2GetSpeakerInfo](../../../core/libs/prx/libSceAudioOut/src/AudioOut2SpeakerArray.cpp) (libSceAudioOut) - the port state reports the channel count of the port's data format, output main, volume 127 and a zero reroute counter and flags; the speaker info reports a stereo front pair at -30 and +30 degrees. Taking the channel count from the data format follows [SharpEmu](https://github.com/sharpemu/sharpemu/blob/main/src/SharpEmu.Libs/Audio/AudioOut2Exports.cs), another emulator, and no console measurement exists for either call: the output of a pad port, the volume after `sceAudioOut2PortSetAttributes` and the reroute counter on a device change are unverified, as are the speaker types other than stereo. ASTRO BOT and ASTRO's PLAYROOM reportedly stop on an assertion that depends on these values (#4317), but which field matters has not been isolated

Expand Down
Loading