Two things the last pass looked at and left, now measured on hardware by the new
audio/blocking/vaudio and audio/blocking/overhead.
sceVaudioChRelease is not shaped like the Output2 and SRC releases. It hands the
channel a null pointer first, which waits for a buffer to finish, so it blocks for
one buffer and returns 0 where the others would refuse - and the buffer is played
out instead of being dropped, which is what we were doing. The reservation goes
now rather than when the drain finishes: the caller is parked either way and gets
the same answer at the same time, and the buffers keep playing because the mixer
does not look at the reservation.
The same test turned up two more: a *failed* sceVaudioChReserve still marks vaudio
reserved, so a caller that lost the channel to Output2 is told 0x80000021 next
time until a release clears it; and sceVaudioChRelease ignores that flag entirely
and releases whatever holds the SRC channel, which pspautotests already called the
"wrong release".
The flat 10000 cycles charged to every Output2 and SRC output turns out to be
right only for the refused case. Every other outcome ends up querying the codec
and costs over 100us on hardware, including finding the channel unreserved, so
those now cost 25000. Mixer channels are the other way round - cheap to refuse,
expensive on the one output that starts the DMA - so that charge moved to the DMA
start. F1 2009 behaves identically and Burnout Dominator's mixed output is
bit-identical to before.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The nine channels were one array of one struct, but only index 8 ever used the
two DMA descriptors, the played/fraction position and the waiting-thread vector,
and only 0-7 ever used the buffer slot - so eight copies of a std::vector sat
there dead. They are two different pieces of hardware and now they are two
structs: AudioChannel for the eight the mixer walks, and a single AudioSRCChannel
for the one the codec reads directly.
Found while rereading it:
- __AudioUpdate can now run from inside an output call, and it reschedules when
it wakes a thread. A context switch there lands before the syscall writes its
return value, so the caller loses it and the woken thread gets it instead.
Deferred with hleReSchedule when a syscall is in flight. The same fix applies
to the release path, which had the problem before this branch existed.
- A finished SRC buffer whose waiting thread had given up dropped the completion
entirely, so the next caller waited a buffer too long. Now it walks past dead
waiters and banks the completion if nobody is left.
- An output with a null pointer changed the channel volume, where the hardware
returns before touching it.
- A busy channel came back as an error from the Output2 path and as debug from
the mixer path. It is an ordinary answer a game polls on, so both are debug
now; the old behavior filled the log with 18k error lines in a minute of F1
2009.
- AudioChannel::reset had no callers left: releasing a channel with a thread
parked on it is refused, so there is nobody to wake.
- The two routing modes are globals and were written once per channel. They get
their own small savestate block.
Savestate: the sceAudio section goes to 3, and the SRC channel gets a section of
its own. States from released builds carry it as a ninth channel record, which is
read and discarded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
I deduced that this was the case, and attempted implementing this path long ago,
but I could never quite get it to work in all games. Set Claude on a quest to
research and implement it, and lo and behold, it works. A bit sobering.
Fixes#12888 and likely more. Additionally, audio latency is likely slightly
improved overall, and memory usage is down by 4.6MB.
Claude says:
The blocking output calls are not a queue that callers line up behind. Each
mixer channel holds exactly one buffer and at most one parked thread; a second
thread arriving while the first is waiting is told the channel is busy and is
expected to skip its turn. The Output2/SRC channel holds two DMA descriptors and
refuses a third caller outright, without waiting at all.
We blocked everyone instead, so a game running a movie thread and a sound-effect
thread over one output made the two alternate - a frame of movie audio, a frame
of effects silence - and the movie played at half rate. That is #12888, seen in
F1 2009 and Colin McRae: DiRT 2. With this, the movie thread keeps the channel
for the whole cutscene and the effects thread is refused, which is what the
hardware trace shows.
The driver also never copies a buffer on the way in: it stores the pointer and
its mixer walks it forward 64 samples at a time out of the game's own memory.
Modelling that fixes#20095 as a side effect, and drops the 4.6MB of per-channel
sample rings we were carrying. The mix event is re-phased to the moment a DMA
starts, since the mixer thread outranks its caller and gets a block in before the
output call returns.
Along the way: the two rest-length calls differ after a null-pointer output,
sceAudioChRelease reports not-reserved rather than not-init,
sceAudioChangeChannelConfig validates the format,
sceAudioChangeChannelVolume validates nothing, and sceAudioOutput2ChangeLength
takes a range of 17..4111. Details in docs/sceAudio.md.
Savestates: AudioChannel goes to version 4. Older ones stored mixed samples that
can't become a pointer and a position again, so they load with the pending audio
dropped and any parked threads released.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The channel took only 256, 1024 and 2048 samples, so a game that hands it MP3
frames got SCE_KERNEL_ERROR_INVALID_SIZE and no music. Dead or Alive Paradise
does exactly that from its music player: sceVaudioChReserve(1152, 44100, 2),
1152 being the MPEG-1 Layer III frame size.
The format check moves below the sample count check to match: the module
returns 0x80000104 for a bad count before it ever looks at the format, so a
call with both wrong got the wrong error out of us. The two error codes we
already returned are the ones it uses.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
GetFreeChannel counted down with an unsigned loop variable, so i >= 0
was always true. With every channel already reserved it wrapped past
zero and kept indexing g_audioChans until it walked off the end -
sceAudioChReserve(-1, ...) segfaulted the emulator instead of returning
"no channels available". Reproduces on audio/sceaudio/reserve, which
crashed before printing anything.
Also gives sceVaudioChReserve the parameter checks it never had. It
took any sample count, channel count and frequency; the hardware allows
256, 1024 or 2048 samples, stereo only, and the same sample rates the
SRC channel accepts. Every value in the test now matches - what's left
there is only reschedule markers.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JvJR8oJNSCimCM9KXVLjfq
* Rename LogType to Log
* Explicitly use the Log:: enum when logging. Allows for autocomplete when editing.
* Mac/ARM64 buildfix
* Do the same with the hle result log macros
* Rename the log names to mixed case while at it.
* iOS buildfix
* Qt buildfix attempt, ARM32 buildfix
sceAudioSetFrequency is kernel mode only, and it sounds like it changes
the audio controller clock (didn't test.) SRC only affects the SRC
channel.
Currently, this doesn't actually use the sample rate yet, but at least
it's tracked appropriately.