diff --git a/src/duskpsp/audio/audiostreampsp.c b/src/duskpsp/audio/audiostreampsp.c index e138cd5e..5adc3faf 100644 --- a/src/duskpsp/audio/audiostreampsp.c +++ b/src/duskpsp/audio/audiostreampsp.c @@ -11,6 +11,11 @@ #include "util/memory.h" #include +// Matches pspaudiolib's own PSP_NUM_AUDIO_SAMPLES convention - PSP audio +// hardware is meant to be fed small chunks continuously, not one large +// buffer per call. +#define AUDIO_PSP_CHUNK_FRAMES 1024 + errorret_t audioStreamPSPInit(audiostream_t *stream) { assertNotNull(stream, "Stream cannot be NULL."); @@ -31,28 +36,20 @@ errorret_t audioStreamPSPInit(audiostream_t *stream) { ); } - 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); + const int channel = sceAudioChReserve( + PSP_AUDIO_NEXT_CHANNEL, AUDIO_PSP_CHUNK_FRAMES, format + ); if(channel < 0) { errorThrow("Failed to reserve PSP audio channel: 0x%08X", channel); } stream->platform.channel = channel; - stream->platform.samples = samples; + stream->platform.finished = false; + threadInit(&stream->platform.thread, audioStreamPSPThreadFeed); errorOk(); } @@ -60,6 +57,7 @@ errorret_t audioStreamPSPInit(audiostream_t *stream) { errorret_t audioStreamPSPDispose(audiostream_t *stream) { assertNotNull(stream, "Stream cannot be NULL."); + threadStop(&stream->platform.thread); sceAudioChRelease(stream->platform.channel); errorOk(); @@ -68,33 +66,9 @@ errorret_t audioStreamPSPDispose(audiostream_t *stream) { 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); - } + stream->platform.finished = false; + stream->platform.thread.data = stream; + threadStartRequest(&stream->platform.thread); errorOk(); } @@ -102,5 +76,48 @@ errorret_t audioStreamPSPBuffer(audiostream_t *stream) { bool_t audioStreamPSPIsFinished(audiostream_t *stream) { assertNotNull(stream, "Stream cannot be NULL."); - return sceAudioGetChannelRestLength(stream->platform.channel) <= 0; + return stream->platform.finished; +} + +void audioStreamPSPThreadFeed(thread_t *thread) { + assertNotNull(thread, "Thread cannot be NULL."); + + audiostream_t *stream = (audiostream_t *) thread->data; + const size_t frameSize = stream->pcm.channels * sizeof(int16_t); + const size_t totalFrames = stream->dataSize / frameSize; + const size_t chunkSize = AUDIO_PSP_CHUNK_FRAMES * frameSize; + + int16_t *chunk = memoryAllocate(chunkSize); + size_t position = 0; + + while(!threadShouldStop(thread) && position < totalFrames) { + const size_t framesRemaining = totalFrames - position; + const size_t framesThisChunk = framesRemaining < AUDIO_PSP_CHUNK_FRAMES + ? framesRemaining + : AUDIO_PSP_CHUNK_FRAMES; + + memoryZero(chunk, chunkSize); + memoryCopy( + chunk, stream->data + (position * frameSize), framesThisChunk * frameSize + ); + + // Re-read every chunk (~23ms at 44100Hz) so SetVolume/SetDirectionality + // take effect mid-playback, unlike the platform's other one-shot calls. + 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)); + + sceAudioOutputPannedBlocking( + stream->platform.channel, leftVolume, rightVolume, chunk + ); + + position += framesThisChunk; + } + + memoryFree(chunk); + stream->platform.finished = true; } diff --git a/src/duskpsp/audio/audiostreampsp.h b/src/duskpsp/audio/audiostreampsp.h index 3d48d6b8..19a7a145 100644 --- a/src/duskpsp/audio/audiostreampsp.h +++ b/src/duskpsp/audio/audiostreampsp.h @@ -7,16 +7,22 @@ #pragma once #include "error/error.h" +#include "thread/thread.h" typedef struct audiostream_s audiostream_t; typedef struct { - // Reserved hardware output channel, from sceAudioChReserve. + // Reserved hardware output channel, from sceAudioChReserve. Reserved with + // a small fixed chunk size (AUDIO_PSP_CHUNK_FRAMES) - PSP audio hardware + // expects continuous small-chunk feeding, not one large buffer per call. 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; + // Dedicated thread that feeds the channel chunk-by-chunk for the + // duration of playback, independent of the engine's frame rate. + thread_t thread; + + // Set by audioStreamPSPThreadFeed() once it has fed the last chunk. + volatile bool_t finished; } audiostreampsp_t; /** @@ -29,8 +35,8 @@ typedef struct { errorret_t audioStreamPSPInit(audiostream_t *stream); /** - * Disposes the PSP-specific playback state of an audio stream, releasing - * its hardware output channel. + * Disposes the PSP-specific playback state of an audio stream, stopping its + * feeder thread and releasing its hardware output channel. * * @param stream The audio stream to dispose. * @return Error state if any. @@ -38,8 +44,8 @@ errorret_t audioStreamPSPInit(audiostream_t *stream); errorret_t audioStreamPSPDispose(audiostream_t *stream); /** - * Sends the stream's currently staged PCM data (stream->data) to its - * reserved hardware output channel. + * Starts the stream's feeder thread, which streams stream->data to its + * reserved hardware output channel in small chunks until exhausted. * * @param stream The audio stream to output. * @return Error state if any. @@ -47,10 +53,20 @@ errorret_t audioStreamPSPDispose(audiostream_t *stream); errorret_t audioStreamPSPBuffer(audiostream_t *stream); /** - * Checks whether the stream's reserved hardware channel has finished - * playing its currently buffered data. + * Checks whether the stream's feeder thread has finished feeding its + * currently buffered data. * * @param stream The audio stream to check. * @return true if playback has finished, false otherwise. */ bool_t audioStreamPSPIsFinished(audiostream_t *stream); + +/** + * Feeder thread entry point. Streams stream->data (passed via thread->data) + * to its hardware channel in fixed-size chunks, blocking naturally on each + * sceAudioOutputPannedBlocking() call, until the whole buffer has been sent + * or the thread is asked to stop. + * + * @param thread The running thread_t, with data set to the audiostream_t. + */ +void audioStreamPSPThreadFeed(thread_t *thread);