summaryrefslogtreecommitdiff
path: root/src/mixer
diff options
context:
space:
mode:
Diffstat (limited to 'src/mixer')
-rw-r--r--src/mixer/audio.h37
-rw-r--r--src/mixer/audio_miniaudio.c190
-rw-r--r--src/mixer/audio_miniaudio.h20
-rw-r--r--src/mixer/meson.build12
-rw-r--r--src/mixer/mixer.c167
-rw-r--r--src/mixer/mixer.h37
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);