#include #include #include #include #include #define DR_FLAC_IMPLEMENTATION #include "dr_flac.h" #define DR_MP3_IMPLEMENTATION #include "dr_mp3.h" #include "audio.hpp" // ============================== // Internal State // ============================== static volatile bool gRunning = false; static volatile bool gPaused = false; static volatile bool gStopRequest = false; static volatile bool stream_eof = false; static SceUID gCurlThread = -1; static SceUID gDecodeThread = -1; static SceUID gAudioThread = -1; static int audio_port = -1; // ============================== // Ringbuffer // ============================== #define STREAMBUF_SIZE (1024 * 1024 * 2) #define FRAMES_PER_CHUNK 1024 static uint8_t streambuf[STREAMBUF_SIZE]; static volatile size_t wb = 0, rb = 0; // ============================== // Double-buffer PCM // ============================== static int16_t gAudioBuf[2][FRAMES_PER_CHUNK * 2]; static volatile int gBufReady[2] = {0,0}; static volatile int gDecodeIndex = 0; static volatile int gPlayIndex = 0; static volatile bool gDecodeDone = false; static volatile uint64_t gDecodedFrames = 0; static volatile uint64_t gTotalFrames = 0; static volatile uint32_t gSampleRate = 44100; // default static volatile bool gHasLength = false; // FLAC streaming may not have this // ======================================================================= // CURL callback // ======================================================================= static size_t curl_cb(void *ptr, size_t size, size_t nmemb, void *userdata) { if (gStopRequest) return 0; size_t total = size * nmemb; const uint8_t *in = (const uint8_t*)ptr; size_t written = 0; while (written < total && !gStopRequest) { size_t used = (wb + STREAMBUF_SIZE - rb) % STREAMBUF_SIZE; size_t free = STREAMBUF_SIZE - used - 1; if (free == 0) { sceKernelDelayThread(1000); continue; } size_t chunk = total - written; if (chunk > free) chunk = free; size_t tillEnd = STREAMBUF_SIZE - wb; size_t first = (chunk < tillEnd) ? chunk : tillEnd; size_t second = chunk - first; memcpy(&streambuf[wb], in + written, first); wb = (wb + first) % STREAMBUF_SIZE; written += first; if (second > 0) { memcpy(&streambuf[wb], in + written, second); wb = (wb + second) % STREAMBUF_SIZE; written += second; } } return total; } // ======================================================================= // Streaming read callback for both FLAC and MP3 // ======================================================================= static unsigned int stream_read(void *user, void *out, unsigned int want) { (void)user; if (gStopRequest) return 0; uint8_t *o = (uint8_t*)out; size_t read = 0; while (read < want && !gStopRequest) { size_t available = (wb + STREAMBUF_SIZE - rb) % STREAMBUF_SIZE; if (available == 0) { if (stream_eof) break; sceKernelDelayThread(1000); continue; } size_t chunk = want - read; if (chunk > available) chunk = available; size_t tillEnd = STREAMBUF_SIZE - rb; size_t first = (chunk < tillEnd) ? chunk : tillEnd; size_t second = chunk - first; memcpy(o + read, &streambuf[rb], first); rb = (rb + first) % STREAMBUF_SIZE; read += first; if (second > 0) { memcpy(o + read, &streambuf[rb], second); rb = (rb + second) % STREAMBUF_SIZE; read += second; } } return read; } static drflac_bool32 flac_seek(void*, int, drflac_seek_origin) { return DRFLAC_FALSE; } static drmp3_bool32 mp3_seek(void* user, int offset, drmp3_seek_origin origin) { (void)user; (void)offset; (void)origin; // Streaming over HTTP – no real seeking. return DRMP3_FALSE; } static drflac_bool32 flac_tell(void*, drflac_int64 *pos) { *pos = 0; return DRFLAC_TRUE; } // ======================================================================= // Threads // ======================================================================= static int curl_thread(unsigned int, void *url_) { CURL *c = curl_easy_init(); curl_easy_setopt(c, CURLOPT_URL, (char*)url_); curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, curl_cb); curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(c, CURLOPT_BUFFERSIZE, 64 * 1024); curl_easy_perform(c); stream_eof = true; curl_easy_cleanup(c); return 0; } // ----------------------------------------------------------- // AUDIO THREAD // ----------------------------------------------------------- static int audio_thread(unsigned int, void*) { for (;;) { if (gStopRequest) break; if (gPaused) { sceKernelDelayThread(10000); continue; } if (!gBufReady[gPlayIndex]) { if (gDecodeDone && !gBufReady[gPlayIndex ^ 1]) break; sceKernelDelayThread(1000); continue; } sceAudioOutOutput(audio_port, gAudioBuf[gPlayIndex]); gBufReady[gPlayIndex] = 0; gPlayIndex ^= 1; } return 0; } static void mp3_meta(void* user, const drmp3_metadata* meta) { // do nothing } // MP3 DECODE THREAD // ----------------------------------------------------------- static int decode_mp3_thread(unsigned int, void*) { // Wait until some header/data is buffered while (((wb + STREAMBUF_SIZE - rb) % STREAMBUF_SIZE) < 32 && !gStopRequest) sceKernelDelayThread(10000); if (gStopRequest) return 0; drmp3 mp3; if (!drmp3_init(&mp3, stream_read, mp3_seek, NULL, mp3_meta, NULL, NULL)) return 0; gSampleRate = mp3.sampleRate; gTotalFrames = 0; // unknown for streaming MP3 gHasLength = false; audio_port = sceAudioOutOpenPort( SCE_AUDIO_OUT_PORT_TYPE_BGM, FRAMES_PER_CHUNK, mp3.sampleRate, SCE_AUDIO_OUT_MODE_STEREO ); if (audio_port < 0) { drmp3_uninit(&mp3); return 0; } int vol[2] = {0x8000, 0x8000}; sceAudioOutSetVolume( audio_port, (SceAudioOutChannelFlag)(SCE_AUDIO_VOLUME_FLAG_L_CH | SCE_AUDIO_VOLUME_FLAG_R_CH), vol ); gDecodeIndex = 0; gPlayIndex = 0; gDecodeDone = false; gBufReady[0] = 0; gBufReady[1] = 0; // Start audio thread (same as FLAC) gAudioThread = sceKernelCreateThread( "audio_out_thread", audio_thread, 0x40, 0x10000, 0, 0, NULL ); sceKernelStartThread(gAudioThread, 0, NULL); int16_t inBuf[FRAMES_PER_CHUNK * 8]; // extra space, channels <= 2 anyway while (!gStopRequest) { int idx = gDecodeIndex; // Wait for playback to consume this buffer while (gBufReady[idx] && !gStopRequest) sceKernelDelayThread(1000); if (gStopRequest) break; drmp3_uint64 frames64 = drmp3_read_pcm_frames_s16(&mp3, FRAMES_PER_CHUNK, inBuf); size_t frames = (size_t)frames64; gDecodedFrames += frames; if (frames == 0) { gDecodeDone = true; break; } int16_t *outBuf = gAudioBuf[idx]; if (mp3.channels == 1) { // Mono -> Stereo for (size_t i = 0; i < frames; i++) { outBuf[i*2 + 0] = inBuf[i]; outBuf[i*2 + 1] = inBuf[i]; } } else { // Interleaved stereo (or more, but we only keep first 2 channels) for (size_t i = 0; i < frames; i++) { outBuf[i*2 + 0] = inBuf[i*mp3.channels + 0]; outBuf[i*2 + 1] = inBuf[i*mp3.channels + 1]; } } // Zero-pad last chunk if short if (frames < FRAMES_PER_CHUNK) { memset(&outBuf[frames * 2], 0, (FRAMES_PER_CHUNK - frames) * 4); } gBufReady[idx] = 1; gDecodeIndex ^= 1; } drmp3_uninit(&mp3); return 0; } // ----------------------------------------------------------- // DECODE THREAD // ----------------------------------------------------------- static int decode_flac_thread(unsigned int, void*) { // Wait FLAC header while (((wb + STREAMBUF_SIZE - rb) % STREAMBUF_SIZE) < 32 && !gStopRequest) sceKernelDelayThread(10000); if (gStopRequest) return 0; drflac *flac = drflac_open(stream_read, flac_seek, flac_tell, NULL, NULL); if (!flac) return 0; gSampleRate = flac->sampleRate; gTotalFrames = flac->totalPCMFrameCount; gHasLength = (gTotalFrames > 0); audio_port = sceAudioOutOpenPort( SCE_AUDIO_OUT_PORT_TYPE_BGM, FRAMES_PER_CHUNK, flac->sampleRate, SCE_AUDIO_OUT_MODE_STEREO ); int vol[2] = {0x8000,0x8000}; sceAudioOutSetVolume(audio_port, (SceAudioOutChannelFlag)(SCE_AUDIO_VOLUME_FLAG_L_CH | SCE_AUDIO_VOLUME_FLAG_R_CH), vol); gDecodeIndex = 0; gPlayIndex = 0; gDecodeDone = false; gBufReady[0] = gBufReady[1] = 0; int16_t inBuf[FRAMES_PER_CHUNK * 8]; // Start audio thread gAudioThread = sceKernelCreateThread("audio_out_thread", audio_thread, 0x40, 0x10000, 0, 0, NULL); sceKernelStartThread(gAudioThread, 0, NULL); // Decode loop while (!gStopRequest) { int idx = gDecodeIndex; while (gBufReady[idx] && !gStopRequest) sceKernelDelayThread(1000); if (gStopRequest) break; size_t frames = drflac_read_pcm_frames_s16(flac, FRAMES_PER_CHUNK, inBuf); gDecodedFrames += frames; if (frames == 0) { gDecodeDone = true; break; } int16_t *outBuf = gAudioBuf[idx]; if (flac->channels == 1) { for (size_t i = 0; i < frames; i++) { outBuf[i*2+0] = inBuf[i]; outBuf[i*2+1] = inBuf[i]; } } else { for (size_t i = 0; i < frames; i++) { outBuf[i*2+0] = inBuf[i*flac->channels + 0]; outBuf[i*2+1] = inBuf[i*flac->channels + 1]; } } if (frames < FRAMES_PER_CHUNK) { memset(&outBuf[frames * 2], 0, (FRAMES_PER_CHUNK - frames) * 4); } gBufReady[idx] = 1; gDecodeIndex ^= 1; } drflac_close(flac); return 0; } // ======================================================================= // Public API // ======================================================================= float Audio_GetLengthSeconds() { if (!gHasLength) return 0.0f; return (float)gTotalFrames / (float)gSampleRate; } float Audio_GetProgress() { if (!gHasLength || gTotalFrames == 0) return 0.0f; return (float)gDecodedFrames / (float)gTotalFrames; } bool Audio_IsPlaying() { if(gStopRequest) { return false; } else { return !gDecodeDone && gRunning; } } float Audio_SetProgress(float p) { if (p < 0.0f) p = 0.0f; if (p > 1.0f) p = 1.0f; if (!gHasLength || gTotalFrames == 0) return 0.0f; return ((float)gTotalFrames / (float)gSampleRate) * p; } float Audio_GetPositionSeconds() { return (float)gDecodedFrames / (float)gSampleRate; } void Audio_Init() { curl_global_init(CURL_GLOBAL_DEFAULT); } void Audio_Play(const char *url) { if (gRunning) Audio_Stop(); gDecodedFrames = 0; gTotalFrames = 0; gHasLength = false; gSampleRate = 44100; gStopRequest = false; gRunning = true; gPaused = false; stream_eof = false; wb = rb = 0; gCurlThread = sceKernelCreateThread("curl_stream", curl_thread, 0x40, 0x10000, 0, 0, NULL); sceKernelStartThread(gCurlThread, strlen(url)+1, (void*)url); gDecodeThread = sceKernelCreateThread("decode_thread_flac", decode_flac_thread, 0x40, 0x20000, 0, 0, NULL); sceKernelStartThread(gDecodeThread, 0, NULL); } void Audio_PlayMP3(const char *url) { if (gRunning) Audio_Stop(); gDecodedFrames = 0; gTotalFrames = 0; gHasLength = false; gSampleRate = 44100; gStopRequest = false; gRunning = true; gPaused = false; stream_eof = false; wb = rb = 0; gCurlThread = sceKernelCreateThread("curl_stream_mp3", curl_thread, 0x40, 0x10000, 0, 0, NULL); sceKernelStartThread(gCurlThread, strlen(url)+1, (void*)url); gDecodeThread = sceKernelCreateThread("decode_thread_mp3", decode_mp3_thread, 0x40, 0x20000, 0, 0, NULL); sceKernelStartThread(gDecodeThread, 0, NULL); } bool Audio_IsPaused() { return gPaused; } void Audio_Pause() { gPaused = true; } void Audio_Resume() { gPaused = false; } void Audio_Stop() { if (!gRunning) return; gStopRequest = true; sceKernelDelayThread(50000); if (audio_port >= 0) sceAudioOutReleasePort(audio_port); audio_port = -1; gRunning = false; } void Audio_Shutdown() { Audio_Stop(); curl_global_cleanup(); }