Implement PSP audio playback via native sceAudio

Reserves a hardware channel per stream (64-aligned sample count,
mono/stereo format) and outputs through sceAudioOutputPannedBlocking,
giving PSP real stereo panning from directionality. Enforces the
44100Hz hardware-channel constraint with a clear error instead of
silently mispitching, and drops the shared test tone to 44100Hz to
match. Verified via the dusk-psp toolchain and a headless PPSSPP run
(correct channel reservation, onEnd firing once, no errors).
This commit is contained in:
2026-08-31 10:36:51 -05:00
parent 8ea370a1d1
commit af8c0f5ccd
6 changed files with 121 additions and 3 deletions
+4 -2
View File
@@ -24,9 +24,11 @@ errorret_t audioStreamPcmInit(
stream->dataSize = dataSize; stream->dataSize = dataSize;
stream->pcm.sampleRate = sampleRate; stream->pcm.sampleRate = sampleRate;
stream->pcm.channels = channels; stream->pcm.channels = channels;
stream->duration = (float_t) dataSize / stream->duration = (
(float_t) dataSize /
(float_t) (channels * sizeof(int16_t)) / (float_t) (channels * sizeof(int16_t)) /
(float_t) sampleRate; (float_t) sampleRate
);
errorChain(audioStreamPlatformInit(stream)); errorChain(audioStreamPlatformInit(stream));
+2 -1
View File
@@ -28,7 +28,8 @@
engine_t ENGINE; engine_t ENGINE;
#define AUDIO_TEST_TONE_SAMPLE_RATE 48000 // 44100Hz: PSP's sceAudioChReserve hardware channels are fixed at this rate.
#define AUDIO_TEST_TONE_SAMPLE_RATE 44100
#define AUDIO_TEST_TONE_FREQUENCY 440 #define AUDIO_TEST_TONE_FREQUENCY 440
#define AUDIO_TEST_TONE_SAMPLES AUDIO_TEST_TONE_SAMPLE_RATE #define AUDIO_TEST_TONE_SAMPLES AUDIO_TEST_TONE_SAMPLE_RATE
#define AUDIO_TEST_TONE_SIZE (AUDIO_TEST_TONE_SAMPLES * sizeof(int16_t)) #define AUDIO_TEST_TONE_SIZE (AUDIO_TEST_TONE_SAMPLES * sizeof(int16_t))
+1
View File
@@ -16,6 +16,7 @@ target_sources(${DUSK_BINARY_TARGET_NAME}
# Subdirs # Subdirs
add_subdirectory(asset) add_subdirectory(asset)
add_subdirectory(audio)
add_subdirectory(input) add_subdirectory(input)
add_subdirectory(log) add_subdirectory(log)
if(DUSK_NETWORK) if(DUSK_NETWORK)
+11
View File
@@ -0,0 +1,11 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
audiopsp.c
audiostreampsp.c
)
+98
View File
@@ -6,3 +6,101 @@
*/ */
#include "audiostreampsp.h" #include "audiostreampsp.h"
#include "audio/audiostream.h"
#include "assert/assert.h"
#include "util/memory.h"
#include <pspaudio.h>
errorret_t audioStreamPSPInit(audiostream_t *stream) {
assertNotNull(stream, "Stream cannot be NULL.");
if(stream->pcm.channels != 1 && stream->pcm.channels != 2) {
errorThrow(
"PSP audio only supports mono or stereo PCM, got %d channels.",
stream->pcm.channels
);
}
// sceAudioChReserve's hardware channels always run at the PSP's native
// 44100Hz; there is no per-channel sample rate. Arbitrary rates would need
// sceAudioSRCChReserve's single exclusive channel instead.
if(stream->pcm.sampleRate != 44100) {
errorThrow(
"PSP audio channels are fixed at 44100Hz, got %uHz.",
stream->pcm.sampleRate
);
}
const size_t frameSize = stream->pcm.channels * sizeof(int16_t);
const size_t frameCount = stream->dataSize / frameSize;
int samples = (int) PSP_AUDIO_SAMPLE_ALIGN(frameCount);
if(samples < PSP_AUDIO_SAMPLE_MIN) samples = PSP_AUDIO_SAMPLE_MIN;
if(samples > PSP_AUDIO_SAMPLE_MAX) {
// TODO: sources longer than ~1.5s need chunked streaming across
// multiple buffer calls instead of a single one.
samples = PSP_AUDIO_SAMPLE_MAX;
}
const int format = stream->pcm.channels == 1
? PSP_AUDIO_FORMAT_MONO
: PSP_AUDIO_FORMAT_STEREO;
const int channel = sceAudioChReserve(PSP_AUDIO_NEXT_CHANNEL, samples, format);
if(channel < 0) {
errorThrow("Failed to reserve PSP audio channel: 0x%08X", channel);
}
stream->platform.channel = channel;
stream->platform.samples = samples;
errorOk();
}
errorret_t audioStreamPSPDispose(audiostream_t *stream) {
assertNotNull(stream, "Stream cannot be NULL.");
sceAudioChRelease(stream->platform.channel);
errorOk();
}
errorret_t audioStreamPSPBuffer(audiostream_t *stream) {
assertNotNull(stream, "Stream cannot be NULL.");
const size_t frameSize = stream->pcm.channels * sizeof(int16_t);
const size_t bufferSize = (size_t) stream->platform.samples * frameSize;
int16_t *buffer = memoryAllocate(bufferSize);
memoryZero(buffer, bufferSize);
memoryCopy(
buffer, stream->data,
stream->dataSize < bufferSize ? stream->dataSize : bufferSize
);
// Simple linear pan; AUDIO_STREAM_LEFT/CENTER/RIGHT map to -128..0..127.
float_t pan = (float_t) stream->directionality / 128.0f;
if(pan < -1.0f) pan = -1.0f;
if(pan > 1.0f) pan = 1.0f;
const int baseVolume = (stream->volume * PSP_AUDIO_VOLUME_MAX) / 0xFF;
const int leftVolume = (int) (baseVolume * (pan > 0 ? (1.0f - pan) : 1.0f));
const int rightVolume = (int) (baseVolume * (pan < 0 ? (1.0f + pan) : 1.0f));
const int result = sceAudioOutputPannedBlocking(
stream->platform.channel, leftVolume, rightVolume, buffer
);
memoryFree(buffer);
if(result < 0) {
errorThrow("Failed to output PSP audio data: 0x%08X", result);
}
errorOk();
}
bool_t audioStreamPSPIsFinished(audiostream_t *stream) {
assertNotNull(stream, "Stream cannot be NULL.");
return sceAudioGetChannelRestLength(stream->platform.channel) <= 0;
}
+5
View File
@@ -11,7 +11,12 @@
typedef struct audiostream_s audiostream_t; typedef struct audiostream_s audiostream_t;
typedef struct { typedef struct {
// Reserved hardware output channel, from sceAudioChReserve.
int channel; int channel;
// Number of samples (frames) per output call the channel was reserved
// with. 64-aligned, between PSP_AUDIO_SAMPLE_MIN and PSP_AUDIO_SAMPLE_MAX.
int samples;
} audiostreampsp_t; } audiostreampsp_t;
/** /**