mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
sceAudio: implement sceAudioOneshotOutput, and cover the rest of the channel rules
Another pass looking for gaps, all of it now recorded by audio/blocking/channels and audio/blocking/oneshot. sceAudioOneshotOutput was the last unimplemented entry in the module, returning "library not linked" to anyone who called it. It plays one buffer on a channel it never reserves, so the channel frees itself when the buffer runs out, and its argument checks are their own set: any positive sample count, aligned or not, no upper bound, a negative volume rejected rather than skipped, and no busy check at all. No game is known to use it; it is implemented because tracing it turned out to be cheap, not because anything needed it. The channels test confirms four behaviors: sceAudioChReserve(-1) skips a released channel that is still playing while a reserve of it succeeds, a second channel joining a running mixer doesn't lose a block unlike a first, mono counts down in the same 64-sample steps over the same time as stereo, and the panned blocking output has no extra delay on the high channels. Also: the SRC resampler now interpolates into the next buffer at a buffer join instead of holding the last sample, since the codec reads the two descriptors as one stream. That only shows up at non-native rates and no test can see it. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
1 parent
bfbede717f
commit
84bd8459e9
7 files changed
+89
-10
No files matched your search
@@ -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);
|
||||
|
||||
+35
-7
@@ -298,6 +298,22 @@ u32 __AudioEnqueue(AudioChannel &chan, u32 samplePtr, int leftVol, int rightVol)
|
||||
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) {
|
||||
@@ -508,6 +524,8 @@ static bool __AudioMixSRC(AudioSRCChannel &chan) {
|
||||
// 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;
|
||||
@@ -523,16 +541,26 @@ static bool __AudioMixSRC(AudioSRCChannel &chan) {
|
||||
|
||||
const AudioPendingBuffer &buf = chan.buffers[0];
|
||||
const u32 addr = buf.address + chan.playedSamples * stride;
|
||||
// Interpolating against the following sample matters when a game reserved 22050Hz
|
||||
// or similar; at the native rate the fraction is always zero and this reduces to a
|
||||
// plain copy.
|
||||
const u32 avail = std::min(buf.samples - chan.playedSamples, 2u);
|
||||
if (!chan.mute && Memory::IsValidRange(addr, avail * 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];
|
||||
const int l1 = avail > 1 ? (int)src[stride / 2] : l0;
|
||||
const int r1 = avail > 1 ? (mono ? l1 : (int)src[stride / 2 + 1]) : r0;
|
||||
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);
|
||||
|
||||
@@ -45,6 +45,9 @@ void __AudioSetSRCFrequency(int freq);
|
||||
// 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);
|
||||
|
||||
+31
-2
@@ -330,6 +330,34 @@ static u32 sceAudioChangeChannelConfig(u32 chan, u32 format) {
|
||||
return hleLogDebug(Log::sceAudio, 0);
|
||||
}
|
||||
|
||||
// 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);
|
||||
@@ -595,8 +623,9 @@ const HLEFunction sceAudio[] =
|
||||
{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
|
||||
|
||||
@@ -150,6 +150,18 @@ checks is observable.
|
||||
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
|
||||
|
||||
+1
-1
Submodule pspautotests updated: 22c5bd9677...4eb50721ea.
@@ -114,9 +114,11 @@ tests_good = [
|
||||
"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",
|
||||
|
||||
Reference in new issue
Block a user