diff options
| author | 2023-11-06 11:54:08 -0500 | |
|---|---|---|
| committer | 2023-11-06 11:54:08 -0500 | |
| commit | a48a68cfb04b2020737c0adfb0e7667451a22b5c (patch) | |
| tree | a92121897f84298c3798a4f2ba56de45cd100eab /src/mixer | |
| download | camu-a48a68cfb04b2020737c0adfb0e7667451a22b5c.tar.gz camu-a48a68cfb04b2020737c0adfb0e7667451a22b5c.tar.bz2 camu-a48a68cfb04b2020737c0adfb0e7667451a22b5c.zip | |
Add camu
- Subprojects temporarily omitted
Signed-off-by: Andrew Opalach <andrew@akon.city>
Diffstat (limited to 'src/mixer')
| -rw-r--r-- | src/mixer/audio.h | 37 | ||||
| -rw-r--r-- | src/mixer/audio_miniaudio.c | 190 | ||||
| -rw-r--r-- | src/mixer/audio_miniaudio.h | 20 | ||||
| -rw-r--r-- | src/mixer/meson.build | 12 | ||||
| -rw-r--r-- | src/mixer/mixer.c | 167 | ||||
| -rw-r--r-- | src/mixer/mixer.h | 37 |
6 files changed, 463 insertions, 0 deletions
diff --git a/src/mixer/audio.h b/src/mixer/audio.h new file mode 100644 index 0000000..e460536 --- /dev/null +++ b/src/mixer/audio.h @@ -0,0 +1,37 @@ +#pragma once + +#include <al/str.h> + +#include "../codec/libav/resampler.h" + +enum { + // Not initialized. + CAMU_AUDIO_STATE_NULL = 0, + // Context created, waiting for stream. + CAMU_AUDIO_STATE_INITIALIZED, + // Stream created, and running. + CAMU_AUDIO_STATE_STARTED, + // Error occured, no data will be requested. + CAMU_AUDIO_STATE_ERROR, + // Not built. + CAMU_AUDIO_STATE_UNSUPPORTED +}; + +struct camu_audio { + u8 state; + + s32 (*data_callback)(void *, u8 *, s32); + void *userdata; + + bool (*init)(struct camu_audio *, str *); + + void (*pick_format)(struct camu_audio *, struct camu_lav_resample_fmt *); + bool (*configure_stream)(struct camu_audio *, void *); + + u64 (*get_latency)(struct camu_audio *); + + void (*start)(struct camu_audio *); + void (*stop)(struct camu_audio *); + + void (*free)(struct camu_audio **); +}; diff --git a/src/mixer/audio_miniaudio.c b/src/mixer/audio_miniaudio.c new file mode 100644 index 0000000..1388fc4 --- /dev/null +++ b/src/mixer/audio_miniaudio.c @@ -0,0 +1,190 @@ +#include <al/log.h> + +#include "audio_miniaudio.h" + +static ma_backend backends[] = { +#ifndef _WIN32 +// ma_backend_alsa, + ma_backend_pulseaudio, +#else + ma_backend_wasapi +#endif +}; + +static void *miniaudio_malloc(size_t size, void *userdata) +{ + (void)userdata; + return al_malloc(size); +} + +static void *miniaudio_realloc(void *ptr, size_t size, void *userdata) +{ + (void)userdata; + return al_realloc(ptr, size); +} + +static void miniaudio_free(void *ptr, void *userdata) +{ + (void)userdata; + al_free(ptr); +} + +static ma_allocation_callbacks alloc_callbacks = { + .pUserData = NULL, + .onMalloc = miniaudio_malloc, + .onRealloc = miniaudio_realloc, + .onFree = miniaudio_free +}; + +static void miniaudio_log_callback(void *userdata, u32 level, const char *message) +{ + (void)userdata; + if (level <= MA_LOG_LEVEL_WARNING) al_log_debug("audio_miniaudio", message); +} + +static bool audio_miniaudio_init(struct camu_audio *audio, str *name) +{ + struct camu_audio_miniaudio *ma = (struct camu_audio_miniaudio *)audio; + ma_log_init(&alloc_callbacks, &ma->log); + ma_log_register_callback(&ma->log, ma_log_callback_init(miniaudio_log_callback, NULL)); + ma->context_config = ma_context_config_init(); + ma->context_config.pLog = &ma->log; + ma->context_config.alsa.useVerboseDeviceEnumeration = true; + char *c_str = al_str_to_c_str(name); + ma->context_config.pulse.pApplicationName = c_str; + //ma->context_config.pulse.pServerName = ""; + ma->context_config.pulse.tryAutoSpawn = true;; + s32 ret = ma_context_init(backends, AL_ARRAY_SIZE(backends), &ma->context_config, &ma->context); + if (ret != MA_SUCCESS) { + al_log_error("audio_miniaudio", "Failed to initialize context (%d).", ret); + return false; + } + //if (ma_context_get_devices(&ma->context, &ma->infos, &ma->info_count, NULL, NULL) != MA_SUCCESS) { + // return false; + //} + //ma->selected_device = NULL; + //for (u32 i = 0; i < ma->info_count; i++) { + // ma_device_info *info = &ma->infos[i]; + // ma_device_info real_info; + // ma_context_get_device_info(&ma->context, ma_device_type_playback, &info->id, &real_info); + // for (u32 j = 0; j < real_info.nativeDataFormatCount; j++) { + // if (strcmp(real_info.id.alsa, "hw:CARD=D10,DEV=0") == 0) { + // ma->selected_device = info; + // break; + // } + // /* + // al_printf("%s\n", real_info.id.alsa); + // al_printf("%i\n", real_info.nativeDataFormats[j].format); + // al_printf("%i\n", real_info.nativeDataFormats[j].channels); + // al_printf("%i\n", real_info.nativeDataFormats[j].sampleRate); + // */ + // } + // if (ma->selected_device) break; + // //al_printf("\n"); + //} + ////al_printf("%i\n", ma->info_count); + return true; +} + +static void audio_miniaudio_pick_format(struct camu_audio *audio, struct camu_lav_resample_fmt *fmt) +{ + struct camu_audio_miniaudio *ma = (struct camu_audio_miniaudio *)audio; + (void)ma; + fmt->req_channel_count = 2; + fmt->req_format = AV_SAMPLE_FMT_S32; + fmt->req_sample_rate = 44100; +} + +static void data_callback(ma_device *device, void *output, const void *input, ma_uint32 frame_count) +{ + struct camu_audio_miniaudio *ma = (struct camu_audio_miniaudio *)device->pUserData; + (void)input; + ma->a.data_callback(ma->a.userdata, output, frame_count); +} + +#define DEFAULT_PERIODS 3 +#define DEFAULT_PERIOD_SIZE_IN_MILLISECONDS 100 + +static bool audio_miniaudio_configure_stream(struct camu_audio *audio, void *opaque) +{ + struct camu_audio_miniaudio *ma = (struct camu_audio_miniaudio *)audio; + struct camu_lav_resample_fmt *fmt = (struct camu_lav_resample_fmt *)opaque; + (void)fmt; + ma->config = ma_device_config_init(ma_device_type_playback); + //ma->config.playback.pDeviceID = &ma->selected_device->id; + ma->config.performanceProfile = ma_performance_profile_conservative; + ma->config.periods = DEFAULT_PERIODS; + ma->config.periodSizeInMilliseconds = DEFAULT_PERIOD_SIZE_IN_MILLISECONDS; + ma->config.playback.channels = 2; + ma->config.playback.format = ma_format_s32; + ma->config.sampleRate = 44100; + ma->config.dataCallback = data_callback; + ma->config.pUserData = ma; + ma->config.noPreSilencedOutputBuffer = true; + s32 ret = ma_device_init(&ma->context, &ma->config, &ma->device); + if (ret != MA_SUCCESS) { + al_log_error("audio_miniaudio", "Failed to initialize device (%d).", ret); + return false; + } + const char *format_name = av_get_sample_fmt_name(AV_SAMPLE_FMT_S32); + al_log_info("audio_miniaudio", "Output: %s (%dch) %dHz.", format_name, 2, 44100); + return true; +} + +static u64 audio_miniaudio_get_latency(struct camu_audio *audio) +{ + struct camu_audio_miniaudio *ma = (struct camu_audio_miniaudio *)audio; + // Latency is one period on PulseAudio even if periods is >1. On WASAPI, latency is periods + // times period size. + // This is a hack, should be possible to determine at runtime. +#ifdef _WIN32 + return ((ma->device.playback.internalPeriodSizeInFrames * ma->device.playback.internalPeriods) / +#else + return ((ma->device.playback.internalPeriodSizeInFrames) / +#endif + (ma->device.playback.internalSampleRate / 1000000.0)); +} + +static void audio_miniaudio_start(struct camu_audio *audio) +{ + struct camu_audio_miniaudio *ma = (struct camu_audio_miniaudio *)audio; + ma_device_start(&ma->device); +} + +static void audio_miniaudio_stop(struct camu_audio *audio) +{ + struct camu_audio_miniaudio *ma = (struct camu_audio_miniaudio *)audio; + ma_device_stop(&ma->device); +} + +static void audio_miniaudio_free(struct camu_audio **audio) +{ + struct camu_audio_miniaudio *ma = (struct camu_audio_miniaudio *)*audio; + ma_device_uninit(&ma->device); + ma_context_uninit(&ma->context); + al_free((void *)ma->context_config.pulse.pApplicationName); + *audio = NULL; +} + +struct camu_audio_miniaudio audio_plugin_miniaudio = { + .a = { + .state = CAMU_AUDIO_STATE_NULL, + .data_callback = NULL, + .userdata = NULL, + .init = audio_miniaudio_init, + .pick_format = audio_miniaudio_pick_format, + .configure_stream = audio_miniaudio_configure_stream, + .get_latency = audio_miniaudio_get_latency, + .start = audio_miniaudio_start, + .stop = audio_miniaudio_stop, + .free = audio_miniaudio_free + }, + .log = { }, + .context_config = { }, + .context = { }, + .infos = NULL, + .info_count = 0, + .selected_device = NULL, + .config = { }, + .device = { } +}; diff --git a/src/mixer/audio_miniaudio.h b/src/mixer/audio_miniaudio.h new file mode 100644 index 0000000..5d5cd03 --- /dev/null +++ b/src/mixer/audio_miniaudio.h @@ -0,0 +1,20 @@ +#pragma once + +#define MINIAUDIO_IMPLEMENTATION +#include <miniaudio.h> + +#include "audio.h" + +struct camu_audio_miniaudio { + struct camu_audio a; + ma_log log; + ma_context_config context_config; + ma_context context; + ma_device_info *infos; + u32 info_count; + ma_device_info *selected_device; + ma_device_config config; + ma_device device; +}; + +extern struct camu_audio_miniaudio audio_plugin_miniaudio; diff --git a/src/mixer/meson.build b/src/mixer/meson.build new file mode 100644 index 0000000..4053210 --- /dev/null +++ b/src/mixer/meson.build @@ -0,0 +1,12 @@ +mixer_src = ['mixer.c'] +mixer_deps = [common_deps] + +miniaudio = subproject('miniaudio').get_variable('miniaudio') +mixer_src += ['audio_miniaudio.c'] +mixer_deps += [miniaudio] +if host_machine.system() != 'windows' + libpulse = dependency('libpulse') + mixer_deps += [libpulse] +endif + +mixer = declare_dependency(sources: mixer_src, dependencies: mixer_deps) diff --git a/src/mixer/mixer.c b/src/mixer/mixer.c new file mode 100644 index 0000000..8cfc2cc --- /dev/null +++ b/src/mixer/mixer.c @@ -0,0 +1,167 @@ +#include "../buffer/audio.h" +#include "../buffer/clock.h" + +#include "mixer.h" + +static s32 data_callback(void *userdata, u8 *data, s32 frame_count) +{ + struct camu_mixer *mixer = (struct camu_mixer *)userdata; +#ifdef CAMU_MIXER_THREADED + if (al_atomic_bool_load(&mixer->queued, AL_ATOMIC_RELAXED)) { + camu_mixer_run_queue(mixer); + } +#endif + size_t req = camu_lav_resample_fmt_samples_to_bytes(&mixer->fmt, (size_t)frame_count); + do { + if (mixer->buffers.size == 0) { + al_memset(data, 0, req); + } else { + struct camu_audio_buffer *buf = al_array_last(mixer->buffers); + size_t signal; + if ((signal = camu_audio_buffer_read(buf, data, req)) < req) { +#ifdef CAMU_MIXER_THREADED + if (al_atomic_bool_load(&mixer->queued, AL_ATOMIC_RELAXED)) { + camu_mixer_run_queue(mixer); + } +#endif + data += signal; + req = req - signal; + continue; + } + } + break; + } while (1); + 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("camu")); + mixer->audio->pick_format(mixer->audio, &mixer->fmt); + av_channel_layout_default(&mixer->fmt.req_channel_layout, mixer->fmt.req_channel_count); + mixer->paused = true; + al_array_init(mixer->buffers); +#ifdef CAMU_MIXER_THREADED + al_array_init(mixer->add_queue); + al_array_init(mixer->rem_queue); + al_atomic_bool_store(&mixer->queued, false, AL_ATOMIC_RELAXED); + aki_mutex_init(&mixer->mutex); +#endif + return true; +} + +void camu_mixer_pick_format(struct camu_mixer *mixer, struct camu_lav_resample_fmt *fmt) +{ + mixer->audio->pick_format(mixer->audio, fmt); +} + +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) +{ +#ifdef CAMU_MIXER_THREADED + al_atomic_bool_store(&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 *cbuf; + al_array_foreach(mixer->buffers, i, cbuf) { + if (cbuf == buf) { +#ifdef CAMU_MIXER_THREADED + al_atomic_bool_store(&buf->ref, false, AL_ATOMIC_RELAXED); +#endif + al_array_remove_at_iter(mixer->buffers, i); + break; + } + } +} + +void camu_mixer_add_buffer(struct camu_mixer *mixer, struct camu_audio_buffer *buf) +{ +#ifdef CAMU_MIXER_THREADED + aki_mutex_lock(&mixer->mutex); + al_array_push(mixer->add_queue, buf); + // Ensure an allocation doesn't happen in data_callback. + al_array_reserve(mixer->buffers, mixer->buffers.size + mixer->add_queue.size); + al_atomic_bool_store(&mixer->queued, true, AL_ATOMIC_RELAXED); + aki_mutex_unlock(&mixer->mutex); +#else + add_buffer_internal(mixer, buf); +#endif +} + +#ifdef CAMU_MIXER_THREADED +static void run_queue_internal(struct camu_mixer *mixer) +{ + struct camu_audio_buffer *buf; + al_array_foreach(mixer->add_queue, i, buf) { + add_buffer_internal(mixer, buf); + al_array_remove_at_iter(mixer->add_queue, i); + } + al_array_foreach(mixer->rem_queue, i, buf) { + remove_buffer_internal(mixer, buf); + al_array_remove_at_iter(mixer->rem_queue, i); + } + al_atomic_bool_store(&mixer->queued, false, AL_ATOMIC_RELAXED); +} +#endif + +void camu_mixer_remove_buffer(struct camu_mixer *mixer, struct camu_audio_buffer *buf) +{ +#ifdef CAMU_MIXER_THREADED + aki_mutex_lock(&mixer->mutex); + al_array_push(mixer->rem_queue, buf); + al_atomic_bool_store(&mixer->queued, true, AL_ATOMIC_RELAXED); + if (mixer->paused) { + run_queue_internal(mixer); + } + aki_mutex_unlock(&mixer->mutex); +#else + remove_buffer_internal(mixer, buf); +#endif +} + +#ifdef CAMU_MIXER_THREADED +void camu_mixer_run_queue(struct camu_mixer *mixer) +{ + aki_mutex_lock(&mixer->mutex); + run_queue_internal(mixer); + aki_mutex_unlock(&mixer->mutex); +} +#endif + +void camu_mixer_pause(struct camu_mixer *mixer) +{ + if (!mixer->paused) { + mixer->audio->stop(mixer->audio); + mixer->paused = true; + } +} + +void camu_mixer_resume(struct camu_mixer *mixer) +{ + if (mixer->paused) { + mixer->audio->start(mixer->audio); + mixer->paused = false; + } +} + +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); + aki_mutex_destroy(&mixer->mutex); +#endif + mixer->audio->free(&mixer->audio); +} diff --git a/src/mixer/mixer.h b/src/mixer/mixer.h new file mode 100644 index 0000000..464431e --- /dev/null +++ b/src/mixer/mixer.h @@ -0,0 +1,37 @@ +#pragma once + +#define CAMU_MIXER_THREADED + +#include <al/types.h> +#include <al/array.h> +#ifdef CAMU_MIXER_THREADED +#include <al/atomic.h> +#include <aki/thread.h> +#endif + +#include "../codec/libav/resampler.h" + +struct camu_mixer { + struct camu_audio *audio; + bool paused; + struct camu_lav_resample_fmt fmt; + array(struct camu_audio_buffer *) buffers; +#ifdef CAMU_MIXER_THREADED + array(struct camu_audio_buffer *) add_queue; + array(struct camu_audio_buffer *) rem_queue; + atomic_bool queued; + struct aki_mutex mutex; +#endif +}; + +bool camu_mixer_init(struct camu_mixer *mixer, struct camu_audio *audio); +void camu_mixer_pick_format(struct camu_mixer *mixer, struct camu_lav_resample_fmt *fmt); +f64 camu_mixer_get_latency(struct camu_mixer *mixer); +void camu_mixer_add_buffer(struct camu_mixer *mixer, struct camu_audio_buffer *buf); +void camu_mixer_remove_buffer(struct camu_mixer *mixer, struct camu_audio_buffer *buf); +#ifdef CAMU_MIXER_THREADED +void camu_mixer_run_queue(struct camu_mixer *mixer); +#endif +void camu_mixer_pause(struct camu_mixer *mixer); +void camu_mixer_resume(struct camu_mixer *mixer); +void camu_mixer_close(struct camu_mixer *mixer); |