diff -ruN a5-orig/addons/acodec/helper.c a5/addons/acodec/helper.c --- a5-orig/addons/acodec/helper.c +++ a5/addons/acodec/helper.c @@ -4,6 +4,25 @@ #include "allegro5/internal/aintern_audio.h" #include "helper.h" +#ifdef __EMSCRIPTEN__ + +/* No threads in the browser: the stream is fed from the main thread (see kcm_stream.c). + * feed_thread only needs to be non-NULL, so that al_destroy_audio_stream() unloads the feeder. */ +void _al_acodec_start_feed_thread(ALLEGRO_AUDIO_STREAM *stream) +{ + stream->feed_thread = (ALLEGRO_THREAD *)stream; + stream->feed_thread_started = true; + _al_kcm_feed_stream_start(stream); +} + +void _al_acodec_stop_feed_thread(ALLEGRO_AUDIO_STREAM *stream) +{ + _al_kcm_feed_stream_stop(stream); + stream->feed_thread = NULL; +} + +#else + void _al_acodec_start_feed_thread(ALLEGRO_AUDIO_STREAM *stream) { stream->feed_thread = al_create_thread(_al_kcm_feed_stream, stream); @@ -37,3 +56,5 @@ stream->feed_thread = NULL; } + +#endif diff -ruN a5-orig/addons/audio/allegro5/internal/aintern_audio.h a5/addons/audio/allegro5/internal/aintern_audio.h --- a5-orig/addons/audio/allegro5/internal/aintern_audio.h +++ a5/addons/audio/allegro5/internal/aintern_audio.h @@ -371,6 +371,12 @@ /* Supposedly internal */ ALLEGRO_KCM_AUDIO_FUNC(void*, _al_kcm_feed_stream, (ALLEGRO_THREAD *self, void *vstream)); +#ifdef __EMSCRIPTEN__ +/* Browser build: no feeder threads; see kcm_stream.c */ +ALLEGRO_KCM_AUDIO_FUNC(void, _al_kcm_feed_stream_start, (ALLEGRO_AUDIO_STREAM *stream)); +ALLEGRO_KCM_AUDIO_FUNC(void, _al_kcm_feed_stream_stop, (ALLEGRO_AUDIO_STREAM *stream)); +ALLEGRO_KCM_AUDIO_FUNC(void, _al_kcm_feed_pump, (void)); +#endif /* Helper to emit an event that the stream has got a buffer ready to be refilled. */ void _al_kcm_emit_stream_events(ALLEGRO_AUDIO_STREAM *stream); diff -ruN a5-orig/addons/audio/kcm_stream.c a5/addons/audio/kcm_stream.c --- a5-orig/addons/audio/kcm_stream.c +++ a5/addons/audio/kcm_stream.c @@ -933,3 +933,135 @@ } /* vim: set sts=3 sw=3 et: */ + +#ifdef __EMSCRIPTEN__ + +/* Streams without a feeder thread (the browser build has no threads). + * + * The feeder thread of _al_kcm_feed_stream is replaced by a list of streams that is + * served from the main thread: _al_kcm_feed_stream_start() fills the buffers of a new + * stream right away, and _al_kcm_feed_pump() (called by the SDL audio callback, before + * each mixing round) refills every fragment that the mixer has used up. The decisions are + * the same as in the thread, including looping, draining and the FINISHED event. + */ +typedef struct KCM_FEEDER KCM_FEEDER; +struct KCM_FEEDER { + ALLEGRO_AUDIO_STREAM *stream; + bool finished_event_sent; + KCM_FEEDER *next; +}; + +static KCM_FEEDER *kcm_feeders = NULL; +static bool kcm_feeding = false; + +static void kcm_feeder_run(KCM_FEEDER *fd) +{ + ALLEGRO_AUDIO_STREAM *stream = fd->stream; + + while (!stream->is_draining) { + char *fragment; + unsigned long bytes; + unsigned long bytes_written; + ALLEGRO_MUTEX *stream_mutex; + + fragment = al_get_audio_stream_fragment(stream); + if (!fragment) + break; + + bytes = (stream->spl.spl_data.len) * + al_get_channel_count(stream->spl.spl_data.chan_conf) * + al_get_audio_depth_size(stream->spl.spl_data.depth); + + stream_mutex = maybe_lock_mutex(stream->spl.mutex); + bytes_written = stream->feeder(stream, fragment, bytes); + maybe_unlock_mutex(stream_mutex); + + if (stream->spl.loop == _ALLEGRO_PLAYMODE_STREAM_ONEDIR) { + /* Keep rewinding until the fragment is filled. */ + while (bytes_written < bytes && + stream->spl.loop == _ALLEGRO_PLAYMODE_STREAM_ONEDIR) { + size_t bw; + al_rewind_audio_stream(stream); + stream_mutex = maybe_lock_mutex(stream->spl.mutex); + bw = stream->feeder(stream, fragment + bytes_written, + bytes - bytes_written); + bytes_written += bw; + maybe_unlock_mutex(stream_mutex); + } + } + else if (bytes_written < bytes) { + /* Fill the rest of the fragment with silence. */ + int silence_samples = (bytes - bytes_written) / + (al_get_channel_count(stream->spl.spl_data.chan_conf) * + al_get_audio_depth_size(stream->spl.spl_data.depth)); + al_fill_silence(fragment + bytes_written, silence_samples, + stream->spl.spl_data.depth, stream->spl.spl_data.chan_conf); + } + + if (!al_set_audio_stream_fragment(stream, fragment)) { + ALLEGRO_ERROR("Error setting stream buffer.\n"); + break; + } + + if (bytes_written != bytes && + (stream->spl.loop == _ALLEGRO_PLAYMODE_STREAM_ONCE || + stream->spl.loop == _ALLEGRO_PLAYMODE_STREAM_LOOP_ONCE)) { + stream->is_draining = true; + + if (!fd->finished_event_sent) { + ALLEGRO_EVENT fin_event; + fin_event.user.type = ALLEGRO_EVENT_AUDIO_STREAM_FINISHED; + fin_event.user.timestamp = al_get_time(); + al_emit_user_event(&stream->spl.es, &fin_event, NULL); + fd->finished_event_sent = true; + } + } else { + fd->finished_event_sent = false; + } + } +} + +void _al_kcm_feed_stream_start(ALLEGRO_AUDIO_STREAM *stream) +{ + KCM_FEEDER *fd = al_calloc(1, sizeof *fd); + fd->stream = stream; + fd->next = kcm_feeders; + kcm_feeders = fd; + stream->quit_feed_thread = false; + kcm_feeder_run(fd); +} + +void _al_kcm_feed_stream_stop(ALLEGRO_AUDIO_STREAM *stream) +{ + KCM_FEEDER **link = &kcm_feeders; + ALLEGRO_EVENT fin_event; + + while (*link) { + if ((*link)->stream == stream) { + KCM_FEEDER *fd = *link; + *link = fd->next; + al_free(fd); + break; + } + link = &(*link)->next; + } + + stream->quit_feed_thread = true; + fin_event.user.type = ALLEGRO_EVENT_AUDIO_STREAM_FINISHED; + fin_event.user.timestamp = al_get_time(); + al_emit_user_event(&stream->spl.es, &fin_event, NULL); +} + +void _al_kcm_feed_pump(void) +{ + KCM_FEEDER *fd; + + if (kcm_feeding) + return; + kcm_feeding = true; + for (fd = kcm_feeders; fd; fd = fd->next) + kcm_feeder_run(fd); + kcm_feeding = false; +} + +#endif diff -ruN a5-orig/addons/audio/sdl_audio.c a5/addons/audio/sdl_audio.c --- a5-orig/addons/audio/sdl_audio.c +++ a5/addons/audio/sdl_audio.c @@ -30,6 +30,11 @@ SDL_VOICE *sv = userdata; ALLEGRO_SAMPLE_INSTANCE *instance = sv->voice->attached_stream; + +#ifdef __EMSCRIPTEN__ + /* no feeder threads in the browser: refill the streams here */ + _al_kcm_feed_pump(); +#endif ALLEGRO_SAMPLE *sample = &instance->spl_data; unsigned int frames = sv->spec.samples;