mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-09-22 20:36:22 +02:00
A re-read of the whole thing after the reservation bug, looking for anything else it got wrong. The SRC channel's buffer count is read straight out of the state and then used to index a two-element array, so a corrupt one is a write outside the struct. It is range-checked now, the same way the channel count above it already was. A channel holding a buffer with no samples left of it was stuck: the mixer skipped it without ever clearing the address, so it read as busy for ever and the game had no way back to sound. The API cannot produce that - a channel is never reserved for zero samples - but a savestate can claim it, so the mixer now retires such a channel instead of stepping over it. Also took the "get rid of this next time we bump" the version-2 resampler section came with, since this branch is that bump. Nothing was ever in it. Checked by loading states written by a released build for a game that uses the mixer channels and one that uses Output2, plus a round trip of the new format. Every section ends in a marker, so a conversion path that consumed the wrong number of bytes would fail the load rather than quietly corrupt what follows. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
705 lines
24 KiB
C++
705 lines
24 KiB
C++
// Copyright (c) 2012- PPSSPP Project.
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, version 2.0 or later versions.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License 2.0 for more details.
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
// Official git repository and contact information can be found at
|
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
|
|
#include <atomic>
|
|
#include <mutex>
|
|
#include <algorithm>
|
|
|
|
#include "Common/Common.h"
|
|
#include "Common/File/Path.h"
|
|
#include "Common/Serialize/Serializer.h"
|
|
#include "Common/Serialize/SerializeFuncs.h"
|
|
#include "Common/Data/Collections/FixedSizeQueue.h"
|
|
#include "Common/System/System.h"
|
|
#include "Common/Math/SIMDHeaders.h"
|
|
#include "Common/StringUtils.h"
|
|
|
|
#include "Core/Config.h"
|
|
#include "Core/CoreTiming.h"
|
|
#include "Core/MemMapHelpers.h"
|
|
#include "Core/Reporting.h"
|
|
#include "Core/System.h"
|
|
#include "Core/WaveFile.h"
|
|
#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"
|
|
#include "Core/HLE/sceKernelThread.h"
|
|
#include "Core/Util/AudioFormat.h"
|
|
|
|
// Should be used to lock anything related to the outAudioQueue.
|
|
// atomic locks are used on the lock. TODO: make this lock-free
|
|
std::atomic_flag atomicLock_;
|
|
|
|
int eventAudioUpdate = -1;
|
|
|
|
// TODO: This is now useless and should be removed. Just scared of breaking states.
|
|
int eventHostAudioUpdate = -1;
|
|
|
|
int mixFrequency = 44100;
|
|
int srcFrequency = 0;
|
|
|
|
const int hwSampleRate = 44100;
|
|
const int hwBlockSize = 64;
|
|
|
|
static int audioIntervalCycles;
|
|
static int audioHostIntervalCycles;
|
|
|
|
static s32 *mixBuffer;
|
|
static s16 *clampedMixBuffer;
|
|
#ifndef MOBILE_DEVICE
|
|
WaveFileWriter g_wave_writer;
|
|
static bool m_logAudio;
|
|
#endif
|
|
|
|
static void hleAudioUpdate(u64 userdata, int cyclesLate) {
|
|
// Schedule the next cycle first. __AudioUpdate() may consume cycles.
|
|
CoreTiming::ScheduleEvent(audioIntervalCycles - cyclesLate, eventAudioUpdate, 0);
|
|
|
|
__AudioUpdate();
|
|
}
|
|
|
|
static void hleHostAudioUpdate(u64 userdata, int cyclesLate) {
|
|
CoreTiming::ScheduleEvent(audioHostIntervalCycles - cyclesLate, eventHostAudioUpdate, 0);
|
|
}
|
|
|
|
void __AudioCPUMHzChange() {
|
|
audioIntervalCycles = (int)(usToCycles(1000000ULL) * hwBlockSize / hwSampleRate);
|
|
|
|
// Soon to be removed.
|
|
audioHostIntervalCycles = (int)(usToCycles(1000000ULL) * 512 / hwSampleRate);
|
|
}
|
|
|
|
void __AudioInit() {
|
|
System_AudioResetStatCounters();
|
|
mixFrequency = 44100;
|
|
srcFrequency = 0;
|
|
|
|
__AudioCPUMHzChange();
|
|
|
|
eventAudioUpdate = CoreTiming::RegisterEvent("AudioUpdate", &hleAudioUpdate);
|
|
eventHostAudioUpdate = CoreTiming::RegisterEvent("AudioUpdateHost", &hleHostAudioUpdate);
|
|
|
|
CoreTiming::ScheduleEvent(audioIntervalCycles, eventAudioUpdate, 0);
|
|
CoreTiming::ScheduleEvent(audioHostIntervalCycles, eventHostAudioUpdate, 0);
|
|
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];
|
|
memset(mixBuffer, 0, hwBlockSize * 2 * sizeof(s32));
|
|
|
|
System_AudioClear();
|
|
}
|
|
|
|
void __AudioDoState(PointerWrap &p) {
|
|
auto s = p.Section("sceAudio", 1, 3);
|
|
if (!s)
|
|
return;
|
|
|
|
Do(p, eventAudioUpdate);
|
|
CoreTiming::RestoreRegisterEvent(eventAudioUpdate, "AudioUpdate", &hleAudioUpdate);
|
|
Do(p, eventHostAudioUpdate);
|
|
CoreTiming::RestoreRegisterEvent(eventHostAudioUpdate, "AudioUpdateHost", &hleHostAudioUpdate);
|
|
|
|
Do(p, mixFrequency);
|
|
if (s >= 2) {
|
|
Do(p, srcFrequency);
|
|
} else {
|
|
// Assume that it was actually the SRC channel frequency.
|
|
srcFrequency = mixFrequency;
|
|
mixFrequency = 44100;
|
|
}
|
|
|
|
// 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<s16, 512 * 16> 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);
|
|
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) {
|
|
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();
|
|
}
|
|
|
|
void __AudioShutdown() {
|
|
delete [] mixBuffer;
|
|
delete [] clampedMixBuffer;
|
|
|
|
mixBuffer = 0;
|
|
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) {
|
|
__StopLogAudio();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// 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.
|
|
|
|
// 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);
|
|
}
|
|
|
|
// 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;
|
|
|
|
// 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");
|
|
}
|
|
}
|
|
|
|
// 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) {
|
|
if (freq != 44100) {
|
|
WARN_LOG_REPORT(Log::sceAudio, "Switching audio frequency to %i", freq);
|
|
} else {
|
|
DEBUG_LOG(Log::sceAudio, "Switching audio frequency to %i", freq);
|
|
}
|
|
mixFrequency = freq;
|
|
}
|
|
|
|
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.
|
|
memset(mixBuffer, 0, hwBlockSize * 2 * sizeof(s32));
|
|
|
|
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 (woke) {
|
|
__AudioReScheduleAfterWake();
|
|
}
|
|
|
|
if (g_Config.bEnableSound) {
|
|
float multiplier = Volume100ToMultiplier(std::clamp(g_Config.iGameVolume, 0, VOLUMEHI_FULL));
|
|
if (PSP_CoreParameter().fpsLimit != FPSLimit::NORMAL || PSP_CoreParameter().fastForward) {
|
|
if (g_Config.iAltSpeedVolume != -1) {
|
|
// Multiply in the alt speed volume instead of replacing like before.
|
|
multiplier *= Volume100ToMultiplier(g_Config.iAltSpeedVolume);
|
|
}
|
|
}
|
|
|
|
System_AudioPushSamples(mixBuffer, hwBlockSize, multiplier);
|
|
|
|
#ifndef MOBILE_DEVICE
|
|
if (g_Config.bSaveLoadResetsAVdumping && resetRecording) {
|
|
__StopLogAudio();
|
|
std::string discID = g_paramSFO.GetDiscID();
|
|
Path audio_file_name = GetSysDirectory(DIRECTORY_AUDIO) / StringFromFormat("%s_%s.wav", discID.c_str(), KernelTimeNowFormatted().c_str()).c_str();
|
|
INFO_LOG(Log::Common, "Restarted audio recording to: %s", audio_file_name.c_str());
|
|
if (!File::Exists(GetSysDirectory(DIRECTORY_AUDIO)))
|
|
File::CreateDir(GetSysDirectory(DIRECTORY_AUDIO));
|
|
File::CreateEmptyFile(audio_file_name);
|
|
__StartLogAudio(audio_file_name);
|
|
}
|
|
if (!m_logAudio) {
|
|
if (g_Config.bDumpAudio) {
|
|
// Use gameID_EmulatedTimestamp for filename
|
|
std::string discID = g_paramSFO.GetDiscID();
|
|
Path audio_file_name = GetSysDirectory(DIRECTORY_AUDIO) / StringFromFormat("%s_%s.wav", discID.c_str(), KernelTimeNowFormatted().c_str());
|
|
INFO_LOG(Log::Common,"Recording audio to: %s", audio_file_name.c_str());
|
|
// Create the path just in case it doesn't exist
|
|
if (!File::Exists(GetSysDirectory(DIRECTORY_AUDIO)))
|
|
File::CreateDir(GetSysDirectory(DIRECTORY_AUDIO));
|
|
File::CreateEmptyFile(audio_file_name);
|
|
__StartLogAudio(audio_file_name);
|
|
}
|
|
} else {
|
|
if (g_Config.bDumpAudio) {
|
|
for (int i = 0; i < hwBlockSize * 2; i++) {
|
|
clampedMixBuffer[i] = clamp_s16(mixBuffer[i]);
|
|
}
|
|
g_wave_writer.AddStereoSamples(clampedMixBuffer, hwBlockSize);
|
|
} else {
|
|
__StopLogAudio();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#ifndef MOBILE_DEVICE
|
|
void __StartLogAudio(const Path& filename) {
|
|
if (!m_logAudio) {
|
|
if (!g_wave_writer.Start(filename, 44100)) {
|
|
// Start() logs the reason. Leave m_logAudio false, or every mixed block from here on
|
|
// would hand samples to a closed file - and turn the setting off too, since otherwise
|
|
// the caller below retries this (creating the file, opening it) once per block.
|
|
ERROR_LOG(Log::sceAudio, "Failed to start audio logging, disabling it");
|
|
g_Config.bDumpAudio = false;
|
|
return;
|
|
}
|
|
m_logAudio = true;
|
|
g_wave_writer.SetSkipSilence(false);
|
|
NOTICE_LOG(Log::sceAudio, "Starting Audio logging");
|
|
} else {
|
|
WARN_LOG(Log::sceAudio, "Audio logging has already been started");
|
|
}
|
|
}
|
|
|
|
void __StopLogAudio() {
|
|
if (m_logAudio) {
|
|
m_logAudio = false;
|
|
g_wave_writer.Stop();
|
|
NOTICE_LOG(Log::sceAudio, "Stopping Audio logging");
|
|
} else {
|
|
WARN_LOG(Log::sceAudio, "Audio logging has already been stopped");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void WAVDump::Reset() {
|
|
__AudioUpdate(true);
|
|
}
|