#include "../buffer/audio.h" #include "mixer.h" #include "audio.h" // Should an initial read from within start() be marked as silence or not. #define MIXER_WANT_INITIAL_SILENCE 1 // Number of silent frames to append before signaling MIXER_EMPTY. #define MIXER_TRAILING_SILENCE 12 static s32 data_callback(void *userdata, u8 *data, s32 frame_count, bool *silence) { struct camu_mixer *mixer = (struct camu_mixer *)userdata; ptrdiff_t req = (ptrdiff_t)camu_audio_format_samples_to_bytes(&mixer->fmt.req, (size_t)frame_count); #ifdef CAMU_MIXER_THREADED_START_STOP if (UNLIKELY(atomic_load(bool)(&mixer->paused, AL_ATOMIC_RELAXED))) { al_memset(data, 0, req); *silence = MIXER_WANT_INITIAL_SILENCE; } else { #endif #ifdef CAMU_MIXER_THREADED if (atomic_load(bool)(&mixer->queued, AL_ATOMIC_RELAXED)) { camu_mixer_run_queue(mixer); } #endif if (UNLIKELY(mixer->empty_after > 0)) { if (--mixer->empty_after == 0) { mixer->callback(mixer->userdata, CAMU_MIXER_EMPTY); } *silence = false; } else { *silence = true; } struct camu_audio_buffer *buf; // Read from all current buffers on each callback to // emulate the behavior of an actual mixer. al_array_foreach(mixer->buffers, i, buf) { if (i < mixer->buffers.count - 1) { camu_audio_buffer_read(buf, data, req); } } struct camu_audio_buffer *probed_for_swap = NULL; for (;;) { if (!mixer->buffers.count) { al_memset(data, 0, req); } else { buf = al_array_last(mixer->buffers); *silence = false; ptrdiff_t signal; if ((signal = camu_audio_buffer_read(buf, data, req)) < req) { // In the case of a swap, two successive buffers cannot share // the same pointer. if (buf == probed_for_swap) { al_memset(data, 0, req); break; } #ifdef CAMU_MIXER_THREADED if (atomic_load(bool)(&mixer->queued, AL_ATOMIC_RELAXED)) { camu_mixer_run_queue(mixer); } #endif data += signal; req = req - signal; probed_for_swap = buf; continue; } } break; } #ifdef CAMU_MIXER_THREADED_START_STOP } #endif return frame_count; } bool camu_mixer_init(struct camu_mixer *mixer, struct camu_audio *audio) { mixer->audio = audio; mixer->audio->data_callback = data_callback; mixer->audio->userdata = mixer; mixer->audio->init(mixer->audio, &al_str_c("cmv")); mixer->volume = 1.f; atomic_store(bool)(&mixer->paused, true, AL_ATOMIC_RELAXED); mixer->empty_after = 0; al_array_init(mixer->buffers); #ifdef CAMU_MIXER_THREADED al_array_init(mixer->add_queue); al_array_init(mixer->rem_queue); atomic_store(bool)(&mixer->queued, false, AL_ATOMIC_RELAXED); nn_mutex_init(&mixer->mutex); #endif return true; } void camu_mixer_pick_format(struct camu_mixer *mixer, struct camu_resampler_format *fmt) { mixer->audio->pick_format(mixer->audio, fmt); } void camu_mixer_set_volume(struct camu_mixer *mixer, f32 volume) { #ifdef CAMU_MIXER_THREADED nn_mutex_lock(&mixer->mutex); #endif mixer->volume = volume; struct camu_audio_buffer *buf; al_array_foreach(mixer->buffers, i, buf) { camu_audio_buffer_set_volume(buf, mixer->volume); } #ifdef CAMU_MIXER_THREADED nn_mutex_unlock(&mixer->mutex); #endif } f32 camu_mixer_offset_volume(struct camu_mixer *mixer, f32 amount) { #ifdef CAMU_MIXER_THREADED nn_mutex_lock(&mixer->mutex); #endif if (FLT_MAX - mixer->volume < amount) { mixer->volume = FLT_MAX; } else { mixer->volume = MAX(0.f, mixer->volume + amount); } f32 volume = mixer->volume; struct camu_audio_buffer *buf; al_array_foreach(mixer->buffers, i, buf) { camu_audio_buffer_set_volume(buf, mixer->volume); } #ifdef CAMU_MIXER_THREADED nn_mutex_unlock(&mixer->mutex); #endif return volume; } f64 camu_mixer_get_latency(struct camu_mixer *mixer) { return mixer->audio->get_latency(mixer->audio) / 1000000.0; } static void add_buffer_internal(struct camu_mixer *mixer, struct camu_audio_buffer *buf) { struct camu_audio_buffer *active; al_array_foreach(mixer->buffers, i, active) { al_assert(active != buf); } camu_audio_buffer_set_volume(buf, mixer->volume); #ifdef CAMU_MIXER_THREADED atomic_store(bool)(&buf->ref, true, AL_ATOMIC_RELAXED); #endif al_array_push(mixer->buffers, buf); } static void remove_buffer_internal(struct camu_mixer *mixer, struct camu_audio_buffer *buf) { struct camu_audio_buffer *added; al_array_foreach(mixer->buffers, i, added) { if (added == buf) { #ifdef CAMU_MIXER_THREADED atomic_store(bool)(&buf->ref, false, AL_ATOMIC_RELAXED); #endif al_array_remove_at(mixer->buffers, i); break; } } } #ifdef CAMU_MIXER_THREADED static void run_queue_internal(struct camu_mixer *mixer) { struct camu_audio_buffer *buf; if (mixer->rem_queue.count > 0) { al_array_foreach(mixer->rem_queue, i, buf) { remove_buffer_internal(mixer, buf); } mixer->rem_queue.count = 0; } if (mixer->add_queue.count > 0) { al_array_reserve(mixer->buffers, mixer->buffers.count + mixer->add_queue.count); al_array_foreach(mixer->add_queue, i, buf) { add_buffer_internal(mixer, buf); } mixer->add_queue.count = 0; } atomic_store(bool)(&mixer->queued, false, AL_ATOMIC_RELAXED); if (!mixer->buffers.count) { mixer->empty_after = MIXER_TRAILING_SILENCE; } else { mixer->empty_after = 0; } } #endif void camu_mixer_add_buffer(struct camu_mixer *mixer, struct camu_audio_buffer *buf) { #ifdef CAMU_MIXER_THREADED nn_mutex_lock(&mixer->mutex); struct camu_audio_buffer *queued; al_array_foreach(mixer->add_queue, i, queued) { if (queued == buf) { nn_mutex_unlock(&mixer->mutex); al_assert_and_return(); } } al_array_foreach_rev(mixer->rem_queue, i, queued) { if (queued == buf) { al_array_remove_at(mixer->rem_queue, i); bool queue_empty = mixer->add_queue.count + mixer->rem_queue.count == 0; if (queue_empty) { atomic_store(bool)(&mixer->queued, false, AL_ATOMIC_RELAXED); } nn_mutex_unlock(&mixer->mutex); return; } } al_array_push(mixer->add_queue, buf); atomic_store(bool)(&mixer->queued, true, AL_ATOMIC_RELAXED); nn_mutex_unlock(&mixer->mutex); #else add_buffer_internal(mixer, buf); mixer->empty_after = 0; #endif } void camu_mixer_remove_buffer(struct camu_mixer *mixer, struct camu_audio_buffer *buf) { #ifdef CAMU_MIXER_THREADED nn_mutex_lock(&mixer->mutex); struct camu_audio_buffer *queued; al_array_foreach(mixer->rem_queue, i, queued) { if (queued == buf) { nn_mutex_unlock(&mixer->mutex); return; } } al_array_foreach_rev(mixer->add_queue, i, queued) { if (queued == buf) { al_array_remove_at(mixer->add_queue, i); bool queue_empty = mixer->add_queue.count + mixer->rem_queue.count == 0; if (queue_empty) { atomic_store(bool)(&mixer->queued, false, AL_ATOMIC_RELAXED); } nn_mutex_unlock(&mixer->mutex); return; } } al_array_push(mixer->rem_queue, buf); atomic_store(bool)(&mixer->queued, true, AL_ATOMIC_RELAXED); if (atomic_load(bool)(&mixer->paused, AL_ATOMIC_RELAXED)) { run_queue_internal(mixer); } nn_mutex_unlock(&mixer->mutex); #else remove_buffer_internal(mixer, buf); if (!mixer->buffers.count) { mixer->empty_after = MIXER_TRAILING_SILENCE; } #endif } #ifdef CAMU_MIXER_THREADED void camu_mixer_run_queue(struct camu_mixer *mixer) { nn_mutex_lock(&mixer->mutex); run_queue_internal(mixer); nn_mutex_unlock(&mixer->mutex); } void camu_mixer_clear(struct camu_mixer *mixer) { struct camu_audio_buffer *buf; al_array_foreach(mixer->buffers, i, buf) { atomic_store(bool)(&buf->ref, false, AL_ATOMIC_RELAXED); } } #endif void camu_mixer_pause(struct camu_mixer *mixer) { #ifdef CAMU_MIXER_THREADED_START_STOP nn_mutex_lock(&mixer->mutex); #endif if (!atomic_load(bool)(&mixer->paused, AL_ATOMIC_ACQUIRE)) { mixer->audio->stop(mixer->audio); atomic_store(bool)(&mixer->paused, true, AL_ATOMIC_RELEASE); } #ifdef CAMU_MIXER_THREADED // This requires that stop() blocks until the output actually stops. run_queue_internal(mixer); #endif #ifdef CAMU_MIXER_THREADED_START_STOP nn_mutex_unlock(&mixer->mutex); #endif } void camu_mixer_resume(struct camu_mixer *mixer) { #ifdef CAMU_MIXER_THREADED_START_STOP nn_mutex_lock(&mixer->mutex); #endif if (atomic_load(bool)(&mixer->paused, AL_ATOMIC_ACQUIRE)) { // audio->start() can internally call data_callback() or possibly start a thread which // can call data_callback() before we have a chance to set paused to false. // MIXER_THREADED_START_STOP: If paused is true in data_callback(), immediately return silence. // This will avoid a deadlock in the first case described above. mixer->audio->start(mixer->audio); atomic_store(bool)(&mixer->paused, false, AL_ATOMIC_RELEASE); } #ifdef CAMU_MIXER_THREADED_START_STOP nn_mutex_unlock(&mixer->mutex); #endif } void camu_mixer_close(struct camu_mixer *mixer) { al_array_free(mixer->buffers); #ifdef CAMU_MIXER_THREADED al_array_free(mixer->add_queue); al_array_free(mixer->rem_queue); nn_mutex_destroy(&mixer->mutex); #endif if (mixer->audio) { mixer->audio->free(&mixer->audio); } }