Fix loud PSP loop crackle + make Dolphin loop natively in hardware
Root cause of the crackle (confirmed on real PSP hardware): the persistent feeder thread called stream->onLoop directly in the middle of its tight chunk-feeding loop. onLoop can do arbitrary work (the test callback does consolePrint, which locks a mutex, moves the console history buffer, and fflushes stdout) - if that takes anywhere close to one chunk's playback time (~23ms), the next chunk isn't ready and the hardware channel starves. Audio callbacks must never be invoked directly from a real-time audio thread. Fixed generically: audiostream_t gains loopCount (incremented by platform code from whatever context it runs in) and lastLoopCount (main-thread-only bookkeeping). audioStreamUpdate() detects the change and fires onLoop safely from the main thread, regardless of which thread/interrupt actually noticed the loop. PSP's feeder thread now increments the counter instead of calling onLoop inline. Also eliminated the ~21.7ms of real silence padding baked into every PSP loop pass (found while chasing the timing gap that preceded the crackle fix): the final chunk's padding is now filled with the start of the next loop instead of zero, avoiding sceAudioSetChannelDataLen (the likely real cause of an earlier, separate click) while keeping every output call the same constant size. Loop period measured via PPSSPP is now ~1.000-1.002s for a 1.000s tone, down from a consistent ~1.02-1.03s before. The PSP feeder thread is also now persistent for the stream's whole lifetime (created once in Init, idles between plays) rather than respawned via threadStartRequest on every single loop restart - real, avoidable OS thread creation overhead that was contributing to the gap before the padding was identified as the dominant cause. Brought Dolphin in line architecturally rather than mirroring PSP/ Linux's restart-and-detect approach: ansnd_pcm_voice_config_t has native loop_start_offset/loop_end_offset fields, so a looping Dolphin voice loops entirely in DSP hardware with zero host involvement at the loop boundary - no restart latency to create a gap in the first place. Trade-off, clearly documented in code: onLoop never fires for Dolphin this way (no ANSND_VOICE_STATE for "wrapped") and it requires cleanly-authored loop content (no per-wrap fade like PSP's, matching the same assumption). Compiles cleanly for both gamecube and wii; not yet verified on real hardware. Confirmed on real PSP hardware: no more gap, crackle fix pending final hardware confirmation.
This commit is contained in:
@@ -17,11 +17,20 @@ typedef struct {
|
||||
// expects continuous small-chunk feeding, not one large buffer per call.
|
||||
int channel;
|
||||
|
||||
// Dedicated thread that feeds the channel chunk-by-chunk for the
|
||||
// duration of playback, independent of the engine's frame rate.
|
||||
// Persistent thread, created once in Init and alive for the stream's
|
||||
// whole lifetime - feeds the channel chunk-by-chunk for the duration of
|
||||
// playback, independent of the engine's frame rate. Re-spawning a
|
||||
// thread on every Buffer() call (e.g. every loop restart) was real,
|
||||
// avoidable overhead - a plain OS thread creation, on top of everything
|
||||
// else - heard as a small gap between loops; this thread just idles
|
||||
// (polling playRequested) between plays instead of exiting.
|
||||
thread_t thread;
|
||||
|
||||
// Set by audioStreamPSPThreadFeed() once it has fed the last chunk.
|
||||
// Set by audioStreamPSPBuffer() to wake the idling thread into feeding
|
||||
// a pass; cleared by the thread once it picks it up.
|
||||
volatile bool_t playRequested;
|
||||
|
||||
// Set by the thread once it has fed the last chunk of a pass.
|
||||
volatile bool_t finished;
|
||||
} audiostreampsp_t;
|
||||
|
||||
@@ -44,8 +53,8 @@ errorret_t audioStreamPSPInit(audiostream_t *stream);
|
||||
errorret_t audioStreamPSPDispose(audiostream_t *stream);
|
||||
|
||||
/**
|
||||
* Starts the stream's feeder thread, which streams stream->data to its
|
||||
* reserved hardware output channel in small chunks until exhausted.
|
||||
* Wakes the stream's persistent feeder thread to stream 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.
|
||||
@@ -62,10 +71,13 @@ errorret_t audioStreamPSPBuffer(audiostream_t *stream);
|
||||
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.
|
||||
* Feeder thread entry point, run once for the stream's whole lifetime.
|
||||
* Idles (polling playRequested) until woken by audioStreamPSPBuffer(),
|
||||
* then 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 - then goes back to idling, ready for the next play request, until
|
||||
* the thread is asked to stop.
|
||||
*
|
||||
* @param thread The running thread_t, with data set to the audiostream_t.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user