mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
157 lines
4.4 KiB
C
157 lines
4.4 KiB
C
#include "audio_capture.h"
|
|
#include <SDL3/SDL.h>
|
|
#include <limits.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
struct MicCapture {
|
|
SDL_AudioStream *stream;
|
|
unsigned char *ring;
|
|
int cap, head, used, bpf;
|
|
};
|
|
|
|
const char *mic_error(void) { return SDL_GetError(); }
|
|
int mic_device_count(void) {
|
|
if (!SDL_InitSubSystem(SDL_INIT_AUDIO))
|
|
return 0;
|
|
int n = 0;
|
|
SDL_AudioDeviceID *ids = SDL_GetAudioRecordingDevices(&n);
|
|
SDL_free(ids);
|
|
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
|
return n;
|
|
}
|
|
|
|
/* The SDL stream lock serializes the callback and every ring operation.
|
|
Discard the oldest frames on overflow, so a paused game cannot grow RAM. */
|
|
static void SDLCALL received(void *userdata, SDL_AudioStream *stream, int added, int total) {
|
|
(void)added;
|
|
(void)total;
|
|
MicCapture *m = userdata;
|
|
unsigned char scratch[8192];
|
|
int chunk = (int)sizeof scratch / m->bpf * m->bpf;
|
|
for (;;) {
|
|
int n = SDL_GetAudioStreamData(stream, scratch, chunk);
|
|
if (n <= 0)
|
|
break;
|
|
n -= n % m->bpf;
|
|
if (!n)
|
|
break;
|
|
const unsigned char *src = scratch;
|
|
if (n > m->cap) {
|
|
src += n - m->cap;
|
|
n = m->cap;
|
|
}
|
|
int drop = m->used + n - m->cap;
|
|
if (drop > 0) {
|
|
m->head = (m->head + drop) % m->cap;
|
|
m->used -= drop;
|
|
}
|
|
int tail = (m->head + m->used) % m->cap;
|
|
int first = n < m->cap - tail ? n : m->cap - tail;
|
|
memcpy(m->ring + tail, src, first);
|
|
memcpy(m->ring, src + first, n - first);
|
|
m->used += n;
|
|
}
|
|
}
|
|
|
|
MicCapture *mic_open(int format, int channels, int rate, int frames) {
|
|
SDL_AudioFormat sf;
|
|
switch (format) {
|
|
case 1:
|
|
sf = SDL_AUDIO_S8;
|
|
break;
|
|
case 2:
|
|
sf = SDL_AUDIO_S16LE;
|
|
break;
|
|
case 4:
|
|
sf = SDL_AUDIO_S32LE;
|
|
break;
|
|
case 5:
|
|
sf = SDL_AUDIO_F32LE;
|
|
break;
|
|
default:
|
|
SDL_SetError("unsupported guest sample format %d", format);
|
|
return NULL;
|
|
}
|
|
if (channels < 1 || channels > 8 || rate < 8000 || rate > 192000 || frames < 1) {
|
|
SDL_SetError("invalid capture format or capacity");
|
|
return NULL;
|
|
}
|
|
if (!SDL_InitSubSystem(SDL_INIT_AUDIO))
|
|
return NULL;
|
|
MicCapture *m = calloc(1, sizeof *m);
|
|
if (!m) {
|
|
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
|
return NULL;
|
|
}
|
|
m->bpf = channels * (SDL_AUDIO_BITSIZE(sf) / 8);
|
|
if (frames > rate * 2)
|
|
frames = rate * 2;
|
|
m->cap = frames * m->bpf;
|
|
m->ring = malloc(m->cap);
|
|
if (!m->ring) {
|
|
free(m);
|
|
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
|
return NULL;
|
|
}
|
|
SDL_AudioSpec spec = {.format = sf, .channels = channels, .freq = rate};
|
|
m->stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_RECORDING, &spec, received, m);
|
|
if (!m->stream) {
|
|
free(m->ring);
|
|
free(m);
|
|
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
|
return NULL;
|
|
}
|
|
return m;
|
|
}
|
|
int mic_start(MicCapture *m) { return m && SDL_ResumeAudioStreamDevice(m->stream); }
|
|
void mic_stop(MicCapture *m) {
|
|
if (m)
|
|
SDL_PauseAudioStreamDevice(m->stream);
|
|
}
|
|
void mic_close(MicCapture *m) {
|
|
if (!m)
|
|
return;
|
|
SDL_DestroyAudioStream(m->stream); /* joins capture before freeing userdata */
|
|
free(m->ring);
|
|
free(m);
|
|
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
|
}
|
|
void mic_purge(MicCapture *m) {
|
|
if (!m)
|
|
return;
|
|
SDL_LockAudioStream(m->stream);
|
|
SDL_ClearAudioStream(m->stream);
|
|
m->head = m->used = 0;
|
|
SDL_UnlockAudioStream(m->stream);
|
|
}
|
|
int mic_available(MicCapture *m) {
|
|
if (!m)
|
|
return 0;
|
|
SDL_LockAudioStream(m->stream);
|
|
int frames = m->used / m->bpf;
|
|
SDL_UnlockAudioStream(m->stream);
|
|
return frames;
|
|
}
|
|
int mic_capacity(MicCapture *m) { return m ? m->cap / m->bpf : 0; }
|
|
int mic_read(MicCapture *m, void *out, int frames) {
|
|
if (!m || !out || frames <= 0)
|
|
return 0;
|
|
SDL_LockAudioStream(m->stream);
|
|
int avail = m->used / m->bpf;
|
|
if (frames > avail)
|
|
frames = avail;
|
|
int n = frames * m->bpf;
|
|
int first = n < m->cap - m->head ? n : m->cap - m->head;
|
|
memcpy(out, m->ring + m->head, first);
|
|
memcpy((unsigned char *)out + first, m->ring, n - first);
|
|
m->head = (m->head + n) % m->cap;
|
|
m->used -= n;
|
|
SDL_UnlockAudioStream(m->stream);
|
|
return frames;
|
|
}
|
|
void mic_gain(MicCapture *m, float gain) {
|
|
if (m)
|
|
SDL_SetAudioStreamGain(m->stream, gain);
|
|
}
|