mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-04 13:07:32 +00:00
fix(audioout2): keep each port's grain when ports share a buffer
A title can fill one buffer and hand it to several ports in turn before advancing the context. Reading the buffer only at push time gave every port the contents set last, so the earlier ports played silence. Each set now copies the grain, and a grain whose buffer is handed to another port afterwards plays its copy. Buffers no other port is given are still read at push time.
This commit is contained in:
4 files changed
+67
-6
No files matched your search
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
@@ -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] = "";
|
||||
@@ -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";
|
||||
|
||||
@@ -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() {
|
||||
@@ -299,5 +321,7 @@ int main() {
|
||||
TestAdvancingPortWithoutNewBufferPlaysSilence();
|
||||
TestRewrittenBufferRepeats();
|
||||
TestRenderingSizedByLevelStaysWithinQueueDepth();
|
||||
TestBufferReusedAcrossPortsKeepsEachPortsGrain(false);
|
||||
TestBufferReusedAcrossPortsKeepsEachPortsGrain(true);
|
||||
return 0;
|
||||
}
|
||||
@@ -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
|
||||
- [Legacy vibration ports](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp): PPSA99902's [public input wrapper](https://github.com/Nazky/UnleashedRecomp-Prospero/blob/2ce067a1d51bb58670c7ee4a3aed893c504da805/UnleashedRecomp/hid/driver/sdl_hid.cpp#L316) opens type 10 with 256 stereo S16 or F32 frames at 48000 Hz. Mono duplicates its channel as AudioOut2 does; wider vibration layouts throw. Native controller output uses AudioOut2's four-channel layout, or its six-channel PulseAudio mapping. Without a controller sound card the existing RMS motor transform is applied to segments of at most 256 frames, scheduled by the legacy queue's frame count and rate; drain and close remove only that port's segments. Concurrent sources combine by the maximum per motor, and the pad consumer uses this approximation only for controllers other than DualSense. This host timing and motor model is not a console measurement or waveform reproduction; legacy native output is unmeasured on hardware, and user routing retains the single-controller limitation. AudioOut2 retains its separate 80 ms peak hold.
|
||||
|
||||
- [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
|
||||
|
||||
Reference in new issue
Block a user