9#if defined(SDL_LIBRETRO_IMPLEMENTATION) && !defined(SDL_LIBRETRO_AUDIO_IMPL_ONCE)
10#define SDL_LIBRETRO_AUDIO_IMPL_ONCE
12#ifndef SDL_LIBRETRO_AUDIO_DEFAULT_LATENCY_MS
19#define SDL_LIBRETRO_AUDIO_DEFAULT_LATENCY_MS 100
22#ifndef SDL_LIBRETRO_AUDIO_DEFAULT_SAMPLE_RATE
26#define SDL_LIBRETRO_AUDIO_DEFAULT_SAMPLE_RATE 48000
29#ifndef SDL_LIBRETRO_AUDIO_DRC_MAX_DELTA
37#define SDL_LIBRETRO_AUDIO_DRC_MAX_DELTA 0.005
40static void SDL_Libretro_QueueAudio(SDL_Libretro* lr,
const float* samples,
int bytes) {
41 if (!lr->core.audioStream || bytes <= 0)
return;
43 int queued = SDL_GetAudioStreamQueued(lr->core.audioStream);
44 if (queued >= lr->core.audioQueueThresholdBytes) {
46 if (lr->core.audioDropWarnCount < 10) {
47 SDL_LogDebug(SDL_LOG_CATEGORY_AUDIO,
48 "[SDL_Libretro] Audio queue full with %d bytes queued, dropping %d bytes",
50 lr->core.audioDropWarnCount++;
55 SDL_PutAudioStreamData(lr->core.audioStream, samples, bytes);
68static void SDL_Libretro_QueueAudioReversed(SDL_Libretro* lr,
const int16_t* data,
size_t frames) {
69 float convBuf[512 * 2];
70 size_t remaining = frames;
71 while (remaining > 0) {
72 size_t chunk = remaining > 512 ? 512 : remaining;
73 for (
size_t i = 0; i < chunk; i++) {
74 size_t src = remaining - 1 - i;
75 convBuf[i * 2] = (float)data[src * 2] * (1.0f / 32768.0f);
76 convBuf[i * 2 + 1] = (float)data[src * 2 + 1] * (1.0f / 32768.0f);
78 SDL_Libretro_QueueAudio(lr, convBuf, (
int)(chunk *
sizeof(
float) * 2));
94static void SDL_Libretro_UpdateDRC(SDL_Libretro* lr,
float speed) {
95 if (!lr || !lr->core.audioStream || !lr->core.drcEnabled)
return;
98 if (lr->core.audioQueueThresholdBytes <= 0) {
99 SDL_SetAudioStreamFrequencyRatio(lr->core.audioStream, speed);
103 int queued = SDL_GetAudioStreamQueued(lr->core.audioStream);
104 if (queued < 0)
return;
107 double drift = (double)queued / (
double)lr->core.audioQueueThresholdBytes - 0.5;
108 lr->core.drcDriftAvg += 0.0625 * (drift - lr->core.drcDriftAvg);
112 lr->core.drcAdjustment = (float)adj;
113 SDL_SetAudioStreamFrequencyRatio(lr->core.audioStream, speed * (
float)adj);
123static unsigned SDL_Libretro_UpdateAudioThreshold(SDL_Libretro* lr) {
124 unsigned latencyMs = lr->core.minimumAudioLatencyMs;
127 int threshold = (int)(SDL_Libretro_GetSampleRate(lr) * latencyMs / 1000.0 * (double)(
sizeof(
float) * 2));
130 int minThreshold = 4 * (SDL_LIBRETRO_AUDIO_SINGLE_SAMPLE_BUFFER_SIZE * (int)(
sizeof(
float) * 2));
131 lr->core.audioQueueThresholdBytes = threshold > minThreshold ? threshold : minThreshold;
144static void SDL_Libretro_ReportAudioBufferStatus(SDL_Libretro* lr) {
145 if (!lr || !lr->core.audio_buffer_status.callback)
return;
150 bool active = (lr->core.audioStream != NULL);
159 unsigned occupancy = 0;
164 bool underrunLikely =
false;
166 if (active && lr->core.audioQueueThresholdBytes > 0) {
167 int queued = SDL_GetAudioStreamQueued(lr->core.audioStream);
168 if (queued < 0) queued = 0;
170 double pct = (double)queued * 100.0 / (
double)lr->core.audioQueueThresholdBytes;
171 occupancy = (unsigned)(pct > 100.0 ? 100.0 : pct);
174 double fps = lr->core.fps > 0.0 ? lr->core.fps : 60.0;
175 int frameBytes = (int)(SDL_Libretro_GetSampleRate(lr) / fps * (double)(
sizeof(
float) * 2));
176 underrunLikely = (queued < frameBytes);
179 lr->core.audio_buffer_status.callback(active, occupancy, underrunLikely);
185static bool SDL_Libretro_InitAudio(SDL_Libretro* lr) {
186 if (!lr || !lr->core.loaded) {
187 SDL_SetError(
"[SDL_Libretro] No core loaded");
191 SDL_Libretro_CloseAudio(lr);
193 unsigned latencyMs = SDL_Libretro_UpdateAudioThreshold(lr);
194 double sampleRate = SDL_Libretro_GetSampleRate(lr);
197 spec.freq = (int)sampleRate;
198 spec.format = SDL_AUDIO_F32;
201 lr->core.audioStream = SDL_OpenAudioDeviceStream(
202 SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK,
207 if (!lr->core.audioStream) {
208 SDL_SetError(
"[SDL_Libretro] Failed to open audio device: %s", SDL_GetError());
213 lr->core.audioDropWarnCount = 0;
214 lr->core.audioReinitPending =
false;
217 lr->core.drcAdjustment = 1.0f;
218 lr->core.drcEnabled =
true;
219 lr->core.drcDriftAvg = 0.0;
220 if (!SDL_SetAudioStreamFrequencyRatio(lr->core.audioStream, lr->speed * lr->core.drcAdjustment)) {
221 SDL_SetError(
"[SDL_Libretro] Failed to set audio frequency: %s", SDL_GetError());
222 SDL_DestroyAudioStream(lr->core.audioStream);
223 lr->core.audioStream = NULL;
228 SDL_SetAudioStreamGain(lr->core.audioStream, lr->volume);
229 SDL_ResumeAudioStreamDevice(lr->core.audioStream);
232 if (lr->core.audio_callback.set_state) {
233 lr->core.audio_callback.set_state(
true);
236 SDL_Log(
"[SDL_Libretro] Audio initialized [%d Channels @ %d Hz]", spec.channels, spec.freq);
241static void SDL_Libretro_CloseAudio(SDL_Libretro* lr) {
245 if (lr->core.audio_callback.set_state) {
246 lr->core.audio_callback.set_state(
false);
249 if (lr->core.audioStream) {
250 SDL_DestroyAudioStream(lr->core.audioStream);
251 lr->core.audioStream = NULL;
262 lr->core.minimumAudioLatencyMs = latencyMs;
265 if (lr->core.audioStream) {
266 SDL_Libretro_UpdateAudioThreshold(lr);
270unsigned SDL_Libretro_GetAudioLatency(
const SDL_Libretro* lr) {
275double SDL_Libretro_GetSampleRate(
const SDL_Libretro* lr) {
279static void SDL_Libretro_AudioSample(int16_t left, int16_t right) {
280 SDL_Libretro* lr = SDL_Libretro_active;
283 if (lr->core.singleSampleCount >= SDL_LIBRETRO_AUDIO_SINGLE_SAMPLE_BUFFER_SIZE) {
284 SDL_Libretro_FlushSingleSamples(lr);
287 size_t idx = lr->core.singleSampleCount * 2;
288 lr->core.singleSampleBuffer[idx] = left;
289 lr->core.singleSampleBuffer[idx + 1] = right;
290 lr->core.singleSampleCount++;
293static size_t SDL_Libretro_AudioSampleBatch(
const int16_t* data,
size_t frames) {
294 SDL_Libretro* lr = SDL_Libretro_active;
295 if (!lr || !lr->core.audioStream || frames == 0)
return frames;
297 if (lr->core.singleSampleCount > 0) {
298 SDL_Libretro_FlushSingleSamples(lr);
302 if (lr->rewindActive) {
303 SDL_Libretro_QueueAudioReversed(lr, data, frames);
307 float convBuf[512 * 2];
309 while (written < frames) {
310 size_t chunk = frames - written;
311 if (chunk > 512) chunk = 512;
313 for (
size_t i = 0; i < chunk * 2; i++) {
314 convBuf[i] = (float)data[written * 2 + i] * (1.0f / 32768.0f);
317 SDL_Libretro_QueueAudio(lr, convBuf, (
int)(chunk *
sizeof(
float) * 2));
324static void SDL_Libretro_FlushSingleSamples(SDL_Libretro* lr) {
325 if (!lr || lr->core.singleSampleCount == 0)
return;
328 if (lr->rewindActive) {
329 SDL_Libretro_QueueAudioReversed(lr, lr->core.singleSampleBuffer, lr->core.singleSampleCount);
330 lr->core.singleSampleCount = 0;
334 float convBuf[SDL_LIBRETRO_AUDIO_SINGLE_SAMPLE_BUFFER_SIZE * 2];
335 for (
size_t i = 0; i < lr->core.singleSampleCount * 2; i++) {
336 convBuf[i] = (float)lr->core.singleSampleBuffer[i] * (1.0f / 32768.0f);
339 SDL_Libretro_QueueAudio(lr, convBuf,
340 (
int)(lr->core.singleSampleCount *
sizeof(
float) * 2));
341 lr->core.singleSampleCount = 0;
346#ifndef SDL_LIBRETRO_MIC_DEFAULT_RATE
352#define SDL_LIBRETRO_MIC_DEFAULT_RATE 44100
355static retro_microphone_t* SDL_Libretro_MicOpen(
const retro_microphone_params_t* params) {
356 SDL_Libretro* lr = SDL_Libretro_active;
357 if (!lr)
return NULL;
359 if (lr->core.microphone) {
360 SDL_LogWarn(SDL_LOG_CATEGORY_AUDIO,
"[SDL_libretro] Microphone already open");
361 return (retro_microphone_t*)lr->core.microphone;
367 spec.freq = (int)rate;
368 spec.format = SDL_AUDIO_S16;
371 SDL_AudioStream* stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_RECORDING, &spec, NULL, (
void*)lr);
373 SDL_LogError(SDL_LOG_CATEGORY_AUDIO,
"SDL_libretro: Failed to open microphone: %s", SDL_GetError());
379 SDL_DestroyAudioStream(stream);
384 mic->stream = stream;
388 lr->core.microphone = mic;
390 SDL_Log(
"SDL_libretro: Microphone opened (%u Hz)", rate);
391 return (retro_microphone_t*)mic;
394static void SDL_Libretro_MicClose(retro_microphone_t* microphone) {
399 if (mic->
lr && mic->
lr->core.microphone == mic) {
400 mic->
lr->core.microphone = NULL;
405 SDL_DestroyAudioStream(mic->stream);
411static bool SDL_Libretro_MicGetParams(
const retro_microphone_t* microphone, retro_microphone_params_t* params) {
413 if (!mic || !params)
return false;
414 params->rate = mic->rate;
418static bool SDL_Libretro_MicSetState(retro_microphone_t* microphone,
bool state) {
420 if (!mic || !mic->stream)
return false;
424 ok = SDL_ResumeAudioStreamDevice(mic->stream);
427 ok = SDL_PauseAudioStreamDevice(mic->stream);
437static bool SDL_Libretro_MicGetState(
const retro_microphone_t* microphone) {
439 if (!mic)
return false;
443static int SDL_Libretro_MicRead(retro_microphone_t* microphone, int16_t* samples,
size_t num_samples) {
445 if (!mic || !mic->stream || !samples || num_samples == 0)
return 0;
447 int bytes = SDL_GetAudioStreamData(mic->stream, samples, (
int)(num_samples *
sizeof(int16_t)));
448 if (bytes < 0)
return 0;
449 return bytes / (int)
sizeof(int16_t);
452static void SDL_Libretro_CloseMicrophone(SDL_Libretro* lr) {
453 if (!lr || !lr->core.microphone)
return;
454 SDL_Libretro_MicClose((retro_microphone_t*)lr->core.microphone);
455 lr->core.microphone = NULL;
#define SDL_LIBRETRO_MIC_DEFAULT_RATE
The default sample rate for recording microphone input.
#define SDL_LIBRETRO_AUDIO_DRC_MAX_DELTA
The amount to push the DRC to correct audio pitch.
#define SDL_LIBRETRO_AUDIO_DEFAULT_SAMPLE_RATE
Fallback rate used when a core reports no audio sample rate (<= 0).
#define SDL_LIBRETRO_AUDIO_DEFAULT_LATENCY_MS
The threshold of audio latency in milliseconds.
void SDL_Libretro_SetAudioLatency(SDL_Libretro *lr, unsigned latencyMs)
Set the desired minimum audio latency for the audio stream.
SDL_Libretro * lr
Whether or not the micropohne device is active.
bool active
The sample rate.