/** * SDL_sound; An abstract sound format decoding API. * * Please see the file LICENSE.txt in the source's root directory. * * This file written by Ryan C. Gordon. */ /* * Ogg Vorbis decoder for SDL_sound. * * This driver handles .OGG audio files, and uses Sean Barrett's excellent * stb_vorbis for the heavy lifting. Since stb_vorbis is a single, * public domain C header file, it's just compiled into this source and * needs no external dependencies. * * stb_vorbis homepage: https://nothings.org/stb_vorbis/ */ #define __SDL_SOUND_INTERNAL__ #include "SDL_sound_internal.h" #if SOUND_SUPPORTS_VORBIS /* Configure and include stb_vorbis for compiling... */ #define STB_VORBIS_SDL 1 /* for SDL_sound-specific stuff. */ #define STB_VORBIS_NO_STDIO 1 #define STB_VORBIS_NO_CRT 1 #define STB_VORBIS_NO_PUSHDATA_API 1 #define STB_VORBIS_MAX_CHANNELS 8 /* For 7.1 surround sound */ #define STB_VORBIS_NO_COMMENTS 1 #define STB_FORCEINLINE SDL_FORCE_INLINE #if SDL_BYTEORDER == SDL_BIG_ENDIAN #define STB_VORBIS_BIG_ENDIAN 1 #endif #define STBV_CDECL SDLCALL /* for SDL_qsort() */ #if !defined(__clang_analyzer__) #ifdef assert #undef assert #endif #ifdef memset #undef memset #endif #ifdef memcpy #undef memcpy #endif #define assert SDL_assert #define memset SDL_memset #define memcmp SDL_memcmp #define memcpy SDL_memcpy #define qsort SDL_qsort #define malloc SDL_malloc #define realloc SDL_realloc #define free SDL_free #define pow SDL_pow #define floor SDL_floor #define ldexp(v, e) SDL_scalbn((v), (e)) #define abs(x) SDL_abs(x) #define cos(x) SDL_cos(x) #define sin(x) SDL_sin(x) #define log(x) SDL_log(x) #if SDL_VERSION_ATLEAST(2, 0, 9) #define exp SDL_exp #endif #endif #include "stb_vorbis.h" static const char *vorbis_error_string(const int err) { switch (err) { case VORBIS__no_error: return NULL; case VORBIS_need_more_data: return "VORBIS: need more data"; case VORBIS_invalid_api_mixing: return "VORBIS: can't mix API modes"; case VORBIS_outofmem: return "VORBIS: out of memory"; case VORBIS_feature_not_supported: return "VORBIS: feature not supported"; case VORBIS_too_many_channels: return "VORBIS: too many channels"; case VORBIS_seek_without_length: return "VORBIS: can't seek in unknown length stream"; case VORBIS_unexpected_eof: return "VORBIS: unexpected eof"; case VORBIS_seek_invalid: return "VORBIS: invalid seek"; case VORBIS_invalid_setup: return "VORBIS: invalid setup"; case VORBIS_invalid_stream: return "VORBIS: invalid stream"; case VORBIS_missing_capture_pattern: return "VORBIS: missing capture pattern"; case VORBIS_invalid_stream_structure_version: return "VORBIS: invalid stream structure version"; case VORBIS_continued_packet_flag_invalid: return "VORBIS: continued packet flag invalid"; case VORBIS_incorrect_stream_serial_number: return "VORBIS: incorrect stream serial number"; case VORBIS_invalid_first_page: return "VORBIS: invalid first page"; case VORBIS_bad_packet_type: return "VORBIS: bad packet type"; case VORBIS_cant_find_last_page: return "VORBIS: can't find last page"; case VORBIS_seek_failed: return "VORBIS: seek failed"; default: break; } /* switch */ return "VORBIS: unknown error"; } /* vorbis_error_string */ static SDL_bool VORBIS_init(void) { return SDL_TRUE; /* always succeeds. */ } /* VORBIS_init */ static void VORBIS_quit(void) { /* it's a no-op. */ } /* VORBIS_quit */ static int VORBIS_open(Sound_Sample *sample, const char *ext) { Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; SDL_RWops *rw = internal->rw; int err = 0; stb_vorbis *stb = stb_vorbis_open_rwops(rw, 0, &err, NULL); unsigned int num_frames; BAIL_IF_MACRO(!stb, vorbis_error_string(err), 0); SNDDBG(("VORBIS: Accepting data stream.\n")); internal->decoder_private = stb; sample->flags = SOUND_SAMPLEFLAG_CANSEEK; sample->actual.format = AUDIO_F32SYS; sample->actual.channels = stb->channels; sample->actual.rate = stb->sample_rate; num_frames = stb_vorbis_stream_length_in_samples(stb); if (!num_frames) { BAIL_MACRO("VORBIS: No samples in ogg/vorbis stream.", 0); } else { const unsigned int rate = stb->sample_rate; internal->total_time = (num_frames / rate) * 1000; internal->total_time += (num_frames % rate) * 1000 / rate; } /* else */ return 1; /* we'll handle this data. */ } /* VORBIS_open */ static void VORBIS_close(Sound_Sample *sample) { Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; stb_vorbis *stb = (stb_vorbis *) internal->decoder_private; stb_vorbis_close(stb); } /* VORBIS_close */ static Uint32 VORBIS_read(Sound_Sample *sample) { Uint32 retval; int rc; int err; Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; stb_vorbis *stb = (stb_vorbis *) internal->decoder_private; const int channels = (int) sample->actual.channels; const int want_samples = (int) (internal->buffer_size / sizeof (float)); stb_vorbis_get_error(stb); /* clear any error state */ rc = stb_vorbis_get_samples_float_interleaved(stb, channels, (float *) internal->buffer, want_samples); retval = (Uint32) (rc * channels * sizeof (float)); /* rc == number of sample frames read */ err = stb_vorbis_get_error(stb); if (retval == 0) { if (!err) sample->flags |= SOUND_SAMPLEFLAG_EOF; else { sample->flags |= SOUND_SAMPLEFLAG_ERROR; /* !!! FIXME: should I set this? __Sound_SetError(vorbis_error_string(err)); */ } /* else */ } /* if */ else if (retval < internal->buffer_size) sample->flags |= SOUND_SAMPLEFLAG_EAGAIN; return retval; } /* VORBIS_read */ static int VORBIS_rewind(Sound_Sample *sample) { Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; stb_vorbis *stb = (stb_vorbis *) internal->decoder_private; BAIL_IF_MACRO(!stb_vorbis_seek_start(stb), vorbis_error_string(stb_vorbis_get_error(stb)), 0); return 1; } /* VORBIS_rewind */ static int VORBIS_seek(Sound_Sample *sample, Uint32 ms) { Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; stb_vorbis *stb = (stb_vorbis *) internal->decoder_private; const float frames_per_ms = ((float) sample->actual.rate) / 1000.0f; const Uint32 frame_offset = (Uint32) (frames_per_ms * ((float) ms)); const unsigned int sampnum = (unsigned int) frame_offset; BAIL_IF_MACRO(!stb_vorbis_seek(stb, sampnum), vorbis_error_string(stb_vorbis_get_error(stb)), 0); return 1; } /* VORBIS_seek */ static const char *extensions_vorbis[] = { "OGG", NULL }; const Sound_DecoderFunctions __Sound_DecoderFunctions_VORBIS = { { extensions_vorbis, "Ogg Vorbis audio", "Ryan C. Gordon ", "https://icculus.org/SDL_sound/" }, VORBIS_init, /* init() method */ VORBIS_quit, /* quit() method */ VORBIS_open, /* open() method */ VORBIS_close, /* close() method */ VORBIS_read, /* read() method */ VORBIS_rewind, /* rewind() method */ VORBIS_seek /* seek() method */ }; #endif /* SOUND_SUPPORTS_VORBIS */ /* end of SDL_sound_vorbis.c ... */