/* * System Interface Library for games * Copyright (c) 2007-2020 Andrew Church * Released under the GNU GPL version 3 or later; NO WARRANTY is provided. * See the file COPYING.txt for details. * * src/sound/core.c: Core sound playback routines. */ #include "src/base.h" #include "src/math.h" #include "src/memory.h" #include "src/mutex.h" #include "src/sound.h" #include "src/sound/decode.h" #include "src/sound/filter.h" #include "src/sound/mixer.h" #include "src/sysdep.h" #include "src/thread.h" /* Disable the memory debugging macros from the bottom of sound.h. Also * define convenience macros for declaring and passing debug parameters, to * avoid unneeded messiness in the actual code. */ #ifdef DEBUG # undef sound_create # undef sound_create_stream # undef sound_destroy # undef sound_play # undef sound_play_decoder # undef sound_cut # undef sound_fade # define __DEBUG_PARAMS , const char *file, int line # define __DEBUG_ARGS , file, line # define __LOCAL_DEBUG_ARGS , __FILE__, __LINE__ #else # define __DEBUG_PARAMS /*nothing*/ # define __DEBUG_ARGS /*nothing*/ # define __LOCAL_DEBUG_ARGS /*nothing*/ #endif /*************************************************************************/ /****************************** Local data *******************************/ /*************************************************************************/ /* Sound structure definition. */ struct Sound { /* Usage counter, to prevent sound_destroy() from freeing in-use sounds. */ uint16_t usage_counter; /* Flag indicating whether this object should be freed when the last * channel it's used by is stopped. */ uint8_t free_on_stop; /* Flag indicating whether the data source is a memory buffer or a * file handle. */ uint8_t is_file; /* Sound data format. */ SoundFormat format; /* Data source, either a buffer pointer or a file handle depending on * the value of "is_file". */ union { void *data; SysFile *fh; }; /* Data offset (only used for file sources) and length. */ int64_t dataofs; int datalen; /* Loop start and length set with sound_set_loop(), or -1 if no * explicit loop has been defined. */ int loopstart; int looplen; /* Audio parameters. */ uint8_t have_audio_params; // Flag: have we looked these up yet? uint8_t stereo; // Stereo (true) or monaural (false)? int native_freq; // Native playback frequency (Hz). }; /*-----------------------------------------------------------------------*/ /* Flag: Have we been initialized? */ static uint8_t initted; /* Flag: Has an audio output device been opened? */ static uint8_t device_opened; /* Flag: Enable interpolation for resampled sounds? */ static uint8_t enable_interpolation; /* Flag: Enable headphone disconnect handling? */ static uint8_t enable_headphone_disconnect; /* Flag: Is playback globally paused (via sound_pause_all())? */ static uint8_t global_pause; /* Total number of sound channels available for playback. */ static int num_channels; /* Playback sampling rate. */ static int playback_rate; /*----------------------------------*/ /* Data for each channel. We number channels starting with 1, so entry 0 * in this array is unused, but the waste of a mere 16-24 bytes is a small * price to pay for improved code clarity. */ typedef struct Channel Channel; struct Channel { /* Channel ID for use with sys_sound_*() functions. */ int id; /* Is the channel reserved (cannot be dynamically allocated)? */ uint8_t reserved; /* Is the channel currently in use (playing something)? */ uint8_t in_use; /* Is the channel currently paused (whether due to a global pause or * a channel-specific pause)? */ uint8_t paused; /* Has this specific channel been paused with sound_pause()? */ uint8_t channel_pause; /* Sound object in use by this channel. */ Sound *sound; /* Decoder instance for this channel. */ SoundDecodeHandle *decoder; /* Filter instance for this channel, or NULL if no filter is active. */ SoundFilterHandle *filter; /* Locking flags used for changing the active filter. These flags and * the associated code implement Peterson's mutual exclusion algorithm * to protect updates to the "filter" field, required to ensure that * the main thread does not free a filter being used by a separate * decoding thread. We implement locking ourselves rather than using * system synchronization primitives because the system primitives on * certain platforms (yes, Apple, I'm looking at you) are too slow to * be usable at the potentially high frequency required here, and in * normal use there will almost never be any lock contention. */ uint8_t filter_lock_main, filter_lock_decode; uint8_t filter_lock_turn_is_main; }; static Channel *channels; /* Mutex for allocating channels for playback. */ static int allocate_channel_mutex; /*-----------------------------------------------------------------------*/ /* Local routine declarations. */ /** * sound_decode_callback: Callback function for providing decoded PCM data * to the system sound interface. Decodes the next "pcm_len" samples of * audio data to S16LE PCM and stores them in "pcm_buffer". If at least * one sample but less than "pcm_len" samples are available, the remaining * portion of the buffer is cleared to zero. * * The output buffer is guaranteed to have enough room for "pcm_len" stereo * samples, i.e. 4*pcm_len bytes. * * [Parameters] * handle: Audio data handle (opaque pointer). * pcm_buffer: Output buffer for PCM samples. * pcm_len: Number of samples to retrieve. * [Return value] * True on success, false on end of stream or error. */ static int sound_decode_callback(void *handle, void *pcm_buffer, int pcm_len); /** * autodetect_format: Attempt to determine the format of the given audio data. * * [Parameters] * data: Buffer containing the audio data (or an initial portion thereof). * datalen: Length of data, in bytes. * [Return value] * Audio data format (SOUND_FORMAT_*, nonzero), or zero if unknown. */ static SoundFormat autodetect_format(const uint8_t *data, int datalen); /** * find_audio_params: Look up the audio parameters for a Sound object by * analyzing the audio data. Does nothing if the parameters have already * been successfully looked up. * * [Parameters] * sound: Sound object. * [Return value] * True if parameters were successfully looked up (or already known), * false on error. */ static int find_audio_params(Sound *sound); /** * open_decoder: Open a decoder for the given Sound object. * * [Parameters] * sound: Sound object for which to open a decoder. * loop: True to loop, false to play once and stop. * [Return value] * Decoder handle, or NULL on error. */ static SoundDecodeHandle *open_decoder(Sound *sound, int loop); /** * allocate_channel: Allocate an unused channel, with thread safety. * * [Parameters] * reserve: True to mark the new channel as reserved, false to mark it * as in-use (dynamically allocated for playback). * [Return value] * Allocated channel, or 0 on failure. */ static int allocate_channel(int reserve); /** * lock_filter, unlock_filter: Lock or unlock the given channel for filter * operations. * * A memory barrier is executed before returning from each of these functions. * * [Parameters] * channel: Pointer to channel to lock or unlock. * from_main: 1 if called from the main interface functions; 0 if called * from the decode callback. */ static void lock_filter(Channel *channel, const int from_main); static void unlock_filter(Channel *channel, const int from_main); /** * reset_channel: Reset the given channel and free associated resources. * * [Parameters] * channel: Channel to reset. */ static void reset_channel(int channel __DEBUG_PARAMS); /** * free_sound: Free resources associated with a Sound object. The object * is assumed to not be in use by any channels. * * [Parameters] * sound: Sound object to free. */ static void free_sound(Sound *sound __DEBUG_PARAMS); /*************************************************************************/ /******************** Interface: Global sound control ********************/ /*************************************************************************/ void sound_init(void) { if (initted) { return; } enable_interpolation = 1; enable_headphone_disconnect = 0; /* sys_sound_init() and other possibly-failing calls are deferred to * sound_open_device(). */ initted = 1; } /*-----------------------------------------------------------------------*/ int sound_open_device(const char *device_name, int num_channels_) { if (device_opened) { return 1; } if (UNLIKELY(num_channels_ <= 0)) { DLOG("Invalid parameters: %p[%s] %d", device_name, device_name ? device_name : "", num_channels_); goto error_return; } if (!device_name) { device_name = ""; } /* Set up local data structures. */ num_channels = num_channels_; channels = mem_alloc(sizeof(*channels) * (num_channels+1), 0, MEM_ALLOC_CLEAR); if (UNLIKELY(!channels)) { DLOG("No memory for channel array (%d channels)", num_channels); goto error_return; } for (int i = 1; i <= num_channels; i++) { channels[i].id = i; } allocate_channel_mutex = mutex_create(MUTEX_SIMPLE, MUTEX_UNLOCKED); if (UNLIKELY(!allocate_channel_mutex)) { goto error_free_channels; } /* Open the requested output device. */ if (UNLIKELY(!sys_sound_init(device_name))) { goto error_destroy_allocate_channel_mutex; } const int sys_playback_rate = sys_sound_playback_rate(); if (UNLIKELY(sys_playback_rate <= 0)) { DLOG("sys_sound_playback_rate() returned invalid value %d", sys_playback_rate); goto error_close_device; } playback_rate = sys_playback_rate; /* Set up the software mixer. */ if (!sound_mixer_init(num_channels, playback_rate)) { DLOG("Mixer initialization failed"); goto error_close_device; } sys_sound_enable_headphone_disconnect_check(enable_headphone_disconnect); enable_interpolation = 1; device_opened = 1; return 1; error_close_device: sys_sound_cleanup(); error_destroy_allocate_channel_mutex: mutex_destroy(allocate_channel_mutex); error_free_channels: mem_free(channels); error_return: return 0; } /*-----------------------------------------------------------------------*/ void sound_set_interpolate(int enable) { if (!device_opened) { return; } enable_interpolation = (enable != 0); } /*-----------------------------------------------------------------------*/ extern float sound_get_latency(void) { if (!device_opened) { return 0; } return sys_sound_set_latency(0); } /*-----------------------------------------------------------------------*/ float sound_set_latency(float latency) { if (!device_opened) { return 0; } if (UNLIKELY(latency <= 0)) { DLOG("Invalid parameters: %g", latency); return sound_get_latency(); } return sys_sound_set_latency(latency); } /*-----------------------------------------------------------------------*/ int sound_check_format(enum SoundFormat format) { if (!device_opened) { return 0; } return sound_decode_has_handler(format); } /*-----------------------------------------------------------------------*/ void sound_set_global_volume(float volume) { if (!device_opened) { return; } if (UNLIKELY(!(volume >= 0 && volume <= 15))) { DLOG("Invalid volume: %g", volume); return; } sound_mixer_set_base_volume(volume); } /*-----------------------------------------------------------------------*/ void sound_update(void) { if (!device_opened) { return; } for (int channel = 1; channel <= num_channels; channel++) { if (channels[channel].in_use && !channels[channel].paused && !sound_mixer_status(channels[channel].id)) { reset_channel(channel __LOCAL_DEBUG_ARGS); } } } /*-----------------------------------------------------------------------*/ void sound_pause_all(void) { if (!device_opened) { return; } for (int channel = 1; channel <= num_channels; channel++) { if (channels[channel].in_use) { channels[channel].paused = 1; sound_mixer_stop(channels[channel].id); } } global_pause = 1; } /*-----------------------------------------------------------------------*/ void sound_resume_all(void) { if (!device_opened) { return; } global_pause = 0; for (int channel = 1; channel <= num_channels; channel++) { if (channels[channel].in_use && channels[channel].paused && !channels[channel].channel_pause) { sound_mixer_start(channels[channel].id); channels[channel].paused = 0; } } } /*-----------------------------------------------------------------------*/ void sound_enable_headphone_disconnect_check(void) { if (!device_opened) { return; } enable_headphone_disconnect = 1; sys_sound_enable_headphone_disconnect_check(1); } /*-----------------------------------------------------------------------*/ int sound_check_headphone_disconnect(void) { if (!device_opened) { return 0; } return sys_sound_check_headphone_disconnect(); } /*-----------------------------------------------------------------------*/ void sound_acknowledge_headphone_disconnect(void) { if (!device_opened) { return; } sys_sound_acknowledge_headphone_disconnect(); } /*-----------------------------------------------------------------------*/ void sound_cleanup(void) { if (!initted) { return; } if (device_opened) { for (int channel = 1; channel <= num_channels; channel++) { if (channels[channel].in_use) { reset_channel(channel __LOCAL_DEBUG_ARGS); } } sys_sound_cleanup(); sound_mixer_cleanup(); mutex_destroy(allocate_channel_mutex); allocate_channel_mutex = 0; mem_free(channels); channels = NULL; num_channels = 0; device_opened = 0; } initted = 0; } /*************************************************************************/ /****************** Interface: Sound object management *******************/ /*************************************************************************/ Sound *sound_create(void *data, int datalen, SoundFormat format, int reuse __DEBUG_PARAMS) { if (UNLIKELY(data == NULL) || UNLIKELY(datalen == 0)) { DLOG("Invalid parameters: %p %d 0x%X", data, datalen, format); goto error_return; } if (format == SOUND_FORMAT_AUTODETECT) { if (!(format = autodetect_format(data, datalen))) { goto error_return; } } Sound *sound = debug_mem_alloc(sizeof(*sound), 0, 0, file, line, MEM_INFO_SOUND); if (!sound) { DLOG("Out of memory for sound object"); goto error_return; } if (reuse) { sound->data = data; mem_debug_set_info(data, MEM_INFO_SOUND); } else { sound->data = debug_mem_alloc(datalen, 0, 0, file, line, MEM_INFO_SOUND); if (!sound->data) { DLOG("Out of memory for sound data (%u bytes)", datalen); debug_mem_free(sound, file, line); goto error_return; } memcpy(sound->data, data, datalen); } sound->usage_counter = 0; sound->free_on_stop = 0; sound->is_file = 0; sound->format = format; sound->datalen = datalen; sound->loopstart = -1; sound->looplen = -1; sound->have_audio_params = 0; return sound; error_return: if (reuse) { debug_mem_free(data, file, line); } return NULL; } /*-----------------------------------------------------------------------*/ Sound *sound_create_stream(SysFile *fh, int64_t dataofs, int datalen, SoundFormat format __DEBUG_PARAMS) { if (UNLIKELY(fh == NULL) || UNLIKELY(datalen == 0)) { DLOG("Invalid parameters: %p %lld %d 0x%X", fh, (long long)dataofs, datalen, format); return 0; } if (format == SOUND_FORMAT_AUTODETECT) { uint8_t readbuf[2048]; sys_file_seek(fh, dataofs, FILE_SEEK_SET); int toread = ubound(datalen, (int)sizeof(readbuf)); int nread = sys_file_read(fh, readbuf, toread); if (nread < 0) { DLOG("sys_file_read(%p, %d) failed: %s", fh, toread, sys_last_errstr()); return NULL; } else if (nread != toread) { DLOG("sys_file_read(%p, %d) failed: Premature EOF", fh, toread); return NULL; } if (!(format = autodetect_format(readbuf, nread))) { return NULL; } } Sound *sound = debug_mem_alloc(sizeof(*sound), 0, 0, file, line, MEM_INFO_SOUND); if (!sound) { DLOG("Out of memory for sound object"); return NULL; } sound->usage_counter = 0; sound->free_on_stop = 0; sound->is_file = 1; sound->format = format; sound->fh = fh; sound->dataofs = dataofs; sound->datalen = datalen; sound->loopstart = -1; sound->looplen = -1; sound->have_audio_params = 0; return sound; } /*-----------------------------------------------------------------------*/ int sound_is_stereo(Sound *sound) { if (UNLIKELY(!sound)) { DLOG("sound == NULL"); return 0; } if (!find_audio_params(sound)) { return 0; } return sound->stereo; } /*-----------------------------------------------------------------------*/ int sound_native_freq(Sound *sound) { if (UNLIKELY(!sound)) { DLOG("sound == NULL"); return 0; } if (!find_audio_params(sound)) { return 0; } return sound->native_freq; } /*-----------------------------------------------------------------------*/ void sound_set_loop(Sound *sound, int start, int len) { if (UNLIKELY(!sound) || UNLIKELY(start < 0) || UNLIKELY(len < 0)) { DLOG("Invalid parameters: %p %d %d", sound, start, len); return; } sound->loopstart = start; sound->looplen = len; } /*-----------------------------------------------------------------------*/ void sound_destroy(Sound *sound __DEBUG_PARAMS) { if (sound) { if (sound->usage_counter > 0) { sound->free_on_stop = 1; } else { free_sound(sound __DEBUG_ARGS); } } } /*************************************************************************/ /******************* Interface: Sound channel control ********************/ /*************************************************************************/ int sound_reserve_channel(void) { if (!device_opened) { return 0; } const int channel = allocate_channel(1); if (!channel) { DLOG("No channels available for reservation"); } return channel; } /*-----------------------------------------------------------------------*/ void sound_free_channel(int channel) { if (!device_opened) { return; } if (UNLIKELY(channel < 0 || channel > num_channels)) { DLOG("Invalid parameter: %d", channel); return; } if (channel) { if (UNLIKELY(!channels[channel].reserved)) { DLOG("Channel %d was not reserved", channel); return; } channels[channel].reserved = 0; } } /*-----------------------------------------------------------------------*/ int sound_play(Sound *sound, int channel, float volume, float pan, int loop __DEBUG_PARAMS) { if (!device_opened) { return 0; } if (UNLIKELY(sound == NULL) || UNLIKELY(channel < 0) || UNLIKELY(channel > num_channels) || UNLIKELY(volume < 0) || UNLIKELY(pan < -1 || pan > 1)) { DLOG("Invalid parameters: %p %d %g %g %d", sound, channel, volume, pan, loop); return 0; } SoundDecodeHandle *decoder = open_decoder(sound, loop); if (!decoder) { DLOG("Failed to get a decode handle"); return 0; } channel = sound_play_decoder(decoder, channel, volume, pan __DEBUG_ARGS); if (channel != 0) { channels[channel].sound = sound; sound->usage_counter++; } else { sound_decode_close(decoder); } return channel; } /*-----------------------------------------------------------------------*/ int sound_play_decoder(struct SoundDecodeHandle *decoder, int channel, float volume, float pan __DEBUG_PARAMS) { if (!device_opened) { return 0; } if (UNLIKELY(decoder == NULL) || UNLIKELY(channel < 0) || UNLIKELY(channel > num_channels) || UNLIKELY(volume < 0) || UNLIKELY(pan < -1 || pan > 1)) { DLOG("Invalid parameters: %p %d %g %g", decoder, channel, volume, pan); return 0; } if (channel != 0) { mutex_lock(allocate_channel_mutex); if (!channels[channel].reserved) { DLOG("Channel %d has not been reserved", channel); mutex_unlock(allocate_channel_mutex); return 0; } if (channels[channel].in_use) { reset_channel(channel __DEBUG_ARGS); } channels[channel].in_use = 1; mutex_unlock(allocate_channel_mutex); } else { channel = allocate_channel(0); if (channel == 0) { DLOG("Failed to allocate a sound channel"); return 0; } } sound_decode_set_output_freq(decoder, playback_rate); sound_mixer_setvol(channels[channel].id, volume); sound_mixer_setpan(channels[channel].id, pan); channels[channel].paused = 0; channels[channel].channel_pause = 0; channels[channel].decoder = decoder; channels[channel].filter = NULL; /* These must come last -- the low-level playback routines may call * sound_decode_callback() at any time once sound_mixer_setdata() has * been called. */ ASSERT(sound_mixer_setdata(channels[channel].id, sound_decode_callback, &channels[channel], sound_decode_is_stereo(decoder)), return 0); sound_mixer_start(channels[channel].id); return channel; } /*-----------------------------------------------------------------------*/ void sound_pause(int channel) { if (!device_opened) { return; } if (channel < 1 || channel > num_channels) { DLOG("Invalid parameters: %d", channel); return; } if (!channels[channel].in_use) { return; } sound_mixer_stop(channels[channel].id); channels[channel].paused = 1; channels[channel].channel_pause = 1; } /*-----------------------------------------------------------------------*/ void sound_resume(int channel) { if (!device_opened) { return; } if (channel < 1 || channel > num_channels) { DLOG("Invalid parameters: %d", channel); return; } if (!channels[channel].in_use) { return; } channels[channel].channel_pause = 0; if (channels[channel].paused && !global_pause) { sound_mixer_start(channels[channel].id); channels[channel].paused = 0; } } /*-----------------------------------------------------------------------*/ void sound_cut(int channel __DEBUG_PARAMS) { if (!device_opened) { return; } if (channel < 1 || channel > num_channels) { DLOG("Invalid parameters: %d", channel); return; } if (!channels[channel].in_use) { return; } reset_channel(channel __DEBUG_ARGS); } /*-----------------------------------------------------------------------*/ void sound_fade(int channel, float time __DEBUG_PARAMS) { if (!device_opened) { return; } if (channel < 1 || channel > num_channels) { DLOG("Invalid parameters: %d %g", channel, time); return; } if (!channels[channel].in_use) { return; } if (time == 0) { reset_channel(channel __DEBUG_ARGS); } else { sound_mixer_setfade(channels[channel].id, 0.0f, time, 1); } } /*-----------------------------------------------------------------------*/ void sound_adjust_volume(int channel, float new_volume, float time) { if (!device_opened) { return; } if (channel < 1 || channel > num_channels || new_volume < 0 || time < 0) { DLOG("Invalid parameters: %d %g %g", channel, new_volume, time); return; } if (!channels[channel].in_use) { return; } if (time == 0) { sound_mixer_setvol(channels[channel].id, new_volume); } else { sound_mixer_setfade(channels[channel].id, new_volume, time, 0); } } /*-----------------------------------------------------------------------*/ void sound_set_pan(int channel, float new_pan) { if (!device_opened) { return; } if (channel < 1 || channel > num_channels || new_pan < -1 || new_pan > 1) { DLOG("Invalid parameters: %d %g", channel, new_pan); return; } if (!channels[channel].in_use) { return; } sound_mixer_setpan(channels[channel].id, new_pan); } /*-----------------------------------------------------------------------*/ void sound_set_playback_rate(int channel, float new_rate) { if (!device_opened) { return; } if (channel < 1 || channel > num_channels || new_rate < 0) { DLOG("Invalid parameters: %d %g", channel, new_rate); return; } if (!channels[channel].in_use) { return; } const int native_freq = sound_decode_native_freq(channels[channel].decoder); sound_decode_set_decode_freq(channels[channel].decoder, iroundf(new_rate * native_freq)); } /*-----------------------------------------------------------------------*/ void sound_set_flange(int channel, int enable, float period, float depth) { if (!device_opened) { return; } if (channel < 1 || channel > num_channels || (enable && (period <= 0 || depth < 0))) { DLOG("Invalid parameters: %d %d %g %g", channel, enable, period, depth); return; } if (!channels[channel].in_use) { return; } lock_filter(&channels[channel], 1); if (channels[channel].filter) { sound_filter_close(channels[channel].filter); } if (enable) { channels[channel].filter = sound_filter_open_flange( sound_decode_is_stereo(channels[channel].decoder), playback_rate, period, depth); if (!channels[channel].filter) { DLOG("Failed to create flange filter"); } } else { // !enable channels[channel].filter = NULL; } unlock_filter(&channels[channel], 1); } /*-----------------------------------------------------------------------*/ void sound_set_filter(int channel, SoundFilterHandle *filter) { if (!device_opened) { return; } if (channel < 1 || channel > num_channels) { DLOG("Invalid parameters: %d %p", channel, filter); sound_filter_close(filter); return; } if (!channels[channel].in_use) { sound_filter_close(filter); return; } lock_filter(&channels[channel], 1); if (channels[channel].filter) { sound_filter_close(channels[channel].filter); } channels[channel].filter = filter; unlock_filter(&channels[channel], 1); } /*-----------------------------------------------------------------------*/ void sound_enable_loop(int channel, int loop) { if (!device_opened) { return; } if (channel < 1 || channel > num_channels) { DLOG("Invalid parameters: %d %d", channel, loop); return; } if (!channels[channel].in_use) { return; } sound_decode_enable_loop(channels[channel].decoder, loop); } /*-----------------------------------------------------------------------*/ int sound_is_playing(int channel) { if (!device_opened) { return 0; } if (channel < 1 || channel > num_channels) { DLOG("Invalid parameters: %d", channel); return 0; } return channels[channel].in_use; } /*-----------------------------------------------------------------------*/ float sound_playback_pos(int channel) { if (!device_opened) { return 0; } if (channel < 1 || channel > num_channels) { DLOG("Invalid parameters: %d", channel); return 0; } if (!channels[channel].in_use) { return 0; } return sound_decode_get_position(channels[channel].decoder); } /*************************************************************************/ /**************************** Local routines *****************************/ /*************************************************************************/ static int sound_decode_callback(void *handle, void *pcm_buffer, int pcm_len) { Channel *channel = (Channel *)handle; if (!sound_decode_get_pcm(channel->decoder, pcm_buffer, pcm_len)) { return 0; } /* We deliberately read channel->filter before locking to avoid the * lock sequence if no filter is active. If we read a NULL, then * either no filter was ever active or a previous filter has already * been cleared, so we'll end up doing the right thing without locking. */ if (channel->filter) { lock_filter(channel, 0); int result = sound_filter_filter(channel->filter, pcm_buffer, pcm_len); unlock_filter(channel, 0); if (!result) { return 0; } } return 1; } /*-----------------------------------------------------------------------*/ static SoundFormat autodetect_format(const uint8_t *data, int datalen) { if (datalen >= 12 && memcmp(data, "RIFF", 4) == 0 && memcmp(data+8, "WAVE", 4) == 0) { return SOUND_FORMAT_WAV; } else if (datalen >= 2 && data[0] == 0xFF && (data[1] & 0xE0) == 0xE0) { return SOUND_FORMAT_MP3; } else if (datalen >= 4 && memcmp(data, "OggS", 4) == 0) { return SOUND_FORMAT_OGG; } else { DLOG("Unknown audio data format"); return (SoundFormat)0; } } /*-----------------------------------------------------------------------*/ static int find_audio_params(Sound *sound) { PRECOND(sound != NULL, return 0); if (!sound->have_audio_params) { SoundDecodeHandle *decoder = open_decoder(sound, 0); if (!decoder) { DLOG("Failed to get a decode handle"); return 0; } sound->have_audio_params = 1; sound->stereo = sound_decode_is_stereo(decoder); sound->native_freq = sound_decode_native_freq(decoder); sound_decode_close(decoder); } return 1; } /*-----------------------------------------------------------------------*/ static SoundDecodeHandle *open_decoder(Sound *sound, int loop) { SoundDecodeHandle *decoder; if (sound->is_file) { decoder = sound_decode_open_from_file( sound->format, sound->fh, sound->dataofs, sound->datalen, loop, enable_interpolation); } else { decoder = sound_decode_open( sound->format, sound->data, sound->datalen, loop, enable_interpolation); } if (sound->looplen >= 0) { sound_decode_set_loop_points(decoder, sound->loopstart, sound->looplen); } return decoder; } /*-----------------------------------------------------------------------*/ static int allocate_channel(int reserve) { mutex_lock(allocate_channel_mutex); int channel = 0; for (int i = 1; i <= num_channels; i++) { if (!channels[i].reserved && !channels[i].in_use) { channel = i; break; } } if (channel) { if (reserve) { channels[channel].reserved = 1; } else { channels[channel].in_use = 1; } } mutex_unlock(allocate_channel_mutex); return channel; } /*-----------------------------------------------------------------------*/ static void lock_filter(Channel *channel, const int from_main) { if (from_main) { channel->filter_lock_main = 1; BARRIER(); channel->filter_lock_turn_is_main = 0; } else { channel->filter_lock_decode = 1; BARRIER(); channel->filter_lock_turn_is_main = 1; } BARRIER(); while ((from_main ? channel->filter_lock_decode : channel->filter_lock_main) /* Note that this second condition only plays a role when two * threads try to take the lock at the same time: * (main)--- filter_lock_main = 1; * filter_lock_decode = 1; ---------(decode) * (main)--- filter_lock_turn_is_main = 0; * filter_lock_turn_is_main = 1; ---(decode) * After the above sequence, the main thread (in this case) will * detect filter_lock_decode set but will win the lock anyway * because of this filter_lock_turn_is_main check. */ && channel->filter_lock_turn_is_main != from_main) { #ifdef SIL_INCLUDE_TESTS extern uint8_t sound_core_blocked_on_filter_lock; sound_core_blocked_on_filter_lock = 1; #endif thread_yield(); } } /*-----------------------------------------------------------------------*/ static void unlock_filter(Channel *channel, const int from_main) { if (from_main) { channel->filter_lock_main = 0; } else { channel->filter_lock_decode = 0; } BARRIER(); } /*-----------------------------------------------------------------------*/ static void reset_channel(int channel __DEBUG_PARAMS) { PRECOND(channel >= 1 && channel <= num_channels, return); PRECOND(channels[channel].in_use != 0, return); PRECOND(channels[channel].decoder != NULL, return); sound_mixer_reset(channels[channel].id); if (channels[channel].filter) { /* Playback is guaranteed to have stopped, so we don't need to lock. */ sound_filter_close(channels[channel].filter); } sound_decode_close(channels[channel].decoder); Sound *sound = channels[channel].sound; if (sound) { ASSERT(sound->usage_counter > 0, sound->usage_counter = 1); sound->usage_counter--; if (sound->usage_counter == 0 && sound->free_on_stop) { free_sound(sound __DEBUG_ARGS); } } channels[channel].decoder = NULL; channels[channel].filter = NULL; channels[channel].sound = NULL; channels[channel].paused = 0; channels[channel].channel_pause = 0; /* Clearing in_use will let allocate_channel() grab the channel (if * it's not reserved), so make sure this is the last store. */ BARRIER(); channels[channel].in_use = 0; } /*-----------------------------------------------------------------------*/ static void free_sound(Sound *sound __DEBUG_PARAMS) { PRECOND(sound != NULL, return); if (sound->is_file) { sys_file_close(sound->fh); } else { debug_mem_free(sound->data, file, line); } debug_mem_free(sound, file, line); } /*************************************************************************/ /*************************************************************************/