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:
@@ -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));
|
||||||
|
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user