From af3c91fcf95106fb8835d65b97a09c1b3b994ac0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 21 Jul 2025 21:57:14 +0200 Subject: [PATCH] ttmp --- Core/HLE/__sceAudio.cpp | 83 ++++++++++++++++++++++++++++-------- Core/HLE/sceAudio.cpp | 38 ++++++++++------- Core/HLE/sceAudio.h | 2 + UI/ImDebugger/ImDebugger.cpp | 5 ++- pspautotests | 2 +- 5 files changed, 94 insertions(+), 36 deletions(-) diff --git a/Core/HLE/__sceAudio.cpp b/Core/HLE/__sceAudio.cpp index 60ee79ab57..b7c659e937 100644 --- a/Core/HLE/__sceAudio.cpp +++ b/Core/HLE/__sceAudio.cpp @@ -193,13 +193,18 @@ u32 __AudioEnqueue(AudioChannel &chan, int chanNum, int leftVol, int rightVol, i } // Update channel volume. - chan.leftVolume = leftVol; - chan.rightVolume = rightVol; + if (leftVol) { + chan.leftVolume = leftVol; + } + if (rightVol) { + chan.rightVolume = rightVol; + } - // Check for blocking. - if (chan.queueLength > 0) { + // Check for blocking when pushing the *second* block. + if (chan.queueLength == 1) { if (blocking) { if (__KernelIsDispatchEnabled()) { + DEBUG_LOG(Log::sceAudio, "Blocking thread %i on audio channel %i", __KernelGetCurThread(), chanNum); chan.waitingThreads.push_back(__KernelGetCurThread()); // Also remember the value to return in the waitValue. __KernelWaitCurThread(WAITTYPE_AUDIOCHANNEL, (SceUID)chanNum + 1, ret, 0, false, "blocking audio"); @@ -210,6 +215,11 @@ u32 __AudioEnqueue(AudioChannel &chan, int chanNum, int leftVol, int rightVol, i } else { return SCE_ERROR_AUDIO_CHANNEL_BUSY; } + } else if (chan.queueLength == 0) { + // This we shouldn't see very much, or? + DEBUG_LOG(Log::sceAudio, "Empty, not blocking thread %i on audio channel %i", __KernelGetCurThread(), chanNum); + } else { + return SCE_ERROR_AUDIO_CHANNEL_BUSY; } if (samplePtr == 0) { @@ -222,16 +232,17 @@ u32 __AudioEnqueue(AudioChannel &chan, int chanNum, int leftVol, int rightVol, i // read the actual data. QueueEntry entry; - entry.leftVol = leftVol; - entry.rightVol = rightVol; + entry.leftVol = chan.leftVolume; + entry.rightVol = chan.rightVolume; entry.sampleAddress = samplePtr; + entry.sampleCount = chan.sampleCount; chan.queue[chan.queueLength++] = entry; _dbg_assert_(chan.queueLength <= AudioChannel::MAX_QUEUE_LENGTH); return ret; } -void __AudioWakeThreads(AudioChannel &chan, int result, int step) { +void __AudioWakeThreads(AudioChannel &chan, int result) { u32 error; bool wokeThreads = false; for (size_t w = 0; w < chan.waitingThreads.size(); ++w) { @@ -257,10 +268,6 @@ void __AudioWakeThreads(AudioChannel &chan, int result, int step) { } } -void __AudioWakeThreads(AudioChannel &chan, int result) { - __AudioWakeThreads(chan, result, 0x7FFFFFFF); -} - void __AudioSetOutputFrequency(int freq) { if (freq != 44100) { WARN_LOG_REPORT(Log::sceAudio, "Switching audio frequency to %i", freq); @@ -276,6 +283,8 @@ void __AudioSetSRCFrequency(int freq) { // Mix samples from the various audio channels into a single sample queue, managed by the backend implementation. void __AudioUpdate(bool resetRecording) { + DEBUG_LOG(Log::sceAudio, "=========== Audio update ============ "); + // 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. @@ -294,34 +303,69 @@ void __AudioUpdate(bool resetRecording) { } if (chan.queueLength == 0) { + chan.numUnderruns++; // Can't play an empty queue. // Log? continue; } - int channels = chan.format == PSP_AUDIO_FORMAT_STEREO ? 2 : 1; + if (!chan.queue[0].sampleAddress) { + DEBUG_LOG(Log::sceAudio, "Channel %i queue[0] had a zero sampleAddress, skipping", i); + chan.queue[0] = chan.queue[1]; + chan.queueLength--; + __AudioWakeThreads(chan, 0); + continue; + } + + _dbg_assert_(Memory::IsValid4AlignedAddress(chan.queue[0].sampleAddress)); + + const int channels = chan.format == PSP_AUDIO_FORMAT_STEREO ? 2 : 1; const bool needsResample = i == PSP_AUDIO_CHANNEL_SRC && srcFrequency != 0 && srcFrequency != mixFrequency; const size_t sz = needsResample ? (hwBlockSize * srcFrequency) / mixFrequency : hwBlockSize; + static_assert(chan.MAX_QUEUE_LENGTH == 2); + const s16 *buf1 = 0, *buf2 = 0; size_t sz1 = 0, sz2 = 0; - if (sz <= chan.sampleCount - chan.queuePlayOffset) { - // Easy case, no wrapping to the next buffer, and no change in blocking. - buf1 = (s16 *)(Memory::base + chan.queue[0].sampleAddress) + chan.queuePlayOffset * channels; + buf1 = (s16 *)(Memory::base + chan.queue[0].sampleAddress) + chan.queuePlayOffset * channels; + + if (sz <= chan.queue[0].sampleCount - chan.queuePlayOffset) { + // Easy case, no wrapping to the next buffer. chan.queuePlayOffset += (int)sz; sz1 = sz * channels; - if (chan.queuePlayOffset == chan.sampleCount) { + + // If reached the end? + if (chan.queuePlayOffset == chan.queue[0].sampleCount) { // If we reached the end of the queue, pop. Let's do it the easy way. - static_assert(chan.MAX_QUEUE_LENGTH == 2); chan.queue[0] = chan.queue[1]; + chan.queue[1] = {}; chan.queueLength--; chan.queuePlayOffset = 0; __AudioWakeThreads(chan, 0); + if (chan.queueLength == 0) { + DEBUG_LOG(Log::sceAudio, "Channel %i queue ran empty at offset %d", i, chan.queuePlayOffset); + } } } else { - _dbg_assert_(false); + // We reached the end. Figure out how much is left in the first buffer. + int left = (chan.queue[0].sampleCount - chan.queuePlayOffset); + int right = sz - left; + sz1 = left * channels; + + __AudioWakeThreads(chan, 0); + if (chan.queueLength == 0) { + DEBUG_LOG(Log::sceAudio, "Channel %i queue ran empty at offset %d", i, chan.queuePlayOffset); + } else { + // Then fill up with data from the second buffer, then pop. + buf2 = (s16 *)(Memory::base + chan.queue[1].sampleAddress); + sz2 = right * channels; + chan.queue[0] = chan.queue[1]; + chan.queue[1] = {}; + } + + // _dbg_assert_(false); /* if (sz > legacyChanSampleQueues[i].size()) { ERROR_LOG(Log::sceAudio, "Channel %i buffer underrun at %i of %i", i, (int)legacyChanSampleQueues[i].size() / 2, (int)sz / 2); @@ -374,11 +418,14 @@ void __AudioUpdate(bool resetRecording) { // 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... + // TODO: Before merge, we must incorporate the volume here. + // sz1 includes the channels multiplier, so the below core is channel-neutral. for (size_t s = 0; s < sz1; s++) { _dbg_assert_(s < ARRAY_SIZE(mixBuffer)); mixBuffer[s] += buf1[s]; } + if (buf2) { for (size_t s = 0; s < sz2; s++) mixBuffer[s + sz1] += buf2[s]; diff --git a/Core/HLE/sceAudio.cpp b/Core/HLE/sceAudio.cpp index cc08cb8981..a013bf14a8 100644 --- a/Core/HLE/sceAudio.cpp +++ b/Core/HLE/sceAudio.cpp @@ -102,6 +102,9 @@ void AudioChannel::clear() { // chanSampleQueues[index].clear(); queueLength = 0; queuePlayOffset = 0; + queue[0] = {}; + queue[1] = {}; + numUnderruns = 0; waitingThreads.clear(); } @@ -134,7 +137,11 @@ static u32 sceAudioOutputPannedBlocking(u32 chan, int leftvol, int rightvol, u32 } u32 result = __AudioEnqueue(g_audioChans[chan], chan, leftvol, rightvol, samplePtr, true); - return hleLogDebug(Log::sceAudio, result); + if (chan == 4) { + return hleLogDebug(Log::sceAudio, result); + } else { + return hleLogVerbose(Log::sceAudio, result); + } } static u32 sceAudioOutput(u32 chan, int vol, u32 samplePtr) { @@ -163,7 +170,7 @@ static u32 sceAudioOutputPanned(u32 chan, int leftvol, int rightvol, u32 sampleP } } -static int sceAudioGetChannelRestLen(u32 chan) { +static int __RestLenCommon(u32 chan) { if (chan >= PSP_AUDIO_CHANNEL_MAX) { return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_INVALID_CHANNEL, "bad channel"); } @@ -172,14 +179,9 @@ static int sceAudioGetChannelRestLen(u32 chan) { return hleLogVerbose(Log::sceAudio, remainingSamples); } -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)g_audioChans[chan].queueLength * g_audioChans[chan].sampleCount; - return hleLogVerbose(Log::sceAudio, remainingSamples); -} +// These two are the same so share the implementation. +static int sceAudioGetChannelRestLen(u32 chan) { return __RestLenCommon(chan); } +static int sceAudioGetChannelRestLength(u32 chan) { return __RestLenCommon(chan); } static u32 GetFreeChannel() { for (u32 i = PSP_AUDIO_CHANNEL_MAX - 1; i > 0; --i) { @@ -209,11 +211,16 @@ static u32 sceAudioChReserve(int chan, u32 sampleCount, u32 format) { return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_INVALID_CHANNEL, "reserve channel failed"); } - g_audioChans[chan].sampleCount = sampleCount; - g_audioChans[chan].format = format; - g_audioChans[chan].reserved = true; - g_audioChans[chan].leftVolume = 0; - g_audioChans[chan].rightVolume = 0; + // Reset the channel. + AudioChannel &channel = g_audioChans[chan]; + channel.sampleCount = sampleCount; + channel.format = format; + channel.reserved = true; + channel.leftVolume = 0; + channel.rightVolume = 0; + channel.queuePlayOffset = 0; + channel.queueLength = 0; + channel.numUnderruns = 0; return hleLogDebug(Log::sceAudio, chan); } @@ -238,7 +245,6 @@ static u32 sceAudioSetChannelDataLen(u32 chan, u32 len) { } else if ((len & 63) != 0 || len == 0 || len > PSP_AUDIO_SAMPLE_MAX) { return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_OUTPUT_SAMPLE_DATA_SIZE_NOT_ALIGNED, "invalid sample count"); } - g_audioChans[chan].sampleCount = len; return hleLogDebug(Log::sceAudio, 0); } diff --git a/Core/HLE/sceAudio.h b/Core/HLE/sceAudio.h index 708fea55c7..8ebd0ca108 100644 --- a/Core/HLE/sceAudio.h +++ b/Core/HLE/sceAudio.h @@ -37,6 +37,7 @@ struct QueueEntry { u32 leftVol; u32 rightVol; u32 sampleAddress; + u32 sampleCount; }; struct AudioChannel { @@ -61,6 +62,7 @@ struct AudioChannel { // For the debugger only. Not saved. bool mute = false; + int numUnderruns = 0; std::vector waitingThreads; diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index 18c937a4ff..5526837c7e 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -1459,7 +1459,7 @@ void DrawAudioChannels(ImConfig &cfg, ImControl &control) { return; } - if (ImGui::BeginTable("audios", 7, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) { + if (ImGui::BeginTable("audios", 8, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) { ImGui::TableSetupColumn("Index", ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("Mute", ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("SampleAddr", ImGuiTableColumnFlags_WidthFixed); @@ -1467,6 +1467,7 @@ void DrawAudioChannels(ImConfig &cfg, ImControl &control) { ImGui::TableSetupColumn("Volume", ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("Format", ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("Waiting Thread", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("Underruns", ImGuiTableColumnFlags_WidthFixed); ImGui::TableHeadersRow(); @@ -1512,6 +1513,8 @@ void DrawAudioChannels(ImConfig &cfg, ImControl &control) { ImGui::Text("%s", thread->GetName()); } } + ImGui::TableNextColumn(); + ImGui::Text("%d", g_audioChans[i].numUnderruns); ImGui::PopID(); } diff --git a/pspautotests b/pspautotests index 4374348477..2d09fac64f 160000 --- a/pspautotests +++ b/pspautotests @@ -1 +1 @@ -Subproject commit 4374348477b425f48f99c8fd58a853c439f78acd +Subproject commit 2d09fac64fa70355f119434b8555352165aba371