speex resampling instead of libsamplerate?

This commit is contained in:
Anthony Samms
2025-11-21 16:47:05 -05:00
parent f3c77df017
commit b74b0bb0ac
2 changed files with 77 additions and 39 deletions

View File

@@ -71,10 +71,15 @@ ifneq (,$(findstring MINGW,$(UNAME_S)))
CORE_LIBS += -lsndfile CORE_LIBS += -lsndfile
endif endif
ifneq (,$(wildcard /mingw64/lib/libsamplerate.a)) # Resampling libraries - prefer speexdsp, fallback to libsamplerate
ifneq (,$(wildcard /mingw64/lib/libspeexdsp.a))
CORE_LIBS += /mingw64/lib/libspeexdsp.a
CFLAGS += -DHAVE_SPEEXDSP
else ifneq (,$(wildcard /mingw64/lib/libsamplerate.a))
CORE_LIBS += /mingw64/lib/libsamplerate.a CORE_LIBS += /mingw64/lib/libsamplerate.a
CFLAGS += -DHAVE_SAMPLERATE
else else
CORE_LIBS += -lsamplerate CORE_LIBS += -lspeexdsp -lsamplerate
endif endif
# Windows system libraries (these provide the missing symbols) # Windows system libraries (these provide the missing symbols)
@@ -103,7 +108,10 @@ else ifeq ($(UNAME_S),Darwin)
CORE_LIBS += -lportaudio CORE_LIBS += -lportaudio
endif endif
CORE_LIBS += -lsndfile -lsamplerate CORE_LIBS += -lsndfile
# Resampling libraries - prefer speexdsp, fallback to libsamplerate
CORE_LIBS += -lspeexdsp -lsamplerate
# macOS frameworks # macOS frameworks
LIBS = $(CORE_LIBS) -framework CoreAudio -framework AudioToolbox -framework AudioUnit LIBS = $(CORE_LIBS) -framework CoreAudio -framework AudioToolbox -framework AudioUnit
@@ -127,12 +135,15 @@ else ifeq ($(UNAME_S),Linux)
CORE_LIBS += -lsndfile CORE_LIBS += -lsndfile
# Check for libsamplerate # Check for speexdsp (preferred) or libsamplerate
ifeq ($(call check_lib,samplerate),yes) ifeq ($(call check_lib,speexdsp),yes)
CORE_LIBS += -lspeexdsp
CFLAGS += -DHAVE_SPEEXDSP
else ifeq ($(call check_lib,samplerate),yes)
CORE_LIBS += -lsamplerate CORE_LIBS += -lsamplerate
CFLAGS += -DHAVE_SAMPLERATE CFLAGS += -DHAVE_SAMPLERATE
else else
$(warning libsamplerate not found - building without sample rate conversion) $(warning Neither speexdsp nor libsamplerate found - building without sample rate conversion)
endif endif
# Audio backend libraries (optional) # Audio backend libraries (optional)
@@ -188,7 +199,7 @@ else ifeq ($(UNAME_S),Linux)
else else
# Generic Unix fallback - minimal dependencies # Generic Unix fallback - minimal dependencies
LIBNAME = libaudio.so LIBNAME = libaudio.so
LIBS = -lportaudio -lsndfile -lpthread -lm LIBS = -lportaudio -lsndfile -lspeexdsp -lpthread -lm
OBJ_EXT = .o OBJ_EXT = .o
endif endif
@@ -250,7 +261,7 @@ ifneq (,$(findstring MINGW,$(UNAME_S)))
@echo "Checking for Windows-specific libportaudio:" @echo "Checking for Windows-specific libportaudio:"
@ls -la ./libportaudio-win.a 2>/dev/null && echo "✓ Found local libportaudio-win.a" || echo "✗ No local libportaudio-win.a" @ls -la ./libportaudio-win.a 2>/dev/null && echo "✓ Found local libportaudio-win.a" || echo "✗ No local libportaudio-win.a"
@echo "Available system static libraries:" @echo "Available system static libraries:"
@ls /mingw64/lib/lib{portaudio,sndfile,samplerate,FLAC,vorbis*,ogg}.a 2>/dev/null || echo "None found" @ls /mingw64/lib/lib{portaudio,sndfile,speexdsp,samplerate,FLAC,vorbis*,ogg}.a 2>/dev/null || echo "None found"
@echo "Static pthread library:" @echo "Static pthread library:"
@ls /mingw64/lib/libwinpthread.a 2>/dev/null && echo "✓ Found libwinpthread.a" || echo "✗ Missing libwinpthread.a" @ls /mingw64/lib/libwinpthread.a 2>/dev/null && echo "✓ Found libwinpthread.a" || echo "✗ Missing libwinpthread.a"
@echo "Libraries to link: $(LIBS)" @echo "Libraries to link: $(LIBS)"
@@ -262,6 +273,7 @@ ifdef PKG_CONFIG
@echo "pkg-config available: yes" @echo "pkg-config available: yes"
@echo -n "PortAudio: "; pkg-config --exists portaudio-2.0 && echo "✓" || echo "✗" @echo -n "PortAudio: "; pkg-config --exists portaudio-2.0 && echo "✓" || echo "✗"
@echo -n "libsndfile: "; pkg-config --exists sndfile && echo "✓" || echo "✗" @echo -n "libsndfile: "; pkg-config --exists sndfile && echo "✓" || echo "✗"
@echo -n "speexdsp: "; pkg-config --exists speexdsp && echo "✓" || echo "✗"
@echo -n "libsamplerate: "; pkg-config --exists samplerate && echo "✓" || echo "✗" @echo -n "libsamplerate: "; pkg-config --exists samplerate && echo "✓" || echo "✗"
@echo -n "ALSA: "; pkg-config --exists alsa && echo "✓" || echo "✗" @echo -n "ALSA: "; pkg-config --exists alsa && echo "✓" || echo "✗"
@echo -n "PulseAudio: "; pkg-config --exists libpulse && echo "✓" || echo "✗" @echo -n "PulseAudio: "; pkg-config --exists libpulse && echo "✓" || echo "✗"
@@ -288,7 +300,7 @@ list-libs:
@echo "All libraries: $(LIBS)" @echo "All libraries: $(LIBS)"
# Build with only essential libraries (fallback) # Build with only essential libraries (fallback)
minimal: override LIBS = -lportaudio -lsndfile -lpthread -lm minimal: override LIBS = -lportaudio -lsndfile -lspeexdsp -lpthread -lm
minimal: $(LIBNAME) minimal: $(LIBNAME)
@echo "Built minimal version with basic dependencies only" @echo "Built minimal version with basic dependencies only"

View File

@@ -15,7 +15,8 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <sndfile.h> #include <sndfile.h>
#include <samplerate.h> //#include <samplerate.h>
#include <speex/speex_resampler.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <math.h> #include <math.h>
@@ -78,7 +79,7 @@ typedef struct music {
// Music context data, required for music streaming // Music context data, required for music streaming
typedef struct music_ctx { typedef struct music_ctx {
SNDFILE *snd_file; SNDFILE *snd_file;
SRC_STATE *resampler; SpeexResamplerState *resampler;
double src_ratio; double src_ratio;
} music_ctx; } music_ctx;
@@ -693,28 +694,50 @@ sound load_sound_from_wave(wave wave) {
if (wave.sampleRate != AUDIO.System.sampleRate) { if (wave.sampleRate != AUDIO.System.sampleRate) {
TRACELOG(LOG_INFO, "Resampling wave from %d Hz to %f Hz", wave.sampleRate, AUDIO.System.sampleRate); TRACELOG(LOG_INFO, "Resampling wave from %d Hz to %f Hz", wave.sampleRate, AUDIO.System.sampleRate);
SRC_DATA src_data; int error = 0;
src_data.data_in = wave.data; SpeexResamplerState *resampler = speex_resampler_init(
src_data.input_frames = wave.frameCount; wave.channels,
src_data.src_ratio = AUDIO.System.sampleRate / wave.sampleRate; wave.sampleRate,
src_data.output_frames = (sf_count_t)(wave.frameCount * src_data.src_ratio); (int)AUDIO.System.sampleRate,
SPEEX_RESAMPLER_QUALITY_DESKTOP,
&error
);
resampled_wave.data = calloc(src_data.output_frames * wave.channels, sizeof(float)); if (error || resampler == NULL) {
if (resampled_wave.data == NULL) { TRACELOG(LOG_WARNING, "Failed to initialize resampler: %d", error);
TRACELOG(LOG_WARNING, "Failed to allocate memory for resampling");
return sound; return sound;
} }
src_data.data_out = resampled_wave.data;
int error = src_simple(&src_data, SRC_SINC_BEST_QUALITY, wave.channels); spx_uint32_t out_frames = (spx_uint32_t)(wave.frameCount * AUDIO.System.sampleRate / wave.sampleRate) + 10;
if (error) {
TRACELOG(LOG_WARNING, "Resampling failed: %s", src_strerror(error)); resampled_wave.data = calloc(out_frames * wave.channels, sizeof(float));
if (resampled_wave.data == NULL) {
TRACELOG(LOG_WARNING, "Failed to allocate memory for resampling");
speex_resampler_destroy(resampler);
return sound;
}
spx_uint32_t in_len = wave.frameCount;
spx_uint32_t out_len = out_frames;
error = speex_resampler_process_interleaved_float(
resampler,
wave.data,
&in_len,
resampled_wave.data,
&out_len
);
speex_resampler_destroy(resampler);
if (error != RESAMPLER_ERR_SUCCESS) {
TRACELOG(LOG_WARNING, "Resampling failed with error: %d", error);
FREE(resampled_wave.data); FREE(resampled_wave.data);
return sound; return sound;
} }
resampled_wave.frameCount = src_data.output_frames_gen; resampled_wave.frameCount = out_len;
resampled_wave.sampleRate = AUDIO.System.sampleRate; resampled_wave.sampleRate = (int)AUDIO.System.sampleRate;
resampled_wave.channels = wave.channels; resampled_wave.channels = wave.channels;
resampled_wave.sampleSize = wave.sampleSize; resampled_wave.sampleSize = wave.sampleSize;
is_resampled = true; is_resampled = true;
@@ -913,9 +936,9 @@ music load_music_stream(const char* filename) {
if (sf_info.samplerate != AUDIO.System.sampleRate) { if (sf_info.samplerate != AUDIO.System.sampleRate) {
TRACELOG(LOG_INFO, "Resampling music from %d Hz to %f Hz", sf_info.samplerate, AUDIO.System.sampleRate); TRACELOG(LOG_INFO, "Resampling music from %d Hz to %f Hz", sf_info.samplerate, AUDIO.System.sampleRate);
int error; int error;
ctx->resampler = src_new(SRC_SINC_FASTEST, sf_info.channels, &error); ctx->resampler = speex_resampler_init(sf_info.channels, sf_info.samplerate, AUDIO.System.sampleRate, SPEEX_RESAMPLER_QUALITY_DESKTOP, &error);
if (ctx->resampler == NULL) { if (ctx->resampler == NULL) {
TRACELOG(LOG_WARNING, "Failed to create resampler: %s", src_strerror(error)); TRACELOG(LOG_WARNING, "Failed to create resampler");
free(ctx); free(ctx);
sf_close(snd_file); sf_close(snd_file);
return music; return music;
@@ -962,7 +985,7 @@ void unload_music_stream(music music) {
if (music.ctxData) { if (music.ctxData) {
music_ctx *ctx = (music_ctx *)music.ctxData; music_ctx *ctx = (music_ctx *)music.ctxData;
if (ctx->snd_file) sf_close(ctx->snd_file); if (ctx->snd_file) sf_close(ctx->snd_file);
if (ctx->resampler) src_delete(ctx->resampler); if (ctx->resampler) speex_resampler_destroy(ctx->resampler);
free(ctx); free(ctx);
} }
unload_audio_stream(music.stream); unload_audio_stream(music.stream);
@@ -1036,19 +1059,22 @@ void update_music_stream(music music) {
sf_count_t frames_written = 0; sf_count_t frames_written = 0;
if (ctx->resampler) { if (ctx->resampler) {
SRC_DATA src_data; spx_uint32_t in_len = frames_read;
src_data.data_in = input_ptr; spx_uint32_t out_len = subBufferSizeFrames;
src_data.input_frames = frames_read;
src_data.data_out = buffer_data + subBufferOffset;
src_data.output_frames = subBufferSizeFrames;
src_data.src_ratio = ctx->src_ratio;
src_data.end_of_input = (frames_read < frames_to_read);
int error = src_process(ctx->resampler, &src_data); int error = speex_resampler_process_interleaved_float(
if (error) { ctx->resampler,
TRACELOG(LOG_WARNING, "Resampling failed: %s", src_strerror(error)); input_ptr,
&in_len,
buffer_data + subBufferOffset,
&out_len
);
if (error != RESAMPLER_ERR_SUCCESS) {
TRACELOG(LOG_WARNING, "Resampling failed with error: %d", error);
} }
frames_written = src_data.output_frames_gen;
frames_written = out_len;
} else { } else {
if (music.stream.channels == 1 && AUDIO_DEVICE_CHANNELS == 2) { if (music.stream.channels == 1 && AUDIO_DEVICE_CHANNELS == 2) {
for (int j = 0; j < frames_read; j++) { for (int j = 0; j < frames_read; j++) {