10#if defined(SDL_LIBRETRO_IMPLEMENTATION) && !defined(SDL_LIBRETRO_AUDIO_IMPL_ONCE)
11#define SDL_LIBRETRO_AUDIO_IMPL_ONCE
13#ifndef SDL_LIBRETRO_AUDIO_DEFAULT_LATENCY_MS
20#define SDL_LIBRETRO_AUDIO_DEFAULT_LATENCY_MS 100
23#ifndef SDL_LIBRETRO_AUDIO_DEFAULT_SAMPLE_RATE
27#define SDL_LIBRETRO_AUDIO_DEFAULT_SAMPLE_RATE 48000
30#ifndef SDL_LIBRETRO_AUDIO_DRC_MAX_DELTA
38#define SDL_LIBRETRO_AUDIO_DRC_MAX_DELTA 0.005
41static void SDL_Libretro_QueueAudio(SDL_Libretro* lr,
const void* samples,
int bytes) {
42 if (!lr->core.audioStream || bytes <= 0)
return;
48 int cap = lr->core.audioQueueThresholdBytes * 2;
50 int queued = SDL_GetAudioStreamQueued(lr->core.audioStream);
51 if (queued >= 0 && bytes > cap - queued) {
52 int room = cap - queued;
53 if (room < 0) room = 0;
54 room -= room % (int)(
sizeof(int16_t) * 2);
57 if (lr->core.audioDropWarnCount < 10) {
58 SDL_LogDebug(SDL_LOG_CATEGORY_AUDIO,
"[SDL_Libretro] Audio queue full with %d bytes queued, dropping %d bytes", queued, bytes - room);
59 lr->core.audioDropWarnCount++;
63 if (bytes <= 0)
return;
67 SDL_PutAudioStreamData(lr->core.audioStream, samples, bytes);
79static void SDL_Libretro_QueueAudioReversed(SDL_Libretro* lr,
const int16_t* data,
size_t frames) {
80 int16_t revBuf[512 * 2];
81 size_t remaining = frames;
82 while (remaining > 0) {
83 size_t chunk = remaining > 512 ? 512 : remaining;
84 for (
size_t i = 0; i < chunk; i++) {
85 size_t src = remaining - 1 - i;
86 revBuf[i * 2] = data[src * 2];
87 revBuf[i * 2 + 1] = data[src * 2 + 1];
89 SDL_Libretro_QueueAudio(lr, revBuf, (
int)(chunk *
sizeof(int16_t) * 2));
106static void SDL_Libretro_UpdateDRC(SDL_Libretro* lr,
float speed) {
107 if (!lr || !lr->core.audioStream)
return;
111 if (speed <= 0.0f)
return;
115 if (!lr->core.drcEnabled) {
116 if (lr->core.drcAdjustment != 1.0f) {
117 lr->core.drcAdjustment = 1.0f;
118 lr->core.drcDriftAvg = 0.0;
119 SDL_SetAudioStreamFrequencyRatio(lr->core.audioStream, speed);
125 if (lr->core.audioQueueThresholdBytes <= 0) {
126 SDL_SetAudioStreamFrequencyRatio(lr->core.audioStream, speed);
130 int queued = SDL_GetAudioStreamQueued(lr->core.audioStream);
131 if (queued < 0)
return;
134 double drift = (double)queued / (
double)lr->core.audioQueueThresholdBytes - 0.5;
135 lr->core.drcDriftAvg += 0.0625 * (drift - lr->core.drcDriftAvg);
139 lr->core.drcAdjustment = (float)adj;
140 SDL_SetAudioStreamFrequencyRatio(lr->core.audioStream, speed * (
float)adj);
157static unsigned SDL_Libretro_UpdateAudioThreshold(SDL_Libretro* lr) {
159 if (lr->core.minimumAudioLatencyMs > latencyMs) latencyMs = lr->core.minimumAudioLatencyMs;
164 int minThreshold = 4 * (SDL_LIBRETRO_AUDIO_SINGLE_SAMPLE_BUFFER_SIZE * (int)(
sizeof(int16_t) * 2));
165 lr->core.audioQueueThresholdBytes = threshold > minThreshold ? threshold : minThreshold;
178static void SDL_Libretro_ReportAudioBufferStatus(SDL_Libretro* lr) {
179 if (!lr || !lr->core.audio_buffer_status.callback)
return;
184 bool active = (lr->core.audioStream != NULL);
194 unsigned occupancy = 0;
199 bool underrunLikely =
false;
201 if (active && lr->core.audioQueueThresholdBytes > 0) {
202 int queued = SDL_GetAudioStreamQueued(lr->core.audioStream);
203 if (queued < 0) queued = 0;
205 double pct = (double)queued * 100.0 / (
double)lr->core.audioQueueThresholdBytes;
206 occupancy = (unsigned)(pct > 100.0 ? 100.0 : pct);
209 double fps = lr->core.fps > 0.0 ? lr->core.fps : 60.0;
211 underrunLikely = (queued < frameBytes);
214 lr->core.audio_buffer_status.callback(active, occupancy, underrunLikely);
220static bool SDL_Libretro_InitAudio(SDL_Libretro* lr) {
222 SDL_SetError(
"[SDL_Libretro] No core loaded");
226 SDL_Libretro_CloseAudio(lr);
228 SDL_Libretro_UpdateAudioThreshold(lr);
234 spec.freq = (int)sampleRate;
237 spec.format = SDL_AUDIO_S16;
240 lr->core.audioStream = SDL_OpenAudioDeviceStream(
241 SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK,
246 if (!lr->core.audioStream) {
247 SDL_SetError(
"[SDL_Libretro] Failed to open audio device: %s", SDL_GetError());
252 lr->core.audioDropWarnCount = 0;
253 lr->core.audioReinitPending =
false;
256 lr->core.drcAdjustment = 1.0f;
257 lr->core.drcEnabled =
true;
258 lr->core.drcDriftAvg = 0.0;
264 float ratio = SDL_fabsf(lr->core.speed) * lr->core.drcAdjustment;
265 if (ratio < 0.01f) ratio = 1.0f;
266 if (!SDL_SetAudioStreamFrequencyRatio(lr->core.audioStream, ratio)) {
267 SDL_SetError(
"[SDL_Libretro] Failed to set audio frequency: %s", SDL_GetError());
268 SDL_DestroyAudioStream(lr->core.audioStream);
269 lr->core.audioStream = NULL;
274 SDL_SetAudioStreamGain(lr->core.audioStream, lr->volume);
275 SDL_ResumeAudioStreamDevice(lr->core.audioStream);
278 if (lr->core.audio_callback.set_state) {
279 lr->core.audio_callback.set_state(
true);
282 SDL_Log(
"[SDL_Libretro] Audio initialized [%d Channels @ %d Hz]", spec.channels, spec.freq);
287static void SDL_Libretro_CloseAudio(SDL_Libretro* lr) {
291 if (lr->core.audio_callback.set_state) {
292 lr->core.audio_callback.set_state(
false);
295 if (lr->core.audioStream) {
296 SDL_DestroyAudioStream(lr->core.audioStream);
297 lr->core.audioStream = NULL;
316 lr->audioLatencyMs = latencyMs;
319 if (lr->core.audioStream) {
320 SDL_Libretro_UpdateAudioThreshold(lr);
350static void SDL_Libretro_AudioSample(int16_t left, int16_t right) {
354 if (lr->core.singleSampleCount >= SDL_LIBRETRO_AUDIO_SINGLE_SAMPLE_BUFFER_SIZE) {
355 SDL_Libretro_FlushSingleSamples(lr);
358 size_t idx = lr->core.singleSampleCount * 2;
359 lr->core.singleSampleBuffer[idx] = left;
360 lr->core.singleSampleBuffer[idx + 1] = right;
361 lr->core.singleSampleCount++;
364static size_t SDL_Libretro_AudioSampleBatch(
const int16_t* data,
size_t frames) {
366 if (!lr || !lr->core.audioStream || frames == 0)
return frames;
368 if (lr->core.singleSampleCount > 0) {
369 SDL_Libretro_FlushSingleSamples(lr);
373 if (lr->rewindActive) {
374 SDL_Libretro_QueueAudioReversed(lr, data, frames);
378 SDL_Libretro_QueueAudio(lr, data, (
int)(frames *
sizeof(int16_t) * 2));
382static void SDL_Libretro_FlushSingleSamples(SDL_Libretro* lr) {
383 if (!lr || lr->core.singleSampleCount == 0)
return;
386 if (lr->rewindActive) {
387 SDL_Libretro_QueueAudioReversed(lr, lr->core.singleSampleBuffer, lr->core.singleSampleCount);
388 lr->core.singleSampleCount = 0;
392 SDL_Libretro_QueueAudio(lr, lr->core.singleSampleBuffer, (
int)(lr->core.singleSampleCount *
sizeof(int16_t) * 2));
393 lr->core.singleSampleCount = 0;
398#ifndef SDL_LIBRETRO_MIC_DEFAULT_RATE
404#define SDL_LIBRETRO_MIC_DEFAULT_RATE 44100
407static retro_microphone_t* SDL_Libretro_MicOpen(
const retro_microphone_params_t* params) {
409 if (!lr)
return NULL;
413 if (lr->core.microphone) {
414 SDL_LogWarn(SDL_LOG_CATEGORY_AUDIO,
"[SDL_Libretro] Microphone already open");
421 spec.freq = (int)rate;
422 spec.format = SDL_AUDIO_S16;
425 SDL_AudioStream* stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_RECORDING, &spec, NULL, (
void*)lr);
427 SDL_LogError(SDL_LOG_CATEGORY_AUDIO,
"[SDL_Libretro] Failed to open microphone: %s", SDL_GetError());
431 SDL_LibretroMicrophone* mic = (SDL_LibretroMicrophone*)SDL_calloc(1,
sizeof(*mic));
433 SDL_DestroyAudioStream(stream);
438 mic->stream = stream;
442 lr->core.microphone = mic;
444 SDL_Log(
"[SDL_Libretro] Microphone opened (%u Hz)", rate);
445 return (retro_microphone_t*)mic;
448static void SDL_Libretro_MicClose(retro_microphone_t* microphone) {
449 SDL_LibretroMicrophone* mic = (SDL_LibretroMicrophone*)microphone;
453 if (mic->lr && mic->lr->core.microphone == mic) {
454 mic->lr->core.microphone = NULL;
459 SDL_DestroyAudioStream(mic->stream);
465static bool SDL_Libretro_MicGetParams(
const retro_microphone_t* microphone, retro_microphone_params_t* params) {
466 const SDL_LibretroMicrophone* mic = (
const SDL_LibretroMicrophone*)microphone;
467 if (!mic || !params)
return false;
468 params->rate = mic->rate;
472static bool SDL_Libretro_MicSetState(retro_microphone_t* microphone,
bool state) {
473 SDL_LibretroMicrophone* mic = (SDL_LibretroMicrophone*)microphone;
474 if (!mic || !mic->stream)
return false;
478 ok = SDL_ResumeAudioStreamDevice(mic->stream);
481 ok = SDL_PauseAudioStreamDevice(mic->stream);
491static bool SDL_Libretro_MicGetState(
const retro_microphone_t* microphone) {
492 const SDL_LibretroMicrophone* mic = (
const SDL_LibretroMicrophone*)microphone;
493 if (!mic)
return false;
497static int SDL_Libretro_MicRead(retro_microphone_t* microphone, int16_t* samples,
size_t num_samples) {
498 SDL_LibretroMicrophone* mic = (SDL_LibretroMicrophone*)microphone;
499 if (!mic || !mic->stream || !samples || num_samples == 0)
return 0;
501 int bytes = SDL_GetAudioStreamData(mic->stream, samples, (
int)(num_samples *
sizeof(int16_t)));
502 if (bytes < 0)
return 0;
503 return bytes / (int)
sizeof(int16_t);
506static void SDL_Libretro_CloseMicrophone(SDL_Libretro* lr) {
507 if (!lr || !lr->core.microphone)
return;
508 SDL_Libretro_MicClose((retro_microphone_t*)lr->core.microphone);
509 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.
bool SDL_Libretro_IsCoreReady(const SDL_Libretro *lr)
Indicates whether a core is loaded.
double SDL_Libretro_GetSampleRate(const SDL_Libretro *lr)
Get the audio sample rate the loaded core outputs at.
struct SDL_Libretro SDL_Libretro
A libretro frontend context: one loaded core, its game, and the settings that carry across cores.
void SDL_Libretro_SetAudioLatency(SDL_Libretro *lr, unsigned latencyMs)
Set the desired minimum audio latency for the audio stream.
unsigned SDL_Libretro_GetAudioLatency(const SDL_Libretro *lr)
Get the minimum audio latency for the audio stream.