Repository navigation
fix(libSceAudioOut): return from a blocking AudioOut2 push once the queue has room - #12954
Conversation
…ueue has room A blocking sceAudioOut2ContextPush waited only before queueing its grain, so the push that filled the queue returned at once. PRAGMATA's Wwise feeds its context from a thread that pushes and then wakes the renderer; the renderer sees a full queue, sleeps, and is woken again only by the game's next RenderAudio, so the mix advanced four grains per game frame. A blocking push now also waits after queueing until a grain finishes, bounded by the existing 200 ms limit.
…PPSA02530 The wait is described in the debt file instead of in code comments.
rooklz
left a comment
There was a problem hiding this comment.
Reviewed head 7a75ea95b3 against main bb04a4f1ea. The merge preview is clean, git diff --check is clean and tools/check_conventions.py --base upstream/main passes.
What I ran
I built a standalone harness on macOS arm64 (Apple clang, ASan+UBSan). It copies GrainDuration, Drain, the device-less QueueLevel branch, WaitForRoom and the blocking/non-blocking control flow of sceAudioOut2ContextPush verbatim from this head. SDL, rendering and tracing are left out. The new test's rows run against it unchanged (depth 2, 1024-frame grains).
| Build | PR rows | Notes |
|---|---|---|
| this head | pass (rounds 1-3 take 22-24 ms each, level 1 afterwards) | |
drop the post-queue WaitForRoom (equivalent to main) |
3 rows fail (level 2 after the push) | the test catches the fix |
drop the pre-queue WaitForRoom |
pass | survives, see 2 |
Other measurements from the same harness:
- A free-running blocking feeder on depth 4 makes 50 pushes/s, which is the playback rate (46.9/s). The pacing works as the body describes.
- Four blocking pushes into an empty depth-4 queue now take about 23 ms. On
mainthey took 0 ms because the fourth push waits for the oldest grain. This is the observable change for titles that prime the queue before they start their clock. The debt line says the console behaviour is unmeasured, which is the right place for it. - If the queue never drains, one blocking push now takes about 404 ms: two 200 ms waits. On
mainit took 200 ms. The debt line already says "each wait ends after at most 200 ms".
Findings
- Comments in the test. The three
//lines aboveTestBlockingPushWaitsForRoomWithoutADeviceare not technical-debt comments. The CI checker accepts them only because the PR also touchesdocs/dev/debt/. AGENTS.md and CONVENTIONS.md still allow only debt comments,#endifand namespace ends, so please drop these lines and keep the explanation in the PR body. - The pre-queue wait is not covered by a test. With the post-queue wait in place, a blocking push only finds the queue full on entry if the previous push gave up after 200 ms. Removing the first
WaitForRoompasses every row. If it is meant to keepmain's "wait before queueing" behaviour after a timeout, a row with a stalled queue would pin it down. Otherwise it could go, which would also bring the worst-case stall back to 200 ms. This is non-blocking. - Non-blocking, wording: the debt line cites PPSA02530 as the inference. Since the change itself is generic, that is fine. "Inferred from one title's feeder pattern" is still the only evidence, so a console timing of
sceAudioOut2ContextPush(ctx, 1)on a fresh depth-N context (when the Nth push returns) would settle it.
Not verified
I did not build the real audio_out2_timing target. It needs the SDL2 submodule, which is not initialised here. I did not run the device path (device != 0, DeviceQueueLevel), Windows or a console.
AI-assisted: yes (Claude Code)
|
Windows run of this head (
AI-assisted: yes (Claude Code). |
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
@hugodearaujo thanks, the analysis of the title's ticker/feeder pattern is clear, the non-blocking path is untouched, and each new wait is bounded (200 ms), so a push can't hang when nothing calls
I checked #12863, which edits the same file: the two combine cleanly in either order. |
MarcZX10
left a comment
There was a problem hiding this comment.
Tests run and reviewed — console behavior unverified
The focused host test passes on the current head and the base-source negative control aborts, but the console’s blocking return timing remains unmeasured.
What I ran
Head bb6d79e52e3d8ec72a604af30d0e4429b193cf66; base bb04a4f1ea0175e9e4f149f0359a764d62ff3296. git merge-tree produced a clean preview tree (9f11fc6a2c604b83f18d0f53e115185ac6c13a9f); no merge was attempted. Environment: Debian Linux x86_64, kernel 6.1.0-54-amd64, GCC 12.2.0, QEMU x86_64 Haswell.
cmake --build /dev/shm/AnyPS5-build-12915-full --target audio_out2_timing_tests -j2
timeout 240s qemu-x86_64 -cpu Haswell /dev/shm/AnyPS5-build-12915-full/tests/audio_out2_timing_tests > /tmp/AnyPS5-12954-audio-out2-timing-pr-exact-final.log 2>&1
python3 tools/check_conventions.py --base upstream/main
git diff --check bb04a4f1ea0175e9e4f149f0359a764d62ff3296...HEAD
git merge-tree --write-tree upstream/main HEADResults
| Check | Result |
|---|---|
audio_out2_timing_tests build |
Pass |
| Focused executable under QEMU Haswell | Pass, exit 0 |
| Conventions | Pass |
git diff --check |
Pass, no output |
| Hosted conventions, eligibility, labels, report | Pass |
| Hosted Windows job | In progress when checked |
| Hosted Linux and specialized jobs | Skipped |
Build output
[0/2] Re-checking globbed directories...
[1/3] Building CXX object core/libs/prx/libSceAudioOut/CMakeFiles/libSceAudioOut.dir/src/AudioOut2Context.cpp.o
[2/3] Linking CXX shared library core/libs/libs/unpatched/libSceAudioOut.prx
[3/3] Linking CXX executable tests/audio_out2_timing_tests
Focused test output
exit=0
qemu-x86_64: warning: TCG doesn't support requested feature: CPUID.01H:ECX.x2apic [bit 21]
qemu-x86_64: warning: TCG doesn't support requested feature: CPUID.01H:ECX.tsc-deadline [bit 24]
qemu-x86_64: warning: TCG doesn't support requested feature: CPUID.07H:EBX.hle [bit 4]
qemu-x86_64: warning: TCG doesn't support requested feature: CPUID.07H:EBX.invpcid [bit 10]
qemu-x86_64: warning: TCG doesn't support requested feature: CPUID.07H:EBX.rtm [bit 11]
Convention check output
All conventions checks passed.
Negative control
| Assertion | Change to code under test | Did the new test fail? |
|---|---|---|
| The PR’s new blocking-push regression test | Restored AudioOut2Context.cpp from base bb04a4f1ea0175e9e4f149f0359a764d62ff3296, keeping the PR test source |
Yes; the test process aborted with exit 134. Require does not print which assertion failed. |
Negative-control output
negative-control-exit=134
qemu: uncaught target signal 6 (Aborted) - core dumped
Findings
Blocking
None found in the focused host implementation/test review.
Non-blocking
- The code now waits both before and after enqueuing. If the queue does not drain, one call can spend nearly the 200 ms timeout in each loop (roughly 400 ms plus scheduling/processing); the debt note currently describes the per-wait limit. Please confirm that the combined bound is intended and document it clearly.
- The debt note correctly says the console’s blocking return point is unmeasured. The local test verifies the chosen host behavior, not console ABI behavior, so I left a comment-only review instead of approving. A console trace of push return time against queue level would resolve this uncertainty.
Sources
- Wait helper and call sites at this head
- Blocking push implementation at this head
- Regression test at this head
- Debt note recording the unmeasured console behavior
Not verified
- Console timing or PS5 ABI behavior.
- Device-backed SDL audio path or native host execution.
- Full
audio_outCTest suite; the PR body reports it, but I did not rerun it. - Windows job was still running; hosted Linux and specialized jobs were skipped.
To get this to a good state
Please confirm the combined timeout is intended and provide a console timing trace if one becomes available; I will rerun these same commands on the new head.
rooklz
left a comment
There was a problem hiding this comment.
Re-review at bb6d79e52e. The only change since 7a75ea95b3 is commit bb6d79e52e, which removes the three // lines above TestBlockingPushWaitsForRoomWithoutADevice. That settles my point 1. The production code (42beeaa468) and the debt line (7a75ea95b3) have not changed.
What I ran
python3 tools/check_conventions.py --base upstream/main: passgit diff --check upstream/main...HEAD: cleangit merge-tree --write-tree upstream/main HEADagainstbb04a4f1ea: cleangit diff upstream/main...HEAD | grep '^+.*//': no added//lines
My harness results from the first review still apply because the code under test is unchanged: the PR rows pass, dropping the post-queue wait turns 3 rows red, and dropping the pre-queue wait survives.
Still open
- These are still optional and non-blocking: my earlier point 2 (a stalled-queue row to cover the pre-queue
WaitForRoom, or drop that wait) and point 3 (a console timing of the Nth blocking push on a fresh depth-N context). - oneandonlydean's three points are not addressed yet: a handshake-driven deterministic test instead of the elapsed-time check, before/after evidence on a real device, and destroy while a push is waiting. I agree the elapsed-time assertion can flake both ways under scheduler preemption. A clock or Advance hook controlled by the test would fix that and would also give a negative control that fails on
main.
AI-assisted: yes
MarcZX10
left a comment
There was a problem hiding this comment.
Reviewed — changes requested
The elapsed-time regression test can fail with correct code under scheduler delay and can pass with the old code, so its current negative control is not reliable.
What I ran
Head bb6d79e52e3d8ec72a604af30d0e4429b193cf66; base bb04a4f1ea0175e9e4f149f0359a764d62ff3296. The merge preview is clean; no merge was attempted. Environment: Debian Linux x86_64, kernel 6.1.0-54-amd64, GCC 12.2.0, QEMU x86_64 Haswell.
cmake --build /dev/shm/AnyPS5-build-12915-full --target audio_out2_timing_tests -j2
timeout 240s qemu-x86_64 -cpu Haswell /dev/shm/AnyPS5-build-12915-full/tests/audio_out2_timing_tests > /tmp/AnyPS5-12954-audio-out2-timing-pr-exact-final.log 2>&1
python3 tools/check_conventions.py --base upstream/main
git diff --check bb04a4f1ea0175e9e4f149f0359a764d62ff3296...HEAD
git merge-tree --write-tree upstream/main HEADResults
| Check | Result |
|---|---|
audio_out2_timing_tests build |
Pass |
| Focused executable under QEMU Haswell | Pass, exit 0 |
| Conventions | Pass |
git diff --check |
Pass, no output |
| Hosted Windows, conventions, eligibility, labels, report | Pass |
| Hosted Linux and specialized jobs | Skipped |
Focused test output
exit=0
qemu-x86_64: warning: TCG doesn't support requested feature: CPUID.01H:ECX.x2apic [bit 21]
qemu-x86_64: warning: TCG doesn't support requested feature: CPUID.01H:ECX.tsc-deadline [bit 24]
qemu-x86_64: warning: TCG doesn't support requested feature: CPUID.07H:EBX.hle [bit 4]
qemu-x86_64: warning: TCG doesn't support requested feature: CPUID.07H:EBX.invpcid [bit 10]
qemu-x86_64: warning: TCG doesn't support requested feature: CPUID.07H:EBX.rtm [bit 11]
Convention output
All conventions checks passed.
Negative control
| Assertion | Change to code under test | Did the new test fail? |
|---|---|---|
| New blocking-push regression test | Restored AudioOut2Context.cpp from base bb04a4f1ea0175e9e4f149f0359a764d62ff3296, leaving the PR test source in place |
Yes in this run: test process aborted with exit 134; Require does not print the failing assertion. The timing-sensitive test can still pass under a different scheduling delay. |
Negative-control output
negative-control-exit=134
qemu: uncaught target signal 6 (Aborted) - core dumped
Findings
Blocking
- The regression test is scheduling-dependent.
TestBlockingPushWaitsForRoomWithoutADeviceasserts that each measured push takes at least half a grain and checks queue level after return. If the test thread is descheduled between pushes, playback can drain a slot before a correct call begins, making the elapsed-time assertion fail. If it is descheduled after recordingstartbut before callingPush, that delay can satisfy the threshold on the old implementation; a pause after the old call returns can also let the queue-level check observe a drained slot. Please replace the elapsed-time oracle with a handshake and test-controlled playback clock/advance: establish a full queue, verify the push remains blocked, release one slot, and verify it then returns. Run the same test against the base implementation as a deterministic negative control.
Non-blocking
- No changed-code before/after run on a real audio device or console was available to me. The PR itself says its title runs used SDL dummy/no-device audio, and its device-backed run was without this change; the device-backed path and console return condition remain unverified.
- The post-enqueue wait unlocks the context while sleeping. The existing pre-enqueue wait also unlocks, but this adds another interval in which concurrent
ContextDestroycould race with a waiting push. I could not establish whether the API permits destroy concurrent with push; please ensure the lifetime is safe or document that restriction.
Sources
- Wait helper and both call sites at this head
- Push lock and wait flow at this head
- Timing-sensitive test at this head
- Debt note that says console return timing is unmeasured
Not verified
- Console timing, PS5 ABI behavior, or device-backed SDL behavior.
- Full
audio_outCTest suite; the PR body reports it, but I did not rerun it. - Native host execution; the focused executable was run under QEMU.
To get this to a good state
Replace the timing-based test with a synchronized clock/advance test and confirm the push/context-destroy lifetime contract. I will rerun these same commands on the new head.
|
Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.
|
…e; note destroy during a push is unsupported Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
rooklz
left a comment
There was a problem hiding this comment.
Re-review at 1ed07de320. The production code (42beeaa468) has not changed. Commit 1ed07de320 replaces the elapsed-time check in TestBlockingPushWaitsForRoomWithoutADevice with a queue-level check after each push. It also adds one sentence to the debt line: destroying a context while a push waits on it is not supported.
What I ran
python3 tools/check_conventions.py --base upstream/main: passgit diff --check upstream/main...HEAD: cleangit merge-tree --write-tree upstream/main HEADagainstbb04a4f1ea: clean. No added//lines.- Standalone harness on macOS arm64 (Apple clang 21), built from slices copied verbatim from this head:
FULL_WAIT_*,GrainDuration,Drain, the device-lessQueueLevelbranch andWaitForRoom.Pushis reduced to pre-wait /queued++; playHead += grain/ post-wait. The four-round loop andlevel < queueDepth && available > 0match the new test (depth 2, 1024-frame grains).
| variant | -O2 | -O2, all cores busy (yes × ncpu) |
ASan+UBSan | TSan |
|---|---|---|---|---|
| head | 0/100 fail | 0/100 fail | 0/50 fail | 0/20 fail |
post-queue wait dropped (= main) |
100/100 fail | 100/100 fail | 50/50 fail | |
| pre-queue wait dropped | 0/100 fail |
One run takes 0.07 s.
Why the new check is deterministic
Once a push returns, nothing else adds to the queue before GetQueueLevel, and Drain can only lower the level. So correct code cannot fail because of a scheduler delay. The only way it could fail is the 200 ms timeout expiring while the queue is still full, and a 21 ms grain always drains well before that. Old code returns at level 2 in rounds 1 to 3. It could only pass if the thread were descheduled for more than one grain between the push and the poll, in all three rounds, and that never happened even with every core busy. The flake that MarcZX10 and oneandonlydean pointed out (point 1) is fixed.
Still open
- oneandonlydean's point 2: before/after evidence on a real audio device. This review cannot settle that.
- Point 3 is now handled by the debt sentence, which was one of the two options offered.
- Optional, same as before: the pre-queue
WaitForRoomis still not covered by a test (the mutant survives). A stalled-queue row would cover it, or that wait could be dropped.
AI-assisted: yes
|
Thanks for the reviews, and @adriadam10 for the level-based test and the destroy note. Status:
@MarcZX10 the combined bound is intended: 1f90599 adds to the debt entry that on a queue that never drains a blocking push returns after up to 400 ms (200 ms before queueing, 200 ms after). @rooklz on the pre-queue wait: I'd keep it. Without it, a blocking push that finds the queue already full (filled by non-blocking pushes, by another thread, or after a previous timeout) would queue a grain beyond the depth, which |
MarcZX10
left a comment
There was a problem hiding this comment.
Re-checked — new head, changes requested
The elapsed-time threshold is gone, but the new queue-level assertion can still pass the old behavior if the test thread is descheduled after Push returns and before it reads the queue.
What I ran
- Exact PR head:
1f905993a3463cc9aa39460b77491bc9eb4405d7; recorded PR base:bb04a4f1ea0175e9e4f149f0359a764d62ff3296. - Current
upstream/main:1189ec3003672bb364dd9f3119ead7b3976e3c83; merge preview is clean, tree60a6cfd16dca697446a9d58dc37cc99ca499ae43. - Environment: Linux workspace; no compiler, build or test command was run.
git merge-tree --write-tree upstream/main refs/review/pr-12954-current
git diff --check upstream/main...refs/review/pr-12954-current
python3 tools/check_conventions.py --base upstream/main --head refs/review/pr-12954-currentResults
| Check | Result |
|---|---|
| Current-main merge preview | Clean |
git diff --check |
Passed; no output |
| Conventions | Passed |
| Hosted Windows job | In progress when refreshed |
| Product Linux and specialized jobs | Skipped by workflow policy |
| Focused tests and PRAGMATA run | Author-reported in the latest report; not independently run here |
Unedited local command output
$ git merge-tree --write-tree upstream/main refs/review/pr-12954-current
60a6cfd16dca697446a9d58dc37cc99ca499ae43
$ git diff --check upstream/main...refs/review/pr-12954-current
[no output; exit status 0]
$ python3 tools/check_conventions.py --base upstream/main --head refs/review/pr-12954-current
All conventions checks passed.
Negative control
| Assertion | Mutation | Result |
|---|---|---|
After a blocking push, queue level is below queueDepth |
Remove the post-enqueue WaitForRoom |
Not exercised in this review. The current test reads queue level after the call using the live clock, so a scheduling pause can let the old implementation pass. |
Findings
Blocking
TestBlockingPushWaitsForRoomWithoutADevice checks the queue after each synchronous push, but does not control playback or observe that the full-queue push is blocked. QueueLevel advances from the live clock; if the test thread is paused for a grain after the old implementation returns, the queue drains before the assertion and the regression is missed. Please make the playback/clock progression test-controlled (or use an equivalent handshake), assert that the full-queue push remains blocked until a slot is released, and show that the old implementation fails the same test.
Non-blocking
The author records context destruction concurrent with a waiting push as unsupported; console return timing remains unmeasured. I did not find a new source-level blocker in those stated limitations.
Sources
Not verified
I did not build or run the focused test locally, run a negative control, or verify console behavior. The test and title results cited above are author-reported.
To get this to a good state
Add a controlled blocking/release test and a red control using the previous push behavior. I will rerun the same source checks and review the exact new head.
…back frees a slot A device-less context can run its queue model on a clock the test sets. With the clock frozen, a blocking push that fills the queue is seen still waiting with its grain queued and not counted, and returns once the test plays one grain; a blocking push that meets a queue filled by non-blocking pushes does not queue past the depth.
|
@MarcZX10 thanks. The new head adds a test clock for the device-less queue model (
Red controls, 3 of 3 each: |
MarcZX10
left a comment
There was a problem hiding this comment.
Re-checked — new head, test concern addressed
The test-clock changes at head 3fc5485e6c6cc0b25e27d9db9e1b7c3504f56695 replace the scheduler-sensitive oracle with controlled blocking and playback checks, addressing the prior test blocker.
I inspected the new test and clock-injection diff against the prior reviewed head 1f905993a3463cc9aa39460b77491bc9eb4405d7. The author reports 52/52 audio_out tests passing three times and deterministic red controls for the base implementation and for removing either wait; I did not run them locally. The current-main change in the shared implementation file since the PR base is a pad-rumble call and does not touch this wait path. The hosted Windows job was still in progress when refreshed; Linux and specialized jobs are skipped. Console blocking-return timing remains unmeasured, so this is a comment-only recheck, not an approval.
# Conflicts: # core/libs/prx/libSceAudioOut/src/AudioOut2Context.cpp
Re-checked — host tests pass, console behavior unverifiedThe test-clock regression removes the scheduler-sensitive assertion, and the exact head |
|
@MarcZX10 thanks for re-checking. A payload probe for exactly that is ready: a fresh depth-4 context with no ports, a non-blocking phase and then blocking pushes with the return time and queue level logged around each one, so it shows when the console's Nth blocking push returns. I'll run it on the console as soon as I have access to it again and post the raw lines here; the debt entry stays as it is until then. |
|
Checked the blocking push's two waits against current |
What
sceAudioOut2ContextPushwithblocking1 waited only before queueing its grain, while the queue was full. The push that fills the queue therefore returned at once, with the queue at its depth.PRAGMATA (PPSA02530) feeds the context its sound engine renders into (queue depth 4) from three threads: a ticker polls
sceAudioOut2ContextGetQueueLevel, callssceAudioOut2ContextAdvanceonce per free slot and signals a semaphore per slot; a feeder thread waits on that semaphore, callssceAudioOut2ContextPush(ctx, 1)and then sets the event flag that wakes the render thread; the render thread waits on that flag with no timeout. With the push returning as soon as the grain was queued, the fourth push left the queue full, the woken render thread found no free slot and slept, and nothing woke it until the title's next frame. Every call traced on one context (local diagnostic, not part of this PR):So the mix advanced four 21.3 ms grains per rendered frame, and at the 1-3 fps the title renders at under AnyPS5 the context took 0.5-6 pushes per second instead of 46.9. The real-time scene after the opening film follows that audio: it crawls and then stops on one frame, on one of the opening credits, for the rest of the run (in every run that got that far, from about 485 s after start on), while rendering continues and the title still takes input.
A blocking push now also waits after queueing until fewer grains than the queue depth wait to be played, with the existing 200 ms limit, so the feeder is paced by playback and wakes the renderer once per grain. A non-blocking push is unchanged. When the console's blocking push returns is not measured; the debt entry says so.
Tested
Linux (Arch, kernel 7.2.11, GCC 16.2.1, Release), on
mainbb04a4f.New
TestBlockingPushWaitsForRoomWithoutADeviceincore/libs/prx/libSceAudioOut/tests/AudioOut2Timing.cpp(run byaudio_out2_timing): a blocking push that fills a context's queue must not return before the queue has room again. With onlyAudioOut2Context.cpprestored frommain,audio_out2_timingfails (Subprocess aborted).Real audio device, before and after with exact builds. Both builds are
mainbb04a4f + fix(libScePad): return an error for unsupported port probes #10935 6076b85 + fix(shader): load and store 16-bit data of converted packed images #11580 fedf3e9 + fix(agc): keep at most 4 GiB of the host heap from the host import budget #7844 89d6a7c + fix(libc): leave a range that a mutation waits for out of new leases #6565 f2029f9 + fix(libSceAvPlayer): keep synced video at the presentation clock when the title takes frames slowly #12898 d2d4331 + fix(agc): keep buffer owners when a staged address space dissolves #10304 31f895c + fix(agc): refuse writable buffer feedback with native depth #10497 8a0dcd5 + fix(agc): prepare a draw or dispatch again when the memory it captured changed before it was recorded #7446 a4a99c3 + fix(agc): enable independentBlend for color targets with different blend states #6782 4433a62 (0109385f31, the open PRs PRAGMATA needs to reach that scene on currentmain), one with this PR's head 1ed07de merged on top (ea561be1f7); nothing else differs (no local diagnostics; onlylibSceAudioOut.prxchanges). Audio went through SDL's PulseAudio backend into a PipeWire null sink, and both AudioOut2 contexts opened a device (SDL device 2 opened,SDL device 3 opened). Same warmed shader cache, 1400 s each,APS5_TRACE_AUDIOOUT2=1for the per-second summary:primes)vkQueueSubmit recorder: Vulkan result -4; DISPATCH_INDIRECT on queue 0x0)Deterministic blocking/release tests on a test clock (
AudioOut2Timing.cpp): a device-less context can run its queue model onclockForTest, so playback and the 200 ms wait limit only advance when the test moves the clock.TestBlockingPushStaysBlockedUntilPlaybackFreesASlot: with the clock frozen and depth - 1 grains queued, a blocking push from another thread is observed under the context lock with its grain queued (queued == depth) but not yet counted (pushes == depth - 1), its future still pending and the level at depth. Playing one grain on the test clock returns it with 0 and the level at depth - 1.TestBlockingPushOnAFullQueueQueuesOnlyOnceASlotIsFree: with the queue filled by non-blocking pushes, a blocking push never takesqueuedpast the depth, and returns once two grains are played.main'sAudioOut2Context.cpp(bb04a4f) failspushes == depth - 1; the head without the post-queue wait fails the same assertion; the head without the pre-queue wait failsqueued <= depth. On the old code, queueing and counting the push happen in one critical section, so the first assertion fails however the threads are scheduled. The head passes 3 of 3.ctest -R audio_out: 52 of 52 pass, three times in a row.main102c4f9 merged in (56fd9ea):AudioOut2Context.cppconflicted wheremainreplaced the handle cast with its context registry (AudioOut2FindContext); the resolution keeps the registry and this PR's test clock.ctest -R audio_out: 56 of 56, three times in a row.PRAGMATA (PPSA02530), 1000 s runs from boot through the menus into the opening film and the real-time scene after it, SDL
dummyaudio, with a local diagnostic that logs the context's pushes per second. Both builds are the same local integration build PRAGMATA needs to get there (maine61a7c7 plus open PRs, including fix(libc): leave a range that a mutation waits for out of new leases #6565 and fix(libSceAvPlayer): keep synced video at the presentation clock when the title takes frames slowly #12898) and differ only in this change toAudioOut2Context.cpp:4.1 pushes/sat 400 s,0.5 pushes/sat 543 s)46.9 pushes/sfor the rest of both runs, no underruns)Under the
dummydriver only one AudioOut2 context gets a device (SDL_OpenAudioDevice failed: Audio device already open), so the engine's context runs on the device-less clock. A run with the SDLdiskdriver, where both contexts got a device, stopped at the same point without this change (6-10 pushes per second).tools/check_conventions.py --base origin/mainpasses andgit diff --checkis clean.Checklist
main; no other open PR implements the same functions (docs(libSceAudioOut): count the blocking values passed to sceAudioOut2ContextPush #12698 only documents theblockingvalues titles pass)NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file.mdfiles or images added to the repository (attach them to this PR)