mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-13 09:39:33 +01:00
Merge pull request #22279 from hrydgard/audio-blocking-hardware-model
sceAudio: model the real buffering, so contending threads get told BUSY
This commit is contained in:
11 files changed
+1067
-440
No files matched your search
@@ -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 |
|
||||
|
||||
|
||||
@@ -307,6 +307,11 @@ template<u32 func(u32, int , int , int, int, int)> void WrapU_UIIIII() {
|
||||
RETURN(retval);
|
||||
}
|
||||
|
||||
template<u32 func(int, int, int, int, int, u32)> void WrapU_IIIIIU() {
|
||||
u32 retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4), PARAM(5));
|
||||
RETURN(retval);
|
||||
}
|
||||
|
||||
template<u32 func(u32, int , int , int, u32)> void WrapU_UIIIU() {
|
||||
u32 retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4));
|
||||
RETURN(retval);
|
||||
|
||||
+428
-233
@@ -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<s16, 32768 * 8> 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<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);
|
||||
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) {
|
||||
|
||||
+24
-4
@@ -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();
|
||||
|
||||
|
||||
+240
-140
@@ -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 <memory>
|
||||
|
||||
#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<s16, 32768 * 8> 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<AudioChannelWaitInfo> 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<FixedSizeQueue<s16, 32768 * 8>>();
|
||||
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>, "sceAudioGetChannelRestLength", 'i', "i" },
|
||||
{0XCB2E439E, &WrapU_UU<sceAudioSetChannelDataLen>, "sceAudioSetChannelDataLen", 'x', "ii" },
|
||||
{0X95FD0C2D, &WrapU_UU<sceAudioChangeChannelConfig>, "sceAudioChangeChannelConfig", 'x', "ii" },
|
||||
{0XB7E1D8E7, &WrapU_UUU<sceAudioChangeChannelVolume>, "sceAudioChangeChannelVolume", 'x', "ixx" },
|
||||
{0XB7E1D8E7, &WrapU_UII<sceAudioChangeChannelVolume>, "sceAudioChangeChannelVolume", 'x', "ixx" },
|
||||
|
||||
// Like Output2, but with ability to do sample rate conversion.
|
||||
{0X38553111, &WrapU_UUU<sceAudioSRCChReserve>, "sceAudioSRCChReserve", 'x', "iii" },
|
||||
{0X5C37C0AE, &WrapU_V<sceAudioSRCChRelease>, "sceAudioSRCChRelease", 'x', "" },
|
||||
{0XE0727056, &WrapU_UU<sceAudioSRCOutputBlocking>, "sceAudioSRCOutputBlocking", 'x', "xx" },
|
||||
|
||||
// Never seen these used
|
||||
{0X41EFADE7, nullptr, "sceAudioOneshotOutput", '?', "" },
|
||||
{0X41EFADE7, &WrapU_IIIIIU<sceAudioOneshotOutput>, "sceAudioOneshotOutput", 'x', "iiiiix"},
|
||||
|
||||
// Never seen this used
|
||||
{0XB61595C0, nullptr, "sceAudioLoopbackTest", '?', "" },
|
||||
|
||||
// Microphone interface
|
||||
|
||||
+63
-13
@@ -17,7 +17,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
|
||||
#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<AudioChannelWaitInfo> 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<SceUID> 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();
|
||||
|
||||
+40
-22
@@ -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) {
|
||||
|
||||
@@ -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<KernelObject>(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<KernelObject>(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();
|
||||
|
||||
@@ -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.
|
||||
+1
-1
Submodule pspautotests updated: 1dda6a3709...4eb50721ea.
@@ -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",
|
||||
|
||||
Reference in new issue
Block a user