mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
Audio: Don't emulate a failed blocking wait leaving the channel busy for good
When sceAudioOutputBlocking has to wait and the wait fails at once (interrupts or dispatch disabled, inside an interrupt), the firmware returns the error but leaves the channel's waiting flag set, and the channel can never be used or released again. Keep the error, drop the rest: whether the channel was busy at that moment is timing, and a small difference in ours could lose a channel for the rest of a game where hardware wouldn't. No game can depend on losing one. Savestates made while this was emulated have the flag cleared on load. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
68dbbb253d
commit
8ed2d170f5
4 files changed
+23
-14
No files matched your search
@@ -345,12 +345,12 @@ u32 __AudioEnqueueBlocking(AudioChannel &chan, u32 samplePtr, int leftVol, int r
|
||||
if (chan.waitingThread != 0) {
|
||||
return SCE_ERROR_AUDIO_CHANNEL_BUSY;
|
||||
}
|
||||
// The driver sets the channel's waiting flag before its event flag wait, and if that wait
|
||||
// fails at once (interrupts or dispatch disabled, inside an interrupt), it returns the error
|
||||
// without clearing it. From then on the channel is busy for good: only a successful wait
|
||||
// clears the flag, and release is refused while it's set. (audio.prx, sceAudioOutputBlocking.)
|
||||
// The event flag wait fails at once with interrupts or dispatch disabled, or inside an
|
||||
// interrupt. The driver returns that error without clearing the waiting flag it set, which
|
||||
// leaves the channel busy for good (audio.prx, sceAudioOutputBlocking). That's deliberately not
|
||||
// emulated: whether the channel was busy at that moment is timing, and a small difference in
|
||||
// ours would drop a channel for the rest of the game where hardware wouldn't.
|
||||
if (__IsInInterrupt() || !__KernelIsDispatchEnabled()) {
|
||||
chan.waitingThread = AUDIO_WAITING_ABANDONED;
|
||||
return __IsInInterrupt() ? SCE_KERNEL_ERROR_ILLEGAL_CONTEXT : SCE_KERNEL_ERROR_CAN_NOT_WAIT;
|
||||
}
|
||||
|
||||
@@ -367,7 +367,7 @@ u32 __AudioEnqueueBlocking(AudioChannel &chan, u32 samplePtr, int leftVol, int r
|
||||
static bool __AudioChannelFinished(AudioChannel &chan) {
|
||||
chan.sampleAddress = 0;
|
||||
chan.remainingSamples = 0;
|
||||
if (chan.waitingThread == 0 || chan.waitingThread == AUDIO_WAITING_ABANDONED) {
|
||||
if (chan.waitingThread == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,10 @@ void AudioChannel::DoState(PointerWrap &p) {
|
||||
if (s >= 4) {
|
||||
Do(p, remainingSamples);
|
||||
Do(p, waitingThread);
|
||||
// Briefly, a failed blocking wait left -1 here to keep the channel busy for good.
|
||||
if (waitingThread < 0) {
|
||||
waitingThread = 0;
|
||||
}
|
||||
Do(p, waitingAddress);
|
||||
Do(p, waitingLeftVolume);
|
||||
Do(p, waitingRightVolume);
|
||||
|
||||
@@ -32,8 +32,6 @@ const u32 PSP_AUDIO_CHANNEL_MAX = 8;
|
||||
|
||||
// Mixer channels wait on their own index plus one, so the SRC channel takes the id after them.
|
||||
const int PSP_AUDIO_SRC_WAIT_ID = PSP_AUDIO_CHANNEL_MAX + 1;
|
||||
// In AudioChannel::waitingThread: the flag was set by a wait that failed, and nobody will clear it.
|
||||
const SceUID AUDIO_WAITING_ABANDONED = -1;
|
||||
|
||||
// One buffer handed over and not yet fully played.
|
||||
struct AudioPendingBuffer {
|
||||
@@ -65,7 +63,6 @@ struct AudioChannel {
|
||||
// A second thread arriving while one is parked here is told the channel is busy rather than
|
||||
// queueing up behind it. These remember what the parked one wanted to hand over, so the
|
||||
// enqueue can be retried once the buffer finishes.
|
||||
// AUDIO_WAITING_ABANDONED: see __AudioEnqueueBlocking().
|
||||
SceUID waitingThread = 0;
|
||||
u32 waitingAddress = 0;
|
||||
int waitingLeftVolume = 0;
|
||||
|
||||
Reference in new issue
Block a user