diff --git a/AGENTS.md b/AGENTS.md index c670084070..4a3b78baa1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -19,6 +19,7 @@ for it: | [docs/pspautotests.md](docs/pspautotests.md) | Workflow for improving PPSSPP using pspautotests | | [docs/pspautotests-hardware.md](docs/pspautotests-hardware.md) | Writing a new pspautotest, and running it on a real PSP over PSPLink to record its `.expected` | | [docs/frametest.md](docs/frametest.md) | Framedump rendering tests | +| [docs/sceAudio.md](docs/sceAudio.md) | How the audio output calls block, how deep they buffer, and what each error means | | [docs/WebSocketDebugger.md](docs/WebSocketDebugger.md) | WebSocket debugger protocol reference | | [docs/reverse-engineering.md](docs/reverse-engineering.md) | Disassembling a firmware PRX with `--re-module`, to find out what the hardware actually does | diff --git a/Core/HLE/FunctionWrappers.h b/Core/HLE/FunctionWrappers.h index 926ed29f72..fc7765fd23 100644 --- a/Core/HLE/FunctionWrappers.h +++ b/Core/HLE/FunctionWrappers.h @@ -307,6 +307,11 @@ template void WrapU_UIIIII() { RETURN(retval); } +template void WrapU_IIIIIU() { + u32 retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4), PARAM(5)); + RETURN(retval); +} + template void WrapU_UIIIU() { u32 retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4)); RETURN(retval); diff --git a/Core/HLE/__sceAudio.cpp b/Core/HLE/__sceAudio.cpp index 4d433d3b45..763703bb15 100644 --- a/Core/HLE/__sceAudio.cpp +++ b/Core/HLE/__sceAudio.cpp @@ -37,6 +37,7 @@ #include "Core/ELF/ParamSFO.h" #include "Core/HLE/sceKernelTime.h" #include "Core/HLE/ErrorCodes.h" +#include "Core/HLE/HLE.h" #include "Core/HLE/__sceAudio.h" #include "Core/HLE/sceAudio.h" #include "Core/HLE/sceKernel.h" @@ -47,10 +48,6 @@ // atomic locks are used on the lock. TODO: make this lock-free std::atomic_flag atomicLock_; -// We copy samples as they are written into this simple ring buffer. -// Might try something more efficient later. -FixedSizeQueue chanSampleQueues[PSP_AUDIO_CHANNEL_MAX + 1]; - int eventAudioUpdate = -1; // TODO: This is now useless and should be removed. Just scared of breaking states. @@ -72,11 +69,6 @@ WaveFileWriter g_wave_writer; static bool m_logAudio; #endif -// High and low watermarks, basically. For perfect emulation, the correct values are 0 and 1, respectively. -// TODO: Tweak. Hm, there aren't actually even used currently... -static int chanQueueMaxSizeFactor; -static int chanQueueMinSizeFactor; - static void hleAudioUpdate(u64 userdata, int cyclesLate) { // Schedule the next cycle first. __AudioUpdate() may consume cycles. CoreTiming::ScheduleEvent(audioIntervalCycles - cyclesLate, eventAudioUpdate, 0); @@ -100,9 +92,6 @@ void __AudioInit() { mixFrequency = 44100; srcFrequency = 0; - chanQueueMaxSizeFactor = 2; - chanQueueMinSizeFactor = 1; - __AudioCPUMHzChange(); eventAudioUpdate = CoreTiming::RegisterEvent("AudioUpdate", &hleAudioUpdate); @@ -110,10 +99,11 @@ void __AudioInit() { CoreTiming::ScheduleEvent(audioIntervalCycles, eventAudioUpdate, 0); CoreTiming::ScheduleEvent(audioHostIntervalCycles, eventHostAudioUpdate, 0); - for (u32 i = 0; i < PSP_AUDIO_CHANNEL_MAX + 1; i++) { + for (u32 i = 0; i < PSP_AUDIO_CHANNEL_MAX; i++) { g_audioChans[i].index = i; g_audioChans[i].clear(); } + g_audioSRC.clear(); mixBuffer = new s32[hwBlockSize * 2]; clampedMixBuffer = new s16[hwBlockSize * 2]; @@ -123,7 +113,7 @@ void __AudioInit() { } void __AudioDoState(PointerWrap &p) { - auto s = p.Section("sceAudio", 1, 2); + auto s = p.Section("sceAudio", 1, 3); if (!s) return; @@ -141,33 +131,57 @@ void __AudioDoState(PointerWrap &p) { mixFrequency = 44100; } - if (s >= 2) { - // TODO: Next time we bump, get rid of this. It's kinda useless. - auto s = p.Section("resampler", 1); - if (p.mode == p.MODE_READ) { - System_AudioClear(); - } - } else { - // Only to preserve the previous file format. Might cause a slight audio glitch on upgrades? + // Version 1 kept the whole mixed output queue here, and version 2 replaced it with an empty + // "resampler" section that never held anything. Version 3 drops that too. Either way the + // contents go in the bin: the backend is cleared and the game refills it within a block. + if (s == 2) { + auto resampler = p.Section("resampler", 1); + } else if (s < 2) { FixedSizeQueue outAudioQueue; outAudioQueue.DoState(p); - + } + if (p.mode == p.MODE_READ) { System_AudioClear(); } + // Before v3 the SRC channel was a ninth entry in this array rather than its own thing, so + // older states carry one extra record here. int chanCount = ARRAY_SIZE(g_audioChans); Do(p, chanCount); - if (chanCount != ARRAY_SIZE(g_audioChans)) - { + const int expected = s >= 3 ? (int)ARRAY_SIZE(g_audioChans) : (int)ARRAY_SIZE(g_audioChans) + 1; + if (chanCount != expected) { ERROR_LOG(Log::sceAudio, "Savestate failure: different number of audio channels."); p.SetError(p.ERROR_FAILURE); return; } for (int i = 0; i < chanCount; ++i) { - g_audioChans[i].index = i; - g_audioChans[i].DoState(p); + if (i < (int)ARRAY_SIZE(g_audioChans)) { + g_audioChans[i].index = i; + g_audioChans[i].DoState(p); + } else { + // The ninth entry is the SRC channel in its old shape. Its queued audio is the old + // sample-ring format and can't be converted, but what reserve agreed on carries + // over - throwing that away is what leaves Output2 unreserved after the load. + AudioChannel old; + old.index = i; + old.DoState(p); + if (p.mode == p.MODE_READ) { + g_audioSRC.clear(); + g_audioSRC.reserved = old.reserved; + g_audioSRC.sampleCount = old.sampleCount; + g_audioSRC.leftVolume = old.leftVolume; + g_audioSRC.rightVolume = old.rightVolume; + g_audioSRC.format = old.format; + } + } } + if (s >= 3) { + g_audioSRC.DoState(p); + __AudioRoutingDoState(p); + } + // For older states the routing modes were read back once per channel, above. + __AudioCPUMHzChange(); } @@ -176,10 +190,11 @@ void __AudioShutdown() { delete [] clampedMixBuffer; mixBuffer = 0; - for (u32 i = 0; i < PSP_AUDIO_CHANNEL_MAX + 1; i++) { + for (u32 i = 0; i < PSP_AUDIO_CHANNEL_MAX; i++) { g_audioChans[i].index = i; g_audioChans[i].clear(); } + g_audioSRC.clear(); #ifndef MOBILE_DEVICE if (g_Config.bDumpAudio) { @@ -188,132 +203,269 @@ void __AudioShutdown() { #endif } -u32 __AudioEnqueue(AudioChannel &chan, int chanNum, bool blocking) { - u32 ret = chan.sampleCount; +// The audio driver never copies a buffer on the way in. It stores the pointer, and its mixer +// thread reads 64 samples straight out of the game's memory every DMA block, walking +// sampleAddress forward until the buffer is spent. Everything below follows that shape; see +// docs/sceAudio.md for the behavior this is modelled on. - if (chan.sampleAddress == 0) { - // For some reason, multichannel audio lies and returns the sample count here. - if (chanNum == PSP_AUDIO_CHANNEL_SRC || chanNum == PSP_AUDIO_CHANNEL_OUTPUT2) { - ret = 0; - } - } - - // If there's anything on the queue at all, it should be busy, but we try to be a bit lax. - //if (chanSampleQueues[chanNum].size() > chan.sampleCount * 2 * chanQueueMaxSizeFactor || chan.sampleAddress == 0) { - if (chanSampleQueues[chanNum].size() > 0) { - if (blocking) { - // TODO: Regular multichannel audio seems to block for 64 samples less? Or enqueue the first 64 sync? - int blockSamples = (int)chanSampleQueues[chanNum].size() / 2 / chanQueueMinSizeFactor; - - if (__KernelIsDispatchEnabled()) { - AudioChannelWaitInfo waitInfo = {__KernelGetCurThread(), blockSamples}; - chan.waitingThreads.push_back(waitInfo); - // Also remember the value to return in the waitValue. - __KernelWaitCurThread(WAITTYPE_AUDIOCHANNEL, (SceUID)chanNum + 1, ret, 0, false, "blocking audio"); - } else { - // TODO: Maybe we shouldn't take this audio after all? - ret = SCE_KERNEL_ERROR_CAN_NOT_WAIT; - } - - // Fall through to the sample queueing, don't want to lose the samples even though - // we're getting full. The PSP would enqueue after blocking. - } else { - // Non-blocking doesn't even enqueue, but it's not commonly used. - return SCE_ERROR_AUDIO_CHANNEL_BUSY; - } - } - - if (chan.sampleAddress == 0) { - return ret; - } - - // NOTE: The below is WRONG! See issue #20095. - // - // What we should be queueing here is just the sampleAddress and sampleCount. Then when dequeuing is when we should - // read the actual data. - - int leftVol = chan.leftVolume; - int rightVol = chan.rightVolume; - - if (leftVol == (1 << 15) && rightVol == (1 << 15) && chan.format == PSP_AUDIO_FORMAT_STEREO && IS_LITTLE_ENDIAN) { - // TODO: Add mono->stereo conversion to this path. - - // Good news: the volume (1 << 15), specifically, doesn't affect the values at all. - // We can just do a direct memory copy. - const u32 totalSamples = chan.sampleCount * (chan.format == PSP_AUDIO_FORMAT_STEREO ? 2 : 1); - s16 *buf1 = 0, *buf2 = 0; - size_t sz1, sz2; - chanSampleQueues[chanNum].pushPointers(totalSamples, &buf1, &sz1, &buf2, &sz2); - - if (Memory::IsValidAddress(chan.sampleAddress + (totalSamples - 1) * sizeof(s16_le))) { - Memory::Memcpy(buf1, chan.sampleAddress, (u32)sz1 * sizeof(s16)); - if (buf2) - Memory::Memcpy(buf2, chan.sampleAddress + (u32)sz1 * sizeof(s16), (u32)sz2 * sizeof(s16)); - } - } else { - // Remember that maximum volume allowed is 0xFFFFF so left shift is no issue. - // This way we can optimally shift by 16. - leftVol <<=1; - rightVol <<=1; - - if (chan.format == PSP_AUDIO_FORMAT_STEREO) { - const u32 totalSamples = chan.sampleCount * 2; - - s16_le *sampleData = (s16_le *) Memory::GetPointerOrException(chan.sampleAddress); - - // Walking a pointer for speed. But let's make sure we wouldn't trip on an invalid ptr. - if (Memory::IsValidAddress(chan.sampleAddress + (totalSamples - 1) * sizeof(s16_le))) { - s16 *buf1 = 0, *buf2 = 0; - size_t sz1, sz2; - chanSampleQueues[chanNum].pushPointers(totalSamples, &buf1, &sz1, &buf2, &sz2); - AdjustVolumeBlock(buf1, sampleData, sz1, leftVol, rightVol); - if (buf2) { - AdjustVolumeBlock(buf2, sampleData + sz1, sz2, leftVol, rightVol); - } - } - } else if (chan.format == PSP_AUDIO_FORMAT_MONO) { - // Rare, so unoptimized. Expands to stereo. - if (Memory::IsValidRange(chan.sampleAddress, chan.sampleCount * sizeof(s16))) { - for (u32 i = 0; i < chan.sampleCount; i++) { - s16 sample = (s16)Memory::ReadUnchecked_U16(chan.sampleAddress + 2 * i); - chanSampleQueues[chanNum].push(ApplySampleVolume(sample, leftVol)); - chanSampleQueues[chanNum].push(ApplySampleVolume(sample, rightVol)); - } - } - } - } - return ret; +// Unity gain on the PSP is 0x8000. Accumulate at full width and clamp once at the end, the way +// the driver's 32-bit mix accumulator does, rather than clamping each channel separately. +// 64-bit because the SRC channel accepts volumes up to 0xFFFFF, which overflows a 32-bit +// product against a full-scale sample. +static inline int ApplyChannelVolume(int sample, int vol) { + return (int)(((s64)sample * vol) >> 15); } -void __AudioWakeThreads(AudioChannel &chan, int result, int step) { - u32 error; - bool wokeThreads = false; - for (size_t w = 0; w < chan.waitingThreads.size(); ++w) { - AudioChannelWaitInfo &waitInfo = chan.waitingThreads[w]; - waitInfo.numSamples -= step; +// Set while __AudioUpdate is running, so a buffer accepted from inside it - the retry a parked +// thread gets when its predecessor finishes - doesn't try to start the DMA again. +static bool audioMixing; +// Set while that mixing is happening underneath a syscall rather than from the timing event. +static bool audioMixingInSyscall; - // If it's done (there will still be samples on queue) and actually still waiting, wake it up. - u32 waitID = __KernelGetWaitID(waitInfo.threadID, WAITTYPE_AUDIOCHANNEL, error); - if (waitInfo.numSamples <= 0 && waitID != 0) { - // DEBUG_LOG(Log::sceAudio, "Woke thread %i for some buffer filling", waitingThread); - u32 ret = result == 0 ? __KernelGetWaitValue(waitInfo.threadID, error) : SCE_ERROR_AUDIO_CHANNEL_NOT_RESERVED; - __KernelResumeThreadFromWait(waitInfo.threadID, ret); - wokeThreads = true; - - chan.waitingThreads.erase(chan.waitingThreads.begin() + w--); - } - // This means the thread stopped waiting, so stop trying to wake it. - else if (waitID == 0) - chan.waitingThreads.erase(chan.waitingThreads.begin() + w--); - } - - if (wokeThreads) { +// Switching threads is fine from the timing event, but not from inside an output call: the +// syscall's return value is written after the call body runs, so a context switch here would +// put it in the wrong thread's registers. hleReSchedule defers to after the syscall instead. +static void __AudioReScheduleAfterWake() { + if (audioMixingInSyscall) { + hleReSchedule("audio drain"); + } else { __KernelReSchedule("audio drain"); } } -void __AudioWakeThreads(AudioChannel &chan, int result) { - __AudioWakeThreads(chan, result, 0x7FFFFFFF); +// Only channels 0-7. The SRC channel is on its own DMA that the mixer never touches, so the +// two start independently of each other. +static bool __AudioAnyChannelPlaying() { + for (const AudioChannel &chan : g_audioChans) { + if (chan.sampleAddress != 0) { + return true; + } + } + return false; +} + +// The driver starts the mixer's DMA the moment the first buffer arrives, and its mixer thread - +// which outranks whoever called - immediately fills a block from it. So the first 64 samples +// are gone before the output call has returned, and a channel reserved for exactly 64 samples +// is free again right away. Re-phasing the mix event to the buffer's arrival reproduces that +// and costs nothing: the interval, and so the sample rate, is unchanged. +static void __AudioStartMixerDMA() { + if (audioMixing) { + return; + } + // A mixer output is cheap - well under 10us - except for this one, which brings the DMA and + // the codec up and costs over 100us on hardware. + hleEatCycles(25000); + CoreTiming::UnscheduleEvent(eventAudioUpdate, 0); + audioMixingInSyscall = true; + __AudioUpdate(); + audioMixingInSyscall = false; + CoreTiming::ScheduleEvent(audioIntervalCycles, eventAudioUpdate, 0); +} + +// The SRC channel's DMA also starts when its first buffer arrives, but it feeds the codec +// directly rather than going through the mixer, so nothing is read early - only the phase moves. +// Without this the buffer would retire somewhere in the next 1.5ms depending on where the mix +// event happened to be, and a game polling sceAudioOutput2GetRestSample would see a different +// answer every run. Only safe to do while no mixer channel is playing, since the two share one +// event here and the mixer's phase is the one that has samples riding on it. +static void __AudioStartSRCDMA() { + if (audioMixing || __AudioAnyChannelPlaying()) { + return; + } + CoreTiming::UnscheduleEvent(eventAudioUpdate, 0); + CoreTiming::ScheduleEvent(audioIntervalCycles, eventAudioUpdate, 0); +} + +u32 __AudioEnqueue(AudioChannel &chan, u32 samplePtr, int leftVol, int rightVol) { + if (!chan.reserved) { + return SCE_ERROR_AUDIO_CHANNEL_NOT_INIT; + } + // One buffer slot per channel, with no queue behind it. + if (chan.sampleAddress != 0) { + return SCE_ERROR_AUDIO_CHANNEL_BUSY; + } + + chan.remainingSamples = chan.sampleCount; + if (leftVol >= 0) { + chan.leftVolume = leftVol; + } + if (rightVol >= 0) { + chan.rightVolume = rightVol; + } + // Handing over a buffer while nothing was playing is what starts the DMA. + const bool startsDMA = samplePtr != 0 && !__AudioAnyChannelPlaying(); + // A null pointer is accepted and leaves the channel idle, but still counts as a buffer's + // worth of remaining samples - which is the one case where the two rest-length calls + // disagree with each other. + chan.sampleAddress = samplePtr; + if (startsDMA) { + __AudioStartMixerDMA(); + } + return chan.sampleCount; +} + +void __AudioEnqueueOneshot(AudioChannel &chan, u32 samplePtr, u32 sampleCount, u32 format, int leftVol, int rightVol) { + // No reservation and no busy check - handing a channel a second one while the first is + // still playing simply replaces it. sampleCount deliberately stays zero on the channel, + // which is what lets it go back to being free once the buffer runs out. + chan.format = format; + chan.leftVolume = leftVol; + chan.rightVolume = rightVol; + chan.remainingSamples = sampleCount; + + const bool startsDMA = samplePtr != 0 && !__AudioAnyChannelPlaying(); + chan.sampleAddress = samplePtr; + if (startsDMA) { + __AudioStartMixerDMA(); + } +} + +u32 __AudioEnqueueBlocking(AudioChannel &chan, u32 samplePtr, int leftVol, int rightVol) { + u32 result = __AudioEnqueue(chan, samplePtr, leftVol, rightVol); + if (result != SCE_ERROR_AUDIO_CHANNEL_BUSY) { + return result; + } + + // The driver keeps a single "a thread is waiting" flag per channel, so the second thread + // to arrive is turned away rather than lining up behind the first. A game that runs a + // movie thread and a sound-effect thread over one channel depends on being told this - + // blocking it instead makes the two take turns and halves the movie's audio rate. + if (chan.waitingThread != 0) { + return SCE_ERROR_AUDIO_CHANNEL_BUSY; + } + if (!__KernelIsDispatchEnabled()) { + return SCE_KERNEL_ERROR_CAN_NOT_WAIT; + } + + chan.waitingThread = __KernelGetCurThread(); + chan.waitingAddress = samplePtr; + chan.waitingLeftVolume = leftVol; + chan.waitingRightVolume = rightVol; + // __AudioChannelFinished retries the enqueue and supplies the real return value. + __KernelWaitCurThread(WAITTYPE_AUDIOCHANNEL, (SceUID)chan.index + 1, chan.sampleCount, 0, false, "blocking audio"); + return chan.sampleCount; +} + +// The buffer ran out - the driver's mixer would set this channel's bit in its event flag here. +static bool __AudioChannelFinished(AudioChannel &chan) { + chan.sampleAddress = 0; + chan.remainingSamples = 0; + if (chan.waitingThread == 0) { + return false; + } + + const SceUID threadID = chan.waitingThread; + chan.waitingThread = 0; + + u32 error; + if (__KernelGetWaitID(threadID, WAITTYPE_AUDIOCHANNEL, error) == 0) { + // It stopped waiting on its own - deleted, or the wait was cancelled. + return false; + } + + __KernelResumeThreadFromWait(threadID, __AudioEnqueue(chan, chan.waitingAddress, chan.waitingLeftVolume, chan.waitingRightVolume)); + return true; +} + +// Measured on hardware with tests/audio/blocking/overhead. Every SRC output ends up querying +// the codec, which costs upwards of 100us, whether it armed a buffer, found the channel +// unreserved, or did nothing at all. The one shortcut is a channel with both descriptors +// already armed, which lands in the 30-100us range instead. +int __AudioSRCCallCycles(const AudioSRCChannel &chan) { + return chan.Full() ? 10000 : 25000; +} + +u32 __AudioSRCEnqueueBlocking(AudioSRCChannel &chan, u32 samplePtr, int vol) { + if (!chan.reserved) { + return SCE_ERROR_AUDIO_CHANNEL_NOT_RESERVED; + } + // Two DMA descriptors, so two buffers fit. The third caller is refused outright - unlike + // the mixer channels it does not even get the chance to wait for a slot. + if (chan.Full()) { + return SCE_ERROR_AUDIO_CHANNEL_BUSY; + } + + u32 result = 0; + if (samplePtr != 0) { + // The volume rides along with a buffer, so a null pointer leaves it alone. A negative + // one means the same thing. + if (vol >= 0) { + chan.leftVolume = vol; + chan.rightVolume = vol; + } + const bool wasIdle = chan.bufferCount == 0; + if (wasIdle) { + // Starting the DMA signals a completion by itself, which is why the first + // output after an idle stretch returns without blocking. + chan.completion = true; + chan.playedSamples = 0; + chan.frac = 0; + } + chan.buffers[chan.bufferCount].address = samplePtr; + chan.buffers[chan.bufferCount].samples = chan.sampleCount; + chan.bufferCount++; + result = chan.sampleCount; + if (wasIdle) { + __AudioStartSRCDMA(); + } + } else if (chan.bufferCount == 0) { + // Nothing playing and nothing handed over, so there is no completion to wait for. + return 0; + } + + if (chan.completion) { + chan.completion = false; + return result; + } + if (!__KernelIsDispatchEnabled()) { + return SCE_KERNEL_ERROR_CAN_NOT_WAIT; + } + + chan.waitingThreads.push_back(__KernelGetCurThread()); + __KernelWaitCurThread(WAITTYPE_AUDIOCHANNEL, PSP_AUDIO_SRC_WAIT_ID, result, 0, false, "blocking audio"); + return result; +} + +void __AudioSRCSignal(AudioSRCChannel &chan) { + chan.completion = true; +} + +// One of the two SRC buffers finished playing. +static bool __AudioSRCCompleted(AudioSRCChannel &chan) { + // Threads that gave up on their own are dropped rather than counted as woken, so a + // completion is never spent on one - the next real waiter, or the flag, gets it. + while (!chan.waitingThreads.empty()) { + const SceUID threadID = chan.waitingThreads.front(); + chan.waitingThreads.erase(chan.waitingThreads.begin()); + + u32 error; + if (__KernelGetWaitID(threadID, WAITTYPE_AUDIOCHANNEL, error) != 0) { + __KernelResumeThreadFromWait(threadID, __KernelGetWaitValue(threadID, error)); + return true; + } + } + + // Nobody is listening, so the completion sits there for the next caller to consume. + chan.completion = true; + return false; +} + +void __AudioWakeThreads(AudioSRCChannel &chan, int result) { + bool woke = false; + for (SceUID threadID : chan.waitingThreads) { + u32 error; + if (__KernelGetWaitID(threadID, WAITTYPE_AUDIOCHANNEL, error) != 0) { + __KernelResumeThreadFromWait(threadID, result); + woke = true; + } + } + chan.waitingThreads.clear(); + + if (woke) { + // Only ever called from one of the release calls, so always inside a syscall. + hleReSchedule("audio drain"); + } } void __AudioSetOutputFrequency(int freq) { @@ -329,101 +481,144 @@ void __AudioSetSRCFrequency(int freq) { srcFrequency = freq; } +// Mixes one block from a mixer channel, reading straight out of the game's buffer. +static bool __AudioMixChannel(AudioChannel &chan) { + if (chan.sampleAddress == 0) { + // Idle, or holding the remaining count from a null-pointer output. + return false; + } + if (chan.remainingSamples == 0) { + // Can't happen from the API, since a channel is never reserved for zero samples, but a + // savestate could say otherwise - and left alone the channel would read as busy for + // ever, which is silence the game can't recover from. + return __AudioChannelFinished(chan); + } + + const u32 count = std::min(chan.remainingSamples, (u32)hwBlockSize); + const bool mono = chan.format == PSP_AUDIO_FORMAT_MONO; + const u32 stride = mono ? 2 : 4; + + // The samples are consumed either way; muting only drops them on the floor. + if (!chan.mute && Memory::IsValidRange(chan.sampleAddress, count * stride)) { + const s16_le *src = (const s16_le *)Memory::GetPointerUnchecked(chan.sampleAddress); + const int leftVol = chan.leftVolume; + const int rightVol = chan.rightVolume; + if (mono) { + // A mono channel reads the same sample into both sides, which is how the + // hardware expands it - there is no separate mono path in the mixer. + for (u32 s = 0; s < count; s++) { + const s16 sample = src[s]; + mixBuffer[s * 2] += ApplyChannelVolume(sample, leftVol); + mixBuffer[s * 2 + 1] += ApplyChannelVolume(sample, rightVol); + } + } else { + for (u32 s = 0; s < count; s++) { + mixBuffer[s * 2] += ApplyChannelVolume(src[s * 2], leftVol); + mixBuffer[s * 2 + 1] += ApplyChannelVolume(src[s * 2 + 1], rightVol); + } + } + } + + chan.sampleAddress += count * stride; + chan.remainingSamples -= count; + if (chan.remainingSamples == 0) { + return __AudioChannelFinished(chan); + } + return false; +} + +// Channel 8 never reaches the mixer on hardware - the DMA feeds the codec directly and the +// codec resamples. Model that as a read straight through the pending buffers at the ratio +// between the reserved frequency and the output rate. +static bool __AudioMixSRC(AudioSRCChannel &chan) { + if (chan.bufferCount == 0) { + return false; + } + + // Zero means "whatever the output is running at", so no conversion. + const int inRate = srcFrequency != 0 ? srcFrequency : mixFrequency; + const u32 ratio = (u32)(((u64)(u32)inRate << 16) / (u32)mixFrequency); + // At the output rate the fraction never moves off zero, so there is nothing to interpolate. + const bool resampling = ratio != 0x10000; + const bool mono = chan.format == PSP_AUDIO_FORMAT_MONO; + const u32 stride = mono ? 2 : 4; + const int leftVol = chan.leftVolume; + const int rightVol = chan.rightVolume; + + bool woke = false; + for (int out = 0; out < hwBlockSize; out++) { + if (chan.bufferCount == 0) { + // Underrun. The rest of the block stays silent, like a descriptor the game + // never got around to arming. + break; + } + + const AudioPendingBuffer &buf = chan.buffers[0]; + const u32 addr = buf.address + chan.playedSamples * stride; + if (!chan.mute && Memory::IsValidRange(addr, stride)) { + const s16_le *src = (const s16_le *)Memory::GetPointerUnchecked(addr); + const int l0 = src[0]; + const int r0 = mono ? l0 : src[1]; + int l1 = l0; + int r1 = r0; + if (resampling) { + // Interpolate against the sample after this one. At the end of a buffer that + // is the start of the next, since the codec reads the two descriptors as one + // unbroken stream - holding the last sample instead would tick at every join. + u32 nextAddr = addr + stride; + if (chan.playedSamples + 1 >= buf.samples) { + nextAddr = chan.bufferCount > 1 ? chan.buffers[1].address : 0; + } + if (nextAddr != 0 && Memory::IsValidRange(nextAddr, stride)) { + const s16_le *next = (const s16_le *)Memory::GetPointerUnchecked(nextAddr); + l1 = next[0]; + r1 = mono ? l1 : next[1]; + } + } + // 15 bits of fraction, not 16 - a full 16 would overflow the product against a + // full-scale difference. + const int frac = (int)(chan.frac >> 1); + mixBuffer[out * 2] += ApplyChannelVolume(l0 + (((l1 - l0) * frac) >> 15), leftVol); + mixBuffer[out * 2 + 1] += ApplyChannelVolume(r0 + (((r1 - r0) * frac) >> 15), rightVol); + } + + chan.frac += ratio; + u32 step = chan.frac >> 16; + chan.frac &= 0xFFFF; + while (step > 0 && chan.bufferCount > 0) { + const u32 take = std::min(step, chan.buffers[0].samples - chan.playedSamples); + chan.playedSamples += take; + step -= take; + if (chan.playedSamples >= chan.buffers[0].samples) { + chan.buffers[0] = chan.buffers[1]; + chan.bufferCount--; + chan.playedSamples = 0; + woke |= __AudioSRCCompleted(chan); + } + } + } + return woke; +} + // Mix samples from the various audio channels into a single sample queue, managed by the backend implementation. void __AudioUpdate(bool resetRecording) { // AUDIO throttle doesn't really work on the PSP since the mixing intervals are so closely tied // to the CPU. Much better to throttle the frame rate on frame display and just throw away audio // if the buffer somehow gets full. - bool firstChannel = true; - const int16_t srcBufferSize = hwBlockSize * 2; - int16_t srcBuffer[srcBufferSize]; + memset(mixBuffer, 0, hwBlockSize * 2 * sizeof(s32)); - for (u32 i = 0; i < PSP_AUDIO_CHANNEL_MAX + 1; i++) { - if (!g_audioChans[i].reserved) { - continue; - } - - __AudioWakeThreads(g_audioChans[i], 0, hwBlockSize); - - if (!chanSampleQueues[i].size()) { - continue; - } - - bool needsResample = i == PSP_AUDIO_CHANNEL_SRC && srcFrequency != 0 && srcFrequency != mixFrequency; - size_t sz = needsResample ? (srcBufferSize * srcFrequency) / mixFrequency : srcBufferSize; - if (sz > chanSampleQueues[i].size()) { - ERROR_LOG(Log::sceAudio, "Channel %i buffer underrun at %i of %i", i, (int)chanSampleQueues[i].size() / 2, (int)sz / 2); - } - - const s16 *buf1 = 0, *buf2 = 0; - size_t sz1, sz2; - - chanSampleQueues[i].popPointers(sz, &buf1, &sz1, &buf2, &sz2); - - // We do this check as the very last thing before mixing, to maximize compatibility. - if (g_audioChans[i].mute) { - continue; - } - - if (needsResample) { - auto read = [&](size_t i) { - if (i < sz1) - return buf1[i]; - if (i < sz1 + sz2) - return buf2[i - sz1]; - if (buf2) - return buf2[sz2 - 1]; - return buf1[sz1 - 1]; - }; - - // TODO: This is terrible, since it's doing it by small chunk and discarding frac. - const uint32_t ratio = (uint32_t)(65536.0 * srcFrequency / (double)mixFrequency); - uint32_t frac = 0; - size_t readIndex = 0; - for (size_t outIndex = 0; readIndex < sz && outIndex < srcBufferSize; outIndex += 2) { - size_t readIndex2 = readIndex + 2; - int16_t l1 = read(readIndex); - int16_t r1 = read(readIndex + 1); - int16_t l2 = read(readIndex2); - int16_t r2 = read(readIndex2 + 1); - int sampleL = ((l1 << 16) + (l2 - l1) * (uint16_t)frac) >> 16; - int sampleR = ((r1 << 16) + (r2 - r1) * (uint16_t)frac) >> 16; - srcBuffer[outIndex] = sampleL; - srcBuffer[outIndex + 1] = sampleR; - frac += ratio; - readIndex += 2 * (uint16_t)(frac >> 16); - frac &= 0xffff; - } - - buf1 = srcBuffer; - sz1 = srcBufferSize; - buf2 = nullptr; - sz2 = 0; - } - - if (firstChannel) { - for (size_t s = 0; s < sz1; s++) - mixBuffer[s] = buf1[s]; - if (buf2) { - for (size_t s = 0; s < sz2; s++) - mixBuffer[s + sz1] = buf2[s]; - } - firstChannel = false; - } else { - // Surprisingly hard to SIMD efficiently on SSE2 due to lack of 16-to-32-bit sign extension. NEON should be straight-forward though, and SSE4.1 can do it nicely. - // Actually, the cmple/pack trick should work fine... - for (size_t s = 0; s < sz1; s++) - mixBuffer[s] += buf1[s]; - if (buf2) { - for (size_t s = 0; s < sz2; s++) - mixBuffer[s + sz1] += buf2[s]; - } - } + audioMixing = true; + bool woke = false; + for (AudioChannel &chan : g_audioChans) { + // Deliberately not gated on `reserved`: sceAudioChRelease only clears the + // reservation, and a buffer already in flight keeps playing out. + woke |= __AudioMixChannel(chan); } + woke |= __AudioMixSRC(g_audioSRC); + audioMixing = false; - if (firstChannel) { - // Nothing was written above, let's memset. - memset(mixBuffer, 0, hwBlockSize * 2 * sizeof(s32)); + if (woke) { + __AudioReScheduleAfterWake(); } if (g_Config.bEnableSound) { diff --git a/Core/HLE/__sceAudio.h b/Core/HLE/__sceAudio.h index e8ecced166..efe022b9ae 100644 --- a/Core/HLE/__sceAudio.h +++ b/Core/HLE/__sceAudio.h @@ -41,10 +41,30 @@ void __AudioShutdown(); void __AudioSetOutputFrequency(int freq); void __AudioSetSRCFrequency(int freq); -// May return SCE_ERROR_AUDIO_CHANNEL_BUSY if buffer too large -u32 __AudioEnqueue(AudioChannel &chan, int chanNum, bool blocking); -void __AudioWakeThreads(AudioChannel &chan, int result, int step); -void __AudioWakeThreads(AudioChannel &chan, int result); +// The driver's single enqueue point for channels 0-7. Returns the channel's reserved sample +// count, or SCE_ERROR_AUDIO_CHANNEL_BUSY when a buffer is already in flight. A negative +// volume means "leave it alone". +u32 __AudioEnqueue(AudioChannel &chan, u32 samplePtr, int leftVol, int rightVol); +// sceAudioOneshotOutput's variant: hands over a buffer without a reservation, so it carries its +// own sample count and format and never reports busy. +void __AudioEnqueueOneshot(AudioChannel &chan, u32 samplePtr, u32 sampleCount, u32 format, int leftVol, int rightVol); +// Same, but parks the calling thread until the channel frees up. Only one thread can be +// parked; a second one gets SCE_ERROR_AUDIO_CHANNEL_BUSY straight back. +u32 __AudioEnqueueBlocking(AudioChannel &chan, u32 samplePtr, int leftVol, int rightVol); + +// Channel 8 - Output2, SRC and Vaudio all share it. Two buffers fit; a third caller gets +// SCE_ERROR_AUDIO_CHANNEL_BUSY without waiting. A successful call waits for one buffer to +// finish before returning, except when nothing was playing to begin with. +u32 __AudioSRCEnqueueBlocking(AudioSRCChannel &chan, u32 samplePtr, int vol); +// Hands the SRC channel a completion that the next caller can consume without waiting. +void __AudioSRCSignal(AudioSRCChannel &chan); +// What one SRC/Output2 output call costs, in cycles, before it does anything - see the comment +// on the definition. +int __AudioSRCCallCycles(const AudioSRCChannel &chan); + +// Wake everyone parked on the SRC channel with the given error. Mixer channels need no +// equivalent: releasing one while a thread waits on it is refused outright. +void __AudioWakeThreads(AudioSRCChannel &chan, int result); void __AudioCPUMHzChange(); diff --git a/Core/HLE/sceAudio.cpp b/Core/HLE/sceAudio.cpp index 4606507e92..8c315bc079 100644 --- a/Core/HLE/sceAudio.cpp +++ b/Core/HLE/sceAudio.cpp @@ -15,6 +15,8 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. +#include + #include "Common/Serialize/Serializer.h" #include "Common/Serialize/SerializeFuncs.h" #include "Common/Data/Collections/FixedSizeQueue.h" @@ -36,15 +38,18 @@ const int AUDIO_ROUTING_SPEAKER_ON = 1; int defaultRoutingMode = AUDIO_ROUTING_SPEAKER_ON; int defaultRoutingVolMode = AUDIO_ROUTING_SPEAKER_ON; -// TODO: These are way oversized and together consume 4MB of memory. -extern FixedSizeQueue chanSampleQueues[PSP_AUDIO_CHANNEL_MAX + 1]; +// Only still here to read savestates written before the channels held buffer pointers. +struct AudioChannelWaitInfo { + SceUID threadID; + int numSamples; +}; -// The extra channel is for SRC/Output2/Vaudio. -AudioChannel g_audioChans[PSP_AUDIO_CHANNEL_MAX + 1]; +AudioChannel g_audioChans[PSP_AUDIO_CHANNEL_MAX]; +// sceAudioOutput2, sceAudioSRC and sceVaudio are three names for this one channel. +AudioSRCChannel g_audioSRC; -void AudioChannel::DoState(PointerWrap &p) -{ - auto s = p.Section("AudioChannel", 1, 3); +void AudioChannel::DoState(PointerWrap &p) { + auto s = p.Section("AudioChannel", 1, 4); if (!s) return; @@ -54,81 +59,156 @@ void AudioChannel::DoState(PointerWrap &p) Do(p, leftVolume); Do(p, rightVolume); Do(p, format); - Do(p, waitingThreads); + + if (s >= 4) { + Do(p, remainingSamples); + Do(p, waitingThread); + Do(p, waitingAddress); + Do(p, waitingLeftVolume); + Do(p, waitingRightVolume); + return; + } + + // Everything below is the old format, from when the emulator copied each buffer into a + // ring of samples at enqueue time instead of playing out of the game's memory. There is + // no way to turn that back into a buffer pointer and a position, so the pending audio is + // dropped - a fraction of a second of silence on load, and then the game carries on. + std::vector oldWaitingThreads; + Do(p, oldWaitingThreads); if (s >= 2) { + // These are globals rather than per-channel; the old format just happened to write + // them out once per channel. Do(p, defaultRoutingMode); Do(p, defaultRoutingVolMode); } + + auto oldQueue = std::make_unique>(); if (s >= 3) { - // v3: compact queue form — only the live samples, not the whole 512KB - // fixed storage per channel. Cuts ~4.6MB of dead bytes from every - // savestate. Old savestates (s < 3) still load through the - // full-storage path below. - chanSampleQueues[index].DoStateCompact(p); + oldQueue->DoStateCompact(p); } else { - chanSampleQueues[index].DoState(p); + oldQueue->DoState(p); + } + + if (p.mode == p.MODE_READ) { + // Only the play position is dropped. reserved, sampleCount, the volumes and the format + // were all just read out of the state and are still good - clearing those as well + // leaves every channel unreserved, and the game's next output fails with + // SCE_ERROR_AUDIO_CHANNEL_NOT_INIT. + sampleAddress = 0; + remainingSamples = 0; + waitingThread = 0; + waitingAddress = 0; + waitingLeftVolume = 0; + waitingRightVolume = 0; + // The threads that were parked in a blocking output call are still parked, and + // nothing is going to wake them now, so hand them their buffer back. + for (const AudioChannelWaitInfo &waitInfo : oldWaitingThreads) { + u32 error; + if (__KernelGetWaitID(waitInfo.threadID, WAITTYPE_AUDIOCHANNEL, error) != 0) { + __KernelResumeThreadFromWait(waitInfo.threadID, sampleCount); + } + } } } -void AudioChannel::reset() -{ - __AudioWakeThreads(*this, SCE_ERROR_AUDIO_CHANNEL_NOT_RESERVED); - clear(); -} - -void AudioChannel::clear() -{ +void AudioChannel::clear() { reserved = false; leftVolume = 0; rightVolume = 0; format = 0; - sampleAddress = 0; sampleCount = 0; - chanSampleQueues[index].clear(); - waitingThreads.clear(); + sampleAddress = 0; + remainingSamples = 0; + waitingThread = 0; + waitingAddress = 0; + waitingLeftVolume = 0; + waitingRightVolume = 0; } -// Enqueues the buffer pointed to on the channel. If channel buffer queue is full (2 items?) will block until it isn't. -// For solid audio output we'll need a queue length of 2 buffers at least. +void AudioSRCChannel::DoState(PointerWrap &p) { + auto s = p.Section("AudioSRCChannel", 1); + if (!s) + return; + + Do(p, reserved); + Do(p, sampleCount); + Do(p, leftVolume); + Do(p, rightVolume); + Do(p, format); + Do(p, bufferCount); + if (bufferCount < 0 || bufferCount > (int)ARRAY_SIZE(buffers)) { + // Guarded because the enqueue indexes buffers[] with this, so a negative one would be + // a write outside the struct. + ERROR_LOG(Log::sceAudio, "Savestate failure: %d buffers on the SRC channel.", bufferCount); + p.SetError(p.ERROR_FAILURE); + return; + } + for (AudioPendingBuffer &buf : buffers) { + Do(p, buf.address); + Do(p, buf.samples); + } + Do(p, playedSamples); + Do(p, frac); + Do(p, completion); + Do(p, waitingThreads); +} + +void __AudioRoutingDoState(PointerWrap &p) { + auto s = p.Section("sceAudioRouting", 1); + if (!s) + return; + + Do(p, defaultRoutingMode); + Do(p, defaultRoutingVolMode); +} + +void AudioSRCChannel::reset() { + __AudioWakeThreads(*this, SCE_ERROR_AUDIO_CHANNEL_NOT_RESERVED); + clear(); +} + +void AudioSRCChannel::clear() { + reserved = false; + leftVolume = 0; + rightVolume = 0; + format = 0; + sampleCount = 0; + bufferCount = 0; + playedSamples = 0; + frac = 0; + completion = false; + waitingThreads.clear(); + for (AudioPendingBuffer &buf : buffers) { + buf.address = 0; + buf.samples = 0; + } +} + +// The blocking output calls do not queue callers up. Each channel holds one buffer and at most +// one parked thread; the SRC channel holds two buffers and no parked-thread slot at all. A +// caller that finds no room is told SCE_ERROR_AUDIO_CHANNEL_BUSY and expected to come back +// later. See docs/sceAudio.md. -// Not sure about the range of volume, I often see 0x800 so that might be either -// max or 50%? static u32 sceAudioOutputBlocking(u32 chan, int vol, u32 samplePtr) { if (vol > 0xFFFF) { return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_INVALID_VOLUME, "invalid volume"); } else if (chan >= PSP_AUDIO_CHANNEL_MAX) { return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_INVALID_CHANNEL, "bad channel"); - } else if (!g_audioChans[chan].reserved) { - return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_CHANNEL_NOT_INIT, "channel not reserved"); } - if (vol >= 0) { - g_audioChans[chan].leftVolume = vol; - g_audioChans[chan].rightVolume = vol; - } - g_audioChans[chan].sampleAddress = samplePtr; - return hleLogDebug(Log::sceAudio, __AudioEnqueue(g_audioChans[chan], chan, true)); + return hleLogDebug(Log::sceAudio, __AudioEnqueueBlocking(g_audioChans[chan], samplePtr, vol, vol)); } static u32 sceAudioOutputPannedBlocking(u32 chan, int leftvol, int rightvol, u32 samplePtr) { - // For some reason, this is the only one that checks for negative. - if (leftvol > 0xFFFF || rightvol > 0xFFFF || leftvol < 0 || rightvol < 0) { + // This one ORs the two volumes together before comparing, so unlike the others a negative + // volume fails instead of meaning "leave it alone". + if ((u32)(leftvol | rightvol) > 0xFFFF) { return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_INVALID_VOLUME, "invalid volume"); } else if (chan >= PSP_AUDIO_CHANNEL_MAX) { return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_INVALID_CHANNEL, "bad channel"); - } else if (!g_audioChans[chan].reserved) { - return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_CHANNEL_NOT_INIT, "channel not reserved"); } - if (leftvol >= 0) { - g_audioChans[chan].leftVolume = leftvol; - } - if (rightvol >= 0) { - g_audioChans[chan].rightVolume = rightvol; - } - g_audioChans[chan].sampleAddress = samplePtr; - u32 result = __AudioEnqueue(g_audioChans[chan], chan, true); - return hleLogDebug(Log::sceAudio, result); + return hleLogDebug(Log::sceAudio, __AudioEnqueueBlocking(g_audioChans[chan], samplePtr, leftvol, rightvol)); } static u32 sceAudioOutput(u32 chan, int vol, u32 samplePtr) { @@ -136,17 +216,9 @@ static u32 sceAudioOutput(u32 chan, int vol, u32 samplePtr) { return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_INVALID_VOLUME, "invalid volume"); } else if (chan >= PSP_AUDIO_CHANNEL_MAX) { return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_INVALID_CHANNEL, "bad channel"); - } else if (!g_audioChans[chan].reserved) { - return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_CHANNEL_NOT_INIT, "channel not reserved"); } - if (vol >= 0) { - g_audioChans[chan].leftVolume = vol; - g_audioChans[chan].rightVolume = vol; - } - g_audioChans[chan].sampleAddress = samplePtr; - u32 result = __AudioEnqueue(g_audioChans[chan], chan, false); - return hleLogDebug(Log::sceAudio, result); + return hleLogDebug(Log::sceAudio, __AudioEnqueue(g_audioChans[chan], samplePtr, vol, vol)); } static u32 sceAudioOutputPanned(u32 chan, int leftvol, int rightvol, u32 samplePtr) { @@ -154,43 +226,44 @@ static u32 sceAudioOutputPanned(u32 chan, int leftvol, int rightvol, u32 sampleP return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_INVALID_VOLUME, "invalid volume"); } else if (chan >= PSP_AUDIO_CHANNEL_MAX) { return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_INVALID_CHANNEL, "bad channel"); - } else if (!g_audioChans[chan].reserved) { - return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_CHANNEL_NOT_INIT, "channel not reserved"); - } else { - if (leftvol >= 0) { - g_audioChans[chan].leftVolume = leftvol; - } - if (rightvol >= 0) { - g_audioChans[chan].rightVolume = rightvol; - } - g_audioChans[chan].sampleAddress = samplePtr; - u32 result = __AudioEnqueue(g_audioChans[chan], chan, false); - return hleLogDebug(Log::sceAudio, result); } + + return hleLogDebug(Log::sceAudio, __AudioEnqueue(g_audioChans[chan], samplePtr, leftvol, rightvol)); } +// A thread parked in a blocking output call counts as a whole extra buffer, on top of whatever +// is left of the one playing. static int sceAudioGetChannelRestLen(u32 chan) { if (chan >= PSP_AUDIO_CHANNEL_MAX) { return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_INVALID_CHANNEL, "bad channel"); } - int remainingSamples = (int)chanSampleQueues[chan].size() / 2; - return hleLogVerbose(Log::sceAudio, remainingSamples); + const AudioChannel &c = g_audioChans[chan]; + int rest = (int)c.remainingSamples; + if (c.waitingThread != 0) { + rest += (int)c.sampleCount; + } + return hleLogVerbose(Log::sceAudio, rest); } static int sceAudioGetChannelRestLength(u32 chan) { if (chan >= PSP_AUDIO_CHANNEL_MAX) { return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_INVALID_CHANNEL, "bad channel"); } - int remainingSamples = (int)chanSampleQueues[chan].size() / 2; - return hleLogVerbose(Log::sceAudio, remainingSamples); + // Unlike its sibling this one checks that a buffer is really playing first, so after an + // output with a null pointer the two disagree. + const AudioChannel &c = g_audioChans[chan]; + int rest = c.sampleAddress != 0 ? (int)c.remainingSamples : 0; + if (c.waitingThread != 0) { + rest += (int)c.sampleCount; + } + return hleLogVerbose(Log::sceAudio, rest); } static int GetFreeChannel() { - // Changed this to allow channel 0. Fixes the startup sound in VSH. TODO: Why did we not allow channel 0 before? - // The counter has to be signed: as a u32 the i >= 0 condition is always true, so with every - // channel reserved it wrapped past zero and ran off the array instead of giving up. + // The search runs downwards from 7, and a channel only counts as free once it has both + // been released and finished playing whatever it still held. for (int i = PSP_AUDIO_CHANNEL_MAX - 1; i >= 0; --i) { - if (!g_audioChans[i].reserved) + if (g_audioChans[i].sampleCount == 0 && g_audioChans[i].sampleAddress == 0) return i; } return -1; @@ -206,15 +279,15 @@ static u32 sceAudioChReserve(int chan, u32 sampleCount, u32 format) { if ((u32)chan >= PSP_AUDIO_CHANNEL_MAX) { return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_INVALID_CHANNEL, "bad channel %d", chan); } + if (g_audioChans[chan].reserved) { + return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_INVALID_CHANNEL, "reserve channel failed"); + } if ((sampleCount & 63) != 0 || sampleCount == 0 || sampleCount > PSP_AUDIO_SAMPLE_MAX) { return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_OUTPUT_SAMPLE_DATA_SIZE_NOT_ALIGNED, "invalid sample count (not aligned)"); } if (format != PSP_AUDIO_FORMAT_MONO && format != PSP_AUDIO_FORMAT_STEREO) { return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_INVALID_FORMAT, "invalid format"); } - if (g_audioChans[chan].reserved) { - return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_INVALID_CHANNEL, "reserve channel failed"); - } g_audioChans[chan].sampleCount = sampleCount; g_audioChans[chan].format = format; @@ -228,24 +301,30 @@ static u32 sceAudioChRelease(u32 chan) { if (chan >= PSP_AUDIO_CHANNEL_MAX) { return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_INVALID_CHANNEL, "bad channel %d", chan); } else if (!g_audioChans[chan].reserved) { - return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_CHANNEL_NOT_INIT, "channel %d not reserved", chan); + return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_CHANNEL_NOT_RESERVED, "channel %d not reserved", chan); + } else if (g_audioChans[chan].waitingThread != 0) { + return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_CHANNEL_BUSY, "channel %d has a thread waiting", chan); } - // TODO: Does this error if busy? - g_audioChans[chan].reset(); + // Only the reservation goes away. A buffer already handed over keeps playing to the end, + // and the channel stays unavailable to sceAudioChReserve(-1) until it does. g_audioChans[chan].reserved = false; + g_audioChans[chan].sampleCount = 0; return hleLogDebug(Log::sceAudio, 0); } static u32 sceAudioSetChannelDataLen(u32 chan, u32 len) { if (chan >= PSP_AUDIO_CHANNEL_MAX) { return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_INVALID_CHANNEL, "bad channel %d", chan); - } else if (!g_audioChans[chan].reserved) { - return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_CHANNEL_NOT_INIT, "channel %d not reserved", chan); } else if ((len & 63) != 0 || len == 0 || len > PSP_AUDIO_SAMPLE_MAX) { + // Checked before the reservation, unlike most of the others. return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_OUTPUT_SAMPLE_DATA_SIZE_NOT_ALIGNED, "invalid sample count"); + } else if (g_audioChans[chan].waitingThread != 0) { + return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_CHANNEL_BUSY, "channel %d has a thread waiting", chan); + } else if (!g_audioChans[chan].reserved) { + return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_CHANNEL_NOT_INIT, "channel %d not reserved", chan); } - + g_audioChans[chan].sampleCount = len; return hleLogDebug(Log::sceAudio, 0); } @@ -253,25 +332,60 @@ static u32 sceAudioSetChannelDataLen(u32 chan, u32 len) { static u32 sceAudioChangeChannelConfig(u32 chan, u32 format) { if (chan >= PSP_AUDIO_CHANNEL_MAX) { return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_INVALID_CHANNEL, "invalid channel number %d", chan); + } else if (g_audioChans[chan].waitingThread != 0 || g_audioChans[chan].sampleAddress != 0) { + return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_CHANNEL_BUSY, "channel %d busy", chan); } else if (!g_audioChans[chan].reserved) { return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_CHANNEL_NOT_RESERVED, "channel %d not reserved", chan); + } else if (format != PSP_AUDIO_FORMAT_MONO && format != PSP_AUDIO_FORMAT_STEREO) { + return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_INVALID_FORMAT, "invalid format"); } g_audioChans[chan].format = format; return hleLogDebug(Log::sceAudio, 0); } -static u32 sceAudioChangeChannelVolume(u32 chan, u32 leftvol, u32 rightvol) { +// Plays one buffer on a channel without reserving it, so the channel frees itself once the +// buffer runs out. The checks are looser than sceAudioChReserve's - any positive sample count +// goes, aligned or not - and stricter on volume, where a negative one is an error rather than +// meaning "leave it alone". +static u32 sceAudioOneshotOutput(int chan, int sampleCount, int format, int leftvol, int rightvol, u32 samplePtr) { + if ((u32)leftvol > 0xFFFF || (u32)rightvol > 0xFFFF) { + return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_INVALID_VOLUME, "invalid volume"); + } + if (chan < 0) { + chan = GetFreeChannel(); + if (chan < 0) { + return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_NO_CHANNELS_AVAILABLE, "no channels remaining"); + } + } else if (chan >= (int)PSP_AUDIO_CHANNEL_MAX || g_audioChans[chan].reserved) { + // A reserved channel is refused with the same error as one that doesn't exist. + return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_INVALID_CHANNEL, "bad channel %d", chan); + } + if (sampleCount <= 0) { + return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_OUTPUT_SAMPLE_DATA_SIZE_NOT_ALIGNED, "invalid sample count"); + } + if (format != PSP_AUDIO_FORMAT_MONO && format != PSP_AUDIO_FORMAT_STEREO) { + return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_INVALID_FORMAT, "invalid format"); + } + + __AudioEnqueueOneshot(g_audioChans[chan], samplePtr, sampleCount, format, leftvol, rightvol); + return hleLogDebug(Log::sceAudio, chan); +} + +static u32 sceAudioChangeChannelVolume(u32 chan, int leftvol, int rightvol) { if (leftvol > 0xFFFF || rightvol > 0xFFFF) { return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_INVALID_VOLUME, "invalid chan %d volume %d %d", chan, leftvol, rightvol); } else if (chan >= PSP_AUDIO_CHANNEL_MAX) { return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_INVALID_CHANNEL, "invalid channel %d", chan); - } else if (!g_audioChans[chan].reserved) { - return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_CHANNEL_NOT_RESERVED, "channel %d not reserved", chan); } - g_audioChans[chan].leftVolume = leftvol; - g_audioChans[chan].rightVolume = rightvol; + // There is no reservation check here, and a negative volume means "leave that side alone". + if (leftvol >= 0) { + g_audioChans[chan].leftVolume = leftvol; + } + if (rightvol >= 0) { + g_audioChans[chan].rightVolume = rightvol; + } return hleLogDebug(Log::sceAudio, 0); } @@ -286,7 +400,7 @@ static u32 sceAudioEnd() { } static u32 sceAudioOutput2Reserve(u32 sampleCount) { - auto &chan = g_audioChans[PSP_AUDIO_CHANNEL_OUTPUT2]; + auto &chan = g_audioSRC; // This seems to ignore the MSB, for some reason. sampleCount &= 0x7FFFFFFF; if (sampleCount < 17 || sampleCount > 4111) { @@ -295,6 +409,7 @@ static u32 sceAudioOutput2Reserve(u32 sampleCount) { return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_CHANNEL_ALREADY_RESERVED, "channel already reserved"); } + chan.clear(); chan.sampleCount = sampleCount; chan.format = PSP_AUDIO_FORMAT_STEREO; chan.reserved = true; @@ -308,53 +423,47 @@ static u32 sceAudioOutput2OutputBlocking(u32 vol, u32 dataPtr) { return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_INVALID_VOLUME, "invalid volume"); } - auto &chan = g_audioChans[PSP_AUDIO_CHANNEL_OUTPUT2]; - if (!chan.reserved) { - return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_CHANNEL_NOT_RESERVED, "channel not reserved"); - } - - chan.leftVolume = vol; - chan.rightVolume = vol; - chan.sampleAddress = dataPtr; - - hleEatCycles(10000); - int result = __AudioEnqueue(chan, PSP_AUDIO_CHANNEL_OUTPUT2, true); - if (result < 0) - return hleLogError(Log::sceAudio, result); - return hleLogDebug(Log::sceAudio, result); + hleEatCycles(__AudioSRCCallCycles(g_audioSRC)); + // A busy channel is an ordinary answer here that a game is expected to poll on, not a + // fault, so this stays at debug like the mixer channels rather than filling the error log. + return hleLogDebug(Log::sceAudio, __AudioSRCEnqueueBlocking(g_audioSRC, dataPtr, vol)); } static u32 sceAudioOutput2ChangeLength(u32 sampleCount) { - auto &chan = g_audioChans[PSP_AUDIO_CHANNEL_OUTPUT2]; + // The length is range-checked before the channel is, and 4111 is the same ceiling the + // reserve takes. + if (sampleCount - 17 >= 0xFFF) { + return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_OUTPUT_SAMPLE_DATA_SIZE_NOT_ALIGNED, "invalid sample count"); + } + auto &chan = g_audioSRC; if (!chan.reserved) { return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_CHANNEL_NOT_RESERVED, "channel not reserved"); } + // Buffers already handed over keep their original length; only what is reported and what + // is accepted from here on changes. chan.sampleCount = sampleCount; return hleLogDebug(Log::sceAudio, 0); } static u32 sceAudioOutput2GetRestSample() { - auto &chan = g_audioChans[PSP_AUDIO_CHANNEL_OUTPUT2]; + auto &chan = g_audioSRC; if (!chan.reserved) { return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_CHANNEL_NOT_RESERVED, "channel not reserved"); } - u32 size = (u32)chanSampleQueues[PSP_AUDIO_CHANNEL_OUTPUT2].size() / 2; - if (size > chan.sampleCount) { - // If ChangeLength reduces the size, it still gets output but this return is clamped. - size = chan.sampleCount; - } - return hleLogDebug(Log::sceAudio, size); + // Counts armed DMA descriptors, in units of the current length - so it reports two + // buffers' worth while both are in flight. + return hleLogDebug(Log::sceAudio, chan.bufferCount * chan.sampleCount); } static u32 sceAudioOutput2Release() { - auto &chan = g_audioChans[PSP_AUDIO_CHANNEL_OUTPUT2]; + auto &chan = g_audioSRC; if (!chan.reserved) return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_CHANNEL_NOT_RESERVED, "channel not reserved"); - if (!chanSampleQueues[PSP_AUDIO_CHANNEL_OUTPUT2].empty()) + if (chan.bufferCount != 0) return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_CHANNEL_ALREADY_RESERVED, "output busy"); chan.reset(); - chan.reserved = false; + __AudioSRCSignal(chan); return hleLogDebug(Log::sceAudio, 0); } @@ -384,7 +493,7 @@ bool SRCFrequencyAllowed(int freq) { } static u32 sceAudioSRCChReserve(u32 sampleCount, u32 freq, u32 format) { - auto &chan = g_audioChans[PSP_AUDIO_CHANNEL_SRC]; + auto &chan = g_audioSRC; // This seems to ignore the MSB, for some reason. sampleCount &= 0x7FFFFFFF; if (format == 4) { @@ -399,6 +508,7 @@ static u32 sceAudioSRCChReserve(u32 sampleCount, u32 freq, u32 format) { return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_CHANNEL_ALREADY_RESERVED, "channel already reserved"); } + chan.clear(); chan.reserved = true; chan.sampleCount = sampleCount; chan.format = format == 2 ? PSP_AUDIO_FORMAT_STEREO : PSP_AUDIO_FORMAT_MONO; @@ -408,14 +518,15 @@ static u32 sceAudioSRCChReserve(u32 sampleCount, u32 freq, u32 format) { } static u32 sceAudioSRCChRelease() { - auto &chan = g_audioChans[PSP_AUDIO_CHANNEL_SRC]; + auto &chan = g_audioSRC; if (!chan.reserved) return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_CHANNEL_NOT_RESERVED, "channel not reserved"); - if (!chanSampleQueues[PSP_AUDIO_CHANNEL_SRC].empty()) + if (chan.bufferCount != 0) return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_CHANNEL_ALREADY_RESERVED, "output busy"); chan.reset(); - chan.reserved = false; + // Releasing signals a completion, which the next caller after a fresh reserve consumes. + __AudioSRCSignal(chan); return hleLogDebug(Log::sceAudio, 0); } @@ -424,20 +535,8 @@ static u32 sceAudioSRCOutputBlocking(u32 vol, u32 buf) { return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_INVALID_VOLUME, "invalid volume"); } - auto &chan = g_audioChans[PSP_AUDIO_CHANNEL_SRC]; - if (!chan.reserved) { - return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_CHANNEL_NOT_RESERVED, "channel not reserved"); - } - - chan.leftVolume = vol; - chan.rightVolume = vol; - chan.sampleAddress = buf; - - hleEatCycles(10000); - int result = __AudioEnqueue(chan, PSP_AUDIO_CHANNEL_SRC, true); - if (result < 0) - return hleLogError(Log::sceAudio, result); - return hleLogDebug(Log::sceAudio, result); + hleEatCycles(__AudioSRCCallCycles(g_audioSRC)); + return hleLogDebug(Log::sceAudio, __AudioSRCEnqueueBlocking(g_audioSRC, buf, vol)); } static int sceAudioInputBlocking(u32 maxSamples, u32 sampleRate, u32 bufAddr) { @@ -531,15 +630,16 @@ const HLEFunction sceAudio[] = {0XB011922F, &WrapI_U, "sceAudioGetChannelRestLength", 'i', "i" }, {0XCB2E439E, &WrapU_UU, "sceAudioSetChannelDataLen", 'x', "ii" }, {0X95FD0C2D, &WrapU_UU, "sceAudioChangeChannelConfig", 'x', "ii" }, - {0XB7E1D8E7, &WrapU_UUU, "sceAudioChangeChannelVolume", 'x', "ixx" }, + {0XB7E1D8E7, &WrapU_UII, "sceAudioChangeChannelVolume", 'x', "ixx" }, // Like Output2, but with ability to do sample rate conversion. {0X38553111, &WrapU_UUU, "sceAudioSRCChReserve", 'x', "iii" }, {0X5C37C0AE, &WrapU_V, "sceAudioSRCChRelease", 'x', "" }, {0XE0727056, &WrapU_UU, "sceAudioSRCOutputBlocking", 'x', "xx" }, - // Never seen these used - {0X41EFADE7, nullptr, "sceAudioOneshotOutput", '?', "" }, + {0X41EFADE7, &WrapU_IIIIIU, "sceAudioOneshotOutput", 'x', "iiiiix"}, + + // Never seen this used {0XB61595C0, nullptr, "sceAudioLoopbackTest", '?', "" }, // Microphone interface diff --git a/Core/HLE/sceAudio.h b/Core/HLE/sceAudio.h index 03fe44673d..097b91ffee 100644 --- a/Core/HLE/sceAudio.h +++ b/Core/HLE/sceAudio.h @@ -17,7 +17,7 @@ #pragma once -#include +#include #include "CommonTypes.h" #include "sceKernel.h" @@ -27,24 +27,26 @@ class PointerWrap; enum PspAudioFormats { PSP_AUDIO_FORMAT_STEREO = 0, PSP_AUDIO_FORMAT_MONO = 0x10 }; enum PspAudioFrequencies { PSP_AUDIO_FREQ_44K = 44100, PSP_AUDIO_FREQ_48K = 48000 }; +// Channels 0-7, the ones the software mixer walks. const u32 PSP_AUDIO_CHANNEL_MAX = 8; -const int PSP_AUDIO_CHANNEL_SRC = 8; -const int PSP_AUDIO_CHANNEL_OUTPUT2 = 8; -const int PSP_AUDIO_CHANNEL_VAUDIO = 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; -struct AudioChannelWaitInfo { - SceUID threadID; - int numSamples; +// One buffer handed over and not yet fully played. +struct AudioPendingBuffer { + u32 address; + u32 samples; }; +// A channel the software mixer walks. It holds exactly one buffer, played straight out of the +// game's memory 64 samples at a time, and has room for exactly one parked thread. See +// docs/sceAudio.md. struct AudioChannel { int index = 0; bool reserved = false; - // last sample address - u32 sampleAddress = 0; - u32 sampleCount = 0; // Number of samples written in each OutputBlocking + u32 sampleCount = 0; // Buffer size agreed at reserve time. u32 leftVolume = 0; u32 rightVolume = 0; u32 format = 0; @@ -52,19 +54,67 @@ struct AudioChannel { // For the debugger only. Not saved. bool mute = false; - std::vector waitingThreads; + // sampleAddress walks forward as the mixer consumes the buffer and drops back to zero when + // remainingSamples runs out. An output with a null pointer sets remainingSamples but leaves + // sampleAddress at zero, which is the one case where the two rest-length calls disagree. + u32 sampleAddress = 0; + u32 remainingSamples = 0; + + // 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. + SceUID waitingThread = 0; + u32 waitingAddress = 0; + int waitingLeftVolume = 0; + int waitingRightVolume = 0; + + void DoState(PointerWrap &p); + + void clear(); +}; + +// Channel 8, and there is only one of it: sceAudioOutput2, sceAudioSRC and sceVaudio are three +// names for the same channel. It skips the mixer entirely - its two DMA descriptors point +// straight at the game's buffers and the codec resamples them - so it shares nothing with the +// mixer channels beyond what reserve agrees on. Two buffers fit; a third caller is turned away. +struct AudioSRCChannel { + bool reserved = false; + + u32 sampleCount = 0; // Buffer size agreed at reserve time. + u32 leftVolume = 0; + u32 rightVolume = 0; + u32 format = 0; + + // For the debugger only. Not saved. + bool mute = false; + + AudioPendingBuffer buffers[2]{}; + int bufferCount = 0; + u32 playedSamples = 0; // Consumed from buffers[0]. + u32 frac = 0; // 16.16 position between two input samples, for resampling. + // The driver signals a finished buffer with an event flag bit, so one completion can sit + // there unclaimed - which is why the first output after an idle period doesn't block. + bool completion = false; + std::vector waitingThreads; void DoState(PointerWrap &p); void reset(); void clear(); + + bool Full() const { + return bufferCount >= (int)ARRAY_SIZE(buffers); + } }; -// The extra channel is for SRC/Output2/Vaudio (who all share, apparently.) -extern AudioChannel g_audioChans[PSP_AUDIO_CHANNEL_MAX + 1]; +extern AudioChannel g_audioChans[PSP_AUDIO_CHANNEL_MAX]; +extern AudioSRCChannel g_audioSRC; // The sample rates the SRC and VAUDIO channels will accept. bool SRCFrequencyAllowed(int freq); +// The two routing modes are globals rather than per-channel; they get their own little block. +void __AudioRoutingDoState(PointerWrap &p); + void Register_sceAudio(); diff --git a/Core/HLE/sceVaudio.cpp b/Core/HLE/sceVaudio.cpp index 6aeb488c25..71ccce2dbf 100644 --- a/Core/HLE/sceVaudio.cpp +++ b/Core/HLE/sceVaudio.cpp @@ -55,49 +55,67 @@ static u32 sceVaudioChReserve(int sampleCount, int freq, int format) { ERROR_LOG(Log::sceAudio, "sceVaudioChReserve(%i, %i, %i) - unexpected format", sampleCount, freq, format); return SCE_KERNEL_ERROR_INVALID_FORMAT; } - if (freq != 0 && !SRCFrequencyAllowed(freq)) { - ERROR_LOG(Log::sceAudio, "sceVaudioChReserve(%i, %i, %i) - invalid frequency", sampleCount, freq, format); - return SCE_ERROR_AUDIO_INVALID_FREQUENCY; - } if (vaudioReserved) { ERROR_LOG(Log::sceAudio, "sceVaudioChReserve(%i, %i, %i) - already reserved", sampleCount, freq, format); return SCE_KERNEL_ERROR_BUSY; } + + // Everything past here is the underlying sceAudioSRCChReserve, and the module marks itself + // reserved before handing over - it does not undo that when the reserve fails. So a caller + // that got 0x80268002 because Output2 held the channel is told 0x80000021 next time round, + // until a release clears it. + vaudioReserved = true; + if (freq != 0 && !SRCFrequencyAllowed(freq)) { + ERROR_LOG(Log::sceAudio, "sceVaudioChReserve(%i, %i, %i) - invalid frequency", sampleCount, freq, format); + return SCE_ERROR_AUDIO_INVALID_FREQUENCY; + } // We still have to check the channel also, which gives a different error. - if (g_audioChans[PSP_AUDIO_CHANNEL_VAUDIO].reserved) { + if (g_audioSRC.reserved) { ERROR_LOG(Log::sceAudio, "sceVaudioChReserve(%i, %i, %i) - channel already reserved", sampleCount, freq, format); return SCE_ERROR_AUDIO_CHANNEL_ALREADY_RESERVED; } DEBUG_LOG(Log::sceAudio, "sceVaudioChReserve(%i, %i, %i)", sampleCount, freq, format); - g_audioChans[PSP_AUDIO_CHANNEL_VAUDIO].reserved = true; - g_audioChans[PSP_AUDIO_CHANNEL_VAUDIO].sampleCount = sampleCount; - g_audioChans[PSP_AUDIO_CHANNEL_VAUDIO].format = format == 2 ? PSP_AUDIO_FORMAT_STEREO : PSP_AUDIO_FORMAT_MONO; - g_audioChans[PSP_AUDIO_CHANNEL_VAUDIO].leftVolume = 0; - g_audioChans[PSP_AUDIO_CHANNEL_VAUDIO].rightVolume = 0; - vaudioReserved = true; + g_audioSRC.clear(); + g_audioSRC.reserved = true; + g_audioSRC.sampleCount = sampleCount; + g_audioSRC.format = format == 2 ? PSP_AUDIO_FORMAT_STEREO : PSP_AUDIO_FORMAT_MONO; __AudioSetSRCFrequency(freq); return 0; } static u32 sceVaudioChRelease() { DEBUG_LOG(Log::sceAudio, "sceVaudioChRelease(...)"); - if (!g_audioChans[PSP_AUDIO_CHANNEL_VAUDIO].reserved) { + // Not gated on vaudio's own flag: the release goes straight at the SRC channel, so it will + // happily release a reservation that Output2 made. pspautotests calls that the "wrong + // release" and the hardware allows it. + vaudioReserved = false; + if (!g_audioSRC.reserved) { return SCE_ERROR_AUDIO_CHANNEL_NOT_RESERVED; - } else { - g_audioChans[PSP_AUDIO_CHANNEL_VAUDIO].reset(); - g_audioChans[PSP_AUDIO_CHANNEL_VAUDIO].reserved = false; - vaudioReserved = false; - return 0; } + + // Unlike the Output2 and SRC releases, which refuse while a buffer is in flight, this one + // hands the channel a null pointer first. That parks the caller until a buffer finishes, so + // what was playing is played out instead of being cut off. + __AudioSRCEnqueueBlocking(g_audioSRC, 0, -1); + if (g_audioSRC.Full()) { + // One drain was not enough to free a descriptor, so the release itself fails. + return SCE_ERROR_AUDIO_CHANNEL_ALREADY_RESERVED; + } + + // 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; the buffers keep playing because the mixer + // does not look at the reservation. Only another thread peeking during that window could + // tell the difference. + g_audioSRC.reserved = false; + g_audioSRC.sampleCount = 0; + __AudioSRCSignal(g_audioSRC); + return 0; } static u32 sceVaudioOutputBlocking(int vol, u32 buffer) { DEBUG_LOG(Log::sceAudio, "sceVaudioOutputBlocking(%i, %08x)", vol, buffer); - g_audioChans[PSP_AUDIO_CHANNEL_VAUDIO].leftVolume = vol; - g_audioChans[PSP_AUDIO_CHANNEL_VAUDIO].rightVolume = vol; - // TODO: This may be wrong, not sure if's in a different format? - g_audioChans[PSP_AUDIO_CHANNEL_VAUDIO].sampleAddress = buffer; - return __AudioEnqueue(g_audioChans[PSP_AUDIO_CHANNEL_VAUDIO], PSP_AUDIO_CHANNEL_VAUDIO, true); + // Shares the SRC channel, so it also shares the two-buffer depth and the busy return. + return __AudioSRCEnqueueBlocking(g_audioSRC, buffer, vol); } static u32 sceVaudioSetEffectType(int effectType, int vol) { diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index 9a2827f3f1..d0e2850da7 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -1630,46 +1630,76 @@ void DrawAudioChannels(ImConfig &cfg, ImControl &control) { ImGui::TableHeadersRow(); - // vaudio / output2 uses channel 8. - for (int i = 0; i < PSP_AUDIO_CHANNEL_MAX + 1; i++) { - if (!g_audioChans[i].reserved) { + static const auto formatName = [](u32 format) { + switch (format) { + case PSP_AUDIO_FORMAT_STEREO: return "Stereo"; + case PSP_AUDIO_FORMAT_MONO: return "Mono"; + default: return "UNK"; + } + }; + static const auto threadName = [](SceUID threadID) -> const char * { + KernelObject *thread = kernelObjects.GetFast(threadID); + return thread ? thread->GetName() : nullptr; + }; + + for (int i = 0; i < (int)PSP_AUDIO_CHANNEL_MAX; i++) { + const AudioChannel &chan = g_audioChans[i]; + if (!chan.reserved) { continue; } ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::PushID(i); - if (i == 8) { - ImGui::TextUnformatted("audio2"); - } else { - ImGui::Text("%d", i); - } + ImGui::Text("%d", i); ImGui::TableNextColumn(); ImGui::Checkbox("", &g_audioChans[i].mute); ImGui::TableNextColumn(); char id[2]{}; id[0] = i + 1; - ImClickableValue(id, g_audioChans[i].sampleAddress, control, ImCmd::SHOW_IN_MEMORY_VIEWER); + ImClickableValue(id, chan.sampleAddress, control, ImCmd::SHOW_IN_MEMORY_VIEWER); ImGui::TableNextColumn(); - ImGui::Text("%08x", g_audioChans[i].sampleCount); + ImGui::Text("%08x", chan.sampleCount); ImGui::TableNextColumn(); - ImGui::Text("%d | %d", g_audioChans[i].leftVolume, g_audioChans[i].rightVolume); + ImGui::Text("%d | %d", chan.leftVolume, chan.rightVolume); ImGui::TableNextColumn(); - switch (g_audioChans[i].format) { - case PSP_AUDIO_FORMAT_STEREO: - ImGui::TextUnformatted("Stereo"); - break; - case PSP_AUDIO_FORMAT_MONO: - ImGui::TextUnformatted("Mono"); - break; - default: - ImGui::TextUnformatted("UNK: %04x"); - break; + ImGui::TextUnformatted(formatName(chan.format)); + ImGui::TableNextColumn(); + // Only one thread can ever be parked on a mixer channel. + if (chan.waitingThread != 0) { + const char *name = threadName(chan.waitingThread); + if (name) { + ImGui::TextUnformatted(name); + } + } + ImGui::PopID(); + } + + // Output2, SRC and Vaudio all mean this one, which holds two buffers rather than one + // and can have more than one thread waiting on it. + if (g_audioSRC.reserved) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::PushID("src"); + ImGui::TextUnformatted("audio2"); + ImGui::TableNextColumn(); + ImGui::Checkbox("", &g_audioSRC.mute); + ImGui::TableNextColumn(); + if (g_audioSRC.bufferCount > 0) { + ImClickableValue("src", g_audioSRC.buffers[0].address, control, ImCmd::SHOW_IN_MEMORY_VIEWER); + } else { + ImGui::TextUnformatted("-"); } ImGui::TableNextColumn(); - for (auto t : g_audioChans[i].waitingThreads) { - KernelObject *thread = kernelObjects.GetFast(t.threadID); - if (thread) { - ImGui::Text("%s: %d", thread->GetName(), t.numSamples); + ImGui::Text("%08x (%d queued)", g_audioSRC.sampleCount, g_audioSRC.bufferCount); + ImGui::TableNextColumn(); + ImGui::Text("%d | %d", g_audioSRC.leftVolume, g_audioSRC.rightVolume); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(formatName(g_audioSRC.format)); + ImGui::TableNextColumn(); + for (SceUID threadID : g_audioSRC.waitingThreads) { + const char *name = threadName(threadID); + if (name) { + ImGui::TextUnformatted(name); } } ImGui::PopID(); diff --git a/docs/sceAudio.md b/docs/sceAudio.md new file mode 100644 index 0000000000..a99c7598c1 --- /dev/null +++ b/docs/sceAudio.md @@ -0,0 +1,200 @@ +# sceAudio: how the PSP's audio output actually behaves + +This describes the behavior `Core/HLE/sceAudio.cpp` and `Core/HLE/__sceAudio.cpp` model, and +where that behavior came from. Confirmed on a real PSP by `pspautotests/tests/audio/blocking`. + +The headline, because it is the thing most likely to be assumed wrong: **the blocking output +calls are not a queue that callers line up behind.** Each channel holds one buffer, the +Output2/SRC channel holds two, and a caller who finds no room is told +`SCE_ERROR_AUDIO_CHANNEL_BUSY` and expected to go away and come back. + +## Two different pieces of hardware + +`sceAudio` presents nine channels, but they are not the same thing underneath, which is why the +emulator keeps them in two different structures rather than one array of nine: `AudioChannel` +for the eight the mixer walks, and a single `AudioSRCChannel` for the ninth. + +**Channels 0-7** go through a software mixer running as a kernel thread. Every time the audio +DMA finishes a block, that thread wakes, takes up to 64 samples from each channel that has a +buffer, sums them into a 32-bit accumulator, clamps, and writes 64 stereo frames into the other +half of a double buffer. + +**Channel 8** - `sceAudioOutput2*`, `sceAudioSRC*` and `sceVaudio*` are all the same channel - +never touches the mixer. It builds DMA descriptors that point straight at the game's buffer and +lets the codec resample it. `sceAudioOutput2Reserve(n)` is literally +`sceAudioSRCChReserve(n, 44100, 2)`, and the other Output2 entry points are one-instruction tail +calls to the SRC ones. + +They also have separate DMA channels, so one starting does not disturb the other. + +## The driver plays out of the game's memory + +Nothing is copied when a buffer is handed over. `sceAudioOutputBlocking` stores the pointer, and +the mixer walks it forward 64 samples at a time until it is spent, then clears it and signals. +`sceAudioGetChannelRestLen` counts down in steps of 64 for exactly that reason. + +The emulator used to copy the whole buffer into a ring of samples at enqueue time, with the +volume already applied. That made a game rewriting a buffer it had already handed over +invisible, and it made the queue depth an emulator choice rather than a hardware fact. + +## One buffer per channel + +The driver's single enqueue point, which all four `sceAudioOutput*` functions reach, is: + +```c +if (sampleCount == 0) return SCE_ERROR_AUDIO_CHANNEL_NOT_INIT; +if (sampleAddress != 0) return SCE_ERROR_AUDIO_CHANNEL_BUSY; +remaining = sampleCount; +sampleAddress = ptr; // may be 0 +return sampleCount; +``` + +`sceAudioOutput` and `sceAudioOutputPanned` return that result as it stands, so a second +non-blocking output is refused until the first buffer has finished. + +The blocking pair adds a wait, but only for the first thread to ask: + +```c +r = enqueue(...); +if (r != BUSY) return r; +if (channel->waiting) return BUSY; // somebody else is already parked here +channel->waiting = 1; +wait for this channel's bit in the driver's event flag; +retry the enqueue; +channel->waiting = 0; +``` + +**Only one thread can be parked on a channel.** That single flag is why a game that runs a movie +thread and a sound-effect thread over one output gets sensible behavior on hardware and did not +in the emulator: the loser is told the channel is busy and skips its turn, where an emulator that +blocked it instead made the two threads alternate, halving the movie's audio rate. That is +https://github.com/hrydgard/ppsspp/issues/12888, and it is what +`tests/audio/blocking/contend` pins down. + +The same flag locks out `sceAudioChRelease`, `sceAudioSetChannelDataLen` and +`sceAudioChangeChannelConfig`, which all return `SCE_ERROR_AUDIO_CHANNEL_BUSY` while a thread is +parked. `sceAudioChangeChannelVolume` does not check anything. + +A null pointer is accepted and sets `remaining` without setting `sampleAddress`, so nothing +plays. That is the documented way to wait for a channel to drain, and it is the one case where +the two rest-length calls disagree - see below. + +## Two buffers on the SRC channel + +Channel 8 has two DMA descriptors, so two buffers can be in flight. The third caller is refused +outright and does not even get the chance to wait: + +```c +if (!reserved) return SCE_ERROR_AUDIO_CHANNEL_NOT_RESERVED; +if (both descriptors armed) return SCE_ERROR_AUDIO_CHANNEL_BUSY; +arm one; +wait for one descriptor to retire; +return sampleCount; +``` + +Starting the DMA sets the same event flag bit that a retiring descriptor does, which is why the +first output after an idle stretch returns without waiting while every one after it waits exactly +one buffer. The emulator models that unclaimed completion with a flag on the channel. + +## The two rest-length calls are not the same function twice + +```c +sceAudioGetChannelRestLen(ch) = remaining + (waiting ? sampleCount : 0) +sceAudioGetChannelRestLength(ch) = (sampleAddress ? remaining : 0) + (waiting ? sampleCount : 0) +sceAudioOutput2GetRestSample() = (armed descriptor count) * current sampleCount +``` + +Both channel versions count a parked thread's buffer as well as the one playing. They only +differ after an output with a null pointer, which leaves `remaining` set with no buffer, and the +hardware duly reports `0x400` from one and `0` from the other. + +`sceAudioOutput2GetRestSample` reports in units of the *current* length, so after +`sceAudioOutput2ChangeLength(64)` a 4096-sample buffer armed earlier reads back as `0x40`. + +## Where the emulator has to fake something + +The mixer thread outranks whoever called, so on hardware the first 64 samples of a buffer are +consumed before the output call has returned - a channel reserved for exactly 64 samples is free +again immediately. The emulator's mixer is a timer event instead, so it re-phases that event to +the moment the mixer's DMA starts and runs one block right then. The SRC channel gets the phase +reset but no early read, since its DMA feeds the codec directly. Without this the answer to +`sceAudioGetChannelRestLen` right after an output would depend on where the timer happened to +be, and would differ from run to run. + +Two things are still approximate, both below one mix block: + +- A descriptor retires when the DMA transfer finishes, which is slightly ahead of the audio + being heard, so hardware frees an Output2 buffer about 100us earlier than the emulator does. +- The emulator only retires SRC buffers on block boundaries, so a buffer whose length is not a + multiple of the block can be up to 1.5ms late. `audio/output2/frequency` and + `audio/output2/rest` are in `tests_next` for this reason. + +## Argument checking + +Recorded here because several of these were guesses before, and because the *order* of the +checks is observable. + +| function | checks, in order | +|---|---| +| `sceAudioChReserve` | the free-channel search runs 7 down to 0 and needs the channel both released and finished playing; bad channel `80260003`; already reserved `80260003`; count not a positive multiple of 64 up to 0xFFC0 `80260006`; format not 0 or 0x10 `80260007` | +| `sceAudioChRelease` | bad channel `80260003`; not reserved `80260008`; a thread parked `80260002`. Clears only the reservation - a buffer already playing plays out. | +| `sceAudioSetChannelDataLen` | bad channel `80260003`; **bad length `80260006`, before the reservation is looked at**; parked thread `80260002`; not reserved `80260001` | +| `sceAudioChangeChannelConfig` | bad channel `80260003`; parked thread or buffer in flight `80260002`; not reserved `80260008`; bad format `80260007` | +| `sceAudioChangeChannelVolume` | either side above 0xFFFF `8026000b`; bad channel `80260003`; **no reservation check**; a negative volume leaves that side alone | +| `sceAudioOutput`, `sceAudioOutputPanned` | each volume compared signed, so negatives pass and mean "leave unchanged" | +| `sceAudioOutputPannedBlocking` | the two volumes are ORed before comparing, so a negative one fails with `8026000b` | +| `sceAudioOutput2ChangeLength` | **length outside 17..4111 `80260006`, before the reservation**; not reserved `80260008` | +| `sceAudioSRCChReserve` | channels not 2 or 4 `80000104`; channels 4 `80000003`; count outside 17..4111 `80000104`; bad frequency `8026000a`; already reserved `80268002` | +| `sceAudioSRCChRelease` | not reserved `80260008`; a descriptor still armed `80268002` | + +Accepted SRC frequencies are 8000, 11025, 12000, 16000, 22050, 24000, 32000 and 48000, plus +whatever the output is currently running at - which is how 44100 and 0 get through. + +## sceAudioOneshotOutput + +The odd one out: it plays a single buffer on a channel it never reserves. The channel stays +free as far as everything else is concerned, so `sceAudioChRelease` on it answers `80260008`, +and it returns to the pool by itself once the buffer runs out. It returns the channel number, +takes any positive sample count - alignment and the 0xFFC0 ceiling do not apply - and unlike +the other outputs a negative volume is an error rather than "leave it alone". There is no busy +check either: a second one-shot while the first is playing just replaces it. + +No game is known to call it. It is implemented because the behavior turned out to be simple +once traced, not because anything needed it. + +## sceVaudio is a third shape + +`sceVaudio` reserves the same channel as Output2 and SRC, but its own release is not the same +call. It hands the channel a null pointer first - the drain idiom above - and only then does the +ordinary SRC release. So it blocks for one buffer and returns 0 where the other two would refuse +with `80268002`, and whatever was playing is played out rather than cut off. It also ignores its +own reservation flag and releases whatever holds the SRC channel, which is why +`tests/audio/sceaudio/reserve` gets away with what it calls the "wrong release". + +`sceVaudioChReserve` marks vaudio reserved *before* delegating and does not undo that when the +delegate fails, so a caller that got `80268002` because Output2 held the channel is told +`80000021` next time round until a release clears it. + +## What a call costs + +Worth knowing because a game whose losing thread retries in a loop feels it directly. +`tests/audio/blocking/overhead` measures it in buckets: + +| call | cost | +|---|---| +| Output2/SRC output, both descriptors already armed | 30-100us | +| Output2/SRC output, any other outcome, including an unreserved channel | over 100us | +| mixer output that is refused, blocking or not | under 10us | +| mixer output that starts the DMA | over 100us | + +Every SRC output ends up querying the codec, which is the slow part; only the refused case gets +out before it. Mixer channels are the other way round - cheap unless the call is the one that +brings the DMA up. + +## Savestates + +`AudioChannel` is at section version 4 and the SRC channel has a section of its own. Anything +older stored a ring of already-mixed samples, which cannot be turned back into a buffer pointer +and a position, so loading one drops the pending audio and wakes any parked threads. That costs +a fraction of a second of silence on load and nothing else. Older states also carry the SRC +channel as a ninth entry in the channel array, which is read and discarded. diff --git a/pspautotests b/pspautotests index 1dda6a3709..4eb50721ea 160000 --- a/pspautotests +++ b/pspautotests @@ -1 +1 @@ -Subproject commit 1dda6a37091c1dcf7bc23b5ac5c48811751e9f2c +Subproject commit 4eb50721ea35a57f786b4230d2d852896ddfb74b diff --git a/test.py b/test.py index 22142cb689..62a540448d 100755 --- a/test.py +++ b/test.py @@ -113,7 +113,17 @@ tests_good = [ "audio/mp3/reserve", "audio/mp3/setloopnum", "audio/mp3/stream", + "audio/blocking/contend", + "audio/blocking/channels", + "audio/blocking/depth", + "audio/blocking/errors", + "audio/blocking/overhead", + "audio/blocking/oneshot", + "audio/blocking/restlen", + "audio/blocking/vaudio", + "audio/sceaudio/datalen", "audio/output2/changelength", + "audio/output2/release", "audio/output2/reserve", "audio/output2/threads", "audio/reverb/basic", @@ -411,13 +421,11 @@ tests_next = [ "cpu/vfpu/prefixes", "cpu/vfpu/vector", "cpu/vfpu/vregs", - "audio/sceaudio/datalen", "audio/sceaudio/output", "audio/sceaudio/reserve", "audio/sascore/setadsr", "audio/mp3/init", "audio/output2/frequency", - "audio/output2/release", "audio/output2/rest", "ccc/convertstring", "display/hcount",