summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVito Caputo <vcaputo@pengaru.com>2023-08-28 18:57:55 -0700
committerVito Caputo <vcaputo@pengaru.com>2023-11-14 01:20:48 -0800
commitb22df31feeb7e695faabecfd7cc6fdd24609b0e1 (patch)
treeb1c33983aec45ba17de58276d2c3696695e573dd
parent8245857f9f07043039affd7b92a740e002b1b81b (diff)
til: add preliminary audio backend
This is an early implementation of something resembling an audio backend for rototiller/libtil. The assumption for now is that everything will use signed 16-bit native-endian stereo output @ 44.1khz.
-rw-r--r--src/Makefile.am7
-rw-r--r--src/main.c101
-rw-r--r--src/mem_audio.c143
-rw-r--r--src/sdl_audio.c204
-rw-r--r--src/til_args.c5
-rw-r--r--src/til_args.h1
-rw-r--r--src/til_audio.c148
-rw-r--r--src/til_audio.h39
-rw-r--r--src/til_audio_context.c35
-rw-r--r--src/til_audio_context.h21
10 files changed, 699 insertions, 5 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index c6877a7..fda9b40 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -6,6 +6,10 @@ libtil_la_SOURCES = \
til.h \
til_args.c \
til_args.h \
+ til_audio.c \
+ til_audio.h \
+ til_audio_context.c \
+ til_audio_context.h \
til_builtins.c \
til_fb.c \
til_fb.h \
@@ -75,6 +79,7 @@ rototiller_SOURCES = \
fps.c \
fps.h \
main.c \
+ mem_audio.c \
mem_fb.c \
setup.c \
setup.h \
@@ -89,7 +94,7 @@ rototiller_SOURCES = \
til_util.h
if ENABLE_SDL
-rototiller_SOURCES += sdl_fb.c
+rototiller_SOURCES += sdl_audio.c sdl_fb.c
endif
if ENABLE_DRM
rototiller_SOURCES += drm_fb.c
diff --git a/src/main.c b/src/main.c
index 45e8c3b..70f83e2 100644
--- a/src/main.c
+++ b/src/main.c
@@ -12,6 +12,7 @@
#include "til.h"
#include "til_args.h"
+#include "til_audio.h"
#include "til_fb.h"
#include "til_settings.h"
#include "til_stream.h"
@@ -45,11 +46,25 @@
#define DEFAULT_VIDEO "mem"
#endif
+#ifndef DEFAULT_AUDIO
+#ifdef HAVE_SDL
+#define DEFAULT_AUDIO "sdl"
+#endif
+#endif
+
+#ifndef DEFAULT_AUDIO
+#define DEFAULT_AUDIO "mem"
+#endif
+
extern til_fb_ops_t drm_fb_ops;
extern til_fb_ops_t mem_fb_ops;
extern til_fb_ops_t sdl_fb_ops;
static til_fb_ops_t *fb_ops;
+extern til_audio_ops_t sdl_audio_ops;
+extern til_audio_ops_t mem_audio_ops;
+static til_audio_ops_t *audio_ops;
+
typedef struct rototiller_t {
til_args_t args;
const til_module_t *module;
@@ -58,6 +73,7 @@ typedef struct rototiller_t {
til_fb_fragment_t *fragment;
pthread_t thread;
til_fb_t *fb;
+ til_audio_context_t *audio;
} rototiller_t;
static rototiller_t rototiller;
@@ -66,6 +82,8 @@ static rototiller_t rototiller;
typedef struct setup_t {
til_settings_t *module_settings;
til_setup_t *module_setup;
+ til_settings_t *audio_settings;
+ til_setup_t *audio_setup;
til_settings_t *video_settings;
til_setup_t *video_setup;
unsigned seed;
@@ -77,6 +95,59 @@ typedef struct setup_t {
* subclass the video backend vs. renderer stuff.
*/
+/* select audio backend if not yet selected, then setup the selected backend. */
+static int setup_audio(const til_settings_t *settings, til_setting_t **res_setting, const til_setting_desc_t **res_desc, til_setup_t **res_setup)
+{
+ til_setting_t *setting;
+ const char *audio;
+
+ audio = til_settings_get_value_by_idx(settings, 0, &setting);
+ if (!audio || !setting->desc) {
+ const char *values[] = {
+#ifdef HAVE_SDL
+ "sdl",
+#endif
+ "mem",
+ NULL,
+ };
+ int r;
+
+ r = til_setting_desc_new( settings,
+ &(til_setting_spec_t){
+ .name = "Audio backend",
+ .key = NULL,
+ .regex = "[a-z]+",
+ .preferred = DEFAULT_AUDIO,
+ .values = values,
+ .annotations = NULL,
+ .as_label = 1,
+ }, res_desc);
+
+ if (r < 0)
+ return r;
+
+ *res_setting = audio ? setting : NULL;
+
+ return 1;
+ }
+
+ /* XXX: this is kind of hacky for now */
+ if (!strcasecmp(audio, "mem")) {
+ audio_ops = &mem_audio_ops;
+
+ return mem_audio_ops.setup(settings, res_setting, res_desc, res_setup);
+ }
+#ifdef HAVE_SDL
+ if (!strcasecmp(audio, "sdl")) {
+ audio_ops = &sdl_audio_ops;
+
+ return sdl_audio_ops.setup(settings, res_setting, res_desc, res_setup);
+ }
+#endif
+
+ return -EINVAL;
+}
+
/* select video backend if not yet selected, then setup the selected backend. */
static int setup_video(const til_settings_t *settings, til_setting_t **res_setting, const til_setting_desc_t **res_desc, til_setup_t **res_setup)
{
@@ -228,6 +299,10 @@ static int setup_from_args(til_args_t *args, setup_t *res_setup, const char **re
if (!setup.module_settings)
goto _err;
+ setup.audio_settings = til_settings_new(NULL, NULL, "audio", args->audio);
+ if (!setup.audio_settings)
+ goto _err;
+
setup.video_settings = til_settings_new(NULL, NULL, "video", args->video);
if (!setup.video_settings)
goto _err;
@@ -238,6 +313,12 @@ static int setup_from_args(til_args_t *args, setup_t *res_setup, const char **re
if (r)
changes = 1;
+ r = setup_interactively(setup.audio_settings, setup_audio, args->use_defaults, &setup.audio_setup, res_failed_desc_path);
+ if (r < 0)
+ goto _err;
+ if (r)
+ changes = 1;
+
r = setup_interactively(setup.video_settings, setup_video, args->use_defaults, &setup.video_setup, res_failed_desc_path);
if (r < 0)
goto _err;
@@ -251,6 +332,7 @@ static int setup_from_args(til_args_t *args, setup_t *res_setup, const char **re
_err:
free((void *)setup.title);
til_settings_free(setup.module_settings);
+ til_settings_free(setup.audio_settings);
til_settings_free(setup.video_settings);
return r;
@@ -269,7 +351,7 @@ static char * seed_as_arg(unsigned seed)
static int print_setup_as_args(setup_t *setup, int wait)
{
- char *seed_arg, *module_args, *video_args;
+ char *seed_arg, *module_args, *audio_args, *video_args;
char buf[64];
int r = -ENOMEM;
@@ -281,13 +363,18 @@ static int print_setup_as_args(setup_t *setup, int wait)
if (!module_args)
goto _out_seed;
+ audio_args = til_settings_as_arg(setup->audio_settings);
+ if (!audio_args)
+ goto _out_module;
+
video_args = til_settings_as_arg(setup->video_settings);
if (!video_args)
- goto _out_module;
+ goto _out_audio;
- r = printf("\nConfigured settings as flags:\n --seed=%s '--module=%s' '--video=%s'\n",
+ r = printf("\nConfigured settings as flags:\n --seed=%s '--module=%s' '--audio=%s' '--video=%s'\n",
seed_arg,
module_args,
+ audio_args,
video_args);
if (r < 0)
goto _out_video;
@@ -302,6 +389,8 @@ static int print_setup_as_args(setup_t *setup, int wait)
_out_video:
free(video_args);
+_out_audio:
+ free(audio_args);
_out_module:
free(module_args);
_out_seed:
@@ -395,6 +484,9 @@ int main(int argc, const char *argv[])
exit_if((r = til_fb_new(fb_ops, setup.title, setup.video_setup, NUM_FB_PAGES, &rototiller.fb)) < 0,
"unable to create fb: %s", strerror(-r));
+ exit_if((r = til_audio_open(audio_ops, setup.audio_setup, &rototiller.audio)) < 0,
+ "unable to open audio: %s", strerror(-r));
+
exit_if(!(rototiller.stream = til_stream_new()),
"unable to create root stream");
@@ -426,12 +518,15 @@ int main(int argc, const char *argv[])
til_module_context_free(rototiller.module_context);
til_stream_free(rototiller.stream);
+ til_audio_shutdown(rototiller.audio);
til_fb_free(rototiller.fb);
{ /* free setup (move to function? and disambiguate from til_setup_t w/rename? TODO) */
free((void *)setup.title);
til_setup_free(setup.video_setup);
til_settings_free(setup.video_settings);
+ til_setup_free(setup.audio_setup);
+ til_settings_free(setup.audio_settings);
til_setup_free(setup.module_setup);
til_settings_free(setup.module_settings);
}
diff --git a/src/mem_audio.c b/src/mem_audio.c
new file mode 100644
index 0000000..b8d51df
--- /dev/null
+++ b/src/mem_audio.c
@@ -0,0 +1,143 @@
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include "til.h"
+#include "til_audio.h"
+#include "til_audio_context.h"
+#include "til_settings.h"
+#include "til_setup.h"
+
+/* "mem" audio backend */
+
+typedef struct mem_audio_setup_t {
+ til_setup_t til_setup;
+} mem_audio_setup_t;
+
+typedef struct mem_audio_t {
+ til_audio_context_t til_audio_context;
+
+ unsigned n_queued;
+ unsigned n_queued_start_ticks;
+ unsigned paused:1;
+} mem_audio_t;
+
+
+static int mem_audio_init(til_setup_t *setup, til_audio_context_t **res_context);
+static int mem_audio_queue(til_audio_context_t *context, int16_t *frames, int n_frames);;
+static unsigned mem_audio_n_queued(til_audio_context_t *context);
+static void mem_audio_drop(til_audio_context_t *context);
+static void mem_audio_pause(til_audio_context_t *context);
+static void mem_audio_unpause(til_audio_context_t *context);
+static int mem_audio_setup(const til_settings_t *settings, til_setting_t **res_setting, const til_setting_desc_t **res_desc, til_setup_t **res_setup);
+
+
+til_audio_ops_t mem_audio_ops = {
+ .init = mem_audio_init,
+ .drop = mem_audio_drop,
+ .pause = mem_audio_pause,
+ .unpause = mem_audio_unpause,
+ .queue = mem_audio_queue,
+ .n_queued = mem_audio_n_queued,
+ .setup = mem_audio_setup,
+};
+
+
+/* this simulates an audio timer grinding through the queued frames when unpaused,
+ * returns the remaining n_queued (if any) for convenience, but it's also maintained
+ * at c->n_queued.
+ */
+static unsigned mem_refresh_n_queued(mem_audio_t *c)
+{
+ if (!c->paused && c->n_queued) {
+ unsigned now = til_ticks_now();
+ unsigned n_played = ((float)(now - c->n_queued_start_ticks) * 44.1f);
+
+ c->n_queued -= MIN(c->n_queued, n_played);
+ }
+
+ return c->n_queued;
+}
+
+
+static int mem_audio_init(til_setup_t *setup, til_audio_context_t **res_context)
+{
+ mem_audio_t *c;
+
+ assert(setup);
+ assert(res_context);
+
+ c = til_audio_context_new(&mem_audio_ops, sizeof(mem_audio_t), setup);
+ if (!c)
+ return -ENOMEM;
+
+ c->paused = 1;
+ *res_context = &c->til_audio_context;
+
+ return 0;
+}
+
+
+static void mem_audio_drop(til_audio_context_t *context)
+{
+ mem_audio_t *c = (mem_audio_t *)context;
+
+ c->n_queued = 0;
+}
+
+
+static void mem_audio_pause(til_audio_context_t *context)
+{
+ mem_audio_t *c = (mem_audio_t *)context;
+
+ if (!c->paused) {
+ mem_refresh_n_queued(c);
+ c->paused = 1;
+ }
+}
+
+
+static void mem_audio_unpause(til_audio_context_t *context)
+{
+ mem_audio_t *c = (mem_audio_t *)context;
+
+ if (c->paused) {
+ c->paused = 0;
+ c->n_queued_start_ticks = til_ticks_now();
+ }
+}
+
+
+static int mem_audio_queue(til_audio_context_t *context, int16_t *frames, int n_frames)
+{
+ mem_audio_t *c = (mem_audio_t *)context;
+
+ mem_refresh_n_queued(c);
+ c->n_queued += n_frames;
+
+ return 0;
+}
+
+
+static unsigned mem_audio_n_queued(til_audio_context_t *context)
+{
+ mem_audio_t *c = (mem_audio_t *)context;
+
+ return mem_refresh_n_queued(c);
+}
+
+
+static int mem_audio_setup(const til_settings_t *settings, til_setting_t **res_setting, const til_setting_desc_t **res_desc, til_setup_t **res_setup)
+{
+ if (res_setup) {
+ mem_audio_setup_t *setup;
+
+ setup = til_setup_new(settings, sizeof(*setup), NULL, &mem_audio_ops);
+ if (!setup)
+ return -ENOMEM;
+
+ *res_setup = &setup->til_setup;
+ }
+
+ return 0;
+}
diff --git a/src/sdl_audio.c b/src/sdl_audio.c
new file mode 100644
index 0000000..90fea35
--- /dev/null
+++ b/src/sdl_audio.c
@@ -0,0 +1,204 @@
+#define SDL_MAIN_HANDLED
+#include <assert.h>
+#include <SDL.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "til_audio.h"
+#include "til_audio_context.h"
+#include "til_settings.h"
+#include "til_setup.h"
+
+/* sdl audio backend */
+
+#define SDL_AUDIO_DEFAULT_SAMPLES 1024
+
+typedef struct sdl_audio_setup_t {
+ til_setup_t til_setup;
+
+ unsigned frames;
+} sdl_audio_setup_t;
+
+typedef struct sdl_audio_t {
+ til_audio_context_t til_audio_context;
+
+ SDL_AudioDeviceID dev;
+} sdl_audio_t;
+
+
+static int sdl_audio_init(til_setup_t *setup, til_audio_context_t **res_context);
+static void sdl_audio_shutdown(til_audio_context_t *context);
+static int sdl_audio_queue(til_audio_context_t *context, int16_t *frames, int n_frames);
+static unsigned sdl_audio_n_queued(til_audio_context_t *context);
+static void sdl_audio_drop(til_audio_context_t *context);
+static void sdl_audio_pause(til_audio_context_t *context);
+static void sdl_audio_unpause(til_audio_context_t *context);
+static int sdl_audio_setup(const til_settings_t *settings, til_setting_t **res_setting, const til_setting_desc_t **res_desc, til_setup_t **res_setup);
+
+
+til_audio_ops_t sdl_audio_ops = {
+ .init = sdl_audio_init,
+ .shutdown = sdl_audio_shutdown,
+ .drop = sdl_audio_drop,
+ .pause = sdl_audio_pause,
+ .unpause = sdl_audio_unpause,
+ .queue = sdl_audio_queue,
+ .n_queued = sdl_audio_n_queued,
+ .setup = sdl_audio_setup,
+};
+
+
+static int sdl_err_to_errno(int err)
+{
+ switch (err) {
+ case SDL_ENOMEM:
+ return ENOMEM;
+ case SDL_EFREAD:
+ case SDL_EFWRITE:
+ case SDL_EFSEEK:
+ return EIO;
+ case SDL_UNSUPPORTED:
+ return ENOTSUP;
+ default:
+ return EINVAL;
+ }
+}
+
+
+static int sdl_audio_init(til_setup_t *setup, til_audio_context_t **res_context)
+{
+ sdl_audio_setup_t *s = (sdl_audio_setup_t *)setup;
+ sdl_audio_t *c;
+ int r;
+
+ assert(setup);
+ assert(res_context);
+
+ c = til_audio_context_new(&sdl_audio_ops, sizeof(sdl_audio_t), setup);
+ if (!c)
+ return -ENOMEM;
+
+ SDL_SetMainReady(); /* is it a problem (or necessary) for both sdl_fb and sdl_audio to do this? */
+ r = SDL_InitSubSystem(SDL_INIT_AUDIO);
+ if (r < 0) {
+ free(c);
+ return -sdl_err_to_errno(r);
+ }
+
+ {
+ SDL_AudioSpec aspec = {
+ .freq = 44100,
+ .format = AUDIO_S16,
+ .channels = 2,
+ .samples = s->frames,
+ };
+
+ c->dev = SDL_OpenAudioDevice(NULL, 0, &aspec, NULL, 0);
+ if (!c->dev) {
+ /* SDL_GetError only returns strings, we need an errno FIXME */
+ free(c);
+ return -EPERM;
+ }
+ }
+
+ *res_context = &c->til_audio_context;
+
+ return 0;
+}
+
+
+static void sdl_audio_shutdown(til_audio_context_t *context)
+{
+ sdl_audio_t *c = (sdl_audio_t *)context;
+
+ SDL_CloseAudioDevice(c->dev);
+}
+
+
+static void sdl_audio_drop(til_audio_context_t *context)
+{
+ sdl_audio_t *c = (sdl_audio_t *)context;
+
+ SDL_ClearQueuedAudio(c->dev);
+}
+
+
+static void sdl_audio_pause(til_audio_context_t *context)
+{
+ sdl_audio_t *c = (sdl_audio_t *)context;
+
+ SDL_PauseAudioDevice(c->dev, 1);
+}
+
+
+static void sdl_audio_unpause(til_audio_context_t *context)
+{
+ sdl_audio_t *c = (sdl_audio_t *)context;
+
+ SDL_PauseAudioDevice(c->dev, 0);
+}
+
+
+static int sdl_audio_queue(til_audio_context_t *context, int16_t *frames, int n_frames)
+{
+ sdl_audio_t *c = (sdl_audio_t *)context;
+
+ /* TODO FIXME: SDL_audio.h says this returns a negative error code on
+ * error, but that's not the same as a -errno, so directly returning
+ * from here isn't exactly appropriate.
+ */
+ return SDL_QueueAudio(c->dev, frames, n_frames * (sizeof(*frames) * 2));
+}
+
+
+static unsigned sdl_audio_n_queued(til_audio_context_t *context)
+{
+ sdl_audio_t *c = (sdl_audio_t *)context;
+
+ return SDL_GetQueuedAudioSize(c->dev) / (sizeof(int16_t) * 2);
+}
+
+
+static int sdl_audio_setup(const til_settings_t *settings, til_setting_t **res_setting, const til_setting_desc_t **res_desc, til_setup_t **res_setup)
+{
+ til_setting_t *frames;
+ const char *frames_values[] = {
+ "512",
+ "1024",
+ "2048",
+ "4096",
+ "8192",
+ NULL
+ };
+ int r;
+
+ r = til_settings_get_and_describe_setting(settings,
+ &(til_setting_spec_t){
+ .name = "Audio frames buffered",
+ .key = "frames",
+ .regex = "[0-9]+",
+ .preferred = TIL_SETTINGS_STR(SDL_AUDIO_DEFAULT_SAMPLES),
+ .values = frames_values,
+ .annotations = NULL
+ },
+ &frames,
+ res_setting,
+ res_desc);
+ if (r)
+ return r;
+
+ if (res_setup) {
+ sdl_audio_setup_t *setup;
+
+ setup = til_setup_new(settings, sizeof(*setup), NULL, &sdl_audio_ops);
+ if (!setup)
+ return -ENOMEM;
+
+ if (sscanf(frames->value, "%u", &setup->frames) != 1)
+ return til_setup_free_with_failed_setting_ret_err(&setup->til_setup, frames, res_setting, -EINVAL);
+
+ *res_setup = &setup->til_setup;
+ }
+
+ return 0;
+}
diff --git a/src/til_args.c b/src/til_args.c
index b51f81b..c4dd95f 100644
--- a/src/til_args.c
+++ b/src/til_args.c
@@ -44,7 +44,9 @@ static int args_parse(int argc, const char *argv[], til_args_t *res_args, int *r
/* this is intentionally being kept very simple, no new dependencies like getopt. */
for (int i = 1; i < argc; i++) {
- if (!strncasecmp("--video=", argv[i], 8)) {
+ if (!strncasecmp("--audio=", argv[i], 8)) {
+ res_args->audio = &argv[i][8];
+ } else if (!strncasecmp("--video=", argv[i], 8)) {
res_args->video = &argv[i][8];
} else if (!strncasecmp("--module=", argv[i], 9)) {
res_args->module = &argv[i][9];
@@ -92,6 +94,7 @@ int til_args_parse(int argc, const char *argv[], til_args_t *res_args)
int til_args_help(FILE *out)
{
return fprintf(out,
+ " --audio audio settings\n"
" --defaults use defaults for unspecified settings\n"
" --go start rendering immediately upon fulfilling all required settings\n"
" --help this help\n"
diff --git a/src/til_args.h b/src/til_args.h
index d49211e..fce5f20 100644
--- a/src/til_args.h
+++ b/src/til_args.h
@@ -5,6 +5,7 @@
typedef struct til_args_t {
const char *module;
+ const char *audio;
const char *video;
const char *seed;
const char *title;
diff --git a/src/til_audio.c b/src/til_audio.c
new file mode 100644
index 0000000..ed1ff9b
--- /dev/null
+++ b/src/til_audio.c
@@ -0,0 +1,148 @@
+#include <assert.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "til_audio.h"
+#include "til_audio_context.h"
+
+
+/* initialize audio via ops using setup, context stored in res_audio_context on success.
+ * returns -errno on error, 0 on success
+ * playback is left in a paused state, with an empty queue
+ */
+int til_audio_open(const til_audio_ops_t *ops, til_setup_t *setup, til_audio_context_t **res_audio_context)
+{
+ til_audio_context_t *c;
+ int r;
+
+ assert(ops);
+ assert(ops->init);
+ assert(ops->queue);
+ assert(ops->n_queued);
+ assert(setup);
+ assert(res_audio_context);
+
+ r = ops->init(setup, &c);
+ if (r < 0)
+ return r;
+
+ *res_audio_context = c;
+
+ return 0;
+}
+
+
+/* closes audio and frees context,
+ * callers are expected to use this and not til_audio_context_free().
+ */
+void til_audio_shutdown(til_audio_context_t *audio_context)
+{
+ assert(audio_context);
+ assert(audio_context->ops);
+
+ if (audio_context->ops->shutdown)
+ audio_context->ops->shutdown(audio_context);
+
+ til_audio_context_free(audio_context);
+}
+
+
+/* install audio hooks to receive notification on events like seek/pause/unpause */
+int til_audio_set_hooks(til_audio_context_t *audio_context, til_audio_hooks_t *hooks, void *hooks_context)
+{
+ assert(audio_context);
+ assert(hooks);
+
+ if (audio_context->hooks && audio_context->hooks != hooks)
+ return -EEXIST;
+
+ audio_context->hooks = hooks;
+ audio_context->hooks_context = hooks_context;
+
+ return 0;
+}
+
+
+/* remove audio hooks */
+int til_audio_unset_hooks(til_audio_context_t *audio_context, til_audio_hooks_t *hooks, void *hooks_context)
+{
+ assert(audio_context);
+ assert(hooks);
+
+ /* this is kind of silly, but seems potentially useful in the defensive department */
+ if (audio_context->hooks != hooks ||
+ audio_context->hooks_context != hooks_context)
+ return -EINVAL;
+
+ audio_context->hooks = NULL;
+ audio_context->hooks_context = NULL;
+
+ return 0;
+}
+
+
+/* seek to an absolute ticks, playback is left in a paused state with an empty queue */
+void til_audio_seek(til_audio_context_t *audio_context, unsigned ticks)
+{
+ assert(audio_context);
+
+ if (audio_context->ops->pause)
+ audio_context->ops->pause(audio_context);
+
+ if (audio_context->ops->drop)
+ audio_context->ops->drop(audio_context);
+
+ if (audio_context->hooks && audio_context->hooks->seeked)
+ audio_context->hooks->seeked(audio_context->hooks_context, audio_context, ticks);
+}
+
+
+/* queue n_frames frames to audio_context,
+ * returns 0 on success, -errno on error
+ */
+int til_audio_queue(til_audio_context_t *audio_context, int16_t *frames, int n_frames)
+{
+ assert(audio_context);
+ assert(frames);
+ assert(n_frames > 0);
+
+ return audio_context->ops->queue(audio_context, frames, n_frames);
+}
+
+
+/* query how many frames are currently queued
+ * returns 0 on success, -errno on error
+ */
+unsigned til_audio_n_queued(til_audio_context_t *audio_context)
+{
+ assert(audio_context);
+
+ return audio_context->ops->n_queued(audio_context);
+}
+
+
+/* pause the underlying audio playback, queue is left as-is, should be idempotent */
+void til_audio_pause(til_audio_context_t *audio_context)
+{
+ assert(audio_context);
+
+ if (audio_context->ops->pause)
+ audio_context->ops->pause(audio_context);
+
+ if (audio_context->hooks && audio_context->hooks->paused)
+ audio_context->hooks->paused(audio_context->hooks_context, audio_context);
+}
+
+
+/* unpause the underlying audio playback, queue is left as-is, should be idempotent */
+void til_audio_unpause(til_audio_context_t *audio_context)
+{
+ assert(audio_context);
+
+ if (audio_context->ops->unpause)
+ audio_context->ops->unpause(audio_context);
+
+ if (audio_context->hooks && audio_context->hooks->unpaused)
+ audio_context->hooks->unpaused(audio_context->hooks_context, audio_context);
+}
diff --git a/src/til_audio.h b/src/til_audio.h
new file mode 100644
index 0000000..f5640b6
--- /dev/null
+++ b/src/til_audio.h
@@ -0,0 +1,39 @@
+#ifndef _TIL_AUDIO_H
+#define _TIL_AUDIO_H
+
+#include <stdint.h>
+
+typedef struct til_settings_t til_settings_t;
+typedef struct til_setting_t til_setting_t;
+typedef struct til_setting_desc_t til_setting_desc_t;
+typedef struct til_setup_t til_setup_t;
+typedef struct til_audio_context_t til_audio_context_t;
+
+typedef struct til_audio_ops_t {
+ int (*setup)(const til_settings_t *settings, til_setting_t **res_setting, const til_setting_desc_t **res_desc, til_setup_t **res_setup);
+ int (*init)(til_setup_t *setup, til_audio_context_t **res_audio_context);
+ void (*shutdown)(til_audio_context_t *audio_context);
+ void (*drop)(til_audio_context_t *audio_context);
+ void (*pause)(til_audio_context_t *audio_context);
+ void (*unpause)(til_audio_context_t *audio_context);
+ int (*queue)(til_audio_context_t *audio_context, int16_t *frames, int n_frames);
+ unsigned (*n_queued)(til_audio_context_t *audio_context);
+} til_audio_ops_t;
+
+typedef struct til_audio_hooks_t {
+ void (*seeked)(void *hooks_context, const til_audio_context_t *audio_context, unsigned ticks);
+ void (*paused)(void *hooks_context, const til_audio_context_t *audio_context);
+ void (*unpaused)(void *hooks_context, const til_audio_context_t *audio_context);
+} til_audio_hooks_t;
+
+int til_audio_open(const til_audio_ops_t *ops, til_setup_t *setup, til_audio_context_t **res_audio_context);
+void til_audio_shutdown(til_audio_context_t *audio_context);
+int til_audio_set_hooks(til_audio_context_t *audio_context, til_audio_hooks_t *hooks, void *hooks_context);
+int til_audio_unset_hooks(til_audio_context_t *audio_context, til_audio_hooks_t *hooks, void *hooks_context);
+int til_audio_queue(til_audio_context_t *audio_context, int16_t *frames, int n_frames);
+unsigned til_audio_n_queued(til_audio_context_t *audio_context);
+void til_audio_seek(til_audio_context_t *audio_context, unsigned ticks);
+void til_audio_pause(til_audio_context_t *audio_context);
+void til_audio_unpause(til_audio_context_t *audio_context);
+
+#endif
diff --git a/src/til_audio_context.c b/src/til_audio_context.c
new file mode 100644
index 0000000..63cc6f5
--- /dev/null
+++ b/src/til_audio_context.c
@@ -0,0 +1,35 @@
+#include <stdlib.h>
+
+#include "til_audio.h"
+#include "til_audio_context.h"
+#include "til_setup.h"
+
+/* XXX: this isn't intended to be used by anything other than til_audio.[ch],
+ * use til_audio_{init,shutdown}(). This is purely in service of that.
+ */
+
+void * til_audio_context_new(const til_audio_ops_t *ops, size_t size, til_setup_t *setup)
+{
+ til_audio_context_t *c;
+
+ c = calloc(1, size);
+ if (!c)
+ return NULL;
+
+ c->setup = til_setup_ref(setup);
+ c->ops = ops;
+
+ return c;
+}
+
+
+til_audio_context_t * til_audio_context_free(til_audio_context_t *audio_context)
+{
+ if (!audio_context)
+ return NULL;
+
+ til_setup_free(audio_context->setup);
+ free(audio_context);
+
+ return NULL;
+}
diff --git a/src/til_audio_context.h b/src/til_audio_context.h
new file mode 100644
index 0000000..8efdbac
--- /dev/null
+++ b/src/til_audio_context.h
@@ -0,0 +1,21 @@
+#ifndef _TIL_AUDIO_CONTEXT_H
+#define _TIL_AUDIO_CONTEXT_H
+
+/* XXX: this isn't intended to be used outside of til_audio.[ch] */
+
+#include <stddef.h>
+
+typedef struct til_setup_t til_setup_t;
+typedef struct til_audio_ops_t til_audio_ops_t;
+
+typedef struct til_audio_context_t {
+ til_setup_t *setup;
+ const til_audio_ops_t *ops;
+ const til_audio_hooks_t *hooks;
+ void *hooks_context;
+} til_audio_context_t;
+
+void * til_audio_context_new(const til_audio_ops_t *ops, size_t size, til_setup_t *setup);
+til_audio_context_t * til_audio_context_free(til_audio_context_t *audio_context);
+
+#endif
© All Rights Reserved