first commit

This commit is contained in:
Soph :3 2025-11-16 15:41:37 +02:00
commit 745ae2fff2
22 changed files with 21283 additions and 0 deletions

526
src/lib/audio.cpp Normal file
View file

@ -0,0 +1,526 @@
#include <psp2/kernel/processmgr.h>
#include <psp2/audioout.h>
#include <psp2/kernel/threadmgr.h>
#include <curl/curl.h>
#include <string.h>
#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();
}